46

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

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

4

4 回答 4

99

如果您使用的是 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。

请参阅绩效评估的惯用方式?对于一些一般提示,例如,通过首先测试另一个来检查是否会改变哪个看起来更快。

于 2014-02-18T14:09:17.597 回答
18

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

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

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

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

  • stable_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-namespace 的东西让我感到困惑。唐放心,我会好起来的。)

问:为什么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 从字面上重载了字符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; 

这里有一些参考资料:

  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
    8. 等等
  2. 在 C++ 中获得准确的执行时间(微秒)(@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>教程”

有关的:

  1. 我的 3 组时间戳功能(相互交叉链接):
    1. 对于C 时间戳,请在此处查看我的答案:Get a timestamp in C in microseconds?
    2. 对于C++ 高分辨率时间戳,请在此处查看我的答案:在 C++ 中获取准确的执行时间(微秒)
    3. 对于Python 高分辨率时间戳,请在此处查看我的答案:如何在 Python 中获得毫秒和微秒分辨率的时间戳?

附录

更多关于“用户定义的文字”(C++11 起)

operator"" mysuffix()运算符重载/用户定义文字/后缀函数(从 C++11 开始)是上面奇怪的auto time_ms = 1ms;事情的工作方式。编写1ms实际上是对 function 的函数调用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;

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

这种魔力也是潜在非常有用的 C++fmt库的工作原理,在这里:https ://github.com/fmtlib/fmt 。它由Victor Zverovich编写,他也是 C++20's 的作者std::formatdetail::udl_formatter<char> operator"" _format(const char* s, size_t n) 您可以在此处查看该函数的定义。它的用法是这样的:

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

输出:

你好世界

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

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

样本输出:

我有 29 个鸡蛋和 42 只鸡。

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

于 2018-03-02T09:35:49.777 回答
9

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

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));
于 2014-02-18T14:03:46.867 回答
1

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

请参阅如何使用 QueryPerformanceCounter 函数对 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;

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

于 2014-02-18T14:09:40.837 回答