题
我的工作申请,并一个设计办法涉及到极其沉重的利用 instanceof
操作员。虽然我知道OO设计一般试图避免使用 instanceof
, 那是一个不同的故事,而这个问题纯粹是相关的性能。我想知道如果有任何性能的影响?是的只是一样快 ==
?
例如,我有一个基类有10个子类。在一个功能,需要的基类,我做检查,如果该类是一个例子类,并进行某些程序。
一个其它的方式我认为解决是使用"type id"整数而不是原始的,使用一个位代表的类别的子类,然后只是做一点掩模比较小类型"id"一个不断面具代表的类别。
是 instanceof
以某种方式的优化JVM要以更快的速度比?我想坚持到Java但绩效的程序是至关重要的。这会很酷,如果有人已沿着这条道路之前,可以提供一些建议。我太多吹毛求疵或聚焦在了错误的事情以优化?
解决方案
现代JVM / JIC编译器已经消除了大多数传统<!>“慢<!>”的性能损失。操作,包括instanceof,异常处理,反射等。
正如Donald Knuth所写,<!>;我们应该忘记小的效率,大约97%的时间说:过早的优化是万恶之源。<!> instanceof的性能可能不会成为一个问题,所以在你确定这个问题之前,不要浪费你的时间来提出异乎寻常的解决方法。
其他提示
方法
我写了基准程序来评估不同的实现:
-
instanceof
实施(作为参考) - 通过抽象类定向的对象和
@Override
测试方法 - 使用自己的类型实现
-
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中,getClass()
是最快的方法,尽管<=>非常接近。
我刚做了一个简单的测试,看看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循环中)
决定性能影响的项目是:
- instanceof运算符可以返回true的可能类的数量
- 您的数据分布 - 是在第一次或第二次尝试中解决的大多数操作实例?你最想让你最有可能返回真正的操作。
- 部署环境。在Sun Solaris VM上运行与Sun的Windows JVM有很大不同。默认情况下,Solaris将以“服务器”模式运行,而Windows将以客户端模式运行。 Solaris上的JIT优化将使所有方法访问都相同。 醇>
我为四种不同的派遣方式创建了 microbenchmark 。 Solaris的结果如下,较小的数字更快:
InstanceOf 3156
class== 2925
OO 3083
Id 3067
回答你的最后一个问题:除非探查者告诉你,你在一个实例中花费了大量的时间:是的,你是在挑剔。
在想要优化从未需要优化的事情之前:以最易读的方式编写算法并运行它。运行它,直到jit-compiler有机会自己优化它。如果您在使用这段代码时遇到问题,请使用分析器告诉您,从哪里获得最大收益并进行优化。
在高度优化编译器时,您对瓶颈的猜测可能完全错误。
本着这个答案的真正精神(我完全相信):一旦jit-compiler有机会优化它,我绝对不知道instanceof和==如何关联。
我忘记了:永远不要测量第一次跑步。
我有同样的问题,但由于我没有找到与我的用例相似的“性能指标”,我已经做了一些示例代码。在我的硬件和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知道),<=>可以优化为:
x instanceof X
==> x.getClass()==X.class
==> x.classID == constant_X_ID
主要成本只是阅读!
如果<=>确实加载了子类,则需要更多的读取;它们可能位于同一地点,因此额外费用也很低。
大家好消息!
为什么?因为可能会发生的事情是你有几个接口,提供一些功能(比方说,接口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美分,我希望他们帮助......
'instanceof'实际上是一个运算符,如+或 - ,我相信它有自己的JVM字节码指令。它应该足够快。
我不应该如果你有一个开关来测试一个对象是否是某个子类的实例,那么你的设计可能需要重新设计。考虑将特定于子类的行为推送到子类本身。
Instanceof非常快。它归结为一个字节码,用于类参考比较。在一个循环中尝试几百万个instanceofs并亲自看看。
我非常喜欢可以在很多方面使用的小型数据对象。如果您遵循覆盖(多态)方法,您的对象只能使用<!>“;单向<!>”。
这就是模式的来源......
您可以使用双重调度(如访问者模式)来询问每个对象<!>“;给您打电话<!>”;传递自己 - 这将解决对象的类型。 然而(再次)你需要一个能够<!>“做”东西<!>“的课程。包含所有可能的子类型。
我更喜欢使用策略模式,您可以在其中为要处理的每个子类型注册策略。像下面这样的东西。请注意,这仅对精确类型匹配有帮助,但具有可扩展性的优势 - 第三方贡献者可以添加自己的类型和处理程序。 (这适用于像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);
}
}
instanceof非常有效,因此您的表现不太可能受到影响。 但是,使用大量的instanceof表明存在设计问题。
如果你可以使用xClass == String.class,这会更快。注意:最终类不需要instanceof。
很难说某个JVM如何实现实例,但在大多数情况下,对象与结构和类似,并且每个对象结构都有一个指向它是其实例的类结构的指针。实际上是
的实例if (o instanceof java.lang.String)
可能与以下C代码一样快
if (objectStruct->iAmInstanceOf == &java_lang_String_class)
假设有一个JIT编译器并且做得不错。
考虑到这只是访问一个指针,指针指向一个特定的偏移指针并将其与另一个指针进行比较(这与测试32位数相等基本相同),我会说操作实际上可以非常快。
但是,它并不一定非常依赖于JVM。但是,如果这会成为代码中的瓶颈操作,我会认为JVM实现相当差。即使是没有JIT编译器且只解释代码的人也应该能够在几乎所有时间内进行测试实例。InstanceOf 是对面向对象设计不佳的警告。
当前的JVM确实意味着 instanceOf 本身并不是一个性能上的担忧。如果你发现自己经常使用它,特别是核心功能,那么可能是时候看一下设计了。重构到更好设计的性能(以及简单性/可维护性)增益将大大超过实际 instanceOf 调用所花费的实际处理器周期。
给出一个非常简单的编程示例。
if (SomeObject instanceOf Integer) {
[do something]
}
if (SomeObject instanceOf Double) {
[do something different]
}
一个糟糕的架构是一个更好的选择,让SomeObject成为两个子类的父类,其中每个子类重写一个方法(doSomething),所以代码看起来像这样:
Someobject.doSomething();
我将以性能为例回复您。但是,完全避免问题(或缺少问题)的方法是为您需要执行instanceof的所有子类创建父接口。该接口将是一个超级所有子类中的方法,您需要对其进行instanceof检查。如果方法不适用于特定的子类,只需提供此方法的虚拟实现。如果我没有误解这个问题,这就是我过去常常遇到的问题。
一般来说<!>“instanceof <!>”的原因运算符在这样的情况下(其中instanceof正在检查此基类的子类)是不赞成的,因为您应该做的是将操作移动到方法中并为适当的子类重写它。例如,如果你有:
if (o instanceof Class1)
doThis();
else if (o instanceof Class2)
doThat();
//...
您可以用
替换它o.doEverything();
然后执行<!>“; doEverything()<!>”;在Class1中调用<!>“; 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;
}
可怕的OO设计,但如果你的性能分析表明这是你瓶颈的地方,那么也许。在我的代码中,调度代码占总执行时间的10%,这可能导致总速度提高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();
}
}
你应该措施/配置文件,如果它真的是一个业绩问题的项目。如果我建议一个重新设计-如果可能的。我敢肯定你不可能打败平台的当地执行情况(编写C)。你也应该考虑的多个继承在这种情况。
你应该告诉更多的问题,也许你可以使用一个关联的商店,例如一个地图<Class, Object=""> 如果你是唯一感兴趣的具体类型。
关于Peter Lawrey的说明,你不需要最终类的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;
}
}
}
我认为可能值得提交一个反例,在此页面上的一般共识<!> quot; instanceof <!> quot;不值得担心。我发现我在内循环中有一些代码(在一些历史性的优化尝试中)
if (!(seq instanceof SingleItem)) {
seq = seq.head();
}
在SingleItem上调用head()返回值不变。用
替换代码seq = seq.head();
让我加速从269ms到169ms,尽管循环中发生了一些非常繁重的事情,比如字符串到双重转换。当然,加速更多是由于消除了条件分支而不是消除了instanceof运算符本身;但我认为值得一提。
你专注于错误的事情。 instanceof和用于检查相同事物的任何其他方法之间的差异甚至可能是不可测量的。如果性能至关重要,那么Java可能是错误的语言。主要原因是您无法控制VM何时决定要收集垃圾,这可能会使CPU在大型程序中持续几秒钟(MagicDraw 10非常适合)。除非您控制该程序将运行的每台计算机,否则您无法保证它将在哪个版本的JVM上运行,并且许多较旧的JVM都存在严重的速度问题。如果它是一个小应用程序,您可以使用Java,但如果您经常阅读并丢弃数据,那么当GC启动时,将注意到。