如果我并排拨打两个电话来确定最小的可测量持续时间:
// 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()
循环中,我尝试了不同数量的睡眠,这就是结果:
我有些问题:
答案 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_ns
和SCHED_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。