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