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

时间:2013-08-06 04:09:50

标签: c++11 g++ thread-sleep

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

// 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;

我已经在循环中运行了数千次,并且在我特定的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;

这告诉我平均睡眠时间为55400纳秒,即55.4微秒。远远超过我的预期。

将上面的代码放入for()循环中,我尝试了不同数量的睡眠,这就是结果:

  • sleep_for(4000 ns)=&gt;睡了58000 ns
  • sleep_for(3000 ns)=&gt;睡了57000 ns
  • sleep_for(2000 ns)=&gt;睡了56000 ns
  • sleep_for(1000 ns)=&gt;睡了55000 ns
  • sleep_for(0 ns)=&gt;睡了54000 ns
  • sleep_for(-1000 ns)=&gt;睡了313 ns
  • sleep_for(-2000 ns)=&gt;睡了203 ns
  • sleep_for(-3000 ns)=&gt;睡了215 ns
  • sleep_for(-4000 ns)=&gt;睡了221秒

我有些问题:

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

3 个答案:

答案 0 :(得分:12)

  

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

有一个非常明显的模式,你的所有结果都比你要求睡觉的时间长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;

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

答案 1 :(得分:2)

Straight Fast答案的启发,我评估了timer_slack_nsSCHED_FIFO的影响。对于timer_slack_ns,您必须添加

#include <sys/prctl.h> // prctl
⋮
prctl (PR_SET_TIMERSLACK, 10000U, 0, 0, 0);

表示当前进程,计时器延迟时间应设置为10µs,而不是默认值50µs。效果是更好的响应性,但以稍高的能耗为代价。该进程仍可以由非特权用户运行。要将调度程序策略更改为SCHED_FIDO,您必须是“ root”用户。所需的代码是

#include <unistd.h>    // getpid
#include <sched.h>     // sched_setscheduler
⋮
    const pid_t pid {getpid ()};
    struct sched_param sp = {.sched_priority = 90};
    if (sched_setscheduler (pid, SCHED_FIFO, &sp) == -1) {
        perror ("sched_setscheduler");
        return 1;
    }

我在带有GUI的桌面系统(Debian 9.11,内核)上运行了Stéphane的代码段 4.9.189-3 + deb9u2,g ++ 9.2 -O3,Intel®Core™i5-3470T CPU @ 2.90GHz)。第一种情况的结果(随后的时间测量)为

由于在两者之间没有系统调用,因此延迟约为260ns,并且不受过程设置的明显影响。对于正态分布的时序,这些图是直线,其中横坐标值为0.5,纵坐标为平均值,斜率表示标准偏差。测量值与测量值的不同之处在于存在较高的延迟值。

与此相反,第二种情况(睡眠一纳秒)在过程设置之间有所不同,因为它包含系统调用。因为睡眠时间太短,所以睡眠不会增加任何时间。因此,这些图仅显示间接费用

Stéphane所述,开销的默认值约为64µs(此处稍大)。通过将timer_slack_ns降低到10µs,时间可以减少到大约22µs。通过调用特权 sched_setscheduler(),开销可以减少到大约12µs。但是如图所示,即使在这种情况下,延迟也可能会超过50µs(在运行的0.0001%中)。

这些度量显示了过程设置中开销的基本依赖性。其他测量结果表明,在非GUI XEON服务器系统上,波动幅度降低了一个数量级以上。

答案 2 :(得分:0)

在内核init / init_task.c中的struct task_struct init_task定义的参数中

.timer_slack_ns = 50000, /* 50 usec default slack */

在hrtimer_nanosleep()内核函数中增加了非RT进程,以减少计时器的hardirq。