POSIX 1003.1b-1993 では、clock_gettime()
(および) のインターフェイスが指定されており、MON オプションを使用すると、値がclock_getres()
のクロック タイプを使用できることが示されています (タイマーがシステム時間の調整の影響を受けないようにするため)。システムで利用可能な場合、これらの関数は 1 ナノ秒までの潜在的な分解能を持つ構造体を返しますが、後者の関数はクロックの分解能を正確に示します。clockid_t
CLOCK_MONOTONIC
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* and nanoseconds */
};
クロックがその分解能を超えて経過した時間を記録するために、ループ内でテスト関数を何度も実行する必要があるかもしれません。時計の解像度。
ただし、どうやら Linux の人々は POSIX.1b 仕様を読み違えたり、単調に増加するタイム クロックの定義を理解していなかったりしたCLOCK_MONOTONIC
ようです。彼らのクロックはシステム時間の調整の影響を受けるため、彼らの発明した非標準CLOCK_MONOTONIC_RAW
クロックを使用して、実際の単調なタイム クロックを取得します。
別の方法として、関連する POSIX.1timer_settime()
呼び出しを使用して、タイマーの実行を設定し、シグナル ハンドラーを使用してタイマーによって配信されたシグナルをキャッチし、シグナルtimer_getoverrun()
のキューイングから最終的な配信までの経過時間を調べてから、タイマーが切れるまでループを実行し、設定された時間間隔内の反復回数とオーバーランをカウントします。
もちろん、プリエンプティブなマルチタスク システムでは、これらのクロックとタイマーはプロセスが実行されていないときでも実行されるため、ベンチマークにはあまり役立ちません。
わずかに珍しいのは、オプションの POSIX.1-1999clockid_t
値のです。 fromCLOCK_PROCESS_CPUTIME_ID
の存在によって示されます。これは、呼び出しプロセスの CPU タイム クロックを表し、呼び出しプロセスの実行時間を表す値を提供します。(さらに珍しいのは、マクロで示されるofの TCT オプションです。これは、CPU タイム クロックを表し、呼び出しスレッドの実行時間を表す値を提供します。)_POSIX_CPUTIME
<time.h>
clockid_t
CLOCK_THREAD_CPUTIME_ID
_POSIX_THREAD_CPUTIME
残念ながら、POSIX では、これらのいわゆる CPUTIME クロックがユーザー時間だけをカウントするのか、プロセスまたはスレッドによって蓄積されたユーザー時間とシステム (および割り込み) 時間の両方をカウントするのかについて言及していません。カーネル モードで費やされた時間は、表示される場合と表示されない場合があります。
さらに悪いことに、マルチプロセッサ システムでは、プロセスが実行中にある CPU から別の CPU にたまたま移行した場合、CPUTIME クロックの値が完全に偽物になる可能性があります。これらの CPUTIME クロックを実装するタイマーは、さまざまな CPU コアでさまざまな速度で実行され、さまざまな時間に実行される可能性があり、その意味がさらに複雑になります。つまり、これらは実際の壁時計時間に関連するものではなく、CPU サイクル数の指標にすぎない可能性があります (相対時間が常に使用され、ユーザーが実行時間が外的要因によって異なります)。さらに悪いことに、Linux CPU では TimeStampCounter ベースの CPUTIME クロックがプロセスがスリープした時間を報告することさえあると報告されています。
システムに適切に機能するシステム コールがある場合は、プロセスの実行中にプロセスが消費した実際のユーザー時間とシステム時間のそれぞれgetrusage()
を提供できることを願っています。ただし、これによりせいぜいマイクロ秒のクロックに戻るため、より正確なタイミングを取得するには、テストコードを十分な回数繰り返し実行する必要があります。struct timeval
getrusage()
ループの前に 1 回、その後にもう一度、与えられた時間の差を計算します。単純なアルゴリズムの場合、これは何百万回、またはそれ以上実行することを意味する場合があります。また、多くのシステムでは、ユーザー時間とシステム時間の分割はある程度恣意的に行われ、繰り返されるループで別々に調べると、どちらか一方が逆方向に実行されているように見えることさえあることに注意してください。ただし、アルゴリズムがシステム コールを行わない場合でも、時間デルタを合計すると、コード実行の合計時間はかなり高くなるはずです。
ところで、時間値を比較するときは、@Nim が示唆するように、またはおそらく次のように (NetBSD から<sys/time.h>
)オーバーフローしたり、フィールドで負の値になったりしないように注意してください。
#define timersub(tvp, uvp, vvp) \
do { \
(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
if ((vvp)->tv_usec < 0) { \
(vvp)->tv_sec--; \
(vvp)->tv_usec += 1000000; \
} \
} while (0)
tv_usec
(範囲内でもっと偏執的になりたいと思うかもしれません)
ベンチマークに関するもう 1 つの重要な注意事項: 理想的には、コンパイラからのアセンブリ出力を調べて、関数が実際に呼び出されていることを確認してください。ドライバー ループとは別のソース モジュールで関数をコンパイルすると、通常、オプティマイザーが呼び出しを保持するようになります。もう 1 つのトリックは、ループ内で として定義された変数に割り当てる値を返すようにすることですvolatile
。