Javaでinstanceofを使用した場合のパフォーマンスへの影響


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

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

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

instanceofは、それよりも速くなるようにJVMによって何とか最適化されていますか? 私はJavaに固執したいが、アプリケーションのパフォーマンスは非常に重要です。 この道を歩いていた誰かがアドバイスをすることができれば涼しいでしょう。 私はあまりにも多くのニックピッキングをしているのか、間違ったことに集中して最適化していますか?



Answers


現代のJVM / JICコンパイラは、インスタンス、例外処理、リフレクションなど、従来の「低速」オペレーションの大部分のパフォーマンス・ヒットを取り除いています。

ドナルド・クヌス(Donald Knuth)が書いたように、「時間の約97%という小さな効率を忘れてはならない。早すぎる最適化はすべての悪の根源だ」 instanceofのパフォーマンスはおそらく問題にはならないので、エキゾチックな回避策が出てくるまで時間を無駄にしないようにしてください。




アプローチ

私はさまざまな実装を評価するベンチマークプログラムを書い

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

私は100回のウォームアップコール、測定中の1000回の反復、10回のフォークでベンチマークを実行するためにjmhを使用しました。 各オプションは10,000回測定され、MacOS 10.12.4とJava 1.8でMacBook Pro全体のベンチマークを実行するために12:18:57になります。 ベンチマークは各オプションの平均時間を測定します。 詳細は私の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; dr

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




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

10.000.000のループでinstanceOfが私に63-96msを与え、文字列equalsが私に106-230msを与えました

私はjava jvm 6を使用しました。

だから私の簡単なテストでは、1つの文字列比較の代わりにinstanceOfを実行する方が速いです。

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




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

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

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

InstanceOf 3156
class== 2925 
OO 3083 
Id 3067 



あなたの最後の質問に答えてください:プロファイラがあなたに指示しない限り、あなたはインスタンスの中でばかげた時間を費やしています:はい、あなたはニックピッキングしています。

最適化する必要がないものを最適化することについて不思議に思う前に、アルゴリズムを最も読みやすい方法で記述して実行してください。 jitコンパイラがそれを最適化する機会を得るまで実行してください。 このコードに問題がある場合は、プロファイラを使用して、どこを最大限に活用して最適化するかを教えてください。

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

そしてこの答えの本当の精神(私は全面的に信じています):ジッタコンパイラがそれを最適化する機会を得た後、instanceofと==がどのように関連しているかは絶対に分かりません。

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




私は同じ質問がありますが、私に似たユースケースのための 'パフォーマンスメトリック'が見つかりませんでしたので、私はいくつかのサンプルコードを実行しました。 私のハードウェアとJava 6&7では、10millionの反復でinstanceofとswitchの違いは

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が知っている) instanceofinstanceofは次のように最適化できます。

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

主な費用はちょうど読書です!

Xサブクラスがロードされている場合は、さらに読み込みが必要です。 彼らはおそらく余分なコストが非常に低いので、同じ場所に位置している可能性があります。

誰もが良いニュース!




instanceofはたいていの現実世界の実装(すなわち、instanceofが本当に必要とされる単純なequalsよりもコストがかかります。そして、あなたは、初心者の教科書のように、共通のメソッドをオーバーライドするだけでは解決できません。上記のデミアン)。

何故ですか? たぶん起こるのは、いくつかのインターフェース(いくつかのインターフェースx、y、zと言う)といくつかのオブジェクトを扱ういくつかのインターフェースを持っているということです。直接ではありません。 例えば、私が持っていることを言う:

wはxを延長する

Aの実装

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を使用することができれば、これはより高速です。 注:最終クラスにはinstanceofは必要ありません。




'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]
}

貧弱なアーキテクチャは、各子クラスがメソッド(doSomething)をオーバーライドするように、SomeObjectを2つの子クラスの親クラスにすることをお勧めします。

Someobject.doSomething();



デミアンとポールは良い点を述べています。 しかし 、実際に実行するコードの配置は、データの使い方に依存します...

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

これはパターンが入る場所です...

ダブルディスパッチ(ビジターパターンのように)を使用して、各オブジェクトにそれ自身を渡すように指示することができます。これにより、オブジェクトのタイプが解決されます。 しかし、 (再び)あなたは、可能なすべてのサブタイプを「詰める」ことができるクラスが必要です。

私は戦略パターンを使用することをお勧めします。ここでは、扱う各サブタイプの戦略を登録できます。 次のようなもの これは厳密な型の一致にのみ役立ちますが、拡張可能であるという利点があります。サードパーティのコントリビュータは独自の型とハンドラを追加できます。 (これは、新しいバンドルを追加できる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がこの基本クラスのサブクラスをチェックしているところ)に戸惑う理由は、操作をメソッドに移動し、適切なサブクラス。 例えば、あなたが持っているもの:

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

別のことは、多くのインスタンスをカスケードする必要がある場合です。 一度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;
}

ひどいオブジェクト指向設計、しかしあなたのパフォーマンス分析が、これがあなたのボトルネック箇所であることを示しているのなら、多分です。 私のコードでは、ディスパッチコードは総実行時間の10%を要し、これは合計速度の1%向上に寄与している可能性があります。




私はあなたのパフォーマンスのインスタンスであなたに戻ってきます。 しかし、問題(またはその欠如)を避ける方法は、instanceofを実行する必要のあるすべてのサブクラスに対する親インタフェースを作成することです。 インタフェースは、インスタンスのチェックを行う必要のあるサブクラスのすべてのメソッドのスーパーセットになります。 メソッドが特定のサブクラスに適用されない場合は、このメソッドのダミー実装を提供するだけです。 私がこの問題を誤解しなかった場合、これは私が過去の問題をどのように克服したかです。




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

あなたは具体的な型だけに興味があるなら、おそらくMap <Class、Object>のような連想記憶を使うことができます。




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




また、私はenumアプローチを好むが、抽象基本クラスを使用して、サブクラスが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();

ストリングからダブルへの変換のような、ループ内でかなり重いことが起こっているにもかかわらず、269msから169msへのスピードアップが可能です。 もちろん、instanceof演算子自体を削除するよりも、条件分岐を削除するほうがスピードアップになる可能性があります。 私はそれが言及する価値があると思った。




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