338

私はアプリケーションに取り組んでおり、1 つの設計アプローチには、instanceofオペレーターを非常に頻繁に使用することが含まれます。オブジェクト指向設計では一般的に を使用しないように努めていることは知っていますinstanceofが、それは別の話であり、この質問は純粋にパフォーマンスに関連しています。パフォーマンスへの影響があるかどうか疑問に思っていましたか? と同じくらい速い==ですか?

たとえば、10 個のサブクラスを持つ基本クラスがあります。基本クラスを受け取る 1 つの関数で、そのクラスがサブクラスのインスタンスであるかどうかをチェックし、何らかのルーチンを実行します。

それを解決するために私が考えた他の方法の 1 つは、代わりに「type id」整数プリミティブを使用し、ビットマスクを使用してサブクラスのカテゴリを表し、サブクラス「type id」のビット マスク比較を行うことでした。カテゴリを表す定数マスク。

instanceofそれよりも高速になるようにJVMによって最適化されていますか? Java にこだわりたいのですが、アプリのパフォーマンスが重要です。以前にこの道を進んだことがある人がアドバイスをくれるとうれしいです。つまらないことをしすぎたり、最適化するのに間違ったことに焦点を合わせたりしていませんか?

4

24 に答える 24

319

アプローチ

さまざまな実装を評価するためのベンチマーク プログラムを作成しました。

  1. instanceof実装 (参照として)
  2. @Override抽象クラスとテストメソッドによるオブジェクト指向
  3. 独自の型の実装を使用する
  4. getClass() == _.class実装

jmhを使用して、100 回のウォームアップ コール、測定中の 1000 回の反復、および 10 回のフォークでベンチマークを実行しました。したがって、各オプションは 10,000 回測定され、macOS 10.12.4 および Java 1.8 を搭載した MacBook Pro でベンチマーク全体を実行するには 12:18:57 かかります。ベンチマークは、各オプションの平均時間を測定します。詳細については、GitHub での私の実装を参照してください。

完全を期すために、この回答と私のベンチマークの以前のバージョンがあります。

結果

| | 操作 | 操作ごとのランタイム (ナノ秒) | instanceof に相対的 |
|------------|------------------------------------ --|------------------------|
| | インスタンスオブ | 39,598 ± 0,022 ns/オペレーション | 100,00% |
| | クラスを取得 | 39,687 ± 0,021 ns/オペレーション | 100,22% |
| | タイプ | 46,295 ± 0,026 ns/オペレーション | 116,91% |
| | OO | 48,078 ± 0,026 ns/オペレーション | 121,42% |

tl;dr

Java 1.8では、非常に近いですinstanceofが、最速のアプローチです。getClass()

于 2014-10-22T18:54:40.517 に答える
288

最新の JVM/JIT コンパイラは、instanceof、例外処理、リフレクションなどを含む、従来の「遅い」操作のほとんどのパフォーマンス ヒットを取り除きました。

ドナルド・クヌースが書いたように、「私たちは小さな効率性を忘れるべきです。たとえば、約 97% の場合です。時期尚早の最適化はすべての悪の根源です。」instanceof のパフォーマンスはおそらく問題にならないので、それが問題であると確信できるまで、エキゾチックな回避策を考え出すことに時間を無駄にしないでください。

于 2008-09-19T16:45:45.170 に答える
76

簡単なテストを行って、instanceOfのパフォーマンスが、1文字しかない文字列オブジェクトへの単純なs.equals()呼び出しとどのように比較されているかを確認しました。

10.000.000ループでは、instanceOfは63〜96ミリ秒を与え、文字列は106〜230ミリ秒を与えました。

javajvm6を使用しました。

したがって、私の単純なテストでは、1文字の文字列の比較ではなくinstanceOfを実行する方が高速です。

文字列の代わりに整数の.equals()を使用すると、同じ結果が得られました。これは、== iを使用した場合にのみinstanceOfよりも20ミリ秒高速でした(10.000.000ループ)。

于 2008-12-29T12:13:26.450 に答える
20

パフォーマンスへの影響を決定する項目は次のとおりです。

  1. instanceof 演算子が true を返す可能性のあるクラスの数
  2. データの分布 - ほとんどの instanceof 操作は 1 回目または 2 回目の試行で解決されていますか? 真の操作を返す可能性が最も高いものを最初に配置する必要があります。
  3. デプロイメント環境。Sun Solaris VM での実行は、Sun の Windows JVM とは大きく異なります。デフォルトでは、Solaris は「サーバー」モードで実行されますが、Windows はクライアント モードで実行されます。Solaris での JIT 最適化により、すべてのメソッド アクセスが同じになります。

4 つの異なる発送方法のマイクロベンチマークを作成しました。Solaris の結果は次のとおりです。数値が小さいほど高速です。

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 
于 2009-11-02T03:35:56.720 に答える
19

