我想要获取由C++编写的程序准确的微秒级别的执行时间。
我尝试使用clock_t来获取执行时间,但是它不够准确。
(请注意,微基准测试很困难。准确的计时器只是获得短时间区间的有意义结果所必需的一小部分。参见“性能评估的成语方式?”以了解更一般的注意事项)
我想要获取由C++编写的程序准确的微秒级别的执行时间。
我尝试使用clock_t来获取执行时间,但是它不够准确。
(请注意,微基准测试很困难。准确的计时器只是获得短时间区间的有意义结果所必需的一小部分。参见“性能评估的成语方式?”以了解更一般的注意事项)
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 频率空闲与超频。
有关一些常规提示,请参见 评估性能的惯用方法?,例如通过首先测试另一个来进行理智检查,并查看是否更改了显示较快的内容。
新的C++11 std::chrono
库是我曾经见过并尝试使用的最复杂的一堆C++代码,但至少它是跨平台的!
因此,如果您想将其简化并使其更像"C语言",包括删除所有类型安全的类,这里有3个简单易用的函数可以获取毫秒、微秒和纳秒级别的时间戳……这只花了我大约12小时的时间来编写*:
NB:在下面的代码中,您可能会考虑使用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;
}
* (抱歉,我更多地是嵌入式开发人员而不是标准计算机程序员,所以所有这些高级、抽象的静态成员-类-命名空间-命名空间-命名空间的东西都让我感到困惑。别担心,我会变得更好的。)
std::chrono
?答:因为C++程序员喜欢用一些疯狂的东西,所以他们让它来处理单位。以下是一些C++怪异行为和使用std::chrono
的示例。请参考cppreference社区维基页面: https://en.cppreference.com/w/cpp/chrono/duration。
// 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实际上已经重载了字符ms
、us
、ns
等作为函数调用运算符,以此初始化各种类型的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;
system_clock
系统时钟steady_clock
稳定时钟high_resolution_clock
高精度时钟utc_clock
UTC 时钟tai_clock
TAI 时钟gps_clock
GPS 时钟file_clock
文件时钟operator"" mysuffix()
运算符重载/用户自定义字面量/后缀函数(自C++11起)是上述奇怪的 auto time_ms = 1ms;
的工作原理。写 1ms
实际上是调用了函数 operator"" ms()
,并将 1
作为输入参数传递进去,就像你写了这样一个函数调用: operator"" ms(1)
。要了解更多关于这个概念的信息,请参见这里的参考页面:cppreference.com: 用户自定义字面量 (自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;
为什么不像C和C++几十年来一直使用的double x_rad = degToRad(90.0);
这样做呢?我不知道。可能与C++程序员的思维方式有关吧。也许他们正在尝试使现代C++更具有Pythonic特性。
这种魔法也是潜在非常有用的C++fmt
库的工作原理,https://github.com/fmtlib/fmt在这里。它由Victor Zverovich编写,也是C++20的std::format
的作者。您可以在这里看到函数detail::udl_formatter<char> 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库文档here。
<chrono>
的好例子:https://dev59.com/Nbjna4cB1Zd3GeqP6jz6#59446610。使其成为一个好例子的最大特点是不断地逃离chrono类型系统进入整数类型。 - Howard Hinnantgit
的感觉和你现在对 <chrono>
的感觉一模一样。但是,在一个朋友帮助我克服了最初的障碍之后,再加上 github.com 的加入,我现在不会使用任何其他的源代码控制系统。我能理解你的立场。 - Howard Hinnanthigh_resolution_clock
?它总是最精确的吗?似乎有些人说它不是,比如这个答案。我猜@HowardHinnant也不推荐使用high_resolution_clock
。那么...用毫秒或微秒测量时间的最佳方法是什么? - starriet如果您想知道在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));
__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(...)
。CreateProcess(...)
和WaitForSingleObject(...)
的调用周围,以覆盖整个进程生命周期,否则放在主函数周围。这是一个合理的解决方案,我不认为它真的应该被否定... - parrowdicestd::chrono::high_resolution_clock
不可用,要么设置为不太高的计时器。这种特定的解决方案是必需的。 - stefan