15

いくつかのプリミティブな画像処理操作でSIMD命令組み込み関数を使用して実験するときに、パフォーマンスの変化を測定するためのマイクロベンチマークを構築しています。ただし、有用なマイクロベンチマークを作成することは難しいため、最初に、できるだけ多くの変動とエラーの原因を理解する(そして可能であれば排除する)ことを望んでいます。

私が説明しなければならない1つの要因は、測定コード自体のオーバーヘッドです。私はRDTSCで測定しており、次のコードを使用して測定オーバ​​ーヘッドを見つけています。

extern inline unsigned long long __attribute__((always_inline)) rdtsc64() {
    unsigned int hi, lo;
        __asm__ __volatile__(
            "xorl %%eax, %%eax\n\t"
            "cpuid\n\t"
            "rdtsc"
        : "=a"(lo), "=d"(hi)
        : /* no inputs */
        : "rbx", "rcx");
    return ((unsigned long long)hi << 32ull) | (unsigned long long)lo;
}

unsigned int find_rdtsc_overhead() {
    const int trials = 1000000;

    std::vector<unsigned long long> times;
    times.resize(trials, 0.0);

    for (int i = 0; i < trials; ++i) {
        unsigned long long t_begin = rdtsc64();
        unsigned long long t_end = rdtsc64();
        times[i] = (t_end - t_begin);
    }

    // print frequencies of cycle counts
}

このコードを実行すると、次のような出力が得られます。

Frequency of occurrence (for 1000000 trials):
234 cycles (counted 28 times)
243 cycles (counted 875703 times)
252 cycles (counted 124194 times)
261 cycles (counted 37 times)
270 cycles (counted 2 times)
693 cycles (counted 1 times)
1611 cycles (counted 1 times)
1665 cycles (counted 1 times)
... (a bunch of larger times each only seen once)

私の質問は次のとおりです。

  1. 上記のコードによって生成されたサイクルカウントのバイモーダル分布の考えられる原因は何ですか?
  2. 最速の時間(234サイクル)がほんの数回しか発生しないのはなぜですか?非常に珍しい状況でカウントを減らすことができますか?

さらに詳しい情報

プラットホーム:

  • Linux 2.6.32(Ubuntu 10.04)
  • g ++ 4.4.3
  • Core 2 Duo(E6600); これは一定レートのTSCを持っています。

SpeedStepがオフになっています(プロセッサがパフォーマンスモードに設定され、2.4GHzで実行されています)。「オンデマンド」モードで実行している場合、243サイクルと252サイクルで2つのピークが得られ、360サイクルと369サイクルで2つの(おそらく対応する)ピークが得られます。

sched_setaffinityプロセスを1つのコアにロックするために使用しています。各コアで順番にテストを実行すると(つまり、コア0にロックして実行し、次にコア1にロックして実行)、234サイクルの最速時間がわずかに発生する傾向があることを除いて、2つのコアで同様の結果が得られます。コア0よりもコア1の方が少ない回数。

コンパイルコマンドは次のとおりです。

g++ -Wall -mssse3 -mtune=core2 -O3 -o test.bin test.cpp

GCCがコアループ用に生成するコードは次のとおりです。

.L105:
#APP
# 27 "test.cpp" 1
    xorl %eax, %eax
    cpuid
    rdtsc
# 0 "" 2
#NO_APP
    movl    %edx, %ebp
    movl    %eax, %edi
#APP
# 27 "test.cpp" 1
    xorl %eax, %eax
    cpuid
    rdtsc
# 0 "" 2
#NO_APP
    salq    $32, %rdx
    salq    $32, %rbp
    mov %eax, %eax
    mov %edi, %edi
    orq %rax, %rdx
    orq %rdi, %rbp
    subq    %rbp, %rdx
    movq    %rdx, (%r8,%rsi)
    addq    $8, %rsi
    cmpq    $8000000, %rsi
    jne .L105
4

3 に答える 3

10

