47

最新のCPUサイクルカウントを取得するためのCコードを含むSOに関するこの投稿を見ました。

C / C ++Linuxx86_64でのCPUサイクルカウントベースのプロファイリング

このコードをC++で使用する方法はありますか(WindowsおよびLinuxソリューションを歓迎します)?Cで書かれていますが(そしてCはC ++のサブセットです)、このコードがC ++プロジェクトで機能するかどうか、そして機能しない場合はどのように翻訳するかについてはあまりわかりません。

x86-64を使用しています

EDIT2:

この関数が見つかりましたが、VS2010にアセンブラーを認識させることができません。何か含める必要がありますか?(私はウィンドウズに交換uint64_tする必要があると思います....?)long long

static inline uint64_t get_cycles()
{
  uint64_t t;
  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

EDIT3:

上記のコードから、次のエラーが発生します。

"エラーC2400:'opcode'のインラインアセンブラ構文エラー;見つかった'データ型'"

誰か助けてもらえますか?

4

5 に答える 5

77

GCC 4.5以降では、__rdtsc()組み込み関数はMSVCとGCCの両方でサポートされるようになりました。

ただし、必要なインクルードは異なります。

#ifdef _WIN32
#include <intrin.h>
#else
#include <x86intrin.h>
#endif

これがGCC4.5以前の元の答えです。

私のプロジェクトの1つから直接引き出されました:

#include <stdint.h>

//  Windows
#ifdef _WIN32

#include <intrin.h>
uint64_t rdtsc(){
    return __rdtsc();
}

//  Linux/GCC
#else

uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

#endif

このGNUC拡張asmは、コンパイラーに次のように通知します。

  • volatile:出力は入力の純粋関数ではありません(したがって、古い結果を再利用するのではなく、毎回再実行する必要があります)。
  • "=a"(lo)および"=d"(hi):出力オペランドは固定レジスタです:EAXおよびEDX。(x86マシンの制約)。x86rdtsc命令は64ビットの結果をEDX:EAXに配置するため、コンパイラに出力を選択させることは機能し"=r"ません。CPUに結果を他の場所に送信するように要求する方法はありません。
  • ((uint64_t)hi << 32) | lo-両方の32ビットの半分を64ビットにゼロ拡張し(loとhiがであるためunsigned)、論理的にシフト+ORして単一の64ビットC変数にします。32ビットコードでは、これは単なる再解釈です。値は32ビットレジスタのペアにとどまります。64ビットコードでは、上位半分が最適化されない限り、通常、実際のshift +ORasm命令を取得します。

unsigned long(編集者注:代わりに使用した場合、これはおそらくより効率的である可能性がありますunsigned int。そうすると、コンパイラは、それloがすでにRAXにゼロ拡張されていることを認識します。上半分がゼロであることを認識しないため|+必要に応じて同等です。別の方法でマージします。理論的には、オプティマイザに適切な仕事をさせる限り、本質的に両方の長所を提供する必要があります。)

https://gcc.gnu.org/wiki/DontUseInlineAsm回避できる場合。ただし、このセクションが、インラインasmを使用する古いコードを理解して、組み込み関数で書き直す必要がある場合に役立つことを願っています。https://stackoverflow.com/tags/inline-assembly/infoも参照してください

于 2012-12-07T23:42:59.393 に答える
50

x86-64ではインラインasmが壊れています。 "=A"64ビットモードでは、コンパイラはEDX:EAXではなくRAXまたはRDXのいずれかを選択できます。詳細については、このQ&Aを参照してください


このためにインラインasmは必要ありません。メリットはありません。コンパイラにはとの組み込みがrdtscありrdtscp、(少なくとも最近では)__rdtsc適切なヘッダーを含めると、すべてが組み込み関数を定義します。ただし、他のほとんどすべての場合(https://gcc.gnu.org/wiki/DontUseInlineAsm )とは異なり、@ Mysticialのような適切で安全な実装を使用している限り、 asmに重大な欠点はありません。

(asmの小さな利点の1つは、確かに2 ^ 32カウント未満になる小さな間隔の時間を計りたい場合、結果の上位半分を無視できることです。コンパイラーは、組み込みで最適化を行うことができuint32_t time_low = __rdtsc()ますが、練習することで、シフト/ ORを実行する指示を無駄にすることがあります。)


残念ながら、MSVCは、SIMD以外の組み込み関数に使用するヘッダーについて他のすべての人と意見が一致していません。

Intelの本質的なガイドによると_rdtsc(アンダースコアが1つあります)はにあります<immintrin.h>が、gccとclangでは機能しません。それらはでSIMD組み込み関数を定義するだけなので、 (MSVC)と(最近のICCを含む他のすべて)<immintrin.h>に固執しています。MSVCおよびIntelのドキュメントと互換性があるため、gccおよびclangは、関数の1アンダースコアバージョンと2アンダースコアバージョンの両方を定義します。<intrin.h><x86intrin.h>

おもしろい事実:二重アンダースコアバージョンは符号なし64ビット整数を返しますが、Intel_rdtsc()は(符号付き)を返すものとして文書化し__int64ます。

// valid C99 and C++

#include <stdint.h>  // <cstdint> is preferred in C++, but stdint.h works.

#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif

// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
uint64_t readTSC() {
    // _mm_lfence();  // optionally wait for earlier insns to retire before reading the clock
    uint64_t tsc = __rdtsc();
    // _mm_lfence();  // optionally block later instructions until rdtsc retires
    return tsc;
}

// requires a Nehalem or newer CPU.  Not Core2 or earlier.  IDK when AMD added it.
inline
uint64_t readTSCp() {
    unsigned dummy;
    return __rdtscp(&dummy);  // waits for earlier insns to retire, but allows later to start
}

32ビットまたは64ビットの場合、4つの主要なコンパイラ(gcc / clang / ICC / MSVC)すべてでコンパイルします。いくつかのテスト呼び出し元を含む、Godboltコンパイラエクスプローラーの結果を 参照してください。

これらの組み込み関数は、gcc4.5(2010年から)およびclang3.5(2014年から)で新しく追加されました。Godboltのgcc4.4とclang3.4はこれをコンパイルしませんが、gcc4.5.3(2011年4月)はコンパイルします。古いコードでインラインasmが表示される場合がありますが、これを。に置き換えることができます__rdtsc()。10年以上前のコンパイラは通常、gcc6、gcc7、またはgcc8よりも遅いコードを作成し、有用性の低いエラーメッセージを表示します。

MSVCはx86-64のインラインasmをサポートしていなかったため、MSVC組み込み関数は(私が思うに)はるかに長く存在していました。ICC13には__rdtscimmintrin.hありますが、まったくありませんx86intrin.h。最近のICCにはx86intrin.h、少なくともGodboltがLinux用にインストールする方法があります。

long long特にそれらを減算してfloatに変換する場合は、 それらを符号付きとして定義することをお勧めします。-> float / doubleは、AVX512のないx86int64_tよりも効率的です。uint64_tまた、TSCが完全に同期されていない場合、CPUの移行が原因で小さなマイナスの結果が生じる可能性があります。これは、巨大な符号なしの数値よりもおそらく理にかなっています。


ところで、clangには__builtin_readcyclecounter()あらゆるアーキテクチャで動作するポータブルもあります。(サイクルカウンターのないアーキテクチャでは常にゼロを返します。)clang/LLVM言語拡張ドキュメントを参照してください。


アウトオブオーダー実行をブロックすることにより、 (または)を使用して再現性を改善し、時間間隔内にある/ない命令を正確に制御する方法lfencecpuidrdtscの詳細については、clflushの@HadiBraisの回答を参照して、 C関数とそれがもたらす違いの例についてのコメント。

LFENCEはAMDプロセッサでシリアル化されていますか?も参照してください。(TL:DRはい、Spectre軽減が有効になっています。そうでない場合、カーネルは関連するMSRを未設定のままにするためcpuid、シリアル化に使用する必要があります。)これは常にIntelで部分シリアル化として定義されています。

インテル®IA-32およびIA-64命令セット・アーキテクチャーでコード実行時間をベンチマークする方法、2010年のインテルのホワイトペーパー。


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

ターボ/省電力に関係なく固定周波数でカウントされるため、uops-per-clock分析が必要な場合は、パフォーマンスカウンターを使用してください。 rdtscは実時間と正確に相関しています(システムクロックの調整はカウントされないため、の完璧な時間ソースですsteady_clock)。

TSC周波数は、常にCPUの定格周波数、つまりアドバタイズされたステッカー周波数と同じでした。一部のCPUでは、単に近いだけです。たとえば、i7-6700HQ 2.6 GHzSkylakeでは2592MHz、4000MHzi7-6700kでは4008MHzです。i5-1035 Ice Lakeなどのさらに新しいCPUでは、TSC = 1.5 GHz、ベース= 1.1 GHzであるため、ターボを無効にしても、これらのCPUのTSC=コアサイクルではほぼ機能しません。

マイクロベンチマークに使用する場合は、最初にウォームアップ期間を含めて、タイミングを開始する前にCPUがすでに最大クロック速度になっていることを確認してください。(オプションでターボを無効にし、マイクロベンチマーク中のCPU周波数シフトを回避するために最大クロック速度を優先するようにOSに指示します)。
マイクロベンチマークは難しい:パフォーマンス評価の慣用的な方法を参照してください。他の落とし穴のために。

TSCの代わりに、ハードウェアパフォーマンスカウンターにアクセスできるライブラリを使用できます。複雑ですがオーバーヘッドの少ない方法は、perfカウンターをプログラムrdmsrしてユーザースペースで使用することです。または、時間指定領域が十分に長いためにをアタッチできる場合は、プログラムの一部にperfstatperf stat -p PIDなどのトリックを含めることもできます。

ただし、メモリバウンドなどの場合に、さまざまな負荷によってSkylakeがどのようにクロックダウンするかを確認したい場合を除いて、通常はマイクロベンチマーク用にCPUクロックを固定したままにしておきます。(メモリ帯域幅/遅延は、コアとは異なるクロックを使用してほとんど固定されていることに注意してください。アイドルクロック速度では、L2またはL3キャッシュミスにかかるコアクロックサイクルははるかに少なくなります。)

チューニングの目的でRDTSCを使用してマイクロベンチマークを行う場合、最善の策は、ティックを使用し、ナノ秒に変換しようとしてもスキップすることです。std::chronoそれ以外の場合は、または のような高解像度のライブラリ時間関数を使用しclock_gettimeます。タイムスタンプ関数の説明/比較については、gettimeofdayと同等の高速化を参照してくださいrdtsc。または、タイマー割り込みまたはスレッドが更新するのに十分な精度要件が低い場合は、メモリから共有タイムスタンプを読み取って完全に回避してください。

結晶周波数と乗数の検出については、rdtscを使用したシステム時間の計算も参照してください。

特にマルチコア-マルチプロセッサ環境でのCPUTSCフェッチ操作では、Nehalem以降では、パッケージ内のすべてのコアに対してTSCが同期され、ロックされています(不変=定数およびノンストップTSC機能とともに)。マルチソケット同期に関するいくつかの良い情報については、@amdnの回答を参照してください。

(そして、その機能を備えている限り、最新のマルチソケットシステムでも通常は信頼できるようです。リンクされた質問に関する@amdnの回答、および以下の詳細を参照してください。)


TSCに関連するCPUID機能

Linux/proc/cpuinfoがCPU機能に使用する名前、および同じ機能の他のエイリアスを使用します。

  • tsc-TSCが存在し、rdtscサポートされています。x86-64のベースライン。
  • rdtscp-rdtscpサポートされています。
  • tsc_deadline_timer CPUID.01H:ECX.TSC_Deadline[bit 24] = 1-ローカルAPICは、TSCが入力した値に達したときに割り込みを発生させるようにプログラムできますIA32_TSC_DEADLINE。「ティックレス」カーネルを有効にし、次に起こるはずのことまでスリープしていると思います。
  • constant_tsc:一定のTSC機能のサポートは、CPUファミリとモデル番号を確認することによって決定されます。TSCは、コアクロック速度の変化に関係なく、一定の周波数でティックします。これがないと、RDTSCコアクロックサイクルをカウントします。
  • nonstop_tsc:この機能は、Intel SDMマニュアルでは不変TSCと呼ばれ、が付いたプロセッサでサポートされていますCPUID.80000007H:EDX[8]。TSCは、深い睡眠のC状態でもカチカチ音をたて続けます。すべてのx86プロセッサで、はをnonstop_tsc意味しますconstant_tscが、constant_tsc必ずしもを意味するわけではありませんnonstop_tsc。個別のCPUID機能ビットはありません。IntelとAMDでは、同じ不変のTSCCPUIDビットが両方constant_tscnonstop_tsc機能を意味します。Linuxのx86/kernel / cpu / intel.c検出コードを参照してくださいamd.c。これは、類似したものです。

Saltwell / Silvermont / Airmontに基づくプロセッサの一部(すべてではありません)は、ACPI S3フルシステムスリープでTSCを刻み続けます:nonstop_tsc_s3。これは常時接続TSCと呼ばれます。(エアモントをベースにしたものはリリースされていないようですが。)

一定および不変のTSCの詳細については、次を参照してください。一定の非不変のtscは、CPU状態全体で周波数を変更できますか?

  • tsc_adjust: MSRが利用可能であり、OSCPUID.(EAX=07H, ECX=0H):EBX.TSC_ADJUST (bit 1)は、TSCを読み取るときにまたは読み取るIA32_TSC_ADJUSTときにTSCに追加されるオフセットを設定できます。これにより、論理コア間でTSCを非同期化することなく、一部またはすべてのコアでTSCを効果的に変更できます。(これは、ソフトウェアが各コアでTSCを新しい絶対値に設定した場合に発生します。関連するWRMSR命令をすべてのコアで同じサイクルで実行することは非常に困難です。)rdtscrdtscp

constant_tscまたnonstop_tsc、TSCclock_gettimeをユーザースペースなどのタイムソースとして使用できるようにします。(しかし、LinuxのようなOSはRDTSCのみを使用して、NTPで維持される遅いクロックのティック間を補間し、タイマー割り込みのスケール/オフセット係数を更新します。constant_tscおよびnonstop_tscを使用するCPUで、なぜ私の時間がドリフトするのですか?)ディープスリープ状態または周波数スケーリングをサポートしていない場合でも、タイムソースとしてのTSCは引き続き使用可能です。

Linuxソースコードのコメントは、 constant_tsc/ nonstop_tscfeatures(Intel上)が「コアとソケット間でも信頼できる(ただし、キャビネット間では信頼できない-その場合は明示的にオフにする)」ことを意味することも示しています。

「ソケット間」の部分は正確ではありません。一般に、不変のTSCは、TSCが同じソケット内のコア間で同期されることを保証するだけです。Intelフォーラムのスレッドで、Martin Dixon(Intel)は、 TSCの不変性はソケット間の同期を意味しないと指摘しています。そのためには、プラットフォームベンダーがRESETをすべてのソケットに同期的に配布する必要があります。上記のLinuxカーネルのコメントを考えると、 プラットフォームベンダーは実際にそうしているようです。特にマルチコア-マルチプロセッサ環境でのCPUTSCフェッチ操作に関する回答は、単一のマザーボード上のすべてのソケットが同期して開始する必要があることにも同意しています。

マルチソケット共有メモリシステムでは、すべてのコアのTSCが同期されているかどうかを直接確認する方法はありません。Linuxカーネルは、デフォルトで起動時と実行時のチェックを実行して、TSCをクロックソースとして使用できることを確認します。これらのチェックには、TSCが同期されているかどうかの判別が含まれます。コマンドの出力はdmesg | grep 'clocksource'、カーネルがクロックソースとしてTSCを使用しているかどうかを示します。これは、チェックに合格した場合にのみ発生します。しかし、それでも、これはTSCがシステムのすべてのソケット間で同期されていることの決定的な証拠にはなりません。カーネルパラメータtsc=reliableを使用して、チェックを行わずにTSCをクロックソースとして盲目的に使用できることをカーネルに通知できます。

クロスソケットTSCが同期していない場合があります:(1)CPUのホットプラグ、(2)拡張ノードコントローラーによって接続された異なるボードにソケットが分散している場合、(3)ウェイクアップ後にTSCが再同期されない場合一部のプロセッサでTSCの電源がオフになっているC状態からアップし、(4)異なるソケットには異なるCPUモデルがインストールされています。

TSC_ADJUSTオフセットを使用する代わりにTSCを直接変更するOSまたはハイパーバイザーは、それらの同期を解除できるため、ユーザースペースでは、CPUの移行によって別のクロックを読み取ることができないと想定するのは必ずしも安全ではありません。(これが、追加の出力としてコアIDを生成する理由rdtscpです。これにより、開始/終了時刻が異なるクロックから来る場合を検出できます。これは、不変のTSC機能の前に導入された可能性があります。または、すべての可能性を考慮したかっただけかもしれません。 )。

直接使用している場合はrdtsc、プログラムまたはスレッドをコアに固定することをお勧めします(taskset -c 0 ./myprogramLinuxなど)。TSCに必要かどうかにかかわらず、CPUの移行は通常、多くのキャッシュミスを引き起こし、とにかくテストを台無しにし、余分な時間を要します。(ただし、割り込みも発生します)。


asmは組み込みを使用することでどれほど効率的ですか?

@MysticialのGNUCインラインasmから得られるものとほぼ同じか、RAXの上位ビットがゼロになっていることがわかっているのでそれ以上です。インラインasmを維持したい主な理由は、無愛想な古いコンパイラとの互換性のためです。

readTSC関数自体の非インラインバージョンは、次のように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で作成することをお勧めします。

ただし、@ Mysticialのコードの修正バージョンを使用すると、両方の長所を活用できる可能性があります。

// More efficient than __rdtsc() in some case, but maybe worse in others
uint64_t rdtsc(){
    // long and uintptr_t are 32-bit on the x32 ABI (32-bit pointers in 64-bit mode), so #ifdef would be better if we care about this trick there.

    unsigned long lo,hi;  // let the compiler know that zero-extension to 64 bits isn't required
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) + lo;
    // + allows LEA or ADD instead of OR
}

Godboltでは、これによりgcc / clang / ICCよりも優れたasmが得られる場合がありますが__rdtsc()、コンパイラーをだましてloとhiを別々に保存するように追加のレジスターを使用させ、clangをに最適化できる場合もあります((end_hi-start_hi)<<32) + (end_lo-start_lo)。うまくいけば、実際のレジスターのプレッシャーがあれば、コンパイラーはもっと早く結合するでしょう。(gccとICCは引き続きlo / hiを別々に保存しますが、最適化もしません。)

しかし、32ビットのgcc8はそれを台無しにし、clangのようにedx:eaxで結果を返すのではなく、rdtsc()関数自体をゼロの実数でコンパイルします。add/adc(gcc6以前は|の代わりに問題ありませんが、gccからの32ビットコード生成を気にする場合+は間違いなく組み込みを優先します)。__rdtsc()

于 2018-08-18T10:29:05.203 に答える
9

VC ++は、インラインアセンブリにまったく異なる構文を使用しますが、32ビットバージョンのみです。64ビットコンパイラはインラインアセンブリをまったくサポートしていません。

この場合、それもおそらく同じです-rdtscタイミングコードシーケンスに関しては、(少なくとも)2つの大きな問題があります。最初に(ほとんどの命令と同様に)順序が狂って実行される可能性があるため、コードの短いシーケンスの時間を計測しようとしている場合、rdtscそのコードの前後の両方が実行される可能性があります。 (ただし、2つは常に相互に順番に実行されると確信しているので、少なくとも差が負になることはありません)。

次に、マルチコア(またはマルチプロセッサ)システムでは、1つのrdtscが1つのコア/プロセッサで実行され、もう1つのrdtscが別のコア/プロセッサで実行される場合があります。このような場合、否定的な結果生じる可能性があります。

一般的に言って、Windowsで正確なタイマーが必要な場合は、を使用する方がよいでしょうQueryPerformanceCounter

本当に使用することを主張する場合はrdtsc、完全にアセンブリ言語で記述された(またはコンパイラ組み込み関数を使用した)別のモジュールで実行し、CまたはC++にリンクする必要があると思います。私は64ビットモード用にそのコードを書いたことがありませんが、32ビットモードでは次のようになります。

   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   xor eax, eax
   cpuid
   rdtsc
   ; save eax, edx

   ; code you're going to time goes here

   xor eax, eax
   cpuid
   rdtsc

これは奇妙に見えることは知っていますが、実際には正しいです。CPUIDはシリアル化命令であり(順不同で実行することはできません)、ユーザーモードで使用できるため、CPUIDを実行します。Intelは、最初の実行が2番目の実行とは異なる速度で実行できる/実行されるという事実を文書化しているため、タイミングを開始する前に3回実行します(推奨されるのは3回なので、3回です)。

次に、テスト対象のコードを実行し、別のcpuidを実行してシリアル化を強制し、最後のrdtscを実行してコードが終了してからの時間を取得します。

それに加えて、OSが提供するあらゆる手段を使用して、これらすべてを1つのプロセス/コアで強制的に実行する必要があります。ほとんどの場合、コードのアラインメントも強制する必要があります。アラインメントを変更すると、実行速度にかなり大きな違いが生じる可能性があります。

最後に、何度も実行したいのですが、途中で中断される可能性が常にあるため(タスクの切り替えなど)、実行にかなりの時間がかかる可能性に備える必要があります。残りの部分よりも長くなります。たとえば、1回あたり約40〜43クロックサイクルかかる5回の実行と、10000+クロックサイクルかかる6回目の実行です。明らかに、後者の場合、外れ値を捨てるだけです-それはあなたのコードからではありません。

要約:rdtsc命令自体の実行を管理することは、(ほとんど)心配することはほとんどありません。あなたが結果を得ることができる前にあなたがしなければならないことはかなりたくさんありますrdtscそれは実際に何かを意味します。

于 2012-12-07T23:45:36.457 に答える
5

Windowsの場合、Visual Studioは、RDTSC命令を実行して結果を返す、便利な「コンパイラ組み込み関数」(つまり、コンパイラが理解する特別な関数)を提供します。

unsigned __int64 __rdtsc(void);
于 2012-12-07T23:41:59.943 に答える
5

Linuxperf_event_openシステムコールconfig = PERF_COUNT_HW_CPU_CYCLES

このLinuxシステムコールは、パフォーマンスイベントのクロスアーキテクチャラッパーのようです。

この答えは似ています:Cプログラムで実行された命令の数を数える簡単な方法ですが、PERF_COUNT_HW_CPU_CYCLESの代わりにPERF_COUNT_HW_INSTRUCTIONS。この回答は詳細に焦点を当てPERF_COUNT_HW_CPU_CYCLESます。より一般的な情報については、他の回答を参照してください。

これは、マニュアルページの最後にある例に基づいた例です。

perf_event_open.c

#define _GNU_SOURCE
#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <inttypes.h>
#include <sys/types.h>

static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
                int cpu, int group_fd, unsigned long flags)
{
    int ret;

    ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
                    group_fd, flags);
    return ret;
}

