gettimeofday() はマイクロ秒の分解能であることが保証されていますか?
-
08-06-2019 - |
質問
もともと Win32 API 用に書かれたゲームを Linux に移植しています (つまり、Win32 ポートの OS X ポートを Linux に移植しています)。
実装しました QueryPerformanceCounter
プロセスが起動してからの uSeconds を指定することで、次のようになります。
BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
gettimeofday(¤tTimeVal, 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 を与えるとうまく機能します 私のマシンでは, 、次の内容を含む 64 ビット変数が得られます。 uSeconds
プログラムの開始以来。
それで これはポータブルですか? カーネルが特定の方法などでコンパイルされた場合に動作が異なることを発見したくありません。ただし、Linux 以外には移植できないことは問題ありません。
解決
多分。しかし、もっと大きな問題があります。 gettimeofday()
システム上にタイマーを変更するプロセス (ntpd など) がある場合、タイミングが不正確になる可能性があります。ただし、「通常の」Linux では、次の解決策があると思います。 gettimeofday()
は10usです。その結果、システム上で実行されているプロセスに基づいて、前後に移動したり、時間を移動したりすることができます。これにより、あなたの質問に対する答えは事実上「いいえ」になります。
調べてみるといいよ clock_gettime(CLOCK_MONOTONIC)
タイミング間隔用。マルチコア システムや外部クロック設定などにより、いくつかの問題が発生することは少なくなります。
また、 clock_getres()
関数。
他のヒント
Intel プロセッサ向けの高解像度、低オーバーヘッドのタイミング
Intel ハードウェアを使用している場合は、CPU リアルタイム命令カウンターを読み取る方法を次に示します。プロセッサが起動されてから実行された CPU サイクル数が表示されます。これはおそらく、パフォーマンス測定用に取得できる最も粒度の細かいカウンターです。
これは CPU サイクル数であることに注意してください。Linux では、/proc/cpuinfo から CPU 速度を取得し、それを割って秒数を取得できます。これを double に変換すると非常に便利です。
これをボックスで実行すると、次のようになります
11867927879484732
11867927879692217
it took this long to call printf: 207485
こちらが インテル開発者ガイド たくさんの詳細が得られます。
#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);
}
@バーナード:
正直に言うと、あなたの例のほとんどは私の頭を直撃しました。ただし、コンパイルは完了し、動作するようです。これは SMP システムまたは SpeedStep にとって安全ですか?
それは良い質問です...コードは大丈夫だと思います。実用的な観点から、私たちは毎日私の会社でそれを使用し、2〜8コアのすべてのボックスのかなり幅広いボックスで実行しています。もちろん、YMMVなどですが、信頼性の高い低オーバーヘッド(コンテキストがシステムスペースに切り替えられないため)のタイミング方法のようです。
一般に、その仕組みは次のとおりです。
- コードのブロックをアセンブラーであると宣言します(および揮発性があるため、オプティマイザーはそれをそのままにします)。
- CPUID命令を実行します。CPU情報を取得することに加えて(私たちは何もしません)、CPUの実行バッファーを同期して、オーダーの実行の影響を受けないようにします。
- rdtsc (タイムスタンプの読み取り) を実行します。これにより、プロセッサがリセットされてから実行されるマシンサイクルの数が取得されます。これは64ビットの値であるため、現在のCPU速度では194年ごとにラップします。興味深いことに、元のPentiumリファレンスでは、5800年ごとに包まれていることに気付きます。
- 最後の数行は、レジスタから値を変数HIとLOに保存し、それを64ビットの戻り値に配置します。
具体的な注意事項:
順序外の実行は誤った結果を引き起こす可能性があるため、CPUに関する情報を提供することに加えて、オーダーアウトオブオーダー命令の実行も同期する「CPUID」命令を実行します。
ほとんどのOSは、CPUが起動したときにカウンターを同期するため、答えは数秒以内に適しています。
冬眠コメントはおそらく真実ですが、実際には、おそらく冬眠の境界を越えてタイミングを気にしないでしょう。
スピードステップに関して:新しいIntel CPUは、速度の変化を補償し、調整されたカウントを返します。ネットワーク上のいくつかのボックスを簡単にスキャンしましたが、それを持っていなかったボックスは1つだけでした。Pentium 3 で古いデータベース サーバーを実行している。(これらは Linux ボックスなので、次のように確認しました:grep constant_tsc /proc/cpuinfo)
AMD CPUについてはわかりませんが、私たちは主にIntelショップですが、低レベルのシステムの一部がAMD評価を行ったことは知っています。
これがあなたの好奇心を満たすことを願っています、それはプログラミングの興味深い(imho)過小評価されていない分野です。ジェフとジョエルがいつプログラマーがcを知っておくべきかについて話していたのか知っていますか?私は彼らに叫んでいた、「あの高レベルのCのものを忘れて...アセンブラーは、コンピューターが何をしているのか知りたい場合に学ぶべきことです!」
興味があるかもしれません Linuxに関するよくある質問 clock_gettime(CLOCK_REALTIME)
Wine は実際には gettimeofday() を使用して QueryPerformanceCounter() を実装しており、多くの Windows ゲームを Linux および Mac 上で動作させることが知られています。
始まります http://source.winehq.org/source/dlls/kernel32/cpu.c#L312
につながる http://source.winehq.org/source/dlls/ntdll/time.c#L448
したがって、マイクロ秒を明示的に示していますが、システムクロックの解像度は指定されていないと述べています。この文脈での解像度とは、最小値がどのように増加するかを意味すると思いますか?
データ構造はマイクロ秒を測定単位とするように定義されていますが、それは時計やオペレーティング システムが実際にマイクロ秒を細かく測定できることを意味するわけではありません。
他の人が提案したように、 gettimeofday()
時刻を設定するとクロック スキューが発生し、計算が狂う可能性があるため、これは良くありません。 clock_gettime(CLOCK_MONOTONIC)
それはあなたが望むものであり、 clock_getres()
時計の精度を教えてくれます。
gettimeofday() の実際の解決策は、ハードウェア アーキテクチャによって異なります。Intel プロセッサと SPARC マシンは、マイクロ秒を測定する高解像度タイマーを提供します。他のハードウェア アーキテクチャは、通常 100 Hz に設定されているシステムのタイマーにフォールバックします。このような場合、時間分解能の精度は低くなります。
この回答は次から得ました 高解像度の時間測定とタイマー、パート I
この答えは 調整中の時計の問題について言及しています。ティック単位を保証する問題と調整される時間の問題は両方とも C++11 で解決されます。 <chrono>
図書館。
時計 std::chrono::steady_clock
は調整されないことが保証されており、さらにリアルタイムに対して一定の速度で進むため、SpeedStep などのテクノロジーが影響を与えることはありません。
次のいずれかに変換することで、タイプセーフなユニットを取得できます。 std::chrono::duration
などの専門分野 std::chrono::microseconds
. 。このタイプでは、ティック値で使用される単位についてあいまいさがありません。ただし、時計が必ずしもこの解像度を持っているわけではないことに注意してください。実際にそれほど正確な時計がなくても、継続時間をアト秒に変換できます。
私の経験やインターネットで読んだ情報からすると、答えは「いいえ」であり、それが保証されるわけではありません。CPU 速度、オペレーティング システム、Linux の種類などによって異なります。
各 CPU が独自のカウンタを維持し、各カウンタが別の CPU と同期していることが保証されていないため、SMP システムでは RDTSC の読み取りは信頼できません。
試してみることをお勧めします clock_gettime(CLOCK_REALTIME)
. 。posix マニュアルには、これをすべての準拠システムに実装する必要があることが示されています。ナノ秒のカウントを提供できますが、おそらく確認する必要があります。 clock_getres(CLOCK_REALTIME)
システム上で実際の解像度を確認してください。