gettimeofday()はマイクロ秒の精度であることが保証されていますか?


97

私はもともとWin32 API用に作成されたゲームをLinuxに移植しています(Win32ポートのOS XポートをLinuxに移植しています)。

QueryPerformanceCounterプロセスの起動後、uSeconds を指定して実装しました。

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

これはQueryPerformanceFrequency()、周波数として定数1000000 を与えることと相まって、私のマシンでうまく機能uSeconds、プログラムの起動以降に含まれる64ビット変数を与えます。

これはポータブルですか?カーネルが特定の方法でコンパイルされた場合など、カーネルの動作が異なることを発見したくありません。ただし、Linux以外には移植できなくても問題ありません。

回答:


57

多分。しかし、もっと大きな問題があります。gettimeofday()システムにタイマーを変更するプロセス(ntpdなど)があると、タイミングが正しくなくなる可能性があります。「通常」のLinuxでは、解像度gettimeofday()は10usだと思います。システムで実行されているプロセスに基づいて、時間を前後に移動できます。これにより、質問に対する回答が効果的になります。

clock_gettime(CLOCK_MONOTONIC)タイミング間隔を調べる必要があります。マルチコアシステムや外部クロック設定などにより、いくつかの問題が軽減されます。

また、clock_getres()関数を調べます。


1
clock_gettimeは最新のLinuxにのみ存在します。他のシステムはgettimeofday()しかありません
vitaly.v.ch '18

3
@ vitaly.v.chそれはPOSIXなので、Linuxだけではなく「新着」ですか?Red Hat Enterprise Linuxのような「エンタープライズ」ディストリビューションでさえ、2.6.18に基づいています。 REALLY FREAKING OLD kernels WTFについて話していますか?
Spudd86 2010年

clock_gettimeは2001年にPOSIXに含まれました。私が知る限り、現在Linux 2.6とqnxに実装されているclock_gettime()です。しかし、Linux 2.4は現在、多くの本番システムで使用されています。
vitaly.v.ch 2010年

これは、2001年に導入されたが、2008年POSIXまで義務はなかった
R .. GitHubのSTOP手助けICE

2
LinuxのFAQ for lock_gettime(David Schlosnagleの回答を参照)から「CLOCK_MONOTONIC ...はadjtimex()を介してNTPによって周波数が調整されます。将来的に(私はまだパッチを取得しようとしています)、CLOCK_MONOTONIC_RAWが存在しないでしょう。まったく変更されず、ハードウェアカウンターと線形相関があります。」_RAWクロックがカーネルに組み込まれたことはないと思います(_HRに名前が変更された場合を除きますが、私の研究では、努力も放棄されていることが示唆されています)。
Tony Delroy、

41

Intelプロセッサー向けの高解像度、低オーバーヘッドのタイミング

Intelハードウェアを使用している場合は、CPUリアルタイム命令カウンターを読み取る方法を次に示します。プロセッサの起動以降に実行されたCPUサイクルの数がわかります。これはおそらく、パフォーマンス測定のために取得できる最も細かいカウンターです。

これはCPUサイクル数です。Linuxでは、/ proc / cpuinfoからCPU速度を取得し、除算して秒数を取得できます。これをdoubleに変換すると非常に便利です。

これを自分のボックスで実行すると、

11867927879484732
11867927879692217
it took this long to call printf: 207485

たくさんの詳細を提供するIntel開発者ガイドはこちらです。

#include <stdio.h>
#include <stdint.h>

inline uint64_t rdtsc() {
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx");
    return (uint64_t)hi << 32 | lo;
}

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}

11
TSCは常にコア間で同期されているとは限らず、プロセッサが低電力モードに入ったときに停止または周波数を変更する可能性があり(また、そうしたことを知る方法がありません)、一般に常に信頼できるとは限りません。カーネルは、いつ信頼できるかを検出し、HPETやACPI PMタイマーなどの他の代替手段を検出して、最適なものを自動的に選択できます。TSCが安定していて単調であることが本当に確実でない限り、タイミングのために常にカーネルを使用することは良い考えです。
CesarB 2009

