5

ドキュメントが間違っているようです。どちらが本当か誰か教えてくれませんか?

Performance Mythsセクションは次のとおりです。

JIT のないデバイスでは、フィールド アクセスのキャッシュは、フィールドへの繰り返しアクセスよりも約20%高速です。JIT では、フィールド アクセスのコストはローカル アクセスとほぼ同じです。

内部ゲッター/セッターの回避セクションは次のとおりです。

JIT を使用しない場合、フィールドへの直接アクセスは、単純な getter を呼び出すよりも約3 倍高速です。JIT (直接フィールド アクセスはローカルにアクセスするのと同じくらい安価です) を使用すると、直接フィールド アクセスは単純な getter を呼び出すよりも約 7 倍高速です

JIT ローカル アクセスを使用しない方が高速であることは明らかです。getter を使用するよりも、フィールドに直接アクセスする方が高速であることも明らかです。

しかし、最初のケースではパフォーマンスが20%向上し、2 番目のケースではパフォーマンスが133%向上するのはなぜでしょうか。これは、オブジェクト フィールドを呼び出すための JIT 最適化です。

4

3 に答える 3

5

リンゴとオレンジを比較していると思います。Performance Myths リファレンスでは、フィールド アクセスに対する JIT の利点について説明し、2 番目のリファレンスでは、メソッド アクセスに対する JIT の利点について説明しています。

私が理解しているように、直接フィールドアクセスとローカルアクセスの類似点は次のとおりです(投稿に書いたようなローカルフィールドアクセスではありません-ローカルフィールドなどはありません)。

class foo {
    int bar = 42;

    int doStuff(){
        int x = 1;
        x += bar;
        x += bar;
        x += bar;
        return x;
    }
}

への各参照にbarは、関連するパフォーマンス コストがあります。優れたコンパイラは、最適化の機会を認識し、コードを次のように「書き直します」。

int doStuff(){
    int x = 1f;
    int local_bar = bar;
    x += local_bar;
    x += local_bar;
    x += local_bar;
    return x;
}

JIT を使用しない場合、これは便利な最適化であり、パフォーマンスが20%向上します。

JIT を使用するbarと、そもそもアクセスによるパフォーマンス ヒットが取り除かれるため、最適化は不要です。

2 番目のリファレンスでは、次のシナリオについて説明します。

class foo {
    int bar = 42;

    int getBar() { return bar; }

    int doStuff(){
        int x = 1;
        x += getBar();
        x += getBar();
        x += getBar();
        return x;
    }
}

各関数呼び出しには、関連するパフォーマンス ペナルティがあります。コンパイラは複数のメソッド呼び出しをキャッシュできません(前の例でgetBar()複数の直接フィールド アクセスをキャッシュしたように)。barベースのコンポーネントをその戻り値に)。したがって、3 つのメソッド呼び出しを実行する必要があります。

上記の関数は、JIT の有無にかかわらずほぼ同じ速度で実行されることを理解することが重要です。

getBar()上記の関数を単純に手動で置き換えるとbar、パフォーマンスが向上します。JIT を使用しないマシンでは、フィールド アクセスが依然としてやや遅いため、そのパフォーマンス ブーストは約 3 倍になります。ただし、JIT を使用すると、フィールド アクセスが高速になるため、非常に遅いメソッドを高速フィールド アクセスに置き換えると、はるかに大きな (7 倍) ブーストが得られます。

それが理にかなっていることを願っています!

于 2012-09-10T20:38:14.497 に答える
2

リンゴとオレンジを比較していると思います。最初の引用では:

 caching field accesses is about 20% faster than repeatedly accesssing the field

キャッシュ戦略により、フィールドへの直接アクセス時にのみ JIT コンパイルなしでパフォーマンスが向上する可能性があることを意味します。言い換えると:

int a = this.field;
if (a == 1)
...
if (a == 7) // etc.

よりも優れたパフォーマンスが得られます

if (this.field == 1)
....
if (this.field == 7) //etc.

引用は、フィールドをローカルに保存するのではなく、フィールドを繰り返し参照することでペナルティが発生することを示唆しています。

2 番目の引用は、単純な getter/setter を使用する JIT なしでは、直接フィールド アクセスよりも遅いことを示唆しています。

if (this.getField()) // etc.

より遅い:

if (this.field) // etc.

ドキュメントが間違っているとか、ある声明が他の声明を損なうとは思いません。

于 2012-09-10T20:37:08.460 に答える
1

これは経験に基づいた推測にすぎません。Dalvik の内部構造についてはわかりません。ただし、最初のケースでは、ローカル アクセスのパフォーマンスがフィールド アクセスと比較され、2 番目のケースでは、フィールド アクセスが単純なメソッド呼び出しと比較されることに注意してください。また、x% の高速化は、JIT を追加することによって同じコードにかかる時間が実際に x% 短縮されるわけではないことに注意してください。相対的なパフォーマンスについて話しているのです。(a) 解釈されたローカル アクセスは、解釈されたフィールド アクセスよりも 20% 高速であり、( b) JIT されたローカル アクセスは、JIT されたフィールド アクセスと同じくらい高速です。(c) 解釈されたローカル アクセスは、JIT されたローカル/フィールド アクセスと同じくらい高速です。実際にはもっと遅くなる可能性が高いです。

インタープリターでローカルを読み取ることは、ほとんどの VM アーキテクチャーでは、レジスター・アクセスではなくメモリー・アクセスです (Dalvik レジスターではなく、マシン・レジスターについて話しています)。フィールドの読み取りはさらに遅くなります。理由ははっきりとは言えません(私の推測では、レジスタ フィールドとオブジェクト フィールドの両方を読み取る 2 回目のルックアップになると思います)。一方、JIT はフィールドとローカルの両方をレジスターに入れることができます (これは、パフォーマンスの同等性を説明するために私が想定しなければならないことです。実際、これを行う JIT があります。ここで適用されるかどうかはわかりません)。オーバーヘッドの多くを取り除きます。

メソッド呼び出しの場合、Dalvik JIT がメソッドをインライン化しないと仮定すると (これは暗示されています)、実際の呼び出しに加えてかなりのオーバーヘッドがあり、JIT を行った場合でも呼び出しが高価になります: レジスタをスタックに保存する必要があり、後でそれらを復元する必要があります。すべてのコードが表示されるわけではないため、最大限に最適化します。呼び出しは、呼び出しのないコードよりも比較的高価です。これは、呼び出しのない代替手段が非常に高速であるためであり、インタープリターが呼び出しを行う方が優れているからではありません (そうではなく、他のすべての処理が遅くなるだけです) たとえば、最適化がないため、呼び出しによって最適化が妨げられることはありません。

于 2012-09-10T20:38:00.527 に答える