std :: this_thread :: sleep_for()和纳秒

Sté*_*ane 12 g++ thread-sleep c++11

如果我并排放两个电话来确定最小的可测量持续时间:

// g++ -std=c++11 -O3 -Wall test.cpp
#include <chrono>
typedef std::chrono::high_resolution_clock hrc;

hrc::time_point start = hrc::now();
hrc::time_point end   = hrc::now();
std::chrono::nanoseconds duration = end - start;
std::cout << "duration: " << duration.count() << " ns" << std::endl;
Run Code Online (Sandbox Code Playgroud)

我已经在循环中运行了数千次,并且在我特定的3.40GHz桌面上我一直得到40 ns +/- 2 ns.

但是,当我想看看我能睡到的最短时间时:

#include <thread>

hrc::time_point start = hrc::now();
std::this_thread::sleep_for( std::chrono::nanoseconds(1) );
hrc::time_point end   = hrc::now();
std::chrono::nanoseconds duration = end - start;
std::cout << "slept for: " << duration.count() << " ns" << std::endl;
Run Code Online (Sandbox Code Playgroud)

这告诉我,我平均睡眠55400纳秒,或55.4微秒.远远超过我的预期.

把上面的代码放到for()循环中,我试着睡不同的数量,这就是结果:

  • sleep_for(4000 ns)=>睡眠58000 ns
  • sleep_for(3000 ns)=>睡眠57000 ns
  • sleep_for(2000 ns)=>睡眠时间为56000 ns
  • sleep_for(1000 ns)=>睡眠55000 ns
  • sleep_for(0 ns)=>睡眠54000 ns
  • sleep_for(-1000 ns)=>睡眠时间为313 ns
  • sleep_for(-2000 ns)=>睡眠时间为203 ns
  • sleep_for(-3000 ns)=>睡眠时间为215 ns
  • sleep_for(-4000 ns)=>睡眠时间为221 ns

我有一些问题:

  • 有什么可以解释这些数字?
  • 为什么在负时间内睡眠会返回200+ ns,而睡眠时间为0+纳秒会导致50,000+纳秒?
  • 负数作为睡眠时间是记录/支持的功能,还是我不小心偶然发现了一些我不能依赖的奇怪错误?
  • 是否有更好的C++睡眠调用可以让我更加一致/可预测的睡眠时间?

Jon*_*ely 13

有什么可以解释这些数字?

有一个非常明显的模式,你的所有结果都比你要求睡觉的时间长54000ns.如果你看看GCC this_thread::sleep_for()是如何在GNU/Linux上实现的,你会看到它只是使用nanospleep而且正如Cubbi的评论所说,调用该函数可能需要大约50000ns.我猜一些成本就是进行系统调用,因此从用户空间切换到内核并返回.

为什么在负时间内睡眠会返回200+ ns,而睡眠时间为0+纳秒会导致50,000+纳秒?

猜测我会说C库会检查负数并且不会进行系统调用.

负数作为睡眠时间是记录/支持的功能,还是我不小心偶然发现了一些我不能依赖的奇怪错误?

标准不禁止传递否定参数,因此允许它,并且函数应该"立即"返回,因为相对超时指定的时间已经过去了.你不能依赖负参数返回比非负参数更快,这是你特定实现的一个人工制品.

是否有更好的C++睡眠调用可以让我更加一致/可预测的睡眠时间?

我不这么认为 - 如果我知道一个,那么我们将在GCC中使用它来实现this_thread::sleep_for().

编辑:在更新版本的GCC的libstdc ++中我添加了:

if (__rtime <= __rtime.zero())
  return;
Run Code Online (Sandbox Code Playgroud)

因此,当请求零或负持续时间时,将不会有系统调用.


her*_*nnk 6

受到Straight Fasttimer_slack_ns \xe2\x80\x99s 答案的启发,我评估了和 的效果SCHED_FIFO。因为timer_slack_ns你必须添加

\n\n
#include <sys/prctl.h> // prctl\n\xe2\x8b\xae\nprctl (PR_SET_TIMERSLACK, 10000U, 0, 0, 0);\n
Run Code Online (Sandbox Code Playgroud)\n\n

这意味着对于当前进程,计时器裕量应设置为 10\xc2\xb5s,而不是默认值 50\xc2\xb5s。其效果是响应能力更好,但能耗稍高。该进程仍然可以由非特权用户运行。要更改调度程序策略,SCHED_FIDO您必须是\xe2\x80\x9croot\xe2\x80\x9d。所需的代码是

\n\n
#include <unistd.h>    // getpid\n#include <sched.h>     // sched_setscheduler\n\xe2\x8b\xae\n    const pid_t pid {getpid ()};\n    struct sched_param sp = {.sched_priority = 90};\n    if (sched_setscheduler (pid, SCHED_FIFO, &sp) == -1) {\n        perror ("sched_setscheduler");\n        return 1;\n    }\n
Run Code Online (Sandbox Code Playgroud)\n\n

我在带有 GUI 的桌面系统上运行St\xc3\xa9phane \xe2\x80\x99s 代码片段(Debian 9.11,内核 \n4.9.189-3+deb9u2,g++ 9.2 -O3,Intel\xc2\xae Core\xe2\x84 \xa2 i5-3470T CPU @ 2.90GHz)。第一种情况的结果(后续时间测量)是

\n\n

\n\n

由于中间没有系统调用,延迟约为260ns,受进程设置影响不大。对于正态分布时序,图表是直线,横坐标值为 0.5,纵坐标值为平均值,斜率代表标准偏差。测量值与测量值的不同之处在于存在较高延迟的异常值。

\n\n

与此相反,第二种情况(休眠一纳秒)在进程设置之间有所不同,因为它包含系统调用。因为睡眠时间太少了,睡觉并不会增加任何时间。因此,图表仅显示开销

\n\n

\n\n

正如St\xc3\xa9phane所说,开销默认约为 64\xc2\xb5s (这里 \xe2\x80\x99s 有点大。)。通过将 降低到 10\xc2\xb5s,时间可以减少到大约 22 timer_slack_ns\xc2\xb5s。并通过调用特权 sched_setscheduler(),开销可以减少到大约 12\xc2\xb5s。但如图所示,即使在这种情况下,延迟也可能超过 50\xc2\xb5s(在 0.0001% 的运行中)。

\n\n

测量结果显示了开销与流程设置的基本依赖性。其他测量结果表明,非 GUI XEON 服务器系统上的波动要低一个数量级以上。

\n