17

この質問は、この 2 つのループ本体または 1 つ (結果が同じ) と同じです が、私の場合は Java を使用します。

10 億回実行される 2 つのループがあります。

int a = 188, b = 144, aMax = 0, bMax = 0;

for (int i = 0; i < 1000000000; i++) {
  int t = a ^ i;
  if (t > aMax) 
    aMax = t;     
}  

for (int i = 0; i < 1000000000; i++) {
  int t = b ^ i;
  if (t > bMax) 
    bMax = t;     
}  

私のマシンでこれら 2 つのループを実行するのにかかる時間は約 4 秒です。これら 2 つのループを 1 つのループに融合し、その 1 つのループですべての操作を実行すると、2 秒で実行されます。ご覧のとおり、単純な操作がループの内容を構成しているため、一定の時間が必要です。

私の質問は、このパフォーマンスの向上はどこで得られますか?

2 つの別々のループでパフォーマンスが影響を受ける可能性がある唯一の場所は、i をインクリメントし、i < 1000000000 であるかどうかを 20 億回チェックすることと、ループを融合した場合は 10 億回だけであると推測しています。そこで何か他のことが起こっていますか?

ありがとう!

4

4 に答える 4

6

ウォームアップ フェーズを実行しない場合、最初のループは最適化およびコンパイルされますが、2 番目のループはコンパイルされない可能性があります。一方、それらをマージすると、マージされたループ全体がコンパイルされます。また、serverオプションとコードを使用すると、結果を使用しないため、ほとんどが最適化されます。

以下のテストを実行し、各ループとマージされたループを独自のメソッドに配置し、JVM をウォームアップして、すべてがコンパイルされることを確認しました。

結果 (JVM オプション: -server -XX:+PrintCompilation):

  • ループ 1 = 500ms
  • ループ 2 = 900 ミリ秒
  • マージされたループ = 1,300 ミリ秒

そのため、マージされたループはわずかに高速ですが、それほどではありません。

public static void main(String[] args) throws InterruptedException {

    for (int i = 0; i < 3; i++) {
        loop1();
        loop2();
        loopBoth();
    }

    long start = System.nanoTime();

    loop1();

    long end = System.nanoTime();
    System.out.println((end - start) / 1000000);

    start = System.nanoTime();
    loop2();
    end = System.nanoTime();
    System.out.println((end - start) / 1000000);

    start = System.nanoTime();
    loopBoth();
    end = System.nanoTime();
    System.out.println((end - start) / 1000000);
}

public static void loop1() {
    int a = 188, aMax = 0;
    for (int i = 0; i < 1000000000; i++) {
        int t = a ^ i;
        if (t > aMax) {
            aMax = t;
        }
    }
    System.out.println(aMax);
}

public static void loop2() {
    int b = 144, bMax = 0;
    for (int i = 0; i < 1000000000; i++) {
        int t = b ^ i;
        if (t > bMax) {
            bMax = t;
        }
    }
    System.out.println(bMax);
}

public static void loopBoth() {
    int a = 188, b = 144, aMax = 0, bMax = 0;

    for (int i = 0; i < 1000000000; i++) {
        int t = a ^ i;
        if (t > aMax) {
            aMax = t;
        }
        int u = b ^ i;
        if (u > bMax) {
            bMax = u;
        }
    }
    System.out.println(aMax);
    System.out.println(bMax);
}
于 2012-08-25T16:01:15.217 に答える
2

つまり、CPU はマージされたループ内の命令を並列に実行できるため、パフォーマンスが 2 倍になります。

また、2 番目のループが効率的に最適化されていない可能性もあります。これは、最初のループがメソッド全体のコンパイルをトリガーし、2 番目のループが 2 番目のループのタイミングを乱す可能性のあるメトリックなしでコンパイルされるためです。そうならないように、各ループを別のメソッドに配置します。

CPU は、多数の独立した操作を並行して実行できます ( Pentium III では深さ 10、Xeon では深さ 20 )。並行して実行しようとする操作の 1 つは、分岐予測を使用した分岐ですが、ほぼ毎回同じ分岐を使用しない場合です。

ループをアンロールすると、ループが次のように見えると思います(この場合、ループのアンロールが増える可能性があります)

for (int i = 0; i < 1000000000; i += 2) {
  // this first block is run almost in parallel
  int t1 = a ^ i;
  int t2 = b ^ i;
  int t3 = a ^ (i+1);
  int t4 = b ^ (i+1);
  // this block run in parallel
  if (t1 > aMax) aMax = t1;     
  if (t2 > bMax) bMax = t2;     
  if (t3 > aMax) aMax = t3;     
  if (t4 > bMax) bMax = t4;     
} 
于 2012-08-26T07:16:31.827 に答える
1

単一のループの場合、JITはループのアンローリングを選択する可能性があり、その結果、パフォーマンスがわずかに向上するようです。

于 2012-08-25T15:54:08.000 に答える
1

-server を使用しましたか? いいえの場合は、クライアントの JIT は予測可能ではなく、優れたものでもありません。正確に何が起こっているのか本当に興味がある場合は、UnlockDiagnostic + LogCompilation を使用して、両方のケースで適用されている最適化を確認できます (生成されたアセンブリまで)。

また、提供されたコードから、ウォームアップを行うかどうか、同じJVMに対してテストを1回または複数回実行するかどうか、2回実行するかどうか(異なるJVM)がわかりません。最良の時間、平均時間、または中央値のいずれを考慮に入れている場合でも、外れ値を除外しますか?

Java マイクロベンチマークの作成に関する適切なリンクは次のとおりです: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html

編集: もう 1 つのマイクロベンチマークのヒント、オンザスタック置換に注意してください: http://www.azulsystems.com/blog/cliff/2011-11-22-what-the-heck-is-osr-and-why-悪いか良いか

于 2012-08-25T16:00:24.603 に答える