我正在开发一个应用程序,其中一个设计方法涉及到大量使用instanceof操作符。虽然我知道OO设计通常试图避免使用instanceof,但那是另一回事,这个问题纯粹与性能有关。我想知道是否对性能有影响?和==一样快吗?

例如,我有一个有10个子类的基类。在接受基类的单个函数中,我检查类是否是子类的实例并执行一些例程。

我想到的另一种解决方法是使用“type id”整数原语,并使用位掩码来表示子类的类别,然后对子类“type id”与表示类别的常量掩码进行位掩码比较。

instanceof是否被JVM优化得更快?我想坚持使用Java,但应用程序的性能至关重要。如果有人曾经在这条路上走过,可以提供一些建议,那就太棒了。我是不是太挑剔了,或者专注在错误的地方去优化?


当前回答

我认为在本页提交一个反例来反驳“instanceof”的普遍共识是值得的。我发现我在一个内循环中有一些代码(在一些历史性的优化尝试中)

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

在SingleItem上调用head()返回的值不变。将代码替换为

seq = seq.head();

让我从269毫秒加速到169ms,尽管事实上在循环中发生了一些相当繁重的事情,比如字符串到双精度转换。当然,加速可能更多是由于消除了条件分支,而不是消除了操作符本身的实例;但我觉得值得一提。

其他回答

我认为在本页提交一个反例来反驳“instanceof”的普遍共识是值得的。我发现我在一个内循环中有一些代码(在一些历史性的优化尝试中)

if (!(seq instanceof SingleItem)) {
  seq = seq.head();
}

在SingleItem上调用head()返回的值不变。将代码替换为

seq = seq.head();

让我从269毫秒加速到169ms,尽管事实上在循环中发生了一些相当繁重的事情,比如字符串到双精度转换。当然,加速可能更多是由于消除了条件分支,而不是消除了操作符本身的实例;但我觉得值得一提。

'instanceof'实际上是一个运算符,就像+或-,我相信它有自己的JVM字节码指令。应该够快了。

我不应该说,如果你有一个开关,你正在测试一个对象是否是某个子类的实例,那么你的设计可能需要重做。考虑将特定于子类的行为下推到子类本身。

方法

我写了一个基准程序来评估不同的实现:

实例实现(作为参考) 通过抽象类和@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检查这个基类的子类)不支持使用“instanceof”操作符的原因是,您应该做的是将操作移动到一个方法中,并为适当的子类重写它。例如,如果你有:

if (o instanceof Class1)
   doThis();
else if (o instanceof Class2)
   doThat();
//...

你可以用

o.doEverything();

然后在Class1中调用“doEverything()”的实现,在Class2中调用“doThat()”,以此类推。

instanceof非常快,只需要几个CPU指令。

显然,如果一个类X没有加载子类(JVM知道),instanceof可以被优化为:

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID

主要的花费只是阅读!

如果X确实加载了子类,则需要更多的读取操作;他们可能住在一起,所以额外的费用也很低。

大家好!