在Java中使用instanceof的性能影响

Jos*_*osh 299 java performance instanceof

我正在开发一个应用程序,一种设计方法涉及对instanceof操作员的极大使用.虽然我知道OO设计通常会试图避免使用instanceof,但这是一个不同的故事,这个问题纯粹与性能有关.我想知道是否有任何性能影响?是这么快==吗?

例如,我有一个包含10个子类的基类.在一个获取基类的函数中,我会检查该类是否是子类的实例并执行一些例程.

我想解决它的另一种方法是使用"type id"整数原语,并使用位掩码来表示子类的类别,然后只需对子类"type id"进行掩码比较.表示类别的常量掩码.

instanceof莫名其妙地由JVM优化得比较快?我想坚持使用Java,但应用程序的性能至关重要.如果之前一直走在这条路上的人可以提供一些建议,那将会很酷.我是在挑剔太多还是专注于错误的事情来优化?

Ste*_*eve 260

现代JVM/JIC编译器已经消除了大多数传统上"慢"操作的性能损失,包括实例,异常处理,反射等.

正如唐纳德·克努特(Donald Knuth)所写的那样,"我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源." instanceof的性能可能不会成为一个问题,所以不要浪费你的时间来提出异乎寻常的解决方法,直到你确定这是问题为止.

  • 总是有人在表演是主题时引用Knuth ......忘记了,Knuth也说过(在同一篇文章中)"在已建立的工程学科中,容易获得12%的改进,从未被认为是边缘的,我相信同样的观点应该在软件工程中占上风,"他几乎所有的工作都是关于算法的效率,他在汇编中编写算法(除其他外)以获得更好的性能.咩... (131认同)
  • 如果"object"是ObjT的一个实例,那么投射它比执行一个实例要快一点,但我的快速测试发现的差异是10,000,000次迭代的10-20ms.但是,如果"对象"不是ObjT,那么捕获异常的速度要慢3000倍 - 超过31,000毫秒vs10毫秒的实例. (35认同)
  • 如此强烈的争论没有任何"参考",完全没用,因为只是自以为是. (19认同)
  • 现代JVM/JIC ..请您提一下这些优化的java版本? (13认同)
  • 在这里,但是`试试{ObjT o =(ObjT)对象} catch(e){不,不是其中之一}`会慢得多吗? (4认同)
  • 请来源? (2认同)
  • 您应该始终回答一个问题。不只是告诉他们不要这样做。 (2认同)

Mic*_*ner 260

途径

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

  1. instanceof 实施(作为参考)
  2. 通过抽象类和@Override测试方法定向的对象
  3. 使用自己的类型实现
  4. getClass() == _.class 履行

我使用jmh运行基准测试,有100个热身调用,1000个迭代测量,10个分叉.所以每个选项都测量了10000次,需要12:18:57才能在我的MacBook Pro上使用macOS 10.12.4和Java 1.8运行整个基准测试.基准测量每个选项的平均时间.有关更多详细信息,请参阅我在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()非常接近.

  • `+0.(9)`科学! (55认同)
  • +来自我的另一个:D (15认同)
  • @TobiasReich所以我们得到了`+1.0(9)`.:) (13认同)
  • 我认为这根本没有任何意义.代码使用`System.currentTimeMillis()`对一个操作进行测量,该操作不仅仅是单个方法调用,这应该会给低精度带来很大的影响.使用基准框架,例如[JMH](http://openjdk.java.net/projects/code-tools/jmh/)! (8认同)
  • 或者只是做整个十亿次通话的时间而不是每次通话. (6认同)
  • 好的,给你另一个奖励.每个人都有足够的"+ 1"!:) (3认同)
  • 这应该是公认的答案。实际接受的答案没有回答这个问题。 (3认同)
  • 你是对的,我会尽快使用 OpenJDK 的 JMH 或 Google 的 Caliper 更新我的问题 (2认同)
  • 我在您的测试中看到,对于您执行的每个“instanceof X”,“X”都没有子类型。这可以(理论上)由 JVM 优化(类似于在这种情况下它如何用直接方法调用替换 `invokevirtual`)。使用更深层次的层次结构并包含接口可以提供更准确的结果。但我不希望有明显不同的结果。 (2认同)

小智 73

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

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

我用java jvm 6.

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

使用Integer的.equals()代替字符串给了我相同的结果,只有当我使用== i比instanceOf快20ms(在10.000.000循环中)时

  • 为什么要将instanceof与String.equals()进行比较?如果你想检查你必须的类型object.getClass().equals(SomeType.class) (21认同)
  • instanceOf如何与多态函数调度进行比较? (7认同)
  • 你可以在这里发布代码吗?那将是真棒! (4认同)
  • @marsbear`equals()`不会削减它,因为子类化; 你需要`isAssignableFrom()`. (4认同)

bri*_*gge 19

决定性能影响的项目是:

  1. instanceof运算符可能返回true的可能类的数量
  2. 您的数据分布 - 是在第一次或第二次尝试中解决的大多数操作实例?你最想让你最有可能返回真正的操作.
  3. 部署环境.在Sun Solaris VM上运行与Sun的Windows JVM有很大不同.默认情况下,Solaris将以"服务器"模式运行,而Windows将以客户端模式运行.Solaris上的JIT优化将使所有方法访问都相同.

