18

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
4

4 に答える 4

29

__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 ();
}
于 2014-12-02T19:56:34.030 に答える
28

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
于 2012-03-27T10:36:26.960 に答える
18

更新:より標準的な質問について、この回答を再投稿して更新しました。同様の質問をすべて閉じるための重複ターゲットとして使用する質問を整理したら、おそらくいつかこれを削除します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を未設定のままにします。)


rdtscCPUコアクロックサイクルではなく、参照サイクルをカウントします

ターボ/省電力に関係なく固定周波数でカウントされるため、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だけです。それは重要ではありません、あなたは常にこれをインラインにしたいです。rdtscret

それを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+を使用します。彼らが最適化に失敗するのは、一種の缶詰のシーケンスだと思います。movlea

しかし、インラインasmでそれを自分で書くことはほとんど良いことではありません。32ビットの結果しか保持しないような短い間隔でタイミングをとっている場合は、EDXの結果の上位32ビットを無視する機会をコンパイラーから奪うことになります。または、コンパイラが開始時刻をメモリに格納することを決定した場合、shift/または/movの代わりに2つの32ビットストアを使用することができます。タイミングの一部として1つの余分なuopが気になる場合は、マイクロベンチマーク全体を純粋なasmで作成することをお勧めします。

于 2018-08-18T10:03:35.963 に答える
5

Linuxでgcc、私は以下を使用します:

/* 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
于 2012-03-27T10:41:08.187 に答える