2016-06-14 5 views
1

私は別のキャッシュレベルにメモリアクセスの正確な測定をしようと、プロービングのために、このコードを思いついたんだ:RDTSCとRDTSCPによる正確なメモリアクセス時間のプロービング?

__asm__ __volatile__(
     "xor %%eax, %%eax \n" 
     "xor %%edi, %%edi \n" 
     "xor %%edx, %%edx \n" 
     /* time measurement */ 
     "lfence    \n" 
     "rdtsc    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rdi \n" 
     /* memory access */ 
     "movq (%%rsi), %%rbx\n" 
     /* time measurement */ 
     "rdtscp    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rsi \n" 
     "cpuid    \n" 
     : /* output operands */ 
     "=S"(t2), "=D"(t1) 
     : /* input operands */ 
     "S" (mem) 
     : /* clobber description */ 
     "ebx", "ecx", "edx", "cc", "memory" 
    ); 

L1およびL2キャッシュへのアクセスは、わずか8サイクルと結果によって異なりますが

./cache_testing 
From Memory: 42 
From L3: 46 
From L2: 40 
From L1: 38 

./cache_testing 
From Memory: 40 
From L3: 38 
From L2: 36 
From L1: 40 

__asm__ __volatile__(
     "xor %%eax, %%eax \n" 
     "xor %%edi, %%edi \n" 
     "xor %%edx, %%edx \n" 
     /* time measurement */ 
     "lfence    \n" 
     "rdtsc    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rdi \n" 
     /* memory access */ 
     //"movq (%%rsi), %%rbx\n" 
     /* time measurement */ 
     "rdtscp    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rsi \n" 
     "cpuid    \n" 
     : /* output operands */ 
     "=S"(t2), "=D"(t1) 
     : /* input operands */ 
     "S" (mem) 
     : /* clobber description */ 
     "ebx", "ecx", "edx", "cc", "memory" 
    ); 

結果、このように見えた:はるかに変動しているので、私はタイミングを有するどの程度影響(離れて実際のメモリアクセスからの)周囲のコードをチェックすることを決めました

現時点で目的によって異なるキャッシュレベルにヒットしていないことは知っていますが、メモリアクセスが不足している場合のタイミングがどうしてそんなに変動しているのだろうと思います。 コードは最高優先順位のSCHED_FIFOとして実行され、1つのCPUに固定されており、実行中はディスパッチしないでください。 私のコードを改善して、それによって結果を改善できるかどうかは誰にでも分かりますか?

+1

[Hasner Fogのmicroarch pdf](http://agner.org/optimize/)によると、Has Haswellのキャッシュ負荷 - >使用待ち時間の正確な数値は、L1では4c、L2では12cです。これを測定するための素晴らしい方法(特にL1の場合)は、ポインタを追うことです。 L1の場合は、それ自身を指すポインタを設定し、ループ内で 'mov(%rax)、%rax'を実行してください。 L2の場合、L1に収まらない大きなリンクリストが必要です。 –

答えて

1

測定コードを修正するには、計測オーバーヘッドを差し引くために空の設定をベースラインとして測定する必要があります。

また、TSCはコアクロックサイクルではなく、リファレンスサイクル数をカウントするので、CPUが常に同じ速度で動作していることを確認する必要があります。 (たとえば、ターボを無効にしてウォームアップループを使用してCPUを最高速度にすると、オーバークロックしない場合はTSC数がコアサイクルと一致する必要があります)。


私は通常、RDTSCではなくperfカウンタで測定します。

しかし、私はあなたが最初のRDTSCの前に(CPUIDのような)シリアライズ命令を使うべきだと思います。第2のRDTSCの後にCPUIDを使用することはおそらく役に立たない。 rdstcp for the second measurement is usefulは、ロードが実行された後からタイムスタンプが来ることを意味するためです。 (マニュアルには、「実行」と言い、それが意味している場合IDKは「引退」または単に文字通りロードポートで実行される。)

のでIIRCは、あなたの最善の策は、次のとおりです。

# maybe set eax to something before CPUID 
cpuid 
rdtsc 
shl $32, %%rdx 
lea (%%rax, %%rdx), %%rsi 

... code under test 

# CPUID here, too, if you can only use rdtsc instead of rdtscp 
rdtscp 
shl $32, %%rdx 
or %%rdx, %%rax 
sub %%rsi, %%rax 
# time difference in RAX  

場合は、テスト対象のコードshift/LEAと同じALUポートで競合する場合、最初のRDTSC結果のうちの低い方の32を別のレジスタにmovだけ入れることができます。高い32を扱う代わりに。タイムスタンプの差が2^32よりずっと小さいと仮定すると、どちらのカウントの上位32ビットも必要ありません。


最近のCPUでこのような小さなシーケンスを測定することは、TSCよりパフォーマンスカウンターを使ってうまくいくことができます。 Agner Fog's test programsには、プログラム内部からperfカウンタを使用して何かを測定するためのコードが含まれています。これにより、ターボまたはターボ以外のコアサイクルを測定することができます。これは、コアクロックサイクルのパフォーマンスカウンタが実際に物理クロックサイクルごとに1つカウントするためです。

+0

更新: 'lfence; rdtsc'はそれを直列化し、CPUIDよりも効率的です。 –

関連する問題