为四种不同的调度方法创建了一个微基准测试.Solaris的结果如下,较小的数字更快:

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
Run Code Online (Sandbox Code Playgroud)


Ola*_*ock 18

回答你的最后一个问题:除非探查者告诉你,你在一个实例中花费了大量的时间:是的,你是在挑剔.

在想要优化从未需要优化的东西之前:以最易读的方式编写算法并运行它.运行它,直到jit-compiler有机会自己优化它.如果您在使用这段代码时遇到问题,请使用分析器告诉您,从哪里获得最大收益并对其进行优化.

在高度优化编译器的时候,你对瓶颈的猜测可能是完全错误的.

并且本着这个答案的真正精神(我完全相信):一旦jit-compiler有机会优化它,我绝对不知道instanceof和==如何关联.

我忘记了:永远不要测量第一次跑步.


Xtr*_*der 15

我有同样的问题,但因为我没有找到与我的用例类似的"性能指标",我已经做了一些示例代码.在我的硬件和Java 6&7上,instanceof和10mln迭代之间的区别是

for 10 child classes - instanceof: 1200ms vs switch: 470ms
for 5 child classes  - instanceof:  375ms vs switch: 204ms
Run Code Online (Sandbox Code Playgroud)

因此,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()));
    }
}
Run Code Online (Sandbox Code Playgroud)


irr*_*ble 9

instanceof 真的很快,只需要几条CPU指令.

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

     x instanceof X    
==>  x.getClass()==X.class  
==>  x.classID == constant_X_ID
Run Code Online (Sandbox Code Playgroud)

主要成本只是阅读!

如果X确实加载了子类,则需要更多的读取; 它们可能位于同一地点,因此额外成本也非常低.

大家好消息!

  • *可以*优化还是*优化?资源? (2认同)
  • 最近读到 JIT (JVM 8) 将通过直接调用优化 1 或 2 种类型的调用站点,但如果遇到两种以上的实际类型,则会恢复到 vtable。因此,只有在运行时通过调用站点的两个具体类型代表了性能优势。 (2认同)

Apo*_*isp 5

Instanceof 非常快。它归结为用于类引用比较的字节码。在循环中尝试几百万个 instanceof 并亲眼看看。


小智 5

在大多数现实世界的实现中,instanceof可能比简单的equals更昂贵(也就是真正需要instanceof的那些,你不能通过覆盖常用方法来解决它,比如每个初学者教科书以及上面的Demian建议).

这是为什么?因为可能会发生的事情是你有几个接口,提供一些功能(比方说,接口x,y和z),以及一些操纵的对象,可能(或不)实现其中一个接口...但是不直接.比方说,我有:

w延伸x

工具w

B延伸A.

C扩展B,实现y

D扩展C,实现z

假设我正在处理对象d的D实例.计算(d instanceof x)需要采用d.getClass(),循环通过它实现的接口来知道一个是否是==到x,如果不是再次递归地为它们的所有祖先...在我们的例子中,如果你对该树做广泛的第一次探索,产生至少8次比较,假设y和z不扩展任何东西......

现实世界派生树的复杂性可能更高.在某些情况下,JIT可以优化其中的大部分,如果它能够在所有可能的情况下提前解析为扩展x的某个实例.然而,实际上,您将在大多数时间通过树遍历.

如果这成为一个问题,我建议使用处理程序映射,将对象的具体类链接到执行处理的闭包.它删除了树遍历阶段,支持直接映射.但是,请注意,如果您为C.class设置了处理程序,则无法识别上面的对象d.

这是我的2美分,我希望他们帮助...


Pet*_*rey 5

instanceof 非常有效,因此您的性能不太可能受到影响。但是,使用大量 instanceof 表明存在设计问题。

如果您可以使用 xClass == String.class,则速度会更快。注意:final 类不需要 instanceof。


小智 5

我写了一个基于 jmh-java-benchmark-archetype:2.21 的性能测试。JDK 为 openjdk,版本为 1.8.0_212。测试机是mac pro。测试结果是:

Benchmark                Mode  Cnt    Score   Error   Units
MyBenchmark.getClasses  thrpt   30  510.818 ± 4.190  ops/us
MyBenchmark.instanceOf  thrpt   30  503.826 ± 5.546  ops/us
Run Code Online (Sandbox Code Playgroud)

结果表明:getClass优于instanceOf,这与其他测试相反。但是,我不知道为什么。

测试代码如下:

public class MyBenchmark {

public static final Object a = new LinkedHashMap<String, String>();

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean instanceOf() {
    return a instanceof Map;
}

@Benchmark
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean getClasses() {
    return a.getClass() == HashMap.class;
}

public static void main(String[] args) throws RunnerException {
    Options opt =
        new OptionsBuilder().include(MyBenchmark.class.getSimpleName()).warmupIterations(20).measurementIterations(30).forks(1).build();
    new Runner(opt).run();
}
}
Run Code Online (Sandbox Code Playgroud)