最後の質問への回答: プロファイラーがインスタンスで途方もない時間を費やしていると言わない限り: はい、あなたはつまらないものを選んでいます。

最適化する必要のないものを最適化することについて考える前に、最も読みやすい方法でアルゴリズムを作成し、実行してください。jit-compiler がそれ自体を最適化する機会を得るまで、それを実行します。その後、このコードで問題が発生した場合は、プロファイラーを使用して、どこで最大の成果を上げ、これを最適化するかを教えてください。

コンパイラが高度に最適化されている時代には、ボトルネックに関する推測は完全に間違っている可能性があります。

そして、この答えの真の精神(私は心から信じています):jit-compilerがそれを最適化する機会を得た後、instanceofと==がどのように関係するかは絶対にわかりません。

忘れていました: 最初の実行を測定しないでください。

于 2008-09-19T16:51:00.260 に答える
16

同じ質問がありますが、私のようなユース ケースの「パフォーマンス メトリック」が見つからなかったため、さらにサンプル コードを作成しました。私のハードウェアと 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()));
    }
}
于 2012-03-23T19:06:10.940 に答える
13

instanceofは非常に高速で、わずかな CPU 命令しか必要としません。

どうやら、クラスXにサブクラスがロードされていない場合 (JVM が認識)、次のinstanceofように最適化できます。

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

主なコストは読むだけです!

サブクラスがロードされている場合Xは、さらにいくつかの読み取りが必要です。それらは同じ場所にある可能性が高いため、追加コストも非常に低くなります。

皆さんに朗報です!

于 2011-07-16T10:13:26.027 に答える
6

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()を取得し、実装するインターフェイスをループして、1つがxに対して==であるかどうかを確認する必要があります。そうでない場合は、すべての祖先に対して再帰的に繰り返します...この場合、そのツリーの幅優先探索を行うと、yとzが何も拡張しないと仮定して、少なくとも8つの比較が得られます...

実際の派生ツリーの複雑さは、より高くなる可能性があります。場合によっては、JITは、すべての可能な場合において、xを拡張する何かのインスタンスであるとして、事前にdを解決できる場合、そのほとんどを最適化できます。ただし、現実的には、ほとんどの場合、そのツリートラバーサルを実行します。

それが問題になる場合は、代わりにハンドラーマップを使用して、オブジェクトの具象クラスを処理を行うクロージャーにリンクすることをお勧めします。直接マッピングを優先して、ツリートラバーサルフェーズを削除します。ただし、C.classのハンドラーを設定した場合、上記のオブジェクトdは認識されないことに注意してください。

これが私の2セントです。彼らが助けてくれることを願っています...

于 2009-01-27T18:30:28.367 に答える
5

インスタンスオブは非常に高速です。クラス参照の比較に使用されるバイトコードに要約されます。ループで数百万の instanceof を試して、自分の目で確かめてください。

于 2008-09-19T16:49:34.663 に答える
5

instanceof は非常に効率的であるため、パフォーマンスが低下することはほとんどありません。ただし、多くの instanceof を使用すると、設計上の問題が示唆されます。

xClass == String.class を使用できる場合は、この方が高速です。注: final クラスには instanceof は必要ありません。

于 2009-01-27T20:07:04.457 に答える
5

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();
}
}
于 2019-06-22T05:07:32.310 に答える
4

デミアンとポールは良い点に言及しています。ただし、実行するコードの配置は、データをどのように使用するかによって異なります...

私は、さまざまな方法で使用できる小さなデータ オブジェクトの大ファンです。オーバーライド (ポリモーフィック) アプローチに従う場合、オブジェクトは「一方向」でしか使用できません。

これがパターンの出番です...

(ビジター パターンのように) ダブル ディスパッチを使用して、各オブジェクトに自分自身を渡して "call you" するように要求できます。これにより、オブジェクトの型が解決されます。ただし(ここでも)、考えられるすべてのサブタイプを「実行」できるクラスが必要です。

私は、処理したいサブタイプごとに戦略を登録できる戦略パターンを使用することを好みます。次のようなもの。これは正確な型の一致にのみ役立つことに注意してください。ただし、拡張可能であるという利点があります。サードパーティの貢献者は、独自の型とハンドラーを追加できます。(これは、新しいバンドルを追加できる 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);
    }
}
于 2008-09-21T15:51:16.937 に答える
4

一般に、そのような場合 (instanceof がこの基本クラスのサブクラスをチェックしている場合) に「instanceof」演算子が嫌われる理由は、操作をメソッドに移動し、適切なメソッドのためにそれをオーバーライドする必要があるためです。サブクラス。たとえば、次の場合:

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

あなたはそれを置き換えることができます

o.doEverything();

次に、「doEverything()」の実装をクラス 1 呼び出し「doThis()」に、クラス 2 呼び出し「doThat()」などにします。

