java优化挑剔:在投射之前,投射一些东西并让它抛出异常是否比调用 instanceof 来检查更快?

2022-09-03 18:03:14

在任何人说什么之前,我只是出于好奇而问这个问题。我不打算根据这个答案做任何过早的优化。

我的问题是关于使用反射和投射的速度。标准的说法是“反思很慢”。我的问题是,究竟哪个部分很慢,为什么;特别是在比较某物是另一个实例的父实例时。

我非常有信心,只是将一个对象的类与另一个类对象进行比较与任何比较一样快,大概只是对已经存储在对象状态中的单例对象进行直接比较;但是,如果一个类是另一个类的父类呢?

我通常认为它和常规的课堂检查一样快,但今天我想到了这一点,似乎必须在“幕后”进行一些反思才能工作。我在网上检查了一下,发现有几个地方有人说很慢;大概是由于需要反射来比较一个对象的父级?instanceofinstanceofinstanceof

这就引出了下一个问题,那么仅仅铸造呢?如果我将某些东西作为对象,则不是我得到一个.但是,如果将对象投射到自身的父对象,则不会发生这种情况。从本质上讲,我正在做一个调用,或者逻辑,当我在运行时进行强制转换时,我不是吗?我以前从未听说过有人暗示投射物体可能会很慢。诚然,并非所有强制转换都属于所提供对象的父级,但许多强制转换属于父类。然而,从来没有人暗示过这可能很慢。ClassCastExceptioninstanceof

所以这是它。真的不是那么慢吗?两者都和投射到父类有点慢吗?还是有某种原因可以比电话更快地完成演员?instanceofinstanceofinstanceof


答案 1

像往常一样,尝试一下,看看你的特定情况,但是:

-例外是昂贵的,非常如此。

-对代码流使用异常几乎总是一个坏主意

编辑:好的,我很感兴趣,所以我写了一个快速测试系统

public class Test{

    public Test(){
        B b=new B();
        C c=new C();


        for(int i=0;i<10000;i++){
            testUsingInstanceOf(b);
            testUsingInstanceOf(c);
            testUsingException(b);
            testUsingException(c);
        }
    }

    public static void main(String[] args){

        Test test=new Test();

    }

    public static boolean testUsingInstanceOf(A possiblyB){
        if (possiblyB instanceof B){
            return true;
        }else{
            return false;
        }
    }
    public static boolean testUsingException(A possiblyB){
        try{
            B b=(B)possiblyB;
            return true;
        }catch(Exception e){
            return false;
        }
    }


    private class A{

    }
    private class B extends A{

    }
    private class C extends A{

    }        
}

个人资料结果:

by InstanceOf: 4.43 ms
by Exception: 79.4 ms

正如我所说,非常昂贵

即使它总是会是B(模拟当你99%确定它的B时,你只需要确保它仍然不快:

始终 B 时的配置文件结果:

by InstanceOf: 4.48 ms
by Exception: 4.51 ms

答案 2

有一个一般的答案和一个特定的答案。

一般情况

if (/* guard against exception */) {
    /* do something that would throw an exception */
} else {
    /* recover */
}

// versus

try {
   /* do something that would throw an exception */
} catch (TheException ex) {
   /* recover */
}

事实上,创建/抛出/捕获异常是昂贵的。而且它们可能比进行测试昂贵得多。但是,这并不意味着“测试优先”版本总是更快。这是因为在“测试第一”版本中,实际上可以执行测试:第一次在 中执行,第二次在引发异常的代码中执行。if

当你考虑到这一点时,很明显,如果(额外)测试的成本足够大,并且异常的相对频率足够小,那么“测试第一”实际上会更慢。例如,在:

if (file.exists() && file.isReadable()) {
    is = new FileInputStream(file);
} else {
    System.err.println("missing file");
}

try {
    is = new FileInputStream(file);
} catch (IOException ex) {
    System.err.println("missing file");
}

“测试优先”方法执行 2 次额外的系统调用,并且系统调用成本很高。如果“丢失文件”的情况也是不寻常的....

第二个混杂因素是,最新的 HotSpot JIT 编译器对异常进行了一些重要的优化。特别是,如果 JIT 编译器可以确定未使用异常对象的状态,则可能会将异常创建/抛出/捕获转换为简单的跳转指令。

具体案例instanceof

在这种情况下,我们最有可能比较这两者:

if (o instanceof Foo) {
    Foo f = (Foo) o;
    /* ... */
} 

// versus

try {
    Foo f = (Foo) o;
} catch (ClassCastException ex) {
   /* */
}

这里发生了第二次优化。后跟 a 是一种常见模式。HotSpot JIT编译器通常可以消除由类型转换执行的动态类型检查...因为这是重复刚刚成功的测试。当您考虑到这一点时,“测试第一”版本不能比“异常”版本慢...即使后者被优化为跳跃。instanceoftype cast


推荐