RDTSCいくつかの理由で一貫性のない結果を返す可能性があります。

  • 一部のCPU(特に特定の古いOpteron)では、TSCがコア間で同期されていません。--goodを使用してすでにこれを処理しているようですsched_setaffinity
  • コードの実行中にOSタイマー割り込みが発生した場合、実行中に遅延が発生します。これを回避する実用的な方法はありません。異常に高い値を捨てるだけです。
  • CPUのパイプラインアーティファクトは、タイトなループでどちらの方向にも数サイクル遅れることがあります。整数以外のクロックサイクル数で実行されるループを作成することは完全に可能です。
  • キャッシュ!CPUキャッシュの変動に応じて、メモリ操作(書き込みなどtimes[])の速度が変わる可能性があります。この場合、std::vector使用されている実装が単なるフラット配列であるのは幸運です。それでも、その書き込みは物事を捨てることができます。これは、おそらくこのコードの最も重要な要素です。

Core2マイクロアーキテクチャの第一人者では、なぜこのバイモーダルディストリビューションを取得するのか、またはコードが28倍高速に実行されたのかを正確に説明することはできませんが、おそらく上記の理由の1つと関係があります。

于 2011-06-21T22:39:56.463 に答える
1

周波数スロットリング/グリーン機能が OS レベルで無効になっていることを確認する必要があります。マシンを再起動します。そうしないと、コアのタイム スタンプ カウンター値が同期されていない状況が発生する可能性があります。

243 の読み値は、これを使用する理由の 1 つである最も一般的なものです。一方、経過時間が 243 未満であると仮定すると、オーバーヘッドを差し引いてアンダーフローが発生します。演算は符号なしであるため、膨大な結果が得られます。この事実は、代わりに最低の読み取り値 (234) を使用することを物語っています。ほんの数サイクルの長さのシーケンスを正確に測定することは非常に困難です。典型的な x86 @ 数 GHz では、10ns よりも短いタイミング シーケンスを推奨しますが、その長さであっても、通常は堅実とはほど遠いものです。

ここでの私の答えの残りは、私が何をするか、結果をどのように処理するか、そして主題に関する私の推論です.

オーバーヘッドに関しては、このようなコードを使用するのが最も簡単な方法です

unsigned __int64 rdtsc_inline (void);
unsigned __int64 rdtsc_function (void);

最初の形式は、生成されたコードに rdtsc 命令を発行します (コードのように)。2 つ目は、関数が呼び出され、rdtsc が実行され、return 命令が実行されます。おそらく、スタック フレームが生成されます。明らかに、2 番目の形式は最初の形式よりもはるかに低速です。

オーバーヘッド計算用の (C) コードは、次のように記述できます。

unsigned __int64 start_cycle,end_cycle;    /* place these @ the module level*/

unsigned __int64 overhead;
    
/* place this code inside a function */
    
start_cycle=rdtsc_inline();
  end_cycle=rdtsc_inline();
overhead=end_cycle-start_cycle;

インラインバリアントを使用している場合は、オーバーヘッドが低くなります。また、(特に関数形式の場合) 「あるべき」よりも大きなオーバーヘッドを計算するリスクもあります。これは、非常に短い/高速のシーケンスを測定すると、以前に計算されたオーバーヘッドが発生する可能性があることを意味します。測定自体よりも大きい。オーバーヘッドを調整しようとすると、アンダーフローが発生し、厄介な状態になります。これを処理する最善の方法は、

  1. オーバーヘッドの時間を数回測定し、常に達成された最小値を使用します。
  2. rdtsc 命令の前に厄介な同期命令を必要とするパイプライン効果に遭遇する可能性があるため、非常に短いコードシーケンスを測定しないでください。
  3. 非常に短いシーケンスを測定する必要がある場合は、結果を事実ではなく指標と見なします

以前、このスレッドで結果をどう処理するかについて説明しました。

私が行うもう 1 つのことは、測定コードをアプリケーションに統合することです。オーバーヘッドはわずかです。結果が計算された後、それを特別な構造に送り、そこで測定値の数を数え、x と x^2 の値を合計し、最小測定値と最大測定値を決定します。後で、データを使用して平均と標準偏差を計算できます。構造自体はインデックス化されており、個々のアプリケーション機能 (「機能パフォーマンス」)、CPU で費やされた時間、ディスクの読み取り/書き込み、ネットワークの読み取り/書き込み (「非機能パフォーマンス」) など、さまざまなパフォーマンスの側面を測定できます。

アプリケーションがこの方法でインストルメント化され、最初から監視されている場合、アプリケーションの存続期間中にパフォーマンスの問題が発生するリスクが大幅に削減されると期待しています.

于 2011-08-17T12:49:45.203 に答える