java优化nitpick:在投射之前调用instanceof进行检查是否更快投出一些东西并让它抛出exception?

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

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

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

我通常认为instanceof与普通类检查一样快,但今天我想到了它,似乎有些reflection必须在“幕后”发生才能工作。 我在网上查了一下,发现有人说instanceof很慢; 大概是因为比较物体的父母所需的reflection?

这导致了下一个问题,即仅仅是铸造。 如果我将某些东西作为对象投射,那么我不会得到ClassCastException 。 但是,如果将对象转换为自身的父对象,则不会发生这种情况。 基本上我正在做一个调用的instanceof ,或者当我在运行时进行演员时,我正在做这个效果的逻辑吗? 我从来没有听到过任何人暗示过投射物体之前可能会很慢。 不可否认,并非所有演员都是提供对象的父级,但很多演员都是父类。 然而,从来没有人暗示这可能会很慢。

那是哪个呢。 真的不是那么慢吗? instanceof和cast都是父类慢吗? 或者是否有某种原因可以比调用instanceof更快地完成演员表?

一如既往地尝试并在您的特定情况下看到,但是:

– 例外是昂贵的,非常明显。

– 使用代码流的exception几乎总是一个坏主意

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

 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 

有一般答案和特定答案。

一般情况

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

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

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

 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编译器对exception进行了一些重要的优化。 特别是,如果JIT编译器可以确定不使用exception对象的状态,它可以将exceptioncreate / throw / catch转换为简单的跳转指令。

instanceof的具体情况

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

 if (o instanceof Foo) { Foo f = (Foo) o; /* ... */ } // versus try { Foo f = (Foo) o; } catch (ClassCastException ex) { /* */ } 

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

不幸的是,Richard的代码无法直接运行以产生时序。 我稍微修改了这个问题,假设你真的想要“如果A是B,在B上做点什么”,而不仅仅是问“A是B吗?”。 这是测试代码。

从原始版本更新编写简单的代码使HotSpot编译器没有减少到任何东西是相当具有挑战性的,但我认为以下是好的:

 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(); } } } 

结果输出:

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

使用带有Intel i7沙桥CPU的Windows 8 x64上的Oracle JRE7 SE进行测试。

如果强制转换可以抛出exception,则意味着它隐含地执行了instanceof会做什么。 因此,在这两种情况下,您都隐式使用reflection,可能采用完全相同的方式。

不同之处在于,如果instanceof的结果返回false ,则不再发生reflection。 如果转换失败并抛出exception,您将展开执行堆栈,并且很可能会有更多的reflection(运行时根据抛出的exception对象是否是该类型的实例来确定正确的catch块)被抓住了。

上面的逻辑告诉我,检查的instanceof应该更快。 当然,针对您的特定情况的基准测试将为您提供明确的答案。

我不认为你会发现一个明显优于另一个。

instanceof ,完成的工作使用内存和CPU时间。 创建exception也会使用内存和CPU时间。 哪个使用较少的每个,只有一个做得好的基准将给你答案。

编码方面,我更愿意看到一个instanceof而不是强制转换并且必须管理exception。