web-dev-qa-db-ja.com

C ++からx86_64のCPUサイクルカウントを取得する方法

SOでこの投稿を見ました。これには、最新のCPUサイクルカウントを取得するCコードが含まれています。

C/C++ Linux x86_64でのCPUサイクル数ベースのプロファイリング

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

私はx86-64を使用しています

EDIT2:

この関数は見つかりましたが、VS2010にアセンブラーを認識させることができません。何も含める必要がありますか? (私はuint64_tからlong long Windowsの場合...?)

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

EDIT3:

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

「エラーC2400:「opcode」のインラインアセンブラ構文エラー。「データ型」が見つかりました」

誰か助けてくれますか?

26
user997112

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

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

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

GCC 4.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
_

これは GNU C拡張asm コンパイラーに伝えます:

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

(編集者注:_unsigned long_の代わりに_unsigned int_を使用した場合、おそらくこれはより効率的です。コンパイラはloが既にRAXにゼロ拡張されていることを認識します。上半分がゼロであったことを知っているので、異なる方法をマージしたい場合は_|_と_+_は同等です。いい仕事。)

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

51
Mysticial

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

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

第二に、マルチコア(またはマルチプロセッサ)システムでは、1つのrdtscが一方のコア/プロセッサで実行され、他方が別のコア/プロセッサで実行される場合があります。そのような場合、否定的な結果isは完全に可能です。

一般的に、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を実行するのは、それがシリアル化命令であり(順不同で実行できない)、ユーザーモードで使用できるためです。インテルは、最初の実行が2番目とは異なる速度で実行できる/実行するという事実を文書化しているため、タイミングを開始する前に3回実行します(推奨されるのは3なので、3つです)。

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

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

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

まとめ:rdtsc命令自体の実行を管理することは、(ほとんど)心配する必要がほとんどありません。 rdtscから結果を取得する前に、実際には何でも意味するneedが必要です。

7
Jerry Coffin

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

unsigned __int64 __rdtsc(void);
5
Nik Bougalis