代码之家  ›  专栏  ›  技术社区  ›  Josh

在Java中使用instanceof对性能的影响

  •  282
  • Josh  · 技术社区  · 17 年前

    我正在开发一个应用程序,其中一种设计方法涉及大量使用 instanceof 操作人员虽然我知道OO设计通常会避免使用 运算符 ==

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

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

    23 回复  |  直到 11 年前
        1
  •  312
  •   Michael Dorner Satyanarayana    4 年前

    方法

    a benchmark program 要评估不同的实现,请执行以下操作:

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

    我曾经 jmh my implementation on GitHub .

    为了完整性:有一个 previous version of this answer and my benchmark .

    | 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;博士

    在Java1.8中 尽管如此,这是最快的方法 getClass()

        2
  •  288
  •   Gurwinder Singh    5 年前

    现代JVM/JIT编译器已经消除了大多数传统的“慢”操作的性能影响,包括instanceof、异常处理、反射等。

    正如Donald Knuth所写,“我们应该忘记小效率,比如说97%的时间:过早优化是万恶之源。”instanceof的性能可能不会成为问题,所以在确定问题所在之前,不要浪费时间想出奇异的解决方法。

        3
  •  75
  •   Dan Mihai Ile Dan Mihai Ile    17 年前

    我只是做了一个简单的测试,看看instanceOf performance与对一个只有一个字母的string对象的简单s.equals()调用相比有多好。

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

    我使用了JavaJVM6。

    使用整数的.equals()而不是字符串的结果是相同的,只是当我使用==I时,它比instanceOf快20毫秒(在10.000.000循环中)

        4
  •  20
  •   brianegge    16 年前

    1. instanceof运算符可能返回true的可能类的数目
    2. 数据的分布—大多数操作实例是在第一次还是第二次尝试中解决的?您将希望将最有可能返回真实操作放在第一位。
    3. 部署环境。在Sun Solaris虚拟机上运行与Sun的Windows JVM有很大不同。默认情况下,Solaris将以“服务器”模式运行,而Windows将以客户端模式运行。Solaris上的JIT优化将使所有方法访问都相同。

    我创造了一个 microbenchmark for four different methods of dispatch

    InstanceOf 3156
    class== 2925 
    OO 3083 
    Id 3067 
    
        5
  •  19
  •   Olaf Kock    16 年前

    回答你的最后一个问题:除非档案员告诉你,你在一个瞬间花费了荒谬的时间。F:是的,你在吹毛求疵。

    根据这个答案的真实精神(我完全相信):一旦jit编译器有机会对instanceof和==进行优化,我绝对不知道它们之间的关系。

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

        6
  •  15
  •   Xtra Coder    13 年前

    我也有同样的问题,但因为我并没有找到类似于我的用例的“性能指标”,所以我编写了更多的示例代码。在我的硬件和Java 6&7,instanceof和switch on 10mln迭代之间的差异为

    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()));
        }
    }
    
        7
  •  12
  •   irreputable    14 年前

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

    显然,如果一节课 X 没有加载子类(JVM知道), 运算符 可以优化为:

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

    主要成本只是一个阅读!

    X 没有加载子类,需要更多的读取;它们可能位于同一地点,因此额外成本也非常低。

        8
  •  6
  •   Varkhan Varkhan    17 年前

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

    为什么呢?因为可能会发生的是,您有几个接口,它们提供一些功能(比如说,接口x、y和z),以及一些要操作的对象,这些对象可能(或不)实现其中一个接口。。。但不是直接的。比如说,我有:

    w延伸x

    A工具w

    B扩展A

    C扩展B,实现y

    假设我正在处理一个D的实例,对象D。计算(d instanceof x)需要取d.getClass(),通过它实现的接口循环,以知道一个是否为==到x,如果不是,则对其所有祖先再次递归执行此操作。。。 在我们的例子中,如果你对这棵树进行广度优先的探索,假设y和z没有扩展任何东西,那么至少会得到8个比较结果。。。

    真实世界的派生树的复杂性可能更高。在某些情况下,如果JIT能够在所有可能的情况下将d预先解析为扩展x的某个实例,那么JIT可以优化其中的大部分。然而,实际上,大多数情况下,您都将经历树遍历。

        9
  •  5
  •   Apocalisp    17 年前

        10
  •  5
  •   Peter Lawrey    17 年前

    然而,使用大量instanceof表明存在设计问题。

        11
  •  5
  •   salexinx    6 年前

    我基于jmh java基准原型2.21编写了一个性能测试。JDK是openjdk,版本是1.8.0_212。测试机器是MacPro。 测试结果如下:

    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
    

    结果表明: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();
    }
    }
    
        12
  •  4
  •   Paul Tomblin    17 年前

    通常,在这种情况下(instanceof正在检查此基类的子类),不赞成使用“instanceof”运算符的原因是,您应该将操作移动到方法中,并为适当的子类重写它。例如,如果您有:

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

    你可以用

    o.doEverything();
    

    然后在类1中调用“doThis()”,在类2中调用“doThat()”,等等,实现“doEverything()”。

        13
  •  4
  •   Tim Frey    17 年前

    “instanceof”实际上是一个操作符,比如+或-,我相信它有自己的JVM字节码指令。它应该足够快。

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

        14
  •  4
  •   Mecki    17 年前

    if (o instanceof java.lang.String)
    

    可能与下面的C代码一样快

    if (objectStruct->iAmInstanceOf == &java_lang_String_class)
    

    假设一个JIT编译器已经就位并且做得很好。

    不过,这并不一定非得如此,这在很大程度上取决于JVM。但是,如果这将成为代码中的瓶颈操作,我会认为JVM实现相当差。即使是一个没有JIT编译器并且只解释代码的系统,也应该能够在几乎没有时间的情况下进行instanceof测试。

        15
  •  4
  •   Scott Stanchfield    17 年前

    德米安和保罗提到了一个很好的观点; 然而

    我非常喜欢小数据对象,这些小数据对象可以以多种方式使用。如果遵循覆盖(多态)方法,则对象只能“单向”使用。

    这就是模式的由来。。。

    您可以使用双重分派(就像在访问者模式中一样)来要求每个对象“调用”传递自己——这将解析对象的类型。 然而 (再次)您将需要一个可以“处理”所有可能的子类型的类。

    我更喜欢使用策略模式,您可以为要处理的每个子类型注册策略。类似下面的内容。请注意,这只对精确的类型匹配有帮助,但其优点是它是可扩展的——第三方贡献者可以添加自己的类型和处理程序。(这对于OSGi这样的动态框架很好,可以添加新的bundle)

    package com.javadude.sample;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class StrategyExample {
        static class SomeCommonSuperType {}
        static class SubType1 extends SomeCommonSuperType {}
        static class SubType2 extends SomeCommonSuperType {}
        static class SubType3 extends SomeCommonSuperType {}
    
        static interface Handler<T extends SomeCommonSuperType> {
            Object handle(T object);
        }
    
        static class HandlerMap {
            private Map<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>> handlers_ =
                new HashMap<Class<? extends SomeCommonSuperType>, Handler<? extends SomeCommonSuperType>>();
            public <T extends SomeCommonSuperType> void add(Class<T> c, Handler<T> handler) {
                handlers_.put(c, handler);
            }
            @SuppressWarnings("unchecked")
            public <T extends SomeCommonSuperType> Object handle(T o) {
                return ((Handler<T>) handlers_.get(o.getClass())).handle(o);
            }
        }
    
        public static void main(String[] args) {
            HandlerMap handlerMap = new HandlerMap();
    
            handlerMap.add(SubType1.class, new Handler<SubType1>() {
                @Override public Object handle(SubType1 object) {
                    System.out.println("Handling SubType1");
                    return null;
                } });
            handlerMap.add(SubType2.class, new Handler<SubType2>() {
                @Override public Object handle(SubType2 object) {
                    System.out.println("Handling SubType2");
                    return null;
                } });
            handlerMap.add(SubType3.class, new Handler<SubType3>() {
                @Override public Object handle(SubType3 object) {
                    System.out.println("Handling SubType3");
                    return null;
                } });
    
            SubType1 subType1 = new SubType1();
            handlerMap.handle(subType1);
            SubType2 subType2 = new SubType2();
            handlerMap.handle(subType2);
            SubType3 subType3 = new SubType3();
            handlerMap.handle(subType3);
        }
    }
    
        16
  •  3
  •   Jose Quijada    8 年前

    我会马上给你回电话。但一种完全避免问题(或缺少问题)的方法是为所有需要执行instanceof的子类创建父接口。该接口将是一组超级 需要对其执行instanceof check的子类中的方法。如果方法不适用于特定的子类,只需提供此方法的虚拟实现。如果我没有误解这个问题,我过去就是这样绕过这个问题的。

        17
  •  2
  •   Horcrux7    17 年前

    在现代Java版本中,instanceof操作符作为一个简单的方法调用速度更快。这意味着:

    if(a instanceof AnyObject){
    }
    

    if(a.getType() == XYZ){
    }
    

    另一件事是,如果需要级联多个instanceof。那么只调用一次getType()的开关速度更快。

        18
  •  1
  •   Demian Krige    17 年前

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

    瞬间 这本身并不是什么性能问题。如果您发现自己经常使用它,特别是在核心功能方面,那么可能是时候看看设计了。重构到更好的设计所带来的性能(以及简单性/可维护性)收益将大大超过在实际设计上花费的任何实际处理器周期 瞬间 呼叫

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

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

    Someobject.doSomething();
    
        19
  •  1
  •   Salix alba    14 年前

    若速度是你们唯一的目标,那个么使用int常量来识别子类似乎只需要几毫秒的时间

    static final int ID_A = 0;
    static final int ID_B = 1;
    abstract class Base {
      final int id;
      Base(int i) { id = i; }
    }
    class A extends Base {
     A() { super(ID_A); }
    }
    class B extends Base {
     B() { super(ID_B); }
    }
    ...
    Base obj = ...
    switch(obj.id) {
    case  ID_A: .... break;
    case  ID_B: .... break;
    }
    

    糟糕的OO设计,但如果您的性能分析表明这就是您的瓶颈所在,那么可能。在我的代码中,分派代码占总执行时间的10%,这可能有助于总速度提高1%。

        20
  •  0
  •   Karl    17 年前

    如果这真的是项目中的一个性能问题,您应该对其进行测量/分析。如果可能的话,我建议重新设计。我敢肯定,您无法打败平台的本机实现(用C编写)。在这种情况下,您还应该考虑多重继承。

    您应该更多地了解这个问题,也许您可以使用关联存储,例如地图<类、对象>如果你只对混凝土类型感兴趣。

        21
  •  0
  •   Miles Elam Miles Elam    16 年前

        22
  •  0
  •   mike    12 年前

    我也喜欢enum方法,但我会使用抽象基类强制子类实现 getType() 方法

    public abstract class Base
    {
      protected enum TYPE
      {
        DERIVED_A, DERIVED_B
      }
    
      public abstract TYPE getType();
    
      class DerivedA extends Base
      {
        @Override
        public TYPE getType()
        {
          return TYPE.DERIVED_A;
        }
      }
    
      class DerivedB extends Base
      {
        @Override
        public TYPE getType()
        {
          return TYPE.DERIVED_B;
        }
      }
    }
    
        23
  •  0
  •   Michael Kay    8 年前

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

    其中,对单个项调用head()将返回未更改的值。将代码替换为

    seq = seq.head();
    

        24
  •  -4
  •   tloach    17 年前

    你把注意力放错地方了。instanceof和任何其他检查同一事物的方法之间的差异甚至可能无法测量。如果性能至关重要,那么Java可能是错误的语言。主要原因是,您无法控制VM何时决定要去收集垃圾,在一个大型程序中,这会使CPU在几秒钟内达到100%(MagicDraw 10非常适合)。除非你控制着这个程序将要运行的每台计算机,否则你无法保证它将运行在哪个版本的JVM上,而且许多旧版本的JVM都有严重的速度问题。如果它是一个小应用程序,您可能可以使用Java,但如果您不断地读取和丢弃数据,那么您就可以使用Java 请注意GC何时启动。