67

パフォーマンスの監視にtscを使用しようとしており、命令の並べ替えを防止したいとします。

これらは私たちのオプションです:

1: rdtscpシリアル化呼び出しです。rdtscpへの呼び出しの前後の並べ替えを防ぎます。

__asm__ __volatile__("rdtscp; "         // serializing read of tsc
                     "shl $32,%%rdx; "  // shift higher 32 bits stored in rdx up
                     "or %%rdx,%%rax"   // and or onto rax
                     : "=a"(tsc)        // output to tsc variable
                     :
                     : "%rcx", "%rdx"); // rcx and rdx are clobbered

ただし、rdtscp新しいCPUでのみ使用できます。したがって、この場合はを使用する必要がありますrdtsc。ただしrdtsc、シリアル化されていないため、単独で使用してもCPUによる並べ替えが妨げられることはありません。

したがって、次の2つのオプションのいずれかを使用して、並べ替えを防ぐことができます。

2:cpuidこれはとの呼び出しrdtscです。cpuidシリアル化呼び出しです。

volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing
unsigned tmp;
__cpuid(0, tmp, tmp, tmp, tmp);                   // cpuid is a serialising call
dont_remove = tmp;                                // prevent optimizing out cpuid

__asm__ __volatile__("rdtsc; "          // read of tsc
                     "shl $32,%%rdx; "  // shift higher 32 bits stored in rdx up
                     "or %%rdx,%%rax"   // and or onto rax
                     : "=a"(tsc)        // output to tsc
                     :
                     : "%rcx", "%rdx"); // rcx and rdx are clobbered

3:これはclobberリスト内のrdtscwithの呼び出しであり、並べ替えを防ぎますmemory

__asm__ __volatile__("rdtsc; "          // read of tsc
                     "shl $32,%%rdx; "  // shift higher 32 bits stored in rdx up
                     "or %%rdx,%%rax"   // and or onto rax
                     : "=a"(tsc)        // output to tsc
                     :
                     : "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered
                                                  // memory to prevent reordering

3番目のオプションについての私の理解は次のとおりです。

呼び出しを行うと__volatile__、オプティマイザがasmを削除したり、asmの結果を必要とする(または入力を変更する)可能性のある命令間で移動したりするのを防ぎます。ただし、関係のない操作に関しては移動する可能性があります。だから__volatile__十分ではありません。

コンパイラのメモリが破壊されていることを通知します: "memory")"memory"clobberは、GCCがasm全体でメモリの内容が同じままであるという仮定を立てることができないため、その周りで並べ替えられないことを意味します。

だから私の質問は:

  • 1:私の理解__volatile__"memory"正しいですか?
  • 2:次の2つの呼び出しは同じことをしますか?
  • 3:使用"memory"は、別のシリアル化命令を使用するよりもはるかに簡単に見えます。なぜ誰かが2番目のオプションよりも3番目のオプションを使用するのでしょうか?
4

2 に答える 2

50

コメントで述べたように、コンパイラバリアプロセッサバリアには違いがあります。volatileasmステートメントでmemoryはコンパイラバリアとして機能しますが、プロセッサは引き続き命令を自由に並べ替えることができます。

プロセッサバリアは、明示的に指定する必要のある特別な命令です。たとえばrdtscp, cpuid、メモリフェンス命令(mfence, lfence,...)などです。

余談ですが、cpuid以前はバリアとして使用するのが一般的ですが、仮想マシンプラットフォームは、クラスター内の複数のマシンに共通のCPU機能のセットを課すために命令をrdtscトラップしてエミュレートすることが多いため、パフォーマンスの観点からも非常に悪い場合があります。 cpuid(ライブマイグレーションが機能することを確認するため)。したがって、メモリフェンス命令の1つを使用することをお勧めします。

Linuxカーネルはmfence;rdtscAMDプラットフォームとlfence;rdtscIntelで使用します。これらを区別することに煩わされたくない場合は、両方で機能しますが、よりも強力なバリアであるため、mfence;rdtsc少し遅くなります。mfencelfence

編集2019-11-25:Linuxカーネル5.4の時点で、IntelとAMDの両方でrdtscをシリアル化するためにlfenceが使用されています。このコミット「x86:Remove X86_FEATURE_MFENCE_RDTSC」を参照してください:https ://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id = be261ffce6f13229dad50f59c5e491f933d3167f

于 2012-09-28T06:41:54.323 に答える
6

以下のように使用できます。

asm volatile (
"CPUID\n\t"/*serialize*/
"RDTSC\n\t"/*read the clock*/
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r"
(cycles_low):: "%rax", "%rbx", "%rcx", "%rdx");
/*
Call the function to benchmark
*/
asm volatile (
"RDTSCP\n\t"/*read the clock*/
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
"CPUID\n\t": "=r" (cycles_high1), "=r"
(cycles_low1):: "%rax", "%rbx", "%rcx", "%rdx");

上記のコードでは、最初のCPUID呼び出しは、RDTSC命令の上下の命令のアウトオブオーダー実行を回避するためのバリアを実装しています。このメソッドでは、リアルタイムレジスタの読み取りの間にCPUID命令を呼び出すことを回避します

次に、最初のRDTSCがタイムスタンプレジスタを読み取り、値がメモリに格納されます。次に、測定したいコードが実行されます。RDTSCP命令は、タイムスタンプレジスタを2回読み取り、測定したいすべてのコードの実行が完了したことを保証します。その後に続く2つの「mov」命令は、edxおよびeaxレジスタの値をメモリに格納します。最後に、CPUID呼び出しは、バリアが再度実装されることを保証するため、CPUID自体の前に後続の命令が実行されることは不可能です。

于 2013-01-08T11:44:10.197 に答える