这个新答案使用了 C++11 的<chrono>
功能。虽然还有其他答案显示了如何使用<chrono>
,但没有一个显示如何使用此处其他几个答案中提到<chrono>
的设施。RDTSC
所以我想我会展示如何使用RDTSC
with <chrono>
。此外,我将演示如何模板化时钟上的测试代码,以便您可以在RDTSC
系统的内置时钟设施(可能基于clock()
,clock_gettime()
和/或QueryPerformanceCounter
.
请注意,该RDTSC
指令是特定于 x86 的。 QueryPerformanceCounter
仅适用于 Windows。并且clock_gettime()
仅是 POSIX。下面我介绍两个新时钟:std::chrono::high_resolution_clock
和std::chrono::system_clock
,如果你可以假设 C++11,它们现在是跨平台的。
rdtsc
首先,这是从 Intel汇编指令中创建与 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
例如,有一个我可以使用的预制单元。
作为另一个示例,我想将每次迭代的平均时钟周期数打印为浮点数,因此我创建了另一个基于 double 的持续时间,其单位与时钟的刻度相同(Cycle
在代码中调用)。
clock::now()
循环与任一侧的调用一起计时。如果要命名从此函数返回的类型,它是:
typename clock::time_point t0 = clock::now();
(如示例中清楚显示的那样x::clock
,系统提供的时钟也是如此)。
要根据浮点时钟滴答获得持续时间,只需减去两个时间点,并获得每次迭代值,将该持续时间除以迭代次数。
您可以使用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>();
}
上面我使用我们自制的 进行测试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
这表明这些时钟中的每一个都有不同的滴答周期,因为每个时钟每次迭代的滴答声都大不相同。但是,当转换为已知的时间单位(例如皮秒)时,每个时钟我得到的结果大致相同(您的里程可能会有所不同)。
请注意我的代码是如何完全没有“魔法转换常数”的。事实上,整个例子中只有两个幻数:
- 我的机器的时钟速度以定义
x::clock
.
- 要测试的迭代次数。如果更改此数字会使您的结果变化很大,那么您可能应该增加迭代次数,或者在测试时清空计算机中的竞争进程。