web-dev-qa-db-ja.com

RDTSCを使用してCPUサイクルを取得する-RDTSCの値が常に増加するのはなぜですか?

特定の時点でCPUサイクルを取得したい。その時点でこの関数を使用します。

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
    // broken for 64-bit builds; don't copy this code
    return x;
}

(編集者注:"=A"はx86-64では間違っています。 RDXまたはRAXのいずれかを選択します。 32ビットモードでのみ、必要なEDX:EAX出力が選択されます。 C++からx86_64のCPUサイクルカウントを取得する方法 を参照してください。)

問題は、(すべての実行で)常に増加する数を返すことです。それはまるで絶対時間を指しているかのようです。

関数を間違って使用していますか?

16
user1106106

スレッドが同じCPUコア上にある限り、RDTSC命令は、ラップアラウンドするまで増加する数を返し続けます。 2GHzのCPUの場合、これは292年後に発生するので、実際の問題ではありません。あなたはおそらくそれが起こるのを見ないでしょう。それだけ長く生きることが期待できる場合は、たとえば50年ごとにコンピュータを再起動してください。

RDTSCの問題は、古いマルチコアCPUのすべてのコアで同じ時点で起動するという保証がなく、古いマルチCPUボードのすべてのCPUで同じ時点で起動するという保証がないことです。 。
現在のシステムには通常このような問題はありませんが、スレッドのアフィニティを設定して1つのCPUでのみ実行されるようにすることで、古いシステムでも問題を回避できます。これはアプリケーションのパフォーマンスには良くないので、一般的には行うべきではありませんが、ティックの測定には問題ありません。

(別の「問題」は、多くの人が時間の測定にRDTSCを使用することです。これは、notではありませんが、CPUサイクルが必要であると書いたので、それが問題ありません。RDTSCを使用して時間を測定するdo場合、省電力やハイパーブーストなど、さまざまな周波数変更技術を使用していると、驚くことがあります。呼び出されます。実際には、_clock_gettime_ syscallはLinuxで驚くほど優れています。)

rdtscステートメントの中にasmを書くだけです。これは私にとっては問題なく機能し、いくつかのあいまいな16進コードよりも読みやすくなっています。それが正しい16進コードであると仮定すると(そして、クラッシュせず、増え続ける数値を返さないので、そう思われます)、コードは適切です。

コードの一部が取るティック数を測定する場合は、ティックが必要ですdifference、増加し続けるカウンターの2つの値を減算するだけです。 uint64_t t0 = rdtsc(); ... uint64_t t1 = rdtsc() - t0;のようなもの
周囲のコードから分離された非常に正確な測定が必要な場合、rdtscを呼び出す前にシリアル化、つまりパイプラインを停止する必要があることに注意してください(またはrdtscpを使用します)新しいプロセッサでのみサポートされます)。すべての特権レベルで使用できる1つのシリアル化命令はcpuidです。

コメントのさらなる質問への回答:

コンピューターの電源を入れると、TSCはゼロから始まります(数年前の一部のBIOSは確実にリセットしませんでしたが、BIOSはすべてのCPUのすべてのカウンターを同じ値にリセットします)。

したがって、プログラムの観点からは、カウンターは「過去の未知の時間」で開始され、CPUが認識するクロックティックごとに常に増加します。したがって、そのカウンターを返す命令を現在実行している場合、後で別のプロセスでいつでも実行すると、より大きな値が返されます(CPUが中断またはオフになっていない限り)。カウンターが増え続けるため、同じプログラムを実行するたびに数値が大きくなります。常に。

さて、clock_gettime(CLOCK_PROCESS_CPUTIME_ID)は別の問題です。これは、OSがプロセスに割り当てたCPU時間です。プロセスが開始すると、ゼロから始まります。新しいプロセスもゼロから始まります。したがって、次々に実行される2つのプロセスは、非常に類似または同一の数になり、増加することはありません。

clock_gettime(CLOCK_MONOTONIC_RAW)は、RDTSCの動作に近いものです(一部の古いシステムではRDTSCが実装されています)。増加する値を返します。現在、これは通常HPETです。ただし、これは実際にはtimeであり、ticksではありません。コンピュータが低電力状態になると(たとえば、通常の周波数の1/2で動作している場合)、それでも同じペースで進みます。

28
Damon

TSCについては、紛らわしい情報や間違った情報がたくさんあるので、その一部を整理しようと思いました。

Intelが最初にTSCを導入したとき(元のPentium CPUで)、サイクルをカウントすることが(時間ではなく)明確に文書化されました。ただし、当時のCPUはほとんど固定周波数で実行されていたため、文書化された動作を無視し、代わりにそれを使用して時間を測定した人もいました(特に、Linuxカーネル開発者)。彼らのコードは、固定周波数で実行されない後のCPUで壊れました(電源管理などのため)。その頃、他のCPUメーカー(AMD、Cyrix、Transmetaなど)は混乱し、一部はTSCを実装してサイクルを測定し、一部は実装して時間を測定し、一部は(MSRを介して)構成可能にしました。

その後、「マルチチップ」システムがサーバーでより一般的になりました。その後、マルチコアが導入されました。これにより、異なるコアのTSC値に小さな違いが生じました(起動時間が異なるため)。しかし、さらに重要なことに、CPUが異なる速度で実行されているために(電源管理やその他の要因により)、異なるCPUのTSC値に大きな違いが生じました。

最初から間違って使用しようとした人々(サイクルではなく時間を測定するために使用した人々)は多くの不満を述べ、最終的にCPUメーカーにTSCがサイクルではなく時間を測定するように標準化するように説得しました。

もちろんこれはめちゃくちゃでした-例えばすべての80x86 CPUをサポートする場合、TSCが実際に測定するものを決定するためだけに多くのコードが必要です。また、さまざまな電源管理テクノロジ(SpeedStepなどだけでなく、スリープ状態なども含む)は、さまざまなCPUでさまざまな方法でTSCに影響を与える可能性があります。そのため、AMDはCPUIDに「TSC不変」フラグを導入して、TSCを使用して時間を正しく測定できることをOSに通知しました。

最近のすべてのIntelおよびAMDCPUは、しばらくの間このようになっています-TSCは時間をカウントし、サイクルをまったく測定しません。つまり、サイクルを測定する場合は、(モデル固有の)パフォーマンス監視カウンターを使用する必要がありました。残念ながら、パフォーマンス監視カウンターはさらにひどい混乱です(モデル固有の性質と複雑な構成のため)。

21
Brendan

良い答えはすでにあり、Damonはすでに彼の答えでこれについて言及していますが、これをRDTSCの実際のx86マニュアル(ボリューム2、4-301)エントリから追加します。

プロセッサのタイムスタンプカウンタ(64ビットMSR)の現在の値をEDX:EAXレジスタにロードします。 EDXレジスタにはMSRの上位32ビットがロードされ、EAXレジスタには下位32ビットがロードされます。 (Intel 64アーキテクチャーをサポートするプロセッサーでは、RAXとRDXのそれぞれの上位32ビットがクリアされます。)

プロセッサはクロックサイクルごとにタイムスタンプカウンタMSRを単調にインクリメントし、プロセッサがリセットされるたびに0にリセットします。Intelの第17章の「タイムスタンプカウンタ」を参照してください。 ®64およびIA-32アーキテクチャーソフトウェア開発者向けマニュアル、ボリューム3B、タイムスタンプカウンターの動作の詳細については。

1
galois