2

ローカル変数、メンバー変数、他のオブジェクトのメンバー変数、ゲッターセッターのアクセス性能を比較するベンチマークを行ったところです。ベンチマークは、1000万回の反復でループ内の変数を増やします。出力は次のとおりです。

ベンチマーク: ローカル 101、メンバー 1697、外国人メンバー 151、ゲッター セッター 268

これは Motorola XOOM タブレットと Android 3.2 で行われました。数値は実行時間のミリ秒です。メンバー変数の偏差を説明できる人はいますか? 特に、他のオブジェクトのメンバー変数と比較した場合。これらの数値に基づいて、計算で値を使用する前にメンバー変数をローカル変数にコピーする価値があるようです。ところで、HTC One X と Android 4.1 で同じベンチマークを行ったところ、同じ偏差が示されました。

それらの数値は妥当ですか、それとも私が見落としている体系的なエラーはありますか?

ベンチマーク関数は次のとおりです。

private int mID;

public void testMemberAccess() {
    // compare access times for local variables, members, members of other classes
    // and getter/setter functions
    final int numIterations = 10000000;
    final Item item = new Item();
    int i = 0;

    long start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        mID++;
    }
    long member = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        item.mID++;
    }
    long foreignMember = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        item.setID(item.getID() + 1);

    }
    long getterSetter = SystemClock.elapsedRealtime() - start;

    start = SystemClock.elapsedRealtime(); 
    for (int k = 0; k < numIterations; k++) {
        i++;
    }
    long local = SystemClock.elapsedRealtime() - start;

    // just make sure nothing loops aren't optimized away?
    final int dummy = item.mID + i + mID;  
    Log.d(Game.ENGINE_NAME, String.format("BENCHMARK: local %d, member %d, foreign member %d, getter setter %d, dummy %d",
            local, member, foreignMember, getterSetter, dummy));
}

編集:
各ループを関数に入れ、ランダムに100回呼び出しました。結果: ベンチマーク: ローカル 100、メンバー 168、外国人メンバー 190、ゲッター セッター 271 良さそうです。外部オブジェクトは、関数内ではなく、最終的なクラス メンバーとして作成されました。

4

2 に答える 2

1

まあ、Dalvik VM のオプティマイザはかなり賢いと思います ;-) Dalvik VM がレジスタベースであることは知っています。Dalvik VM の中身はわかりませんが、次のことが (多かれ少なかれ) 起こっていると思います。

ローカルの場合、ループ内でメソッドのローカル変数をインクリメントしています。オプティマイザーは、ループが完了するまでこの変数がアクセスされないことを認識するため、レジスターを使用して、ループが完了するまでそこにインクリメントを適用し、値をローカル変数に格納し直すことができます。これにより、1回のフェッチ、10000000回のレジスタインクリメント、および1回のストアが得られます。

メンバーの場合、ループ内でメンバー変数をインクリメントしています。オプティマイザーは、ループの実行中に (別のメソッド、オブジェクト、またはスレッドによって) メンバー変数がアクセスされるかどうかを判断できないため、ループの反復ごとに値をフェッチし、インクリメントしてメンバー変数に格納する必要があります。これにより、10000000回のフェッチ、10000000回のインクリメント、および10000000回のストア操作が得られます。

外部メンバーの場合、ループ内でオブジェクトのメンバー変数をインクリメントしています。メソッド内でそのオブジェクトを作成しました。オプティマイザーは、ループが完了するまで (別のオブジェクト、メソッド、またはスレッドによって) このオブジェクトにアクセスできないことを認識するため、レジスターを使用して、ループが完了するまでそこにインクリメントを適用し、値を外部メンバー変数に格納することができます。 . これにより、1回のフェッチ、10000000回のレジスタインクリメント、および1回のストアが得られます。

ゲッター/セッターの場合、コンパイラーおよび/またはオプティマイザーがゲッター/セッターを「インライン化」するのに十分スマートであると想定します (つまり、実際にはメソッド呼び出しを行わず、 に置き換えますitem.setID(item.getID() + 1)) item.mID = item.mID + 1。オプティマイザーは、ループ内でオブジェクトのメンバー変数をインクリメントしていることを認識します。メソッド内でそのオブジェクトを作成しました。オプティマイザーは、ループが完了するまで (別のオブジェクト、メソッド、またはスレッドによって) このオブジェクトにアクセスできないことを認識するため、レジスターを使用して、ループが完了するまでそこにインクリメントを適用し、値を外部メンバーに格納することができます。変数。これにより、1回のフェッチ、10000000回のレジスタインクリメント、および1回のストアが得られます。

ゲッター/セッターのタイミングが外部メンバーのタイミングの 2 倍である理由を説明することはできませんが、これはオプティマイザーがそれを把握するのに時間がかかるか、または別の原因である可能性があります。

興味深いテストは、外部オブジェクトの作成をメソッドの外に移動し、それによって何かが変わるかどうかを確認することです。この行を移動してみてください:

final Item item = new Item();

メソッドの外側 (つまり、代わりにオブジェクトのプライベート メンバー変数として宣言します)。パフォーマンスはもっと悪くなると思います。

免責事項: 私は Dalvik のエンジニアではありません。

于 2013-02-13T20:45:55.203 に答える
0

順序を変更する以外に、干渉を排除するためにできることは他にもあります。

1- 最初の項目をもう一度計算して境界効果を排除します。できれば別の長い変数を使用してください。

2- 反復回数を 10 増やします。1000000 は大きな数のように見えますが、最初の提案からわかるように。変数を 100 万回増やすことは、最新の CPU では非常に高速であるため、さまざまなキャッシュを埋めるなど、他の多くのことが重要になります。

long l = SystemClock.elapsedRealtime()-start3-ダミー計算を挿入するなどの偽の命令を追加します。これは、この 1000000 回の反復が実際には少ない数であることを示すのに役立ちます。

volatile4-キーワードをmIDフィールドに追加します。これはおそらく、コンパイラーまたは CPU 関連の最適化を除外するための最良の方法です。

于 2013-02-14T07:33:53.523 に答える