12
コア上のインテル・プラットフォーム上TSCは、複数のCPU間で同期される、電力管理状態の一定の周波数独立でインクリメント。Intel Software Developer's Manual、Vol。3セクション18.10。ただし、カウンターが増加するレートは、CPUの周波数と同じではありません。TSCは、「スケーラブルなバス周波数と最大の解決されたバス比率の積に等しい、プラットフォームの最大の解決された周波数」で増加します。3セクション18.18.5。これらの値は、CPUのモデル固有のレジスタ(MSR)から取得します。
sstock 2009

7
次のようにCPUのモデル固有のレジスタ(MSR)をクエリすることで、スケーラブルバス周波数と最大解決バス率を取得できます。スケーラブルバス周波数== MSR_FSB_FREQ [2:0] id 0xCD、最大解決バス率== MSR_PLATFORM_ID [12: 8] ID 0x17。レジスタ値を解釈するには、Intel SDM Vol.3付録B.1を参照してください。Linuxでmsr-toolsを使用して、レジスターを照会できます。 kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
CPUID最初のRDTSC命令の後、ベンチマーク対象のコードを実行する前に、コードを再度使用するべきではありませんか?それ以外の場合、ベンチマークされたコードが最初のの前/と並行して実行RDTSCされ、結果としてRDTSCデルタで過小評価されているのを停止するにはどうすればよいですか?
トニーデルロイ、2011年

18

@バーナード:

認めざるを得ない、あなたの例のほとんどは私の頭の上をまっすぐ行った。コンパイルはできますが、動作するようです。これはSMPシステムまたはSpeedStepにとって安全ですか?

それは良い質問です...コードは大丈夫だと思います。実用的な観点から見ると、私は毎日それを私の会社で使用しており、2から8コアまでのさまざまなボックスで実行しています。もちろん、YMMVなどですが、タイミングの信頼性が高く、オーバーヘッドが少ない(コンテキストがシステム空間に切り替わらないため)メソッドのようです。

一般的にそれがどのように機能するかです:

  • コードのブロックをアセンブラーとして宣言します(揮発性であるため、オプティマイザーはそれをそのままにします)。
  • CPUID命令を実行します。一部のCPU情報(何も行わない)を取得することに加えて、CPUの実行バッファーを同期し、タイミングが順不同の実行の影響を受けないようにします。
  • rdtsc(タイムスタンプの読み取り)実行を実行します。これは、プロセッサがリセットされてから実行されたマシンサイクルの数をフェッチします。これは64ビット値であるため、現在のCPU速度では、194年ごとにラップアラウンドします。興味深いことに、元のPentiumの参考文献では、約5800年ごとにラップされていると指摘しています。
  • 最後の数行は、レジスタからの値を変数hiおよびloに格納し、それを64ビットの戻り値に格納します。

特定のメモ:

  • 順不同の実行は誤った結果を引き起こす可能性があるため、CPUに関する情報を提供することに加えて、順不同の命令の実行を同期する「cpuid」命令を実行します。

  • ほとんどのOSは、起動時にCPUのカウンターを同期するので、答えは数ナノ秒以内で十分です。

  • 休止状態のコメントはおそらく真実ですが、実際には、休止状態の境界を越えたタイミングについてはおそらく気にしません。

  • speedstepについて:新しいIntel CPUは速度の変化を補正し、調整されたカウントを返します。私はネットワーク上のいくつかのボックスをざっとスキャンしたところ、それを持たないボックスが1つだけ見つかりました。古いデータベースサーバーを実行しているPentium 3です。(これらはLinuxボックスなので、次のように確認しました:grep constant_tsc / proc / cpuinfo)

  • AMD CPUについてはわかりませんが、私たちは主にIntelショップですが、低レベルシステムの教祖の一部がAMDの評価を行ったことは知っています。

