Visual Studioを使用すると、以下に示すように、プロセッサからクロックサイクルカウントを読み取ることができます。 GCCで同じことを行うにはどうすればよいですか?
#ifdef _MSC_VER // Compiler: Microsoft Visual Studio
#ifdef _M_IX86 // Processor: x86
inline uint64_t clockCycleCount()
{
uint64_t c;
__asm {
cpuid // serialize processor
rdtsc // read time stamp counter
mov dword ptr [c + 0], eax
mov dword ptr [c + 4], edx
}
return c;
}
#Elif defined(_M_X64) // Processor: x64
extern "C" unsigned __int64 __rdtsc();
#pragma intrinsic(__rdtsc)
inline uint64_t clockCycleCount()
{
return __rdtsc();
}
#endif
#endif
Linuxの最近のバージョンでは、gettimeofdayにナノ秒のタイミングが組み込まれます。
本当にRDTSCを呼び出したい場合は、次のインラインアセンブリを使用できます。
http://www.mcs.anl.gov/~kazutomo/rdtsc.html
#if defined(__i386__)
static __inline__ unsigned long long rdtsc(void)
{
unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
}
#Elif defined(__x86_64__)
static __inline__ unsigned long long rdtsc(void)
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
#endif
他の答えは機能しますが、__rdtsc
を含めることで利用できるGCCのx86intrin.h
組み込み関数を使用して、インラインアセンブリを回避できます。
次のように定義されています: gcc/config/i386/ia32intrin.h
:
/* rdtsc */
extern __inline unsigned long long
__attribute__((__gnu_inline__, __always_inline__, __artificial__))
__rdtsc (void)
{
return __builtin_ia32_rdtsc ();
}
更新:この回答を再投稿して更新より標準的な質問。同様の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
}
_
4つの主要なコンパイラすべてでコンパイルします:gcc/clang/ICC/MSVC、32ビットまたは64ビット用。の結果Godboltコンパイラエクスプローラ 。
lfence
を使用してrdtsc
の再現性を向上させる方法の詳細については、 clflushを使用してC関数を介してキャッシュラインを無効にする に関する@HadiBraisの回答を参照してください。
参照 AMDプロセッサでLFENCEがシリアル化されていますか? (TL:DRはい、Spectre軽減が有効になっています。そうでない場合、カーネルは関連するMSRを未設定のままにします。)
rdtsc
カウント参照サイクル、CPUコアクロックサイクルではありませんターボ/省電力に関係なく固定周波数でカウントされるため、1クロックあたりのuops分析が必要な場合は、パフォーマンスカウンターを使用してください。 rdtsc
は実時間と正確に相関しています(システムクロックの調整を除いて、基本的には_steady_clock
_です)。 CPUの定格周波数、つまりアドバタイズされたステッカー周波数で刻みます。
マイクロベンチマークに使用する場合は、最初にウォームアップ期間を含めて、タイミングを開始する前にCPUがすでに最大クロック速度になっていることを確認してください。または、ハードウェアパフォーマンスカウンターへのアクセスを提供するライブラリを使用するか、 プログラムの一部のperf stat のようなトリックを使用します(時間指定された領域が_perf stat -p PID
_をアタッチできるほど長い場合)。ただし、通常は、マイクロベンチマーク中のCPU周波数シフトを回避する必要があります。
すべてのコアのTSCが同期していることも保証されていません。したがって、スレッドが__rdtsc()
の間に別のCPUコアに移行する場合、余分なスキューが発生する可能性があります。 (ただし、ほとんどのOSはすべてのコアのTSCを同期しようとします。)rdtsc
を直接使用している場合は、プログラムまたはスレッドをコアに固定する必要があります。 Linuxでは_taskset -c 0 ./myprogram
_を使用します。
少なくともインラインasmでできることと同じくらい良いです。
それの非インラインバージョンは、次のようにx86-64用のMSVCをコンパイルします。
_unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
_
_edx:eax
_で64ビット整数を返す32ビットの呼び出し規約の場合、それは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/or/movの代わりに2つの32ビットストアを使用することができます。タイミングの一部として1つの余分なuopが気になる場合は、マイクロベンチマーク全体を純粋なasmで記述したほうがよいでしょう。
gcc
を使用するLinuxでは、次を使用します。
/* define this somewhere */
#ifdef __i386
__inline__ uint64_t rdtsc() {
uint64_t x;
__asm__ volatile ("rdtsc" : "=A" (x));
return x;
}
#Elif __AMD64
__inline__ uint64_t rdtsc() {
uint64_t a, d;
__asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
return (d<<32) | a;
}
#endif
/* now, in your function, do the following */
uint64_t t;
t = rdtsc();
// ... the stuff that you want to time ...
t = rdtsc() - t;
// t now contains the number of cycles elapsed