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

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

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

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


当前回答

方法

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

实例实现(作为参考) 通过抽象类和@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()非常接近。

其他回答

将决定性能影响的项目有:

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 

我只是做了一个简单的测试,看看instanceOf的性能如何与对一个只有一个字母的字符串对象的简单s.equals()调用进行比较。

在10.000.000循环中,instanceOf给了我63-96ms,而字符串equals给了我106-230ms

我使用java jvm 6。

所以在我的简单测试中,做一个instanceOf而不是一个字符串比较更快。

使用Integer的.equals()而不是string的给了我相同的结果,只有当我使用== I比instanceOf快20ms(在10.000.000循环中)

InstanceOf是一个糟糕的面向对象设计的警告。

当前的jvm意味着instanceOf本身并不是一个性能问题。如果您发现自己经常使用它,特别是在核心功能方面,那么可能是时候考虑一下设计了。重构为更好的设计所带来的性能(和简单性/可维护性)收益将大大超过实际instanceOf调用所花费的实际处理器周期。

给出一个非常简单的编程示例。

if (SomeObject instanceOf Integer) {
  [do something]
}
if (SomeObject instanceOf Double) {
  [do something different]
}

是一个糟糕的架构,更好的选择是让SomeObject成为两个子类的父类,其中每个子类重写一个方法(doSomething),这样代码看起来就像这样:

Someobject.doSomething();

我有同样的问题,但因为我没有找到类似于我的用例的“性能指标”,我做了一些更多的示例代码。在我的硬件和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()));
    }
}

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

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