java optimization nitpick: is it faster to cast something and let it throw exception than calling instanceof to check before cast?

二次信任 提交于 2019-11-26 18:28:10

问题


Before anyone says anything I'm asking this out of curiosity only; I'm not planning to do any premature optimization based off of this answer.

My question is about speed in using reflection and casting. The standard saying is 'reflection is slow'. My question is what part exactly is slow, and why; particularly in comparing if something is a parent of another instance.

I'm pretty confident that just comparing the class of an object to another Class object is about as fast as any comparison, presumably just doing direct comparison of singleton objects that are already stored int he Object's state; but what if one class is a parent of the other?

I usually think of instanceof as being about as fast as regular class checking, but today I thought about it and it seems that some reflection has to happen 'under the scenes' for instanceof to work. I checked online and found a few places where someone said instanceof is slow; presumably due to reflection required to compare the parent of an object?

This lead to the next question, what about just casting. If I cast something as an object it's not I get a ClassCastException. But this doesn't happen if a cast an object to a parent of itself. Essentially I'm doing an instanceof call, or logic to that effect, when I do the cast at run time am I not? I've never heard anyone hint that casting an object could be slow before. Admittedly not all casts are to a parent of the provided object, but plenty of casts are to parent classes. Yet never has anyone hinted at all that this could be slow.

So which is it. Is instanceof really not that slow? Are both instanceof and casting to parent class kind of slow? or is there some reason a cast can be done faster then an instanceof call?


回答1:


As always try it and see in your particular situations, but:

-Exceptions are expensive, remarkably so.

-Using exceptions for code flow is almost always a bad idea

Edit: Ok I was interested so I wrote a quick test system

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{

    }        
}

Profile results:

by InstanceOf: 4.43 ms
by Exception: 79.4 ms

as I say, remarkably expensive

And even when its always going to be a B (simulating when you're 99% sure its B you just need to be sure its still no faster:

Profile results when always B:

by InstanceOf: 4.48 ms
by Exception: 4.51 ms



回答2:


There is a general answer and a particular answer.

General case

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 */
}

It is a fact that creating/throwing/catching an exception is expensive. And they are likely to be significantly more expensive than doing the test. However, that doesn't mean that the "test first" version is always faster. This is because in the "test first" version, the tests may actually be performed: first time in the if, and second time in the code that would throw the exception.

When you take that into account, it is clear that if the cost of the (extra) test is large enough and the relative frequency of exceptions is small enough, "test first" will actually be slower. For example, in:

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

versus

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

the "test first" approach performs 2 extra system calls, and system calls are expensive. If the "missing file" scenario is also unusual ....

The second confounding factor is that the most recent HotSpot JIT compilers do some significant optimization of exceptions. In particular, if the JIT compiler can figure out that the state of the exception object is not used, it may turn the exception create/throw/catch into a simple jump instruction.

The specific case of instanceof

In this case we are most likely comparing these two:

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

// versus

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

Here a second optimization occurs. An instanceof followed by a type cast is a common pattern. A HotSpot JIT compiler can often eliminate the dynamic type check that is performed by the type cast ... since this is repeating a test that just succeeded. When you factor this in, it the "test first" version cannot be slower than the "exception" version ... even if the latter is optimized to a jump.




回答3:


Unfortunately, Richard's code can't be run directly to produce timings. I've modified the question slightly, assuming that you really want "if A is a B, do something on B", rather than just asking the question "is A a B?". Here is the test code.

UPDATED FROM ORIGINAL It was rather challenging to write trivial code that the HotSpot compiler didn't reduce to nothing, but I think the following is good:

package net.redpoint.utils;
public class Scratch {
    public long counter = 0;
    public class A { 
        public void inc() { counter++; } 
    }
    public class B extends A { 
        public void inc() { counter++; } 
    }
    public class C extends A {
        public void inc() { counter++; } 
    } 
    public A[] a = new A[3];
    public void test() {
        a[0] = new A();
        a[1] = new B();
        a[2] = new C();
        int iter = 100000000;
        long start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingInstanceOf(a[i%3]);
        }
        long end = System.nanoTime();
        System.out.println("instanceof: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingException(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("try{}: " + iter / ((end - start) / 1000000000.0) + " per second");

        start = System.nanoTime();
        for(int i = iter; i > 0; i--) {
            testUsingClassName(a[i%3]);
        }
        end = System.nanoTime();
        System.out.println("classname: " + iter / ((end - start) / 1000000000.0) + " per second");
    }

    public static void main(String[] args) {
        Scratch s = new Scratch();
        s.test();
    }

    public void testUsingInstanceOf(A possiblyB){
        if (possiblyB instanceof B){
            ((B)possiblyB).inc();
        }
    }

    public void testUsingException(A possiblyB){
        try{
            ((B)possiblyB).inc();
        } catch(Exception e){
        }
    }

    public void testUsingClassName(A possiblyB){
        if (possiblyB.getClass().getName().equals("net.redpoint.utils.Scratch$B")){
            ((B)possiblyB).inc();
        }
    }
}

The resulting output:

instanceof: 4.573174070960945E8 per second
try{}: 3.926650051387284E8 per second
classname: 7.689439655530204E7 per second

Test was performed using Oracle JRE7 SE on Windows 8 x64, with Intel i7 sandy bridge CPU.




回答4:


If the cast can throw an exception, it means that it's doing implicitly what instanceof would do. So, in both cases you are implicitly using reflection, probably in exactly the same way.

The difference is that if the result of instanceof comes back false, no more reflection is happening. If a cast fails and an exception is thrown, you'll have unrolling of the execution stack and, quite possibly, more reflection (for the runtime to determine the correct catch block, based on whether the thrown exception object is an instance of the type to be caught).

The above logic tells me that an instanceof check should be faster. Of course, benchmarking for your particular case would give you the definitive answer.




回答5:


I don't think you would find one being clearly better than the other.

For instanceof, the work done use memory and cpu time. Creating a exception use memory and cpu time also. Which use less of each, only a well done benchmark would give you that answer.

Coding-wise, I would prefer to see a instanceof rather than casting and having to manage exceptions.



来源:https://stackoverflow.com/questions/16320014/java-optimization-nitpick-is-it-faster-to-cast-something-and-let-it-throw-excep

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!