在Java中使用instanceof的性能影响

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

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

我想解决这个问题的其他方法之一就是使用“typesid”整数基元,并使用一个位掩码来表示子类的类别,然后仅对子类“type id”进行位掩码比较表示类别的常量掩码。

是由JVM优化的JVM比这更快吗? 我想坚持到Java,但应用程序的性能是至关重要的。 如果以前走过这条路的人可以提供一些build议,那将是很酷的。 我是挑剔太多还是专注于错误的事情来优化?

现代的JVM / JIC编译器已经消除了大多数传统的“慢”操作的性能打击,包括instanceof,exception处理,reflection等等。

正如唐纳德·克努特(Donald Knuth)所写的:“我们应该忘记小效率,大约97%的时间:不成熟的优化是万恶之源。 instanceof的性能可能不会是一个问题,所以不要浪费你的时间来解决exception的解决方法,直到你确定这是问题所在。

途径

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

  1. instanceof实现(作为参考)
  2. 面向对象的抽象类和@Overridetesting方法
  3. 使用自己的types实现
  4. getClass() == _.class实现

我使用jmh来运行100个热身电话的基准testing,1000次迭代测量和10个分支。 所以每个选项都测量了10 000次,这要花费12:18:57才能在我的MacBook Pro上运行macOS 10.12.4和Java 1.8。 基准测量每个选项的平均时间。 有关更多信息,请参阅我在GitHub上的实现 。

为了完整起见: 这个答案和我的基准有一个以前的版本 。

结果

 | 操作| 运行时间为每个操作纳秒 相对于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是最快的方法,尽pipegetClass()非常接近。

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

在10.000.000循环中,instanceOf给了我63-96ms,而string等于给了我106-230ms

我用java jvm 6。

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

使用整数的.equals()而不是string给了我相同的结果,只有当我使用==我比20秒更快的instanceOf(在一个10.000.000循环)

决定绩效影响的项目是:

  1. instanceof操作符可能返回true的可能类的数量
  2. 你的数据分布 – 是第一次还是第二次尝试解决的大多数instanceof操作? 你会想把你最可能返回真正的操作。
  3. 部署环境。 在Sun Solaris VM上运行与Sun的Windows JVM明显不同。 Solaris将默认以“服务器”模式运行,而Windows将以客户端模式运行。 Solaris上的JIT优化将使所有的方法访问都能够完成。

我为四种不同的调度方法创build了一个微基准 。 Solaris的结果如下,数字越小越快:

 InstanceOf 3156 class== 2925 OO 3083 Id 3067 

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

想知道如何优化一些不需要优化的东西:用最易读的方式编写algorithm并运行它。 运行它,直到jit编译器有机会自己优化它。 如果您在这段代码中遇到了问题,可以使用一个分析器来告诉您,哪里可以获得最大的收益并优化它。

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

这个答案的真实精神(我完全相信):一旦jit编译器有机会优化它,我绝对不知道instanceof和==是如何关联的。

我忘了:不要测量第一次运行。

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

instanceof真的很快,只需要几个CPU指令。

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

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

主要成本只是一个阅读!

如果X没有加载子类,则需要更多的读取; 他们可能位于同一地点,所以额外的成本也非常低。

大家好消息!

在大多数真实世界的实现中,instanceof可能比简单的等价物更昂贵(也就是说,真正需要instanceof的东西,你不能通过重写一个通用的方法来解决它,就像每个初学者的教科书一样,德米安以上build议)。

这是为什么? 因为可能发生的事情是你有几个接口,它提供了一些function(比如接口x,y和z),一些对象可以(或不)实现这些接口之一。不直接。 比方说,我有:

w扩展x

一个工具w

B延伸A

C扩展B,实现y

D扩展C,实现z

假设我正在处理一个D的对象d的实例。 计算(d实例x)需要采取d.getClass(),通过它实现的接口循环来知道是否是==为x,如果不是recursion地为所有的祖先recursion…在我们的例子中,如果你首先对树进行广度探索,那么得到至less8个比较结果,假设y和z不包括任何东西…

现实世界推导树的复杂性可能会更高。 在某些情况下,JIT可以将其中的大部分优化掉,如果它能够事先解决,在所有可能的情况下,就是扩展x的某个事例。 然而,实际上,大部分时间你都要经过这棵树的遍历。

如果这成为一个问题,我会build议使用处理程序映射,而不是将对象的具体类链接到处理的闭包。 它移除了树遍历阶段,有利于直接映射。 但是,要小心,如果你已经为C.class设置了一个处理程序,我上面的对象d将不被识别。

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

instanceof是非常高效的,所以你的性能不太可能受到影响。 但是,使用大量的instanceof提出了一个devise问题。

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

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

我不应该说,如果你有一个开关,你在testing一个对象是否是一些子类的实例,那么你的devise可能需要重新devise。 考虑将子类特定的行为向下推入子类本身。

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

