我正在开发一个应用程序,其中一个设计方法涉及到大量使用instanceof操作符。虽然我知道OO设计通常试图避免使用instanceof,但那是另一回事,这个问题纯粹与性能有关。我想知道是否对性能有影响?和==一样快吗?
例如,我有一个有10个子类的基类。在接受基类的单个函数中,我检查类是否是子类的实例并执行一些例程。
我想到的另一种解决方法是使用“type id”整数原语,并使用位掩码来表示子类的类别,然后对子类“type id”与表示类别的常量掩码进行位掩码比较。
instanceof是否被JVM优化得更快?我想坚持使用Java,但应用程序的性能至关重要。如果有人曾经在这条路上走过,可以提供一些建议,那就太棒了。我是不是太挑剔了,或者专注在错误的地方去优化?
我有同样的问题,但因为我没有找到类似于我的用例的“性能指标”,我做了一些更多的示例代码。在我的硬件和Java 6和7上,instanceof和switch在1000万次迭代上的区别是
for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes - instanceof: 375ms vs switch: 204ms
因此,instanceof确实比较慢,特别是在大量的if-else-if语句上,但是在实际应用中差异可以忽略不计。
import java.util.Date;
public class InstanceOfVsEnum {
public static int c1, c2, c3, c4, c5, c6, c7, c8, c9, cA;
public static class Handler {
public enum Type { Type1, Type2, Type3, Type4, Type5, Type6, Type7, Type8, Type9, TypeA }
protected Handler(Type type) { this.type = type; }
public final Type type;
public static void addHandlerInstanceOf(Handler h) {
if( h instanceof H1) { c1++; }
else if( h instanceof H2) { c2++; }
else if( h instanceof H3) { c3++; }
else if( h instanceof H4) { c4++; }
else if( h instanceof H5) { c5++; }
else if( h instanceof H6) { c6++; }
else if( h instanceof H7) { c7++; }
else if( h instanceof H8) { c8++; }
else if( h instanceof H9) { c9++; }
else if( h instanceof HA) { cA++; }
}
public static void addHandlerSwitch(Handler h) {
switch( h.type ) {
case Type1: c1++; break;
case Type2: c2++; break;
case Type3: c3++; break;
case Type4: c4++; break;
case Type5: c5++; break;
case Type6: c6++; break;
case Type7: c7++; break;
case Type8: c8++; break;
case Type9: c9++; break;
case TypeA: cA++; break;
}
}
}
public static class H1 extends Handler { public H1() { super(Type.Type1); } }
public static class H2 extends Handler { public H2() { super(Type.Type2); } }
public static class H3 extends Handler { public H3() { super(Type.Type3); } }
public static class H4 extends Handler { public H4() { super(Type.Type4); } }
public static class H5 extends Handler { public H5() { super(Type.Type5); } }
public static class H6 extends Handler { public H6() { super(Type.Type6); } }
public static class H7 extends Handler { public H7() { super(Type.Type7); } }
public static class H8 extends Handler { public H8() { super(Type.Type8); } }
public static class H9 extends Handler { public H9() { super(Type.Type9); } }
public static class HA extends Handler { public HA() { super(Type.TypeA); } }
final static int cCycles = 10000000;
public static void main(String[] args) {
H1 h1 = new H1();
H2 h2 = new H2();
H3 h3 = new H3();
H4 h4 = new H4();
H5 h5 = new H5();
H6 h6 = new H6();
H7 h7 = new H7();
H8 h8 = new H8();
H9 h9 = new H9();
HA hA = new HA();
Date dtStart = new Date();
for( int i = 0; i < cCycles; i++ ) {
Handler.addHandlerInstanceOf(h1);
Handler.addHandlerInstanceOf(h2);
Handler.addHandlerInstanceOf(h3);
Handler.addHandlerInstanceOf(h4);
Handler.addHandlerInstanceOf(h5);
Handler.addHandlerInstanceOf(h6);
Handler.addHandlerInstanceOf(h7);
Handler.addHandlerInstanceOf(h8);
Handler.addHandlerInstanceOf(h9);
Handler.addHandlerInstanceOf(hA);
}
System.out.println("Instance of - " + (new Date().getTime() - dtStart.getTime()));
dtStart = new Date();
for( int i = 0; i < cCycles; i++ ) {
Handler.addHandlerSwitch(h1);
Handler.addHandlerSwitch(h2);
Handler.addHandlerSwitch(h3);
Handler.addHandlerSwitch(h4);
Handler.addHandlerSwitch(h5);
Handler.addHandlerSwitch(h6);
Handler.addHandlerSwitch(h7);
Handler.addHandlerSwitch(h8);
Handler.addHandlerSwitch(h9);
Handler.addHandlerSwitch(hA);
}
System.out.println("Switch of - " + (new Date().getTime() - dtStart.getTime()));
}
}
将决定性能影响的项目有:
The number of possible classes for which the instanceof operator could return true
The distribution of your data - are most of the instanceof operations resolved in the first or second attempt? You'll want to put your most likely to return true operations first.
The deployment environment. Running on a Sun Solaris VM is significantly different than Sun's Windows JVM. Solaris will run in 'server' mode by default, while Windows will run in client mode. The JIT optimizations on Solaris, will make all method access able the same.
我为四种不同的分派方法创建了一个微基准测试。Solaris的结果如下所示,数值越小越快:
InstanceOf 3156
class== 2925
OO 3083
Id 3067
方法
我写了一个基准程序来评估不同的实现:
实例实现(作为参考)
通过抽象类和@Override测试方法实现面向对象
使用自己的类型实现
getClass() == _.class实现
我使用jmh运行了100个预热调用、1000个测试迭代和10个分支的基准测试。因此,每个选项都测量了10,000次,在我的MacBook Pro上运行macOS 10.12.4和Java 1.8的整个基准测试需要12:18:57。该基准衡量每个选项的平均时间。要了解更多细节,请参阅我在GitHub上的实现。
为了完整起见:这个答案和我的基准有一个以前的版本。
结果
| Operation | Runtime in nanoseconds per operation | Relative to instanceof |
|------------|--------------------------------------|------------------------|
| INSTANCEOF | 39,598 ± 0,022 ns/op | 100,00 % |
| GETCLASS | 39,687 ± 0,021 ns/op | 100,22 % |
| TYPE | 46,295 ± 0,026 ns/op | 116,91 % |
| OO | 48,078 ± 0,026 ns/op | 121,42 % |
博士tl;
在Java 1.8中,instanceof是最快的方法,尽管getClass()非常接近。
在大多数现实世界的实现中(也就是说,在真正需要instanceof的实现中,您不能像每个初学者教科书和上面的Demian所建议的那样,通过重写一个通用方法来解决它),instanceof可能会比简单的等号更昂贵。
为什么呢?因为可能会发生的情况是,你有几个接口,它们提供了一些功能(比如,接口x, y和z),以及一些要操作的对象,这些对象可能(或不)实现其中一个接口……但不是直接的。例如,我有:
W扩展x
A实现了w
B延伸A
C扩展B,实现y
D扩展C,实现z
假设我正在处理D的一个实例,对象D . Computing (D instanceof x)需要采用d.getClass(),循环通过它实现的接口来知道是否一个是==到x,如果不是这样做,再次递归为它们的所有祖先…
在我们的例子中,如果你对那棵树进行宽度优先的探索,假设y和z没有扩展任何东西,至少会产生8个比较……
现实世界的推导树的复杂性可能更高。在某些情况下,JIT可以优化其中的大部分,如果它能够在所有可能的情况下,将d解析为扩展x的某个实例。然而,实际上,您大部分时间都将遍历该树。
如果这成为一个问题,我建议使用处理程序映射,将对象的具体类链接到进行处理的闭包。它删除了树遍历阶段,以支持直接映射。但是,要注意,如果你为C.class设置了一个处理程序,上面的对象d将不会被识别。
这是我的2美分,我希望他们能帮助…