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

measuring precise time in nanoseconds C++

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

我尝试使用 chrono::high_resolution_clock 来达到这个目的。为了测试它是否正常工作。我执行以下操作:

  1. 使用 high_resolution_clock 获取以纳秒为单位的当前时间,将其命名为 "start"
  2. 使用 nanosleep(x)
  3. 休眠 "x" 纳秒
  4. 使用 high_resolution_clock 获取以纳秒为单位的当前时间,称之为 "end"
  5. 现在 "end" - "start" 应该与 "x" 大致相同。我们称此差异为 "diff"

我 运行 上面的 x 从 10 到 1000000 的测试。我得到的差异在 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

您尝试做的并不是在每个平台上都有效,甚至不是在大多数平台上都有效。有几个原因。

第一个也是最大的原因是,测量代码执行的精确时间 at/within 就其本质而言是不精确的。它需要一个黑盒 OS 调用来确定,如果您首先查看过这些调用是如何实现的,那么很快就会发现该技术存在固有的不精确性。在 Windows 上,这是通过测量处理器的当前 "tick" 及其报告的频率,并将一个乘以另一个来确定两次连续调用之间经过了多少纳秒来完成的。但是 Windows 仅以微秒的精度开始报告,并且如果 CPU 更改其频率,即使只是适度(这在现代 CPUs 中很常见,以降低频率CPU 没有被最大化,以节省电力)可能会扭曲结果。

Linux 也有类似的怪癖,每个 OS 都受制于 CPU 准确报告其自身报价 counter/tick 率的能力。

您将获得与您观察到的结果类似的第二个原因是,出于与第一个原因类似的原因,"Sleep"-ing 线程通常非常不精确。 CPUs 通常不能以比微秒精度更好的精度休眠,而且一次休眠通常不可能快于半毫秒。您的特定环境似乎至少能够达到几百微秒的精度,但显然不会比这更精确。有些环境甚至会完全降低纳秒分辨率。

总而言之,如果不针对显式实时 OS 进行编程,使用 OS 的特定 API,您就可以获得你是 expecting/desiring 的那种精确度。如果您想要有关各个代码片段时间的可靠信息,您需要 运行 一遍又一遍地说代码,获取整个执行的样本,然后取平均值,以获得每个 运行 的大致时间安排。它仍然不精确,但它会帮助解决这些限制。

以下是 nanosleep 的部分描述:

If the interval specified in req is not an exact multiple of the granularity underlying clock (see time(7)), then the interval will be rounded up to the next multiple. Furthermore, after the sleep completes, there may still be a delay before the CPU becomes free to once again execute the calling thread.

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

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

暂停时间少于系统时钟时间的一种可能方法是测量在时钟更改之前执行循环的频率。在启动期间(例如)执行几次,以了解每微秒可以执行多少个循环。

然后暂停一小部分时间,你可以做线性插值来猜测多次执行循环以获得大约相同长度的暂停。

注意:这通常会在暂停期间 运行 CPU 处于 100%,因此您只想 short ] 暂停——最多一两微秒就可以了,但如果你想要更多,你可能想回到纳米睡眠。

但是,即便如此,您也需要注意暂停时间可能会比您计划的时间长得多。 OS 进行时间切片。如果您的进程的时间片在您的暂停循环中间到期,那么它很容易在它被安排为再次 运行 之前几十毫秒(或更多)。

如果您真的需要保证此订单的响应时间,您可能需要考虑另一个 OS(但即使那不是灵丹妙药——您要求的也不是微不足道的,无论您如何处理它)。

参考

nanosleep man page