测量精确的时间,单位为纳秒C++

measuring precise time in nanoseconds C++

本文关键字:C++ 单位 时间 测量      更新时间:2023-10-16

我想测试一种在C++中以纳秒为单位测量代码精确执行时间的方法(精确到100纳秒是可以的)。

为此,我尝试使用chrono::high_resolution_clock。为了测试它是否正常工作。我做以下事情:

  1. 使用high_resolution_clock获取当前时间(以纳秒为单位),称之为"启动">
  2. 使用nanosleep(x)睡眠"x"纳秒
  3. 使用high_resolution_clock以纳秒为单位获取当前时间,称之为"结束">
  4. 现在"end"-"start"应该与"x"大致相同。让我们把这种差异称为"diff">

我对10到1000000之间的x进行了上述测试。我得到的差异大约是100000,即(100微秒)

因为这不应该超过100纳秒。请帮我解决这个问题。

#include <ctime>
#include <unistd.h>
#include <iostream>
#include <chrono>
using namespace std;
int main() {
int sleep_ns[] = {10, 50, 100, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000};
int n = sizeof(sleep_ns)/sizeof(int);
for (int i = 0; i < n; i++) {
auto start = std::chrono::high_resolution_clock::now();
timespec tspec = {0, sleep_ns[i]};
nanosleep(&tspec, NULL);
auto end = std::chrono::high_resolution_clock::now();
chrono::duration<int64_t, nano> dur_ns = (end - start);
int64_t measured_ns = dur_ns.count();
int64_t diff = measured_ns - sleep_ns[i];
cout << "diff: " << diff
<< " sleep_ns: " << sleep_ns[i]
<< " measured_ns: " << measured_ns << endl;
}
return 0;
}

下面是我的机器上这段代码的输出。它运行的"Ubuntu 16.04.4 LTS">

diff: 172747 sleep_ns: 10 measured_ns: 172757
diff: 165078 sleep_ns: 50 measured_ns: 165128
diff: 164669 sleep_ns: 100 measured_ns: 164769
diff: 163855 sleep_ns: 500 measured_ns: 164355
diff: 163647 sleep_ns: 1000 measured_ns: 164647
diff: 162207 sleep_ns: 2000 measured_ns: 164207
diff: 160904 sleep_ns: 5000 measured_ns: 165904
diff: 155709 sleep_ns: 10000 measured_ns: 165709
diff: 145306 sleep_ns: 20000 measured_ns: 165306
diff: 115915 sleep_ns: 50000 measured_ns: 165915
diff: 125983 sleep_ns: 100000 measured_ns: 225983
diff: 115470 sleep_ns: 200000 measured_ns: 315470
diff: 115774 sleep_ns: 500000 measured_ns: 615774
diff: 116473 sleep_ns: 1000000 measured_ns: 1116473

您想要做的并不是在每个平台上,甚至大多数平台上都能工作。原因有几个。

第一个也是最大的原因是,从本质上讲,衡量代码执行的精确时间是不精确的。它需要一个黑盒操作系统调用来确定,如果你一开始就了解过这些调用是如何实现的,那么很快就会发现该技术存在固有的不精确性。在Windows上,这是通过测量处理器的当前"节拍"及其报告的频率来完成的,并将两者相乘以确定两个连续调用之间经过了多少纳秒。但Windows一开始的报告精度仅为微秒,如果CPU改变其频率,即使只是适度地(这在现代CPU中很常见,在CPU未达到最大功率时降低频率,以节省电源),也会扭曲结果。

Linux也有类似的怪癖,每个操作系统都取决于CPU准确报告自己的刻度计数器/刻度率的能力。

第二个原因是,由于与第一个原因类似的原因,"睡眠"线程通常非常不精确。CPU通常无法以比微秒精度更好的精度睡眠,而且一次睡眠速度通常不可能超过半毫秒。您的特定环境似乎至少能够达到几百微秒的精度,但它显然不会比这更精确。有些环境甚至会完全降低纳秒分辨率。

总之,假设在不为明确的实时操作系统编程的情况下,使用该操作系统的特定API,您可以获得您期望/期望的精度,这可能是错误的。如果你想要关于单个代码片段的时间安排的可靠信息,你需要一遍又一遍地运行所述代码,获得整个执行的样本,然后取平均值,以大致了解每次运行的时间安排。它仍然是不精确的,但它将有助于克服这些限制。

以下是纳米睡眠的部分描述:

如果req中指定的间隔不是底层时钟粒度的精确倍数(请参见时间(7)),则该间隔将四舍五入到下一个倍数。此外,在睡眠完成之后,在CPU变得可以再次执行调用线程之前,可能仍然存在延迟。

您得到的行为似乎与描述非常吻合。

对于极短的停顿,你可能不得不自己做一些(大部分?)工作。系统时钟源通常具有微秒左右的粒度

一种可能的暂停时间小于系统时钟时间的方法是测量在时钟改变之前执行循环的频率。在启动过程中(例如)这样做几次,以很好地了解每微秒可以执行多少个循环。

然后,要暂停一小部分时间,可以进行线性插值,以猜测执行循环的次数,从而获得大致相同的暂停长度。

注意:这通常会在暂停期间以100%的速度运行CPU,所以你只想在真正短暂的暂停时这样做——最多一两微秒是可以的,但如果你想要更多,你可能想回到nanosleep。

然而,即便如此,你也需要意识到,暂停可能会比你计划的时间长得多。操作系统进行时间切片。如果进程的时间片在暂停循环的中间过期,那么在计划再次运行之前,很容易需要数十毫秒(或更长时间)。

如果你真的需要保证这个订单的响应时间,你可能需要考虑另一个操作系统(但即使这样也不是万能的——无论你如何处理,你所要求的都不是微不足道的)。

参考

nanosleep手册页