更新:より標準的な質問について、この回答を再投稿して更新しました。同様の質問をすべて閉じるための重複ターゲットとして使用する質問を整理したら、おそらくいつかこれを削除しますrdtsc
。
このためにインラインasmを使用する必要はなく、使用しないでください。メリットはありません。コンパイラにはとの組み込みがrdtsc
ありrdtscp
、(少なくとも最近では)__rdtsc
適切なヘッダーを含めると、すべてが組み込み関数を定義します。 https://gcc.gnu.org/wiki/DontUseInlineAsm
残念ながら、MSVCは、SIMD以外の組み込み関数に使用するヘッダーについて他のすべての人と意見が一致していません。(Intelの組み込みガイドは #include <immintrin.h>
これについて述べていますが、gccとclangを使用すると、非SIMD組み込み関数がほとんどに含まれx86intrin.h
ます。)
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
unsigned long long readTSC() {
// _mm_lfence(); // optionally wait for earlier insns to retire before reading the clock
return __rdtsc();
// _mm_lfence(); // optionally block later instructions until rdtsc retires
}
32ビットまたは64ビットの場合、4つの主要なコンパイラ(gcc / clang / ICC / MSVC)すべてでコンパイルします。Godboltコンパイラエクスプローラの結果を 参照してください。
lfence
の再現性を改善するために使用する方法の詳細についてはrdtsc
、clflushに関する@HadiBraisの回答を参照して、C関数を介してキャッシュラインを無効にしてください。
LFENCEはAMDプロセッサでシリアル化されていますか?も参照してください。(TL:DRはい、Spectre軽減が有効になっています。そうでない場合、カーネルは関連するMSRを未設定のままにします。)
rdtsc
CPUコアクロックサイクルではなく、参照サイクルをカウントします
ターボ/省電力に関係なく固定周波数でカウントされるため、uops-per-clock分析が必要な場合は、パフォーマンスカウンターを使用してください。 rdtsc
実時間と正確に相関しています(システム時計の調整を除いて、基本的にはsteady_clock
)。CPUの定格周波数、つまりアドバタイズされたステッカー周波数でカチカチ音をたてます。
マイクロベンチマークに使用する場合は、最初にウォームアップ期間を含めて、タイミングを開始する前にCPUがすでに最大クロック速度になっていることを確認してください。または、ハードウェアパフォーマンスカウンターにアクセスできるライブラリを使用するか、時間指定領域が十分に長いためにをアタッチできる場合は、プログラムの一部にperfstatperf stat -p PID
などのトリックを使用することをお勧めします。ただし、通常は、マイクロベンチマーク中のCPU周波数シフトを回避する必要があります。
また、すべてのコアのTSCが同期していることも保証されていません。そのため、スレッドがの間__rdtsc()
で別のCPUコアに移行する場合、余分なスキューが発生する可能性があります。(ただし、ほとんどのOSはすべてのコアのTSCを同期しようとします。)rdtsc
直接使用している場合は、Linuxなどでプログラムまたはスレッドをコアに固定することをお勧めしますtaskset -c 0 ./myprogram
。
asmは組み込みを使用することでどれほど優れていますか?
少なくとも、インラインasmで実行できるものと同じくらい優れています。
非インラインバージョンは、次のようにx86-64用のMSVCをコンパイルします。
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
で64ビット整数を返す32ビット呼び出し規約の場合は、 /edx:eax
だけです。それは重要ではありません、あなたは常にこれをインラインにしたいです。rdtsc
ret
それを2回使用し、間隔を時間に差し引くテスト呼び出し元では、次のようになります。
uint64_t time_something() {
uint64_t start = readTSC();
// even when empty, back-to-back __rdtsc() don't optimize away
return readTSC() - start;
}
4つのコンパイラはすべて、非常によく似たコードを作成します。これはGCCの32ビット出力です。
# gcc8.2 -O3 -m32
time_something():
push ebx # save a call-preserved reg: 32-bit only has 3 scratch regs
rdtsc
mov ecx, eax
mov ebx, edx # start in ebx:ecx
# timed region (empty)
rdtsc
sub eax, ecx
sbb edx, ebx # edx:eax -= ebx:ecx
pop ebx
ret # return value in edx:eax
これは、MSVCのx86-64出力です(名前のデマングルが適用されています)。gcc / clang/ICCはすべて同じコードを出力します。
# MSVC 19 2017 -Ox
unsigned __int64 time_something(void) PROC ; time_something
rdtsc
shl rdx, 32 ; high <<= 32
or rax, rdx
mov rcx, rax ; missed optimization: lea rcx, [rdx+rax]
; rcx = start
;; timed region (empty)
rdtsc
shl rdx, 32
or rax, rdx ; rax = end
sub rax, rcx ; end -= start
ret 0
unsigned __int64 time_something(void) ENDP ; time_something
4つのコンパイラはすべて、下半分と上半分を別のレジスタに結合する代わりにor
+を使用します。彼らが最適化に失敗するのは、一種の缶詰のシーケンスだと思います。mov
lea
しかし、インラインasmでそれを自分で書くことはほとんど良いことではありません。32ビットの結果しか保持しないような短い間隔でタイミングをとっている場合は、EDXの結果の上位32ビットを無視する機会をコンパイラーから奪うことになります。または、コンパイラが開始時刻をメモリに格納することを決定した場合、shift/または/movの代わりに2つの32ビットストアを使用することができます。タイミングの一部として1つの余分なuopが気になる場合は、マイクロベンチマーク全体を純粋なasmで作成することをお勧めします。