【问题标题】:Getting an accurate execution time in C++ (micro seconds)在 C++ 中获得准确的执行时间(微秒)
【发布时间】:2014-02-18 13:57:44
【问题描述】:

我想在用 C++ 实现的程序的微秒内获得准确的执行时间。 我曾尝试使用clock_t 获取执行时间,但它不准确。

(请注意,微基准测试是困难。准确的计时器只是在短时间内获得有意义的结果所必需的一小部分。有关一些更一般的警告,请参阅Idiomatic way of performance evaluation?

【问题讨论】:

  • 你为什么认为,它不准确?
  • 鉴于执行时间取决于 CPU、内存可用性、缓存、所有可能的 I/O、线程调度程序等的费用。您确定需要这种级别的准确性吗?
  • @user3323616 如果此处的答案之一解决了您的问题,请将其标记为已接受。这样您的问题就不会出现在未回答的部分。
  • 我需要一个微秒的执行时间,而clock_t提供一个毫秒的执行时间....

标签: c++ performance benchmarking timing microbenchmark


【解决方案1】:

如果您使用的是 c++11 或更高版本,则可以使用 std::chrono::high_resolution_clock

一个简单的用例:

auto start = std::chrono::high_resolution_clock::now();
...
auto elapsed = std::chrono::high_resolution_clock::now() - start;

long long microseconds = std::chrono::duration_cast<std::chrono::microseconds>(
        elapsed).count();

此解决方案具有便携性的优点。


请注意,微基准测试很难。很容易测量出错误的东西(比如你的基准优化掉了),或者在你的定时区域中包含页面错误,或者没有考虑 CPU 频率空闲与 turbo。

请参阅Idiomatic way of performance evaluation? 了解一些一般提示,例如通过首先测试另一个来进行健全性检查,看看是否会改变哪个更快。

【讨论】:

  • 注意:自 GCC 4.8.1 起,g++ 纳秒可用。
  • @qdii 来测试微秒级别的程序性能,设置和销毁部分应该被忽略。
  • @qdii 没错,不同的操作系统有不同的设置时间。这就是为什么。你不是在测量应用程序的性能,而是操作系统的性能,如果你包括那一点的话。为什么要这么做?除非您是操作系统开发人员,否则您对设置时间无能为力。即使在一个操作系统上,硬盘也可能需要启动才能读取可执行文件。你真的想测量到微秒精度吗?
  • @stefan hm,例如,我可能想这样做,因为我是一名游戏开发人员,我想知道我的游戏需要多长时间才能在 Windows 8 上启动。作为开发人员,我对可执行文件的大小有影响:我可以在二进制可执行文件的部分中嵌入资源、剥离符号、优化空间等。我认为需要澄清这个问题,以便准确测量什么.
  • @qdii 对,那是一个应用程序。但是你真的会通过测量时间来做到这一点吗?您肯定需要一个分析器来识别 what 部分会导致设置时间过长。任何程序的启动都应该“感觉是瞬间的”。你真的不需要衡量这个恕我直言。
【解决方案2】:

下面是如何在 C++ 中获取简单的类似于 C 的毫秒、微秒和纳秒时间戳:

新的 C++11 std::chrono 库是我见过或试图弄清楚如何使用的最复杂的一堆 mess C++ 之一,但至少它是交叉的平台!

所以,如果你想简化它并使它更像“C”,包括删除它所做的所有类型安全类的东西,这里有 3 个简单且非常容易-使用函数获取以毫秒、微秒和纳秒为单位的时间戳...只花了我大约 12 小时来编写*:

注意:在下面的代码中,您可能会考虑使用std::chrono::steady_clock 而不是std::chrono::high_resolution_clock。他们从这里(https://en.cppreference.com/w/cpp/chrono)的定义如下:

  • steady_clock (C++11) - 永远不会调整的单调时钟
  • high_resolution_clock (C++11) - 具有最短滴答周期的时钟
#include <chrono>

// NB: ALL OF THESE 3 FUNCTIONS BELOW USE SIGNED VALUES INTERNALLY AND WILL
// EVENTUALLY OVERFLOW (AFTER 200+ YEARS OR SO), AFTER WHICH POINT THEY WILL
// HAVE *SIGNED OVERFLOW*, WHICH IS UNDEFINED BEHAVIOR (IE: A BUG) FOR C/C++.
// But...that's ok...this "bug" is designed into the C++11 specification, so
// whatever. Your machine won't run for 200 years anyway...

// Get time stamp in milliseconds.
uint64_t millis()
{
    uint64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now().time_since_epoch())
            .count();
    return ms; 
}

// Get time stamp in microseconds.
uint64_t micros()
{
    uint64_t us = std::chrono::duration_cast<std::chrono::microseconds>(
            std::chrono::high_resolution_clock::now().time_since_epoch())
            .count();
    return us; 
}

// Get time stamp in nanoseconds.
uint64_t nanos()
{
    uint64_t ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
            std::chrono::high_resolution_clock::now().time_since_epoch())
            .count();
    return ns; 
}