很难说某个JVM如何实现实例,但在大多数情况下,对象与结构和类相当,每个对象结构都有一个指向类结构的指针,这是它的一个实例。 所以实际上是为了instanceof

 if (o instanceof java.lang.String) 

可能与以下C代码一样快

 if (objectStruct->iAmInstanceOf == &java_lang_String_class) 

假设JIT编译器已经到位,并且做了一个体面的工作。

考虑到这只是访问一个指针,得到一个指针在一个特定的偏移量指针指向并与另一个指针(这基本上是相同的testing32位数是相同的),我会说操作可以实际上速度非常快。

不过,它并不一定要依赖于JVM。 但是,如果这会成为代码中的瓶颈操作,那么我认为JVM实现相当糟糕。 即使是没有JIT编译器并且只能解释代码的应用程序,也应该能够立即做出实例testing。

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

目前的JVM确实意味着instanceOf本身并不是性能问题。 如果你发现自己使用了很多,特别是核心function,那么现在就应该看看这个devise了。 重构的性能(以及简单性/可维护性)获得更好的devise将大大超过在实际的实例调用上花费的实际处理器周期。

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

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

如果一个糟糕的架构是一个更好的select,那么将SomeObject作为两个子类的父类,其中每个子类重写一个方法(doSomething),所以代码看起来就像这样:

 Someobject.doSomething(); 

德米安和保罗提到一个好点; 然而 ,要执行的代码的放置取决于你想如何使用数据…

我是一个可以在很多方面使用的小数据对象的忠实粉丝。 如果你遵循覆盖(多态)的方法,你的对象只能用“一种方式”。

这是模式进来的地方…

你可以使用double-dispatch(如访问者模式)来请求每个对象“自动调用”,这将解决对象的types。 然而 (再一次),你需要一个可以“可以做”所有可能的子types的类。

我更喜欢使用策略模式,您可以在其中为每个想要处理的子types注册策略。 像下面这样。 请注意,这只对确切的types匹配有帮助,但具有可扩展的优势 – 第三方贡献者可以添加自己的types和处理程序。 (这对OSGi这样的dynamic框架非常有用,可以添加新的包)

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

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

除非你在内部循环,否则我不会担心。

一般来说,为什么在这种情况下(instanceof正在检查这个基类的子类),“instanceof”操作符是为什么呢?这是因为你应该做的是将操作移动到一个方法中,并将其重写子类。 例如,如果你有:

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

你可以用它replace

 o.doEverything(); 

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

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

 if(a instanceof AnyObject){ } 

更快:

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

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

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

可怕的OOdevise,但如果你的性能分析表明这是你瓶颈的地方,那么也许。 在我的代码中,调度代码占总执行时间的10%,这可能导致总速度提高1%。

我将回到你的instanceof性能。 但是一种避免问题(或者完全没有问题)的方法是为所有需要执行instanceof的子类创build一个父接口。 该接口将是您需要执行instanceof检查的子类中的所有方法的超集。 如果方法不适用于特定的子类,则只需提供此方法的虚拟实现。 如果我没有误解这个问题,我就是这样解决了过去的问题。

你应该测量/configuration文件,如果它真的是你的项目中的性能问题。 如果是的话,我build议重新devise – 如果可能的话。 我敢肯定,你不能击败平台的本地实现(用C编写)。 在这种情况下,你也应该考虑多重inheritance。

你应该告诉更多关于这个问题,也许你可以使用一个联合商店,例如一个Map <Class,Object>,如果你只关心具体的types。

关于Peter Lawrey的说明,您不需要finalof的instanceof,只需使用一个引用的等式,小心! 即使最终的类不能被扩展,也不能保证被相同的类加载器加载。 只有使用x.getClass()== SomeFinal.class或者其他方式,如果你绝对肯定的是这个代码段只有一个类加载器在使用。

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

我认为可能值得在这个页面上提供一个反例来说明“instanceof”是不够昂贵的。 我发现我在内部循环中有一些代码(在一些历史性的优化尝试中)

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

在SingleItem上调用head()返回值不变。 通过replace代码

 seq = seq.head(); 

尽pipe事实上在循环中发生了一些相当繁重的事情,例如从string到双精度的转换,但却使我从269ms加速到169ms。 当然,加速可能是由于消除了条件分支而不是消除了instanceof操作符本身; 但我认为这值得一提。

你专注于错误的事情。 instanceof和其他任何检查相同事物的方法之间的区别可能甚至不可测量。 如果性能至关重要,那么Java可能是错误的语言。 主要原因是你无法控制虚拟机何时决定去收集垃圾,在一个大的程序中可以使CPU达到100%几秒钟(MagicDraw 10非常棒)。 除非你控制着每一台电脑,否则这个程序将运行,你不能保证它将会在哪个版本的JVM上运行,而且很多老版本的主要速度问题。 如果它是一个小应用程序,你可能会与Java,但如果你不断阅读和丢弃数据,那么你注意到当GC启动。