これがあなたの好奇心を満たすことを願って、それはプログラミングの面白くて(IMHO)研究不足の領域です。プログラマーがCを知っているべきかどうかについて、ジェフとジョエルが話していたとき、あなたは知っていますか?私は彼らに怒鳴っていました、「ハイレベルなCのことは忘れてください...アセンブラーは、コンピューターが何をしているのかを知りたいなら、学ぶべきことです!」


1
...カーネルの人々は、しばらくの間rdtscの使用をやめさせようとしてきました...そして、カーネルでの使用は避けてください。
Spudd86 2010年

1
参考までに、私が(別の返信で-コメントの前に)尋ねた質問は次のとおりです。 SMPシステムまたはSpeedStep?」
バーナード



9

つまり、マイクロ秒を明示的に示していますが、システムクロックの分解能は指定されていません。この文脈での解像度は、それがインクリメントされる最小量を意味すると思いますか?

データ構造は、マイクロ秒を測定単位として持つと定義されていますが、それは、クロックまたはオペレーティングシステムが実際にそれを細かく測定できることを意味しません。

他の人が示唆しているようにgettimeofday()、時間を設定するとクロックのずれが発生し、計算が失敗する可能性があるため、これは良くありません。 clock_gettime(CLOCK_MONOTONIC)あなたが望むものでありclock_getres()、あなたの時計の精度を教えてくれます。


では、夏時間を利用してgettimeofday()が前方または後方にジャンプすると、コードはどうなりますか?
mpez0 2008年

3
clock_gettimeは最新のLinuxにのみ存在します。他のシステムはgettimeofday()しかありません
vitaly.v.ch

8

gettimeofday()の実際の解決は、ハードウェアアーキテクチャによって異なります。IntelプロセッサとSPARCマシンは、マイクロ秒を測定する高解像度タイマーを提供します。他のハードウェアアーキテクチャは、通常100 Hzに設定されているシステムのタイマーにフォールバックします。このような場合、時間分解能の精度は低くなります。

この回答は、高分解能時間測定とタイマー、パートI


6

この回答は、クロックの調整に関する問題について述べています。ティック単位を保証する問題と調整される時間の問題の両方が、C ++ 11 <chrono>ライブラリで解決されます。

クロックstd::chrono::steady_clockは調整されないことが保証されており、さらにリアルタイムに対して一定の速度で進むため、SpeedStepなどのテクノロジーがクロックに影響を与えることはありません。

のようなstd::chrono::duration特殊化の1つに変換することで、タイプセーフなユニットを取得できますstd::chrono::microseconds。このタイプでは、目盛り値で使用される単位について曖昧さはありません。ただし、時計は必ずしもこの分解能を備えているわけではないことに注意してください。実際に正確な時計を持たなくても、継続時間をアト秒に変換できます。


4

私の経験、およびインターネットで読んだことから、答えは「いいえ」であり、保証されません。CPU速度、オペレーティングシステム、Linuxの種類などによって異なります。


3

SMPシステムでは、RDTSCの読み取りは信頼できません。各CPUが独自のカウンターを維持し、各カウンターは別のCPUとの同期によって保証されないためです。

試してみることをお勧めしclock_gettime(CLOCK_REALTIME)ます。posixマニュアルは、これはすべての準拠システムに実装する必要があることを示しています。ナノ秒のカウントを提供できますがclock_getres(CLOCK_REALTIME)、実際の解像度が何であるかを確認するために、システムを確認する必要があります。


clock_getres(CLOCK_REALTIME)実際の解像度は得られません。それは、常に「1ナノ秒」(1ナノ秒)を返しhrtimersが用意されていたときに、チェックinclude/linux/hrtimer.h用のファイルdefine HIGH_RES_NSEC 1(でよりstackoverflow.com/a/23044075/196561
osgx
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.