* (抱歉,我更像是一个嵌入式开发人员而不是标准计算机程序员,所以所有这些高级的、抽象的 static-member-within-class-within-namespace-within-namespace-within-命名空间的东西让我很困惑。别担心,我会好起来的。)

问:为什么是std::chrono

A:因为 C++ 程序员喜欢对事物发疯,所以他们让它为你处理单元。以下是一些 C++ 怪异和使用 std::chrono 的案例。参考本页:https://en.cppreference.com/w/cpp/chrono/duration

因此,您可以声明一个 1 秒的变量并将其更改为微秒,而无需像这样进行强制转换:

// Create a time object of type `std::chrono::seconds` & initialize it to 1 sec
std::chrono::seconds time_sec(1); 
// integer scale conversion with no precision loss: no cast
std::cout << std::chrono::microseconds(time_sec).count() << " microseconds\n";

你甚至可以像这样指定时间,在我看来,这非常奇怪而且太过分了。 C++14 从字面上重载了字符msusns 等作为函数调用运算符来初始化各种类型的std::chrono 对象,如下所示:

auto time_sec = 1s; // <== notice the 's' inside the code there 
                    // to specify 's'econds!
// OR:
std::chrono::seconds time_sec = 1s;
// integer scale conversion with no precision loss: no cast
std::cout << std::chrono::microseconds(time_sec).count() << " microseconds\n";

Here are some more examples:

std::chrono::milliseconds time_ms = 1ms;
// OR:
auto time_ms = 1ms;

std::chrono::microseconds time_us = 1us;
// OR:
auto time_us = 1us;

std::chrono::nanoseconds time_ns = 1ns;
// OR:
auto time_ns = 1ns;

就我个人而言,我宁愿只是简化语言并这样做,就像我已经做的那样,就像在此之前几十年在 C 和 C++ 中所做的那样:

// Notice the `_sec` at the end of the variable name to remind me this 
// variable has units of *seconds*!
uint64_t time_sec = 1; 

