web-dev-qa-db-ja.com

rdtscpとrdtscの違い:メモリとcpuid / rdtsc?

パフォーマンスモニタリングに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

これはrdtscをclobberリストに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全体でメモリの内容が同じままであるという仮定を立てることができないことを意味します。したがって、GCCはその周りで並べ替えを行いません。

だから私の質問は:

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

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

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

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

Linuxカーネルは、AMDプラットフォームではmfence;rdtsc、Intelではlfence;rdtscを使用します。これらを区別する必要がない場合は、mfence;rdtscは両方で機能しますが、mfencelfenceよりも強力なバリアなので、少し遅くなります。

42
janneb

次のように使用できます。

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自体の前にその後に来る命令が実行されることは不可能です。

5
Pranjal Verma