在Java中使用instanceof的性能影响

时间:2008-09-19 16:41:12

标签: java performance instanceof

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

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

我想解决它的其他方法之一是使用“type id”整数原语,并使用位掩码来表示子类的类别,然后只进行子类“类型ID”的位掩码比较“到代表该类别的常量掩码。

JVM以某种方式优化instanceof是否比这更快?我想坚持使用Java,但应用程序的性能至关重要。如果之前一直走在这条路上的人可以提供一些建议,那将会很酷。我是在挑剔太多还是专注于错误的事情进行优化?

24 个答案:

答案 0 :(得分:248)

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

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

答案 1 :(得分:240)

方法

我写了a benchmark program来评估不同的实现:

  1. instanceof实施(作为参考)
  2. 通过抽象类定向的对象和@Override测试方法
  3. 使用自己的类型实现
  4. getClass() == _.class实施
  5. 我使用jmh运行基准测试,包括100次预热调用,1000次迭代测量,以及10次分叉。所以每个选项都测量了10000次,需要12:18:57才能在我的MacBook Pro上使用macOS 10.12.4和Java 1.8运行整个基准测试。基准测量每个选项的平均时间。有关详细信息,请参阅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;博士

    在Java 1.8中instanceof是最快的方法,尽管getClass()非常接近。

答案 2 :(得分:72)

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

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

我使用java jvm 6。

因此,在我的简单测试中,更快地执行instanceOf而不是一个字符串比较。

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

答案 3 :(得分:18)

决定性能影响的项目是:

  1. instanceof运算符可以返回true的可能类的数量
  2. 您的数据分布 - 是在第一次或第二次尝试中解决的大多数操作实例?你最想让你最有可能返回真正的操作。
  3. 部署环境。在Sun Solaris VM上运行与Sun的Windows JVM有很大不同。默认情况下,Solaris将以“服务器”模式运行,而Windows将以客户端模式运行。 Solaris上的JIT优化将使所有方法访问都相同。
  4. 我创建了microbenchmark for four different methods of dispatch。 Solaris的结果如下,较小的数字更快:

    InstanceOf 3156
    class== 2925 
    OO 3083 
    Id 3067 
    

答案 4 :(得分:16)

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

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

在高度优化编译器时,您对瓶颈的猜测可能完全错误。

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

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

答案 5 :(得分:13)

我有同样的问题,但由于我没有找到与我的用例相似的“性能指标”,我已经做了一些示例代码。在我的硬件和Java 6& 7,instanceof和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()));
    }
}

答案 6 :(得分:9)

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

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

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

主要成本只是阅读!

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

大家好消息!

答案 7 :(得分:5)

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

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

w extends 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美分,我希望他们帮助......

答案 8 :(得分:4)

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

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

答案 9 :(得分:4)

Instanceof非常快。它归结为一个字节码,用于类参考比较。在一个循环中尝试几百万个instanceofs并亲自看看。

答案 10 :(得分:4)

德米安和保罗提到了一个好点; 然而,执行代码的位置实际上取决于您希望如何使用数据......

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

这就是模式的来源......

您可以使用双重调度(如在访问者模式中)要求每个对象“调用您”传递自身 - 这将解析对象的类型。 然而(再次)你需要一个可以用所有可能的子类型“做事”的类。

我更喜欢使用策略模式,您可以在其中为要处理的每个子类型注册策略。像下面这样的东西。请注意,这仅对精确类型匹配有帮助,但具有可扩展性的优势 - 第三方贡献者可以添加自己的类型和处理程序。 (这适用于像OSGi这样的动态框架,可以添加新的捆绑包)

希望这会激发其他一些想法...

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);
    }
}

答案 11 :(得分:4)

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

如果你可以使用xClass == String.class,这会更快。注意:最终类不需要instanceof。

答案 12 :(得分:3)

我将以性能为例回复您。但是,完全避免问题(或缺少问题)的方法是为您需要执行instanceof的所有子类创建父接口。该接口将是一个超级所有子类中的方法,您需要对其进行instanceof检查。如果方法不适用于特定的子类,只需提供此方法的虚拟实现。如果我没有误解这个问题,这就是我过去常常遇到的问题。

答案 13 :(得分:3)

很难说某个JVM如何实现实例,但在大多数情况下,对象与结构和类似,并且每个对象结构都有一个指向它是其实例的类结构的指针。实际上是

的实例
if (o instanceof java.lang.String)

可能与以下C代码一样快

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

假设有一个JIT编译器并且做得不错。

考虑到这只是访问一个指针,指针指向一个特定的偏移指针并将其与另一个指针进行比较(这与测试32位数相等基本相同),我会说操作实际上可以非常快。

但是,它并不一定非常依赖于JVM。但是,如果这会成为代码中的瓶颈操作,我会认为JVM实现相当差。即使是没有JIT编译器且只解释代码的人也应该能够在几乎所有时间内进行测试实例。

答案 14 :(得分:3)

InstanceOf 是对面向对象设计不佳的警告。

当前的JVM确实意味着 instanceOf 本身并不是一个性能上的担忧。如果你发现自己经常使用它,特别是核心功能,那么可能是时候看一下设计了。重构到更好设计的性能(以及简单性/可维护性)增益将大大超过实际 instanceOf 调用所花费的实际处理器周期。

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

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

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

Someobject.doSomething();

答案 15 :(得分:2)

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

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

您可以用

替换它
o.doEverything();

然后在Class1调用“doThis()”中执行“doEverything()”,在Class2调用“doThat()”中执行,依此类推。

答案 16 :(得分:2)

在现代Java版本中,instanceof运算符作为简单的方法调用更快。这意味着:

if(a instanceof AnyObject){
}

更快:

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

另一件事是,如果你需要级联许多instanceof。然后只调用一次getType()的开关更快。

答案 17 :(得分:1)

如果速度是你唯一的目标,那么使用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%。

答案 18 :(得分:1)

我写了一个基于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

结果表明: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();
}
}

答案 19 :(得分:0)

关于Peter Lawrey的说明,你不需要最终类的instanceof,只能使用引用相等,小心!即使最终的类无法扩展,也不能保证它们不会被同一个类加载器加载。只有使用x.getClass()== SomeFinal.class或者它的同类,如果你绝对肯定只有一个类加载器在这段代码中发挥作用。

答案 20 :(得分:0)

我也更喜欢枚举方法,但我会使用抽象基类来强制子类实现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;
    }
  }
}

答案 21 :(得分:0)

如果项目确实存在性能问题,您应该测量/分析。如果是的话,我建议重新设计 - 如果可能的话。我很确定你无法击败平台的原生实现(用C语言编写)。在这种情况下,您还应该考虑多重继承。

您应该详细了解问题,也许您可​​以使用关联商店,例如:地图&lt;类,对象&gt;如果您只对具体类型感兴趣。

答案 22 :(得分:0)

我认为可能值得提交一个反例,就此页面上的普遍共识而言,“instanceof”并不值得担心。我发现我在内循环中有一些代码(在一些历史性的优化尝试中)

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

在SingleItem上调用head()返回值不变。用

替换代码
seq = seq.head();

让我加速从269ms到169ms,尽管循环中发生了一些非常繁重的事情,比如字符串到双重转换。当然,加速更多是由于消除了条件分支而不是消除了instanceof运算符本身;但我认为值得一提。

答案 23 :(得分:-3)

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