20

セマフォの取得に必要なクロック サイクル数を測定するための C コードを作成しています。私は rdtsc を使用しています。セマフォで測定を行う前に、rdtsc を 2 回連続して呼び出してオーバーヘッドを測定しています。これを for ループで何度も繰り返し、平均値を rdtsc オーバーヘッドとして使用します。

まず平均値を使うということでよろしいでしょうか?

それにもかかわらず、ここでの大きな問題は、オーバーヘッドの負の値が得られることがあることです (必ずしも平均化されたものではなく、少なくとも for ループ内の部分的なもの)。

これは、操作に必要な CPU サイクル数の連続計算にも影響し、sem_wait()負になることもあります。私が書いたことが明確でない場合は、ここに私が取り組んでいるコードの一部があります。

なぜ私はそのような負の値を得ているのですか?


(編集者注:完全な 64 ビット タイムスタンプを取得するための正確で移植可能な方法については、CPU サイクル カウントを取得しますか?"=A"を参照してください。asm 制約は、x86-64 用にコンパイルされた場合、レジスタ割り当てが発生するかどうかに応じて、下位または上位 32 ビットのみを取得します。出力に RAX または RDX を選択しuint64_tます。選択しませんedx:eax。)

(編集者の 2 番目のメモ: おっと、それがなぜ否定的な結果が得られたのかについての答えです。このrdtsc実装をコピーしないようにという警告として、ここにメモを残しておく価値があります。)


#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

