この新しい回答は C++11 の<chrono>
機能を使用しています。の使用方法を示す他の回答がありますが、ここにある他のいくつかの回答で言及されている機能<chrono>
を使用する方法を示すものはありません<chrono>
。RDTSC
そこで、 での使用方法を示したいと思いRDTSC
ました<chrono>
。さらに、クロックのテスト コードをテンプレート化してRDTSC
、システムの組み込みクロック機能 (おそらくclock()
、clock_gettime()
および/またはQueryPerformanceCounter
.
RDTSC
この命令は x86 固有 であることに注意してください。QueryPerformanceCounter
は Windows のみです。そしてclock_gettime()
POSIXのみです。以下に 2 つの新しいクロックを紹介します:std::chrono::high_resolution_clock
とstd::chrono::system_clock
は、C++11 を仮定できる場合、クロスプラットフォームになりました。
まず、Intelrdtsc
アセンブリ命令から C++11 互換のクロックを作成する方法を次に示します。私はそれを呼びますx::clock
:
#include <chrono>
namespace x
{
struct clock
{
typedef unsigned long long rep;
typedef std::ratio<1, 2'800'000'000> period; // My machine is 2.8 GHz
typedef std::chrono::duration<rep, period> duration;
typedef std::chrono::time_point<clock> time_point;
static const bool is_steady = true;
static time_point now() noexcept
{
unsigned lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return time_point(duration(static_cast<rep>(hi) << 32 | lo));
}
};
} // x
このクロックが行うのは、CPU サイクルをカウントし、それを符号なし 64 ビット整数に格納することだけです。コンパイラのアセンブリ言語構文を微調整する必要がある場合があります。または、コンパイラが代わりに使用できる組み込み関数を提供する場合があります (例: now() {return __rdtsc();}
)。
時計を作成するには、表現 (ストレージ タイプ) を指定する必要があります。マシンがさまざまな電力モードでクロック速度を変更する場合でも、コンパイル時定数である必要があるクロック周期も指定する必要があります。そして、それらから、これらの基本的な観点から、時計の「ネイティブ」期間と時点を簡単に定義できます。
クロック ティックの数を出力するだけの場合は、クロック周期に指定する数値は重要ではありません。この定数は、クロック ティック数をナノ秒などのリアルタイム単位に変換する場合にのみ有効です。その場合、クロック速度をより正確に指定できるほど、ナノ秒 (ミリ秒など) への変換がより正確になります。
以下は、 の使用方法を示すコード例ですx::clock
。実際、まったく同じ構文で多くの異なる時計を使用する方法を示したいので、時計のコードをテンプレート化しました。この特定のテストは、ループの下で時間を計測したいものを実行するときのループ オーバーヘッドがどのようなものかを示しています。
#include <iostream>
template <class clock>
void
test_empty_loop()
{
// Define real time units
typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
// or:
// typedef std::chrono::nanoseconds nanoseconds;
// Define double-based unit of clock tick
typedef std::chrono::duration<double, typename clock::period> Cycle;
using std::chrono::duration_cast;
const int N = 100000000;
// Do it
auto t0 = clock::now();
for (int j = 0; j < N; ++j)
asm volatile("");
auto t1 = clock::now();
// Get the clock ticks per iteration
auto ticks_per_iter = Cycle(t1-t0)/N;
std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
// Convert to real time units
std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
<< "ps per iteration\n";
}
このコードが最初に行うことは、結果を表示するための「リアルタイム」単位を作成することです。ここではピコ秒を選択しましたが、整数ベースまたは浮動小数点ベースの任意の単位を選択できます。例として、私が使用できた既製のstd::chrono::nanoseconds
ユニットがあります。
Cycle
別の例として、反復ごとのクロックサイクルの平均数を浮動小数点として出力したいので、クロックのティックと同じ単位を持つ (コードで呼び出される) double に基づいて別の期間を作成します。
clock::now()
ループは、いずれかの側の呼び出しでタイミングが調整されます。この関数から返される型に名前を付ける場合は、次のようになります。
typename clock::time_point t0 = clock::now();
(例で明確に示されているようにx::clock
、システム提供のクロックにも当てはまります)。
浮動小数点クロック ティックで期間を取得するには、単に 2 つの時点を減算し、反復ごとの値を取得するには、その期間を反復回数で割ります。
count()
メンバー関数を使用して、任意の期間のカウントを取得できます。これは内部表現を返します。最後に、期間をstd::chrono::duration_cast
期間に変換して出力するために使用します。Cycle
picoseconds
このコードを使用するのは簡単です:
int main()
{
std::cout << "\nUsing rdtsc:\n";
test_empty_loop<x::clock>();
std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
test_empty_loop<std::chrono::high_resolution_clock>();
std::cout << "\nUsing std::chrono::system_clock:\n";
test_empty_loop<std::chrono::system_clock>();
}
上記では、自作の を使用してテストを実行し、それらの結果を、システム提供の 2 つのクロックとx::clock
を使用して比較し ます。私にとって、これは次のように出力されます:std::chrono::high_resolution_clock
std::chrono::system_clock
Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration
Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration
Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration
これは、反復ごとのティックがクロックごとに大きく異なるため、これらのクロックのティック周期がそれぞれ異なることを示しています。ただし、既知の時間単位 (ピコ秒など) に変換すると、各クロックでほぼ同じ結果が得られます (マイレージは異なる場合があります)。
私のコードが「魔法の変換定数」から完全に解放されていることに注意してください。実際、この例全体でマジック ナンバーは 2 つしかありません。
- 定義するための私のマシンのクロック速度
x::clock
。
- テストする反復回数。この数を変更すると結果が大きく変わる場合は、おそらく反復回数を増やすか、テスト中に競合するプロセスからコンピューターを空にする必要があります。