这个新的答案使用了C++11的<chrono>
库。虽然有其他的答案展示了如何使用<chrono>
,但是没有一个答案展示了如何将<chrono>
与其他答案中提及的RDTSC
工具结合使用。因此,我想展示如何将RDTSC
与<chrono>
结合使用。此外,我还将演示如何在时钟上使用模板化测试代码,以便您可以快速地在RDTSC
和系统内置时钟设施之间切换(这些设施可能基于clock()
、clock_gettime()
和/或QueryPerformanceCounter
)。
请注意,RDTSC
指令是x86特定的。QueryPerformanceCounter
仅适用于Windows。clock_gettime()
仅适用于POSIX。下面我将介绍两个新时钟:std::chrono::high_resolution_clock
和std::chrono::system_clock
,它们现在是跨平台的,如果您能假定使用C++11的话。
首先,这里是如何利用Intel的rdtsc
汇编指令创建一个兼容C++11的时钟。我将其称为x::clock
:
#include <chrono>
namespace x
{
struct clock
{
typedef unsigned long long rep;
typedef std::ratio<1, 2'800'000'000> period;
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));
}
};
}
此时钟所做的只是计算CPU周期并将其存储在一个无符号64位整数中。您可能需要调整汇编语言语法以适应您的编译器。或者你的编译器可以提供一个内置函数来代替(例如:
now() {return __rdtsc();}
)。
要构建一个时钟,您必须给它表示(存储类型)。您还必须提供时钟周期,它必须是编译时常量,即使您的机器在不同的电源模式下可能会更改时钟速度。从这些信息中,您可以轻松地定义时钟的“本地”时间持续时间和时间点。
如果您只想输出时钟周期数,则实际上无论您为时钟周期数提供什么数字都没有关系。只有在您想把时钟周期数转换为某个实时单位(如纳秒)时,此常量才起作用。在这种情况下,您越能够准确提供时钟速度,转换为纳秒(毫秒等)的精度就越高。
以下是示例代码,展示了如何使用
x::clock。实际上,我已经在时钟上为代码进行了模板化,因为我想展示您可以使用许多不同的时钟具有完全相同的语法。此特定测试显示在循环下运行要计时的内容时的循环开销:#include <iostream>
template <class clock>
void
test_empty_loop()
{
typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
typedef std::chrono::duration<double, typename clock::period> Cycle;
using std::chrono::duration_cast;
const int N = 100000000;
auto t0 = clock::now();
for (int j = 0; j < N; ++j)
asm volatile("");
auto t1 = clock::now();
auto ticks_per_iter = Cycle(t1-t0)/N;
std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
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
。
- 要测试的迭代次数。如果更改此数字会导致结果大不相同,则应该将迭代次数增加,或在测试时清空计算机上的竞争进程。
clock()
并不像我想象的那样快。 - Mooing Duck