static inline uint64_t get_cycles()
{
  uint64_t t;
           // editor's note: "=A" is unsafe for this in x86-64
  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

int num_measures = 10;

int main ()
{
   int i, value, res1, res2;
   uint64_t c1, c2;
   int tsccost, tot, a;

   tot=0;    

   for(i=0; i<num_measures; i++)
   {    
      c1 = get_cycles();
      c2 = get_cycles();

      tsccost=(int)(c2-c1);


      if(tsccost<0)
      {
         printf("####  ERROR!!!   ");
         printf("rdtsc took %d clock cycles\n", tsccost);
         return 1;
      }   
      tot = tot+tsccost;
   }

   tsccost=tot/num_measures;
   printf("rdtsc takes on average: %d clock cycles\n", tsccost);      

   return EXIT_SUCCESS;
}
4

9 に答える 9

59

インテルが最初に TSC を発明したとき、インテルは CPU サイクルを測定しました。さまざまな電源管理機能により、「1 秒あたりのサイクル数」は一定ではありません。そのため、TSC はもともとコードのパフォーマンスを測定するのに適していました (そして、経過した時間を測定するのには適していません)。

良くも悪くも; 当時、CPU には実際にはあまり多くの電力管理がありませんでした。多くの場合、CPU は固定された「1 秒あたりのサイクル数」で実行されていました。一部のプログラマーは間違った考えを持ち、サイクルではなく時間を測定するために TSC を誤用しました。その後 (電源管理機能の使用がより一般的になったとき)、TSC を誤用して時間を測定するこれらの人々は、誤用が引き起こしたすべての問題について泣き言を言いました。CPU メーカー (AMD から始まる) は、TSC を変更して、サイクルではなく時間を測定するようにしました (コードのパフォーマンスを測定するためには壊れていますが、経過した時間を測定するには正しくなります)。これにより混乱が生じました (TSC が実際に測定したものをソフトウェアが判断するのは困難でした)。そのため、AMD の少し後に CPUID に「TSC Invariant」フラグが追加されました。

Intel は AMD に続いて、TSC の動作を時間も測定するように変更し、AMD の「TSC Invariant」フラグも採用しました。

これにより、4 つの異なるケースが得られます。

  • TSC は時間とパフォーマンスの両方を測定します (サイクル/秒は一定です)

  • TSCは時間ではなくパフォーマンスを測定します

  • TSC はパフォーマンスではなく時間を測定しますが、「TSC Invariant」フラグを使用してそれを示すことはありません

  • TSCはパフォーマンスではなく時間を測定し、「TSC Invariant」フラグを使用してそう言っています(最新のCPUのほとんど)

TSC が時間を測定する場合、パフォーマンス/サイクルを適切に測定するには、パフォーマンス監視カウンターを使用する必要があります。残念ながら、パフォーマンス監視カウンターは CPU ごとに異なり (モデル固有)、MSR (特権コード) へのアクセスが必要です。これにより、アプリケーションが「サイクル」を測定することはかなり非現実的になります。

また、TSC が時間を測定する場合、他のタイム ソースを使用してスケーリング ファクターを決定しないと、TSC が返すタイム スケール (「ふりサイクル」のナノ秒数) を知ることができないことに注意してください。

2 つ目の問題は、マルチ CPU システムの場合、ほとんどのオペレーティング システムがうまくいかないことです。OS が TSC を処理する正しい方法は、アプリケーションが TSC を直接使用できないようにすることです ( TSDCR4 でフラグを設定することにより、RDTSC 命令で例外が発生します)。これにより、さまざまなセキュリティの脆弱性 (タイミング サイドチャネル) が防止されます。また、OS が TSC をエミュレートし、正しい結果を返すようにすることもできます。たとえば、アプリケーションが RDTSC 命令を使用して例外を発生させた場合、OS の例外ハンドラは正しい「グローバル タイム スタンプ」を見つけて返すことができます。

もちろん、異なる CPU には独自の TSC があります。これは、アプリケーションが TSC を直接使用する場合、異なる CPU で異なる値を取得することを意味します。OS が問題を解決できなかった場合に (RDTSC を適切にエミュレートすることによって) 人々が回避できるようにするため。AMDRDTSCPは、TSC と「プロセッサ ID」を返す命令を追加しました (IntelもこのRDTSCP命令を採用することになりました)。壊れた OS で実行されているアプリケーションは、「プロセッサ ID」を使用して、前回とは異なる CPU で実行されていることを検出できます。このようにして (RDTSCP命令を使用して)、「elapsed = TSC - previous_TSC」が有効な結果を与える時期を知ることができます。でも; この命令によって返される「プロセッサ ID」は、MSR の単なる値です。RDTSCPすべてのCPUで「プロセッサID」がゼロであると言います。

基本的; CPU が命令をサポートしているRDTSCP場合、および OS が (MSR を使用して) 「プロセッサ ID」を正しく設定している場合。このRDTSCP命令は、アプリケーションがいつ悪い「経過時間」結果を取得したかを知るのに役立ちます (ただし、悪い結果を修正または回避する方法は提供しません)。

そう; 簡単に言えば、正確なパフォーマンス測定が必要な場合は、ほとんど失敗しています。現実的に期待できる最善の方法は、正確な時間測定です。ただし、場合によっては(たとえば、単一の CPU マシンで実行している場合、または特定の CPU に「固定」されている場合、またはRDTSCP無効な値を検出して破棄する限り、適切に設定されている OS で使用している場合など)。

もちろん、それでも IRQ などの理由で危険な測定値が得られます。このために; コードをループで何度も実行し、他の結果よりも高すぎる結果を破棄することをお勧めします。

最後に、本当に適切に実行したい場合は、測定のオーバーヘッドを測定する必要があります。これを行うには、何もしないのにかかる時間を測定します (RDTSC/RDTSCP 命令だけで、危険な測定は破棄します)。次に、「何かを測定する」結果から測定のオーバーヘッドを差し引きます。これにより、「何か」が実際にかかる時間をより正確に見積もることができます。

注: Pentium が最初にリリースされたとき (1990 年代半ば - もうオンラインで入手できるかどうかはわかりません - 1980 年代以降のコピーをアーカイブしています) のインテルのシステム プログラミング ガイドのコピーを掘り起こすことができれば、インテルがタイムスタンプを文書化したことがわかります。 「プロセッサイベントの発生の相対的な時間を監視および識別するために使用できる」ものとしてカウンター。彼らは、(64ビットのラップアラウンドを除いて)単調に増加すること(ただし、一定の割合で増加することではない)、ラップアラウンドするまでに最低10年かかることを保証しました. マニュアルの最新版では、タイム スタンプ カウンターがより詳細に文書化されており、古い CPU (P6、Pentium M、古い Pentium 4) の場合、タイム スタンプ カウンターは「内部プロセッサ クロック サイクルごとにインクリメント」し、「

于 2013-11-13T00:21:05.937 に答える
7
  1. 平均値を使用しない

    大きい値は OS マルチタスクによって中断されているため、代わりに最小値または小さい値の avg を使用してください (CACHE のために avg を取得するため)。

    また、すべての値を記憶してから、OS プロセスの粒度の境界を見つけ、この境界の後のすべての値を除外することもできます (通常、>1msこれは簡単に検出できます)。

    ここに画像の説明を入力

  2. のオーバーヘッドを測定する必要はありませんRDTSC

    しばらくオフセットを測定するだけで、同じオフセットが両方の時間に存在し、減算後にはなくなります。

  3. RDTS(ラップトップのような)の可変クロックソース用

    通常は数秒で十分な、安定した集中的な計算ループによって、 CPUの速度を最大に変更する必要があります。CPU周波数を継続的に測定し、十分に安定してから測定を開始する必要があります。

于 2014-02-05T09:07:11.353 に答える
1

私の質問の主なポイントは、結果の正確さではなく、時々負の値を取得しているという事実でした (rdstc への最初の呼び出しは、2 番目の呼び出しよりも大きな値を返します)。さらに調査を行う (およびこの Web サイトで他の質問を読む) と、rdtsc を使用するときに動作させる方法は、その直前に cpuid コマンドを配置することであることがわかりました。このコマンドは、コードをシリアル化します。これが私が今やっている方法です:

static inline uint64_t get_cycles()
{
  uint64_t t;          

   volatile int dont_remove __attribute__((unused));
   unsigned tmp;
     __asm volatile ("cpuid" : "=a"(tmp), "=b"(tmp), "=c"(tmp), "=d"(tmp)
       : "a" (0));

   dont_remove = tmp; 




  __asm volatile ("rdtsc" : "=A"(t));
  return t;
}

get_cycles 関数の 2 回目の呼び出しと最初の呼び出しの間に、まだ負の差があります。なぜ?cpuid アセンブリ インライン コードの構文については 100% 確信が持てません。これはインターネットで調べたものです。

于 2013-11-13T18:37:21.017 に答える
0

コードを実行しているスレッドがコア間を移動している場合、返される rdtsc 値が別のコアで読み取られる値よりも小さい可能性があります。パッケージの電源投入時に、すべてのコアがまったく同時にカウンターを 0 に設定するわけではありません。したがって、テストを実行するときは、スレッド アフィニティを特定のコアに設定してください。

于 2014-03-18T20:41:44.803 に答える