这里有一些参考资料:

  1. 时钟类型 (https://en.cppreference.com/w/cpp/chrono):
    1. system_clock
    2. steady_clock
    3. high_resolution_clock
    4. utc_clock
    5. tai_clock
    6. gps_clock
    7. file_clock
  2. Getting an accurate execution time in C++ (micro seconds)(@OlivierLi 回答)
  3. http://en.cppreference.com/w/cpp/chrono/time_point/time_since_epoch
  4. http://en.cppreference.com/w/cpp/chrono/duration - 显示小时、分钟、秒、毫秒等类型
  5. http://en.cppreference.com/w/cpp/chrono/system_clock/now

我还需要观看的视频:

  1. CppCon 2016: Howard Hinnant “A <chrono> Tutorial"

相关:

  1. 我的 3 组时间戳函数(相互交叉链接):
    1. 对于 C 时间戳,请在此处查看我的答案:Get a timestamp in C in microseconds?
    2. 对于 C++ 高分辨率时间戳,请在此处查看我的答案:Getting an accurate execution time in C++ (micro seconds)
    3. 对于 Python 高分辨率时间戳,请在此处查看我的答案:How can I get millisecond and microsecond-resolution timestamps in Python?

附录

更多关于"User-defined literals" (since C++11):

operator"" mysuffix() 运算符重载/用户定义文字/后缀函数(从 C++11 开始)是上面奇怪的 auto time_ms = 1ms; 的工作原理。编写1ms 实际上是对函数operator"" ms() 的函数调用,将1 作为输入参数传入,就好像您编写了这样的函数调用:operator"" ms(1)。要了解有关此概念的更多信息,请参阅此处的参考页面:cppreference.com: User-defined literals (since C++11)

这是定义用户定义文字/后缀函数并使用它的基本演示:

// 1. Define a function
// used as conversion from degrees (input param) to radians (returned output)
constexpr long double operator"" _deg(long double deg)
{
    long double radians = deg * 3.14159265358979323846264L / 180;
    return radians;
}

// 2. Use it
double x_rad = 90.0_deg;

为什么不直接使用类似double x_rad = degToRad(90.0); 的东西(就像几十年来在 C 和 C++ 中所做的那样)?我不知道。这与我猜想的 C++ 程序员的方式有关。也许他们正试图让现代 C++ 更加 Pythonic。

这个魔法也是可能非常有用的 C++ fmt 库的工作原理,这里是:https://github.com/fmtlib/fmt。它由Victor Zverovich 编写,也是C++20 的std::format 的作者。你可以看到函数detail::udl_formatter&lt;char&gt; operator"" _format(const char* s, size_t n)here的定义。它的用法是这样的:

"Hello {}"_format("World");

输出:

你好世界

这会将"World" 字符串插入{} 所在的第一个字符串中。这是另一个例子:

"I have {} eggs and {} chickens."_format(num_eggs, num_chickens);

示例输出:

我有 29 个鸡蛋和 42 只鸡。

这与 Python 中的str.format 字符串格式非常相似。阅读 fmt lib 文档here

【讨论】:

  • 多年后,即使在专业使用 C++ 数月之后,我仍然对秒、毫秒、微秒和纳秒等简单事物的不必要复杂性(以及许多其他事物),由 C++ 引入。
  • 您可以使用语言 Y 使用 X 的语言习语进行编程,但它可能会变得一团糟。用任意两种语言代替 X 和 Y,这将是正确的。在这种情况下,C 和 C++。下面是一个如何正确使用 &lt;chrono&gt; 的好例子:stackoverflow.com/a/59446610/576911 使它成为一个好例子的最大特点不是不断地将 chrono 类型系统转义为整数类型。
  • 感谢您亲自回复,霍华德,我去看看。我现在正在研究 C++ 的许多复杂方面。我还有很多东西要学。但是,我认为一个 C 开发人员可以使用 C++ 编译器编写比他/她使用 C 编译器编写的更好、更简洁的“C”(我们称之为“C+”)。 C++ 有许多精彩的补充,我只是发现大多数 C++ 开发人员对它的使用过于复杂,尤其是对于嵌入式系统。我希望看到 C++ 编译器 更多地用于嵌入式系统,但并非适用于所有现代 C++ 语言功能和习语。
  • 每时每刻,我都发现 C++ syntax 让我无法快速有效地完成手头的算法和问题解决任务。
  • Fwiw,我以前对git 的感觉和你现在对&lt;chrono&gt; 的感觉完全一样。但是在一位朋友帮助我克服了第一道障碍之后,坦率地说,加上 github.com,我现在不会使用任何其他源代码控制系统。我可以理解你的立场。
【解决方案3】:

如果您正在查看从 Unix shell 执行程序所消耗的时间,请使用 Linux time,如下所示,

time ./a.out 

real    0m0.001s
user    0m0.000s
sys     0m0.000s

其次,如果您想花时间在程序代码(C)中执行多个语句,请尝试使用gettimeofday(),如下所示,

#include <sys/time.h>
struct timeval  tv1, tv2;
gettimeofday(&tv1, NULL);
/* Program code to execute here */
gettimeofday(&tv2, NULL);
printf("Time taken in execution = %f seconds\n",
     (double) (tv2.tv_usec - tv1.tv_usec) / 1000000 +
     (double) (tv2.tv_sec - tv1.tv_sec));

【讨论】:

  • 使用 clock_gettime() 代替 - 手册页说 - gettimeofday() 返回的时间受系统时间不连续跳转的影响(例如,如果系统管理员手动更改系统时间)。如果您需要一个单调递增的时钟,请参阅clock_gettime(2)。 Opengroup 说 - 应用程序应该使用 clock_gettime() 函数而不是过时的 gettimeofday() 函数。
【解决方案4】:

如果您使用的是 Windows,则可以使用 QueryPerformanceCounter

How to use the QueryPerformanceCounter function to time code in Visual C++

__int64 ctr1 = 0, ctr2 = 0, freq = 0;
int acc = 0, i = 0;

// Start timing the code.
if (QueryPerformanceCounter((LARGE_INTEGER *)&ctr1)!= 0)
{
    // Code segment is being timed.
    for (i=0; i<100; i++) acc++;

    // Finish timing the code.
    QueryPerformanceCounter((LARGE_INTEGER *)&ctr2);

    Console::WriteLine("Start Value: {0}",ctr1.ToString());
    Console::WriteLine("End Value: {0}",ctr2.ToString());

    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

    Console::WriteLine(S"QueryPerformanceCounter minimum resolution: 1/{0} Seconds.",freq.ToString());
    // In Visual Studio 2005, this line should be changed to:     Console::WriteLine("QueryPerformanceCounter minimum resolution: 1/{0} Seconds.",freq.ToString()); 
    Console::WriteLine("100 Increment time: {0} seconds.",((ctr2 - ctr1) * 1.0 / freq).ToString());
}
else
{
    DWORD dwError = GetLastError();
    Console::WriteLine(S"Error value = {0}",dwError.ToString());// In Visual Studio 2005, this line should be changed to: Console::WriteLine("Error value = {0}",dwError.ToString());
}

// Make the console window wait.
Console::WriteLine();
Console::Write("Press ENTER to finish.");
Console::Read();

return 0;

您可以在整个进程生命周期内围绕对 CreateProcess(...)WaitForSingleObject(...) 的调用,否则围绕代码的 main 函数。

【讨论】:

  • 但他想要的是程序的执行时间,而不是函数。这包括您通常无法访问的设置部分。
  • @qdii:他可以在整个进程生命周期内围绕对CreateProcess(...)WaitForSingleObject(...) 的调用,否则围绕他的主要功能。这是一个合理的解决方案,但我认为它真的不值得投反对票……
  • +1,虽然是平台相关的。为什么?简单:在此平台的多个编译器上,std::chrono::high_resolution_clock 不可用或设置为不尽可能高的计时器。这个特定的解决方案必要的。
  • @parrowdice 好点子,如果你改变你的答案包括我会把我的反对票变成赞成票
  • Boost.Chrono 在 Windows 上具有不错的高分辨率时钟。
猜你喜欢
  • 2021-09-30
  • 2016-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-07
  • 2012-02-12
  • 1970-01-01
相关资源
最近更新 更多