于 2008-09-19T16:46:05.090 に答える
4

「instanceof」は実際には + や - などの演算子であり、独自の JVM バイトコード命令を持っていると思います。それは十分に速いはずです。

オブジェクトが何らかのサブクラスのインスタンスであるかどうかをテストするスイッチがある場合、設計をやり直す必要があるかもしれません。サブクラス固有の動作をサブクラス自体にプッシュすることを検討してください。

于 2008-09-19T16:46:56.197 に答える
4

特定の JVM がインスタンスをどのように実装するかを言うのは難しいですが、ほとんどの場合、オブジェクトは構造体に匹敵し、クラスも同様であり、すべてのオブジェクト構造体には、インスタンスであるクラス構造体へのポインターがあります。したがって、実際には instanceof の

if (o instanceof java.lang.String)

次のCコードと同じくらい速いかもしれません

if (objectStruct->iAmInstanceOf == &java_lang_String_class)

JIT コンパイラーが適切に配置されていて、まともな仕事をしていると仮定します。

これはポインタへのアクセスのみであり、ポインタが指す特定のオフセットでポインタを取得し、これを別のポインタと比較することを考慮すると(これは基本的に32ビット数値が等しいことをテストするのと同じです)、操作は実際にできると思います非常に速くなります。

ただし、そうする必要はありません。JVM に大きく依存します。ただし、これがコードのボトルネック操作であることが判明した場合は、JVM の実装がかなり貧弱であると考えます。JIT コンパイラがなく、コードを解釈するだけの場合でも、instanceof テストをほとんどすぐに作成できるはずです。

于 2008-09-19T16:58:13.753 に答える
2

最新のJavaバージョンでは、instanceof演算子は単純なメソッド呼び出しとして高速です。これの意味は:

if(a instanceof AnyObject){
}

次のように高速です:

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

もう1つは、多くのinstanceofをカスケードする必要がある場合です。次に、getType()を1回だけ呼び出すスイッチの方が高速です。

于 2008-09-19T19:09:53.077 に答える
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% の合計速度向上に貢献した可能性があります。

于 2011-07-27T14:13:43.073 に答える
1

InstanceOfは、貧弱なオブジェクト指向設計の警告です。

現在の JVM は、instanceOf自体がパフォーマンスの懸念事項ではないことを意味します。特にコア機能のために頻繁に使用していることに気付いた場合は、おそらく設計を検討する時期です。より良い設計へのリファクタリングによるパフォーマンス (およびシンプルさ/保守性) の向上は、実際のinstanceOf呼び出しに費やされる実際のプロセッサ サイクルを大幅に上回ります。

非常に小さな単純化されたプログラミングの例を挙げます。

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

SomeObject を 2 つの子クラスの親クラスにし、各子クラスがメソッド (doSomething) をオーバーライドすることで、コードは次のようになります。

Someobject.doSomething();
于 2008-09-19T17:15:09.047 に答える
0

最終クラスにinstanceofは必要なく、参照の等式を使用できるというPeter Lawreyのメモに関しては、注意してください。最終クラスを拡張することはできませんが、同じクラスローダーによってロードされることが保証されているわけではありません。x.getClass()== SomeFinal.classまたはその同類を使用するのは、コードのそのセクションで使用されているクラスローダーが1つだけであることが絶対的に肯定的な場合のみです。

于 2009-02-19T01:25:01.620 に答える
0

それが本当にプロジェクトのパフォーマンスの問題である場合は、測定/プロファイルする必要があります。可能であれば、再設計をお勧めします。プラットフォームのネイティブ実装 (C で記述) に勝るものはないと確信しています。この場合、多重継承も考慮する必要があります。

問題について詳しく説明する必要があります。具象型のみに関心がある場合は、Map<Class, Object> などの連想ストアを使用できます。

于 2008-09-19T16:50:42.693 に答える
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;
    }
  }
}
于 2013-08-13T15:38:05.477 に答える
-4

あなたは間違ったことに焦点を合わせています。同じことをチェックするための instanceof と他の方法との違いは、おそらく測定可能ではないでしょう。パフォーマンスが重要な場合、Java はおそらく間違った言語です。主な理由は、VM がガベージを収集することを決定するタイミングを制御できないことです。これにより、大規模なプログラムで数秒間 CPU が 100% になる可能性があります (MagicDraw 10 はその点で優れていました)。このプログラムが実行されるすべてのコンピューターを制御していない限り、どのバージョンの JVM が使用されるかを保証することはできません。また、古いバージョンの多くには重大な速度の問題がありました。小さなアプリの場合は Java で問題ないかもしれませんが、常にデータの読み取りと破棄を行っている場合は、GC が作動することに気付くでしょう。

于 2008-09-19T16:51:11.960 に答える