C++ Linux 最快的时间测量方法(比 std::chrono 快)?包括基准
C++ Linux fastest way to measure time (faster than std::chrono) ? Benchmark included
#include <iostream>
#include <chrono>
using namespace std;
class MyTimer {
private:
std::chrono::time_point<std::chrono::steady_clock> starter;
std::chrono::time_point<std::chrono::steady_clock> ender;
public:
void startCounter() {
starter = std::chrono::steady_clock::now();
}
double getCounter() {
ender = std::chrono::steady_clock::now();
return double(std::chrono::duration_cast<std::chrono::nanoseconds>(ender - starter).count()) /
1000000; // millisecond output
}
// timer need to have nanosecond precision
int64_t getCounterNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - starter).count();
}
};
MyTimer timer1, timer2, timerMain;
volatile int64_t dummy = 0, res1 = 0, res2 = 0;
// time run without any time measure
void func0() {
dummy++;
}
// we're trying to measure the cost of startCounter() and getCounterNs(), not "dummy++"
void func1() {
timer1.startCounter();
dummy++;
res1 += timer1.getCounterNs();
}
void func2() {
// start your counter here
dummy++;
// res2 += end your counter here
}
int main()
{
int i, ntest = 1000 * 1000 * 100;
int64_t runtime0, runtime1, runtime2;
timerMain.startCounter();
for (i=1; i<=ntest; i++) func0();
runtime0 = timerMain.getCounter();
cout << "Time0 = " << runtime0 << "ms\n";
timerMain.startCounter();
for (i=1; i<=ntest; i++) func1();
runtime1 = timerMain.getCounter();
cout << "Time1 = " << runtime1 << "ms\n";
timerMain.startCounter();
for (i=1; i<=ntest; i++) func2();
runtime2 = timerMain.getCounter();
cout << "Time2 = " << runtime2 << "ms\n";
return 0;
}
我正在尝试分析一个程序,其中某些关键部分的执行时间小于 50 纳秒。我发现我的计时器 class 使用 std::chrono
太昂贵了(带计时的代码比不带计时的代码多花 40% 的时间)。如何制作更快的计时器 class?
我认为某些 OS 特定的系统调用将是最快的解决方案。平台是LinuxUbuntu.
编辑:所有代码都是用-O3编译的。确保每个计时器仅初始化一次,因此测量的成本仅由 startMeasure/stopMeasure 函数引起。我没有进行任何文本打印。
编辑 2: 接受的答案不包括将周期数实际转换为纳秒的方法。如果有人能做到这一点,那将非常有帮助。
你想要的是所谓的“微基准测试”。它会变得非常复杂。我假设您在 x86_64 上使用 Ubuntu Linux。这不适用于 ARM、ARM64 或任何其他平台。
std::chrono 在 Linux 上的 libstdc++ (gcc) 和 libc++ (clang) 中实现,作为 GLIBC(C 库)的简单包装器,它完成所有繁重的工作。如果您查看 std::chrono::steady_clock::now(),您会看到对 clock_gettime().
的调用
clock_gettime() 是一个 VDSO,即它是在用户空间中运行的内核代码。它应该非常快,但可能有时它必须做一些内务处理并且每次第 n 次调用都要花费很长时间。所以我不建议进行微基准测试。
几乎每个平台都有循环计数器,x86 有汇编指令rdtsc
。可以通过精心设计 asm
调用或使用特定于编译器的内置函数 __builtin_ia32_rdtsc() 或 __rdtsc().
将此指令插入您的代码中
这些调用将 return 一个 64 位整数,表示自机器启动以来的时钟数。 rdtsc 不是立竿见影但速度很快,大约需要 15-40 个周期才能完成。
不能保证在所有平台上每个内核的计数器都相同,因此当进程从一个内核移动到另一个内核时要小心。不过在现代系统中这应该不是问题。
rdtsc 的另一个问题是,如果编译器发现它们没有副作用,编译器通常会重新排序指令,不幸的是,rdtsc 就是其中之一。因此,如果您发现编译器在欺骗您,则必须在这些计数器读取周围使用假屏障 - 查看生成的程序集。
另外一个大问题是cpu乱序执行本身。不仅编译器可以更改执行顺序,cpu 也可以。由于 x86 486 英特尔 CPU 是流水线式的,因此可以同时执行多条指令 - 粗略地说。所以你最终可能会测量虚假执行。
我建议您熟悉微基准测试的类量子问题。这并不简单。
注意 rdtsc() 将 return 循环数。您必须使用时间戳计数器频率转换为纳秒。
这是一个例子:
#include <iostream>
#include <cstdio>
void dosomething() {
// yada yada
}
int main() {
double sum = 0;
const uint32_t numloops = 100000000;
for ( uint32_t j=0; j<numloops; ++j ) {
uint64_t t0 = __builtin_ia32_rdtsc();
dosomething();
uint64_t t1 = __builtin_ia32_rdtsc();
uint64_t elapsed = t1-t0;
sum += elapsed;
}
std::cout << "Average:" << sum/numloops << std::endl;
}
这篇论文有点过时(2010 年),但它是最新的,可以很好地介绍微基准测试:
How to Benchmark Code Execution Times on Intel® IA-32 and IA-64 Instruction Set Architectures
#include <iostream>
#include <chrono>
using namespace std;
class MyTimer {
private:
std::chrono::time_point<std::chrono::steady_clock> starter;
std::chrono::time_point<std::chrono::steady_clock> ender;
public:
void startCounter() {
starter = std::chrono::steady_clock::now();
}
double getCounter() {
ender = std::chrono::steady_clock::now();
return double(std::chrono::duration_cast<std::chrono::nanoseconds>(ender - starter).count()) /
1000000; // millisecond output
}
// timer need to have nanosecond precision
int64_t getCounterNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - starter).count();
}
};
MyTimer timer1, timer2, timerMain;
volatile int64_t dummy = 0, res1 = 0, res2 = 0;
// time run without any time measure
void func0() {
dummy++;
}
// we're trying to measure the cost of startCounter() and getCounterNs(), not "dummy++"
void func1() {
timer1.startCounter();
dummy++;
res1 += timer1.getCounterNs();
}
void func2() {
// start your counter here
dummy++;
// res2 += end your counter here
}
int main()
{
int i, ntest = 1000 * 1000 * 100;
int64_t runtime0, runtime1, runtime2;
timerMain.startCounter();
for (i=1; i<=ntest; i++) func0();
runtime0 = timerMain.getCounter();
cout << "Time0 = " << runtime0 << "ms\n";
timerMain.startCounter();
for (i=1; i<=ntest; i++) func1();
runtime1 = timerMain.getCounter();
cout << "Time1 = " << runtime1 << "ms\n";
timerMain.startCounter();
for (i=1; i<=ntest; i++) func2();
runtime2 = timerMain.getCounter();
cout << "Time2 = " << runtime2 << "ms\n";
return 0;
}
我正在尝试分析一个程序,其中某些关键部分的执行时间小于 50 纳秒。我发现我的计时器 class 使用 std::chrono
太昂贵了(带计时的代码比不带计时的代码多花 40% 的时间)。如何制作更快的计时器 class?
我认为某些 OS 特定的系统调用将是最快的解决方案。平台是LinuxUbuntu.
编辑:所有代码都是用-O3编译的。确保每个计时器仅初始化一次,因此测量的成本仅由 startMeasure/stopMeasure 函数引起。我没有进行任何文本打印。
编辑 2: 接受的答案不包括将周期数实际转换为纳秒的方法。如果有人能做到这一点,那将非常有帮助。
你想要的是所谓的“微基准测试”。它会变得非常复杂。我假设您在 x86_64 上使用 Ubuntu Linux。这不适用于 ARM、ARM64 或任何其他平台。
std::chrono 在 Linux 上的 libstdc++ (gcc) 和 libc++ (clang) 中实现,作为 GLIBC(C 库)的简单包装器,它完成所有繁重的工作。如果您查看 std::chrono::steady_clock::now(),您会看到对 clock_gettime().
的调用clock_gettime() 是一个 VDSO,即它是在用户空间中运行的内核代码。它应该非常快,但可能有时它必须做一些内务处理并且每次第 n 次调用都要花费很长时间。所以我不建议进行微基准测试。
几乎每个平台都有循环计数器,x86 有汇编指令rdtsc
。可以通过精心设计 asm
调用或使用特定于编译器的内置函数 __builtin_ia32_rdtsc() 或 __rdtsc().
这些调用将 return 一个 64 位整数,表示自机器启动以来的时钟数。 rdtsc 不是立竿见影但速度很快,大约需要 15-40 个周期才能完成。
不能保证在所有平台上每个内核的计数器都相同,因此当进程从一个内核移动到另一个内核时要小心。不过在现代系统中这应该不是问题。
rdtsc 的另一个问题是,如果编译器发现它们没有副作用,编译器通常会重新排序指令,不幸的是,rdtsc 就是其中之一。因此,如果您发现编译器在欺骗您,则必须在这些计数器读取周围使用假屏障 - 查看生成的程序集。
另外一个大问题是cpu乱序执行本身。不仅编译器可以更改执行顺序,cpu 也可以。由于 x86 486 英特尔 CPU 是流水线式的,因此可以同时执行多条指令 - 粗略地说。所以你最终可能会测量虚假执行。
我建议您熟悉微基准测试的类量子问题。这并不简单。
注意 rdtsc() 将 return 循环数。您必须使用时间戳计数器频率转换为纳秒。
这是一个例子:
#include <iostream>
#include <cstdio>
void dosomething() {
// yada yada
}
int main() {
double sum = 0;
const uint32_t numloops = 100000000;
for ( uint32_t j=0; j<numloops; ++j ) {
uint64_t t0 = __builtin_ia32_rdtsc();
dosomething();
uint64_t t1 = __builtin_ia32_rdtsc();
uint64_t elapsed = t1-t0;
sum += elapsed;
}
std::cout << "Average:" << sum/numloops << std::endl;
}
这篇论文有点过时(2010 年),但它是最新的,可以很好地介绍微基准测试:
How to Benchmark Code Execution Times on Intel® IA-32 and IA-64 Instruction Set Architectures