int
main(int argc, char **argv)
{
    struct perf_event_attr pe;
    long long count;
    int fd;

    uint64_t n;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 10000;
    }

    memset(&pe, 0, sizeof(struct perf_event_attr));
    pe.type = PERF_TYPE_HARDWARE;
    pe.size = sizeof(struct perf_event_attr);
    pe.config = PERF_COUNT_HW_CPU_CYCLES;
    pe.disabled = 1;
    pe.exclude_kernel = 1;
    // Don't count hypervisor events.
    pe.exclude_hv = 1;

    fd = perf_event_open(&pe, 0, -1, -1, 0);
    if (fd == -1) {
        fprintf(stderr, "Error opening leader %llx\n", pe.config);
        exit(EXIT_FAILURE);
    }

    ioctl(fd, PERF_EVENT_IOC_RESET, 0);
    ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

    /* Loop n times, should be good enough for -O0. */
    __asm__ (
        "1:;\n"
        "sub $1, %[n];\n"
        "jne 1b;\n"
        : [n] "+r" (n)
        :
        :
    );

    ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
    read(fd, &count, sizeof(long long));

    printf("%lld\n", count);

    close(fd);
}

結果は妥当なようです。たとえば、サイクルを印刷してから命令数を再コンパイルすると、おそらくスーパースカラーの実行などの影響により、反復ごとに約1サイクル(1サイクルで2つの命令が実行されます)になります。ランダムなメモリアクセスレイテンシに。

PERF_COUNT_HW_REF_CPU_CYCLESまた、マンページのドキュメントとして、に興味があるかもしれません。

総サイクル; CPU周波数スケーリングの影響を受けません。

したがって、周波数スケーリングがオンになっている場合、これにより実際の実時間に近いものが得られます。これらは私の簡単な実験の2/3倍でしたPERF_COUNT_HW_INSTRUCTIONS。おそらく、ストレスのないマシンが現在周波数スケーリングされているためです。

于 2020-11-18T17:19:52.547 に答える