C++ Linux 最快的时间测量方法(比 std::chrono 更快)?包含基准

Huy*_* Le 4 c++ linux optimization performance time

#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;
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试分析一个程序,其中某些关键部分的执行时间小于 50 纳秒。我发现我使用的计时器类std::chrono太昂贵(带计时的代码比不带计时的代码多花费 40% 的时间)。如何制作更快的计时器类?

我认为一些特定于操作系统的系统调用将是最快的解决方案。平台是Linux Ubuntu。

编辑:所有代码均使用 -O3 编译。确保每个计时器仅初始化一次,因此测量的成本仅由 startMeasure/stopMeasure 函数引起。我不做任何文本打印。

编辑2:接受的答案不包括实际将周期数转换为纳秒的方法。如果有人能做到这一点,那将非常有帮助。

小智 10

你想要的就是所谓的“微基准测试”。它可能会变得非常复杂。我假设您在 x86_64 上使用 Ubuntu Linux。这对于 ARM、ARM64 或任何其他平台无效。

\n

std::chrono 在 Linux 上的 libstdc++ (gcc) 和 libc++ (clang) 上实现,作为 GLIBC(C 库)的简单包装,它完成了所有繁重的工作。如果您查看 std::chrono::steady_clock::now() 您将看到对clock_gettime() 的调用。

\n

Clock_gettime() 是一个VDSO,即它是在用户空间中运行的内核代码。它应该非常快,但有时它可能需要做一些内务处理,并且每次调用都需要很长时间。所以我不建议进行微基准测试。

\n

几乎每个平台都有一个周期计数器,x86 也有汇编指令rdtscasm可以通过构造调用或使用编译器特定的内置函数 __builtin_ia32_rdtsc() 或 __rdtsc()将该指令插入到您的代码中。

\n

这些调用将返回一个 64 位整数,表示自机器加电以来的时钟数。rdtsc 不是立即执行的,但速度很快,大约需要 15-40 个周期才能完成。

\n

不能保证在所有平台上每个核心的该计数器都相同,因此当进程从一个核心移动到另一个核心时要小心。但在现代系统中这不应该是一个问题。

\n

rdtsc 的另一个问题是,如果编译器发现指令没有副作用,通常会重新排序指令,不幸的是 rdtsc 就是其中之一。因此,如果您发现编译器在欺骗您,则必须在这些计数器读取周围使用假屏障 - 查看生成的程序集。

\n

还有一个大问题是cpu本身乱序执行。不仅编译器可以改变执行顺序,CPU 也可以。由于 x86 486 英特尔 CPU 是流水线式的,因此可以同时执行多条指令 - 粗略地说。因此,您最终可能会测量虚假执行。

\n

我建议您熟悉微基准测试的类似量子问题。这并不简单。

\n

请注意,rdtsc() 将返回周期数。您必须使用时间戳计数器频率转换为纳秒。

\n

这是一个例子:

\n
#include <iostream>\n#include <cstdio>\n\nvoid dosomething() {\n    // yada yada\n}\n\nint main() {\n    double sum = 0;\n    const uint32_t numloops = 100000000;\n    for ( uint32_t j=0; j<numloops; ++j ) {\n        uint64_t t0 = __builtin_ia32_rdtsc();\n        dosomething();\n        uint64_t t1 = __builtin_ia32_rdtsc();\n        uint64_t elapsed = t1-t0;\n        sum += elapsed;\n    }\n    std::cout << "Average:" << sum/numloops << std::endl;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这篇论文有点过时(2010 年),但它足够最新,可以为您提供有关微基准测试的良好介绍:

\n

如何对 Intel\xc2\xae IA-32 和 IA-64 指令集架构上的代码执行时间进行基准测试

\n