6

これは自明のコードです(操作を10億回実行します):

int k = 0;

Stopwatch sw = new Stopwatch();
sw.Start();
for (int a = 0; a < 1000; a++)
    for (int b = 0; b < 1000; b++)
        for (int c = 0; c < 1000; c++)
            k++;

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

sw = new Stopwatch();
sw.Start();

for (int a = 0; a < 1000; a++)
    for (int b = 0; b < 1000; b++)
        for (int c = 0; c < 1000; c++)
            ; // NO-OP

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

結果は(少なくとも私のコンピューターでは)どこか(ミリ秒単位)です

2168
2564

2 番目は常に約 0.5 秒長くなります。

変数を 10 億回インクリメントすると、同じ回数ノーオペレーションを実行するよりも長く実行される可能性があるのはなぜですか?

編集:これは DEBUG でのみ発生します。リリースはこれを正しく行います。少なくとも私のコンピューターでは、最初のものは長持ちします。コメントで指摘されているように、誰かが RELEASE ビルドでもこの問題を経験しました。しかし、この効果を生み出す DEBUG ではどうなるでしょうか?

4

3 に答える 3

4

問題は、Azodious が述べたように、正確ではないため、デバッグ モードを使用して時間を測定できないことです。

リリース モードをオンにすると、次の数値が得られます。

増分k: 445

NOP : 402

ILインクリメント バージョンには、さらに 4 つの命令があります。

IL_0001:  ldc.i4.0    
IL_0002:  stloc.0     
IL_0003:  ldc.i4.0    
IL_0004:  stloc.1     
IL_0005:  br.s        IL_003B
IL_0007:  ldc.i4.0    
IL_0008:  stloc.2     
IL_0009:  br.s        IL_0029
IL_000B:  ldc.i4.0    
IL_000C:  stloc.3     
IL_000D:  br.s        IL_0017
IL_000F:  ldloc.0     
IL_0010:  ldc.i4.1    
IL_0011:  add         
IL_0012:  stloc.0     
IL_0013:  ldloc.3     
IL_0014:  ldc.i4.1    
IL_0015:  add         
IL_0016:  stloc.3     
IL_0017:  ldloc.3     
IL_0018:  ldc.i4      E8 03 00 00 
IL_001D:  clt         
IL_001F:  stloc.s     04 
IL_0021:  ldloc.s     04 
IL_0023:  brtrue.s    IL_000F
IL_0025:  ldloc.2     
IL_0026:  ldc.i4.1    
IL_0027:  add         
IL_0028:  stloc.2     
IL_0029:  ldloc.2     
IL_002A:  ldc.i4      E8 03 00 00 
IL_002F:  clt         
IL_0031:  stloc.s     04 
IL_0033:  ldloc.s     04 
IL_0035:  brtrue.s    IL_000B
IL_0037:  ldloc.1     
IL_0038:  ldc.i4.1    
IL_0039:  add         
IL_003A:  stloc.1     
IL_003B:  ldloc.1     
IL_003C:  ldc.i4      E8 03 00 00 
IL_0041:  clt         
IL_0043:  stloc.s     04 
IL_0045:  ldloc.s     04 
IL_0047:  brtrue.s    IL_0007

-verisonNOPには同じ量のブランチがありますが、1 つ少ないaddです:

IL_0001:  ldc.i4.0    
IL_0002:  stloc.0     
IL_0003:  ldc.i4.0    
IL_0004:  stloc.1     
IL_0005:  br.s        IL_0037
IL_0007:  ldc.i4.0    
IL_0008:  stloc.2     
IL_0009:  br.s        IL_0025
IL_000B:  ldc.i4.0    
IL_000C:  stloc.3     
IL_000D:  br.s        IL_0013
IL_000F:  ldloc.3     
IL_0010:  ldc.i4.1    
IL_0011:  add         
IL_0012:  stloc.3     
IL_0013:  ldloc.3     
IL_0014:  ldc.i4      E8 03 00 00 
IL_0019:  clt         
IL_001B:  stloc.s     04 
IL_001D:  ldloc.s     04 
IL_001F:  brtrue.s    IL_000F
IL_0021:  ldloc.2     
IL_0022:  ldc.i4.1    
IL_0023:  add         
IL_0024:  stloc.2     
IL_0025:  ldloc.2     
IL_0026:  ldc.i4      E8 03 00 00 
IL_002B:  clt         
IL_002D:  stloc.s     04 
IL_002F:  ldloc.s     04 
IL_0031:  brtrue.s    IL_000B
IL_0033:  ldloc.1     
IL_0034:  ldc.i4.1    
IL_0035:  add         
IL_0036:  stloc.1     
IL_0037:  ldloc.1     
IL_0038:  ldc.i4      E8 03 00 00 
IL_003D:  clt         
IL_003F:  stloc.s     04 
IL_0041:  ldloc.s     04 
IL_0043:  brtrue.s    IL_0007

何が起こっているのかを正確に確認したいので、これらは最適化なしでコンパイルされています。

実際のそれらの唯一の違いは次のとおりです。

IL_0012:  stloc.0     
IL_0013:  ldloc.3     
IL_0014:  ldc.i4.1    
IL_0015:  add  

簡単に言えば、デバッグモードになっているため、奇妙な数字が表示されます。

于 2012-09-18T07:32:43.107 に答える
1

間違ったコードをテストしただけでなく、インクリメント演算子のコストを測定したと思い込んでいたことが主な間違いでした。for() ループのコストを測定しました。インクリメントよりもはるかに多くのCPUサイクルが必要です。

for() ループの問題は、CPU が強制的に分岐し、ループの先頭に戻ることです。最新の CPU は分岐をあまり好みませ。コードを順次実行するように最適化されています。プロセッサがコードを高速に実行できるように設計されたコア アーキテクチャ実装の詳細であるパイプラインの副作用。ブランチは、プロセッサにパイプラインを強制的にフラッシュさせ、無駄であることが判明した多くの作業を破棄する可能性があります。パイプラインをフラッシュするコストを削減するために、CPU 設計には多くのリソースが割り当てられます。コア部分は分岐予測子です。これは、分岐がどの方向に進むかを前もって推測しようとするため、パイプラインを命令で満たすことができます実行されます。間違った推測は非常に高価です。for() ループが十分に長い場合、これを恐れる必要はありません。

最新のプロセッサのもう 1 つの問題は、分岐先のアラインメントに非常に敏感であることです。つまり、ループの開始の命令のアドレスです。4 または 8 で割り切れるアドレスに位置合わせされていない場合、プリフェッチ ユニットは正しい命令のデコードを開始するために余分なサイクルを必要とします。これは、ジッターが処理する必要がある実装の詳細です。命令を整列させるために、余分な NOP 命令を挿入する必要がある場合があります。x86 ジッターはその最適化を実行しませんが、x64 ジッターは実行します。

位置合わせの問題の目に見える副作用は、2 つのコードを交換すると測定に影響する可能性があることです。

コードのベンチマークは、最新の CPU では危険な冒険です。コードの合成バージョンをプロファイリングして観察したことを、実際のコードで実際に取得できる可能性は高くありません。15% 以下の差は統計的に意味がありません。

于 2012-12-08T18:54:18.113 に答える
0

私は3回実行し、出力は次のとおりです。

3786

3252


3800

3256


3840

3255

したがって、デバッグ モードで収集された統計情報に基づいて決定を下している場合は、そうしないでください。

デバッグモードでは、デバッグ中にデバッガーを支援するコードに多くのデータが添付されました。

于 2012-09-18T07:15:39.410 に答える