私はアプリケーションに取り組んでおり、1つの設計アプローチでは、instanceof
演算子を非常に頻繁に使用しています。 OO設計は一般にinstanceof
の使用を避けようとすることを知っていますが、これは別の話であり、この質問は純粋にパフォーマンスに関連しています。パフォーマンスに影響があるかどうか疑問に思っていましたか? ==
と同じくらい速いですか?
たとえば、10個のサブクラスを持つ基本クラスがあります。基本クラスを取得する単一の関数で、クラスがサブクラスのインスタンスであるかどうかを確認し、何らかのルーチンを実行します。
私がそれを解決しようと思った他の方法の1つは、代わりに「タイプID」整数プリミティブを使用し、サブクラスのカテゴリを表すためにビットマスクを使用し、サブクラス「タイプID」とカテゴリーを表す定数マスク。
instanceof
は、それよりも速くJVMによって何らかの形で最適化されていますか? Javaに固執したいのですが、アプリのパフォーマンスが重要です。以前にこの道を進んだことがある人がアドバイスを提供できれば、それは素晴らしいことです。私はあまりにも多くを選ぶか、最適化するために間違ったことに集中していますか?
最新のJVM/JICコンパイラは、instanceof、例外処理、リフレクションなどを含む、従来の「低速」操作のほとんどのパフォーマンスヒットを削除しました。
ドナルド・クヌースが書いたように、「私たちはわずかな効率性を忘れて、約97%の時間を言うべきです。時期尚早な最適化はすべての悪の根源です」 instanceofのパフォーマンスはおそらく問題にならないので、問題があると確信するまで、エキゾチックな回避策を考え出す時間を無駄にしないでください。
ベンチマークプログラム を作成して、さまざまな実装を評価しました。
instanceof
実装(参照として)@Override
を介して指向されたオブジェクトはテストメソッドgetClass() == _.class
の実装jmh を使用して、100回のウォームアップコール、測定中の1000回の反復、10回のフォークでベンチマークを実行しました。したがって、各オプションは10000回測定され、macOS 10.12.4およびJava 1.8を搭載したMacBook Proでベンチマーク全体を実行するには12:18:57かかります。ベンチマークは、各オプションの平均時間を測定します。詳細については、 GitHubでの私の実装 を参照してください。
完全を期すために: この回答の以前のバージョンと私のベンチマーク があります。
|操作|操作あたりのナノ秒単位のランタイム| instanceofに関連| | ------------ | --------------------------- ----------- | ------------------------ | | INSTANCEOF | 39,598±0,022 ns/op | 100,00%| | GETCLASS | 39,687±0,021 ns/op | 100,22%| |タイプ| 46,295±0,026 ns/op | 116,91%| | OO | 48,078±0,026 ns/op | 121,42%|
Java 1.8では、getClass()
は非常に近いものですが、instanceof
が最速のアプローチです。
InstanceOfパフォーマンスが、1文字のみの文字列オブジェクトへの単純なs.equals()呼び出しとどのように比較されるかを確認するために、簡単なテストを行いました。
10.000.000ループでは、instanceOfは63〜96ミリ秒を与え、文字列が等しいと106〜230ミリ秒を与えました
Java jvm 6を使用しました。
したがって、私の簡単なテストでは、1文字の文字列比較の代わりにinstanceOfを実行する方が高速です。
文字列の代わりに整数の.equals()を使用すると、== iを使用した場合にのみ、同じ結果が得られました。
パフォーマンスへの影響を決定する項目は次のとおりです。
4つの異なるディスパッチ方法のマイクロベンチマーク を作成しました。 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
そのため、特に大量のif-else-ifステートメントでは、instanceofが実際に遅くなりますが、実際のアプリケーションでは違いは無視できます。
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が実際に必要な実装)の単純な同等のものよりもコストが高くなる可能性があります。すべての初心者の教科書や上記のデミアンが提案します)。
何故ですか?おそらく何が起こるのかは、いくつかの機能を提供するいくつかのインターフェイス(たとえば、インターフェイスx、y、z)と、それらのインターフェイスの1つを実装する(またはしない)操作するオブジェクトがあることですが...直接ではありません。たとえば、私が持っている:
wはxを拡張します
Aはwを実装します
BはAを拡張します
CはBを拡張し、yを実装します
DはCを拡張し、zを実装します
Dのインスタンスであるオブジェクトdを処理しているとします。 (d instanceof x)を計算するには、d.getClass()を取得し、実装するインターフェイスをループして、xが==であるかどうかを確認します。そうでない場合は、すべての祖先について再帰的に繰り返します...そのツリーの幅の広い最初の調査を行うと、yとzは何も拡張しないと仮定して、少なくとも8つの比較を生成します...
実世界の派生ツリーの複雑さはさらに高くなる可能性があります。場合によっては、すべての可能な場合において、xを拡張する何かのインスタンスとして事前にdを解決できる場合、JITはそのほとんどを最適化できます。ただし、現実的には、ほとんどの場合、そのツリートラバーサルを通過することになります。
それが問題になる場合は、代わりにハンドラマップを使用して、オブジェクトの具体的なクラスを処理を行うクロージャにリンクすることをお勧めします。直接マッピングを優先して、ツリートラバーサルフェーズを削除します。ただし、C.classのハンドラを設定した場合、上記のオブジェクトdは認識されないことに注意してください。
ここに私の2セントがあります、彼らが助けることを願っています...
instanceofは非常に効率的であるため、パフォーマンスが低下することはほとんどありません。ただし、instanceofを大量に使用すると、設計上の問題が示唆されます。
XClass == String.classを使用できる場合、これは高速です。注:finalクラスにはinstanceofは必要ありません。
デミアンとポールは良い点に言及しています。 ただし、実行するコードの配置は、データの使用方法によって異なります...
私は、さまざまな方法で使用できる小さなデータオブジェクトの大ファンです。オーバーライド(ポリモーフィック)アプローチに従う場合、オブジェクトは「一方向」でしか使用できません。
これがパターンの出番です...
ダブルディスパッチ(ビジターパターンのように)を使用して、各オブジェクトに自分自身を渡す「呼び出し」を求めることができます。これにより、オブジェクトのタイプが解決されます。 ただし(再度)可能なすべてのサブタイプを「処理」できるクラスが必要です。
処理したいサブタイプごとに戦略を登録できる戦略パターンを使用することを好みます。次のようなもの。これは正確な型の一致にのみ役立ちますが、拡張可能であるという利点があります-サードパーティの貢献者が独自の型とハンドラを追加できることに注意してください。 (これは、新しいバンドルを追加できる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'は実際には+や-などの演算子であり、独自のJVMバイトコード命令を持っていると思います。十分に速いはずです。
オブジェクトが何らかのサブクラスのインスタンスであるかどうかをテストするスイッチがある場合、設計をやり直す必要があるかもしれません。サブクラス固有の動作をサブクラス自体にプッシュダウンすることを検討してください。
Instanceofは非常に高速です。つまり、クラス参照の比較に使用されるバイトコードに要約されます。ループで数百万のinstanceofsを試してみて、自分で確かめてください。
特定の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を2つの子クラスの親クラスにして、各子クラスがメソッド(doSomething)をオーバーライドするようにすると、コードは次のようになります。
Someobject.doSomething();
インスタンスのパフォーマンスについてお話しします。しかし、問題(またはその欠如)を完全に回避する方法は、instanceofを実行する必要があるすべてのサブクラスへの親インターフェースを作成することです。インターフェースはallのスーパーセットになり、instanceofチェックを行う必要があるサブクラスのメソッドになります。メソッドが特定のサブクラスに適用されない場合、このメソッドのダミー実装を単に提供します。問題を誤解していなかった場合、これは過去に問題を回避した方法です。
一般に、そのような場合(instanceofがこの基本クラスのサブクラスをチェックしている場合)で「instanceof」演算子が眉をひそめている理由は、操作をメソッドに移動し、適切にオーバーライドするためです。サブクラス。たとえば、次の場合:
if (o instanceof Class1)
doThis();
else if (o instanceof Class2)
doThat();
//...
あなたはそれを置き換えることができます
o.doEverything();
そして、Class1で「doEverything()」を実装し、「doThis()」を呼び出し、Class2で「doThat()」を呼び出します。
最新のJavaバージョンでは、instanceof演算子は単純なメソッド呼び出しとして高速です。これの意味は:
if(a instanceof AnyObject){
}
次のように高速です:
if(a.getType() == XYZ){
}
もう1つは、多くのinstanceofをカスケードする必要がある場合です。次に、getType()を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();
}
}
速度が唯一の目的である場合、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%の合計速度の向上に貢献しました。
列挙型アプローチも好みますが、抽象基本クラスを使用して、サブクラスに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()を呼び出すと、値が変更されずに返されます。コードを置き換える
seq = seq.head();
文字列からダブルへの変換など、ループ内で非常に重いことが発生しているにもかかわらず、269ミリ秒から169ミリ秒に高速化されます。もちろん、instanceof演算子自体を削除するよりも、条件分岐を削除するほうが速度が向上する可能性があります。しかし、言及する価値があると思いました。
Peter Lawreyの注意点に関して、finalクラスにはinstanceofは不要であり、参照の等式を使用するだけでよいことに注意してください。最終クラスを拡張することはできませんが、同じクラスローダーによってロードされることは保証されていません。 x.getClass()== SomeFinal.classまたはそのilkを使用するのは、そのコードのセクションでクラスローダーが1つだけであることが絶対に肯定的な場合のみです。
それが実際にプロジェクトのパフォーマンスの問題である場合は、測定/プロファイルする必要があります。可能であれば、再設計することをお勧めします。プラットフォームのネイティブ実装(Cで記述された)に勝るものはないと確信しています。この場合、多重継承も考慮する必要があります。
問題について詳しく説明する必要があります。たとえば、連想ストアを使用できます。具象型のみに関心がある場合は、Map <Class、Object>。