C 如何使用liburing实现每秒零纳秒的计时器?
我注意到内核端的io使用的是CLOCK_MONOTONIC at,因此对于第一个计时器,我使用CLOCK_REALTIME和CLOCK_MONOTONIC获取时间,并调整纳秒,如下所示,并使用IORING_TIMEOUT_ABS标志表示io_prep_TIMEOUT 你能告诉我一个比这更好的方法吗C 如何使用liburing实现每秒零纳秒的计时器?,c,linux,C,Linux,我注意到内核端的io使用的是CLOCK_MONOTONIC at,因此对于第一个计时器,我使用CLOCK_REALTIME和CLOCK_MONOTONIC获取时间,并调整纳秒,如下所示,并使用IORING_TIMEOUT_ABS标志表示io_prep_TIMEOUT 你能告诉我一个比这更好的方法吗 谢谢你的评论!我想更新日志记录的当前时间,如ngx\u time\u update()。我修改了我的示例,只使用了CLOCK\u REALTIME,但仍然延迟了大约400微秒。github.com/h
谢谢你的评论!我想更新日志记录的当前时间,如
ngx\u time\u update()
。我修改了我的示例,只使用了CLOCK\u REALTIME
,但仍然延迟了大约400微秒。github.com/hnakamur/iorn/commit/…这是否意味着在我的机器上,clock\u gettime
大约需要400纳秒
是的,听起来有点对。但是,如果您在linux下的x86
PC上,400 ns的clock\u gettime
开销可能有点高(数量级更高,请参见下文)。如果您使用的是arm
CPU(例如Raspberry Pi,nvidia
Jetson),可能还可以
我不知道你是怎么得到400微秒的。但是,我不得不在linux下做很多实时工作,400 us与我所测量的类似,即在系统调用挂起进程/线程后进行上下文切换和/或唤醒进程/线程的开销
我不再使用gettimeofday
。我现在只使用了clock\u gettime(clock\u REALTIME,…)
,因为它是一样的,只是你得到的是纳秒而不是微秒
正如您所知,虽然clock\u gettime
是一个系统调用,但现在在大多数系统上,它使用VDSO
层。内核将特殊代码注入到用户空间应用程序中,以便它能够直接访问时间,而无需系统调用的开销
如果您感兴趣,您可以在gdb
下运行并反汇编代码,查看它只访问一些特殊的内存位置,而不是执行系统调用
我觉得你不必太担心这个。只需使用clock\u gettime(clock\u MONOTONIC,…)
并将标志设置为0。由于iorn
层正在使用它,因此在调用ioring
时,开销并没有考虑到这一点
当我做这种事情时,我想/需要计算clock\u gettime
本身的开销,我在循环中调用clock\u gettime
(例如1000次),并尝试将总时间保持在[可能的]时间片以下。我在每次迭代中使用时间之间的最小差异。这补偿了任何[可能的]时间损失
最小值是呼叫本身的开销[平均值]
您还可以使用其他技巧来最小化用户空间中的延迟(例如,提高进程优先级、限制CPU相关性和I/O中断相关性),但这些技巧可能涉及更多的事情,如果您不十分小心,它们可能会产生更糟糕的结果
在您开始采取非常措施之前,您应该有一个可靠的方法来测量计时/基准测试,以证明您的结果不能满足计时/吞吐量/延迟要求。否则,你在做复杂的事情,没有真正的/可测量的/必要的好处
下面是我刚刚创建的一些代码,经过简化,但基于我已经拥有/使用的代码来校准开销:
#include <stdio.h>
#include <time.h>
#define ITERMAX 10000
typedef long long tsc_t;
// tscget -- get time in nanoseconds
static inline tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
clock_gettime(CLOCK_MONOTONIC,&ts);
tsc = ts.tv_sec;
tsc *= 1000000000;
tsc += ts.tv_nsec;
return tsc;
}
// tscsec -- convert nanoseconds to fractional seconds
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= 1e9;
return sec;
}
tsc_t
calibrate(void)
{
tsc_t tscbeg;
tsc_t tscold;
tsc_t tscnow;
tsc_t tscdif;
tsc_t tscmin;
int iter;
tscmin = 1LL << 62;
tscbeg = tscget();
tscold = tscbeg;
for (iter = ITERMAX; iter > 0; --iter) {
tscnow = tscget();
tscdif = tscnow - tscold;
if (tscdif < tscmin)
tscmin = tscdif;
tscold = tscnow;
}
tscdif = tscnow - tscbeg;
printf("MIN:%.9f TOT:%.9f AVG:%.9f\n",
tscsec(tscmin),tscsec(tscdif),tscsec(tscnow - tscbeg) / ITERMAX);
return tscmin;
}
int
main(void)
{
calibrate();
return 0;
}
所以,我的开销是25纳秒[而不是400纳秒]。但是,同样,每个系统在某种程度上都可能有所不同
更新:
请注意,x86
处理器具有“速度步长”。操作系统可以半自动地向上或向下调整CPU频率。较低的速度可以节省电力。速度越高,性能越好
这是通过启发式方法完成的(例如,如果操作系统检测到进程是一个CPU占用量大的用户,它将加快速度)
为了实现最高速度,linux有以下目录:
/sys/devices/system/cpu/cpuN/cpufreq
其中N
是cpu编号(例如0-7)
在这个目录下,有许多感兴趣的文件。它们应该是不言自明的
特别是,请查看缩放\u调节器
。它具有ondemand
[内核将根据需要进行调整]或性能
[内核将强制实现最大CPU速度]
要强制最大速度,作为根,将此[一次]设置为性能(例如):
对所有CPU执行此操作
然而,我只是在我的系统上这样做,效果很小。因此,内核的启发式可能有所改进
至于400us,当一个进程一直在等待某件事情,当它被“唤醒”时,这是一个两步的过程
进程被标记为“可运行”
在某个时刻,系统/CPU会重新调度。进程将根据调度策略和有效的进程优先级运行
对于许多系统调用,重新调度[仅]发生在下一个系统计时器/时钟滴答声/中断时。因此,对于某些情况,如果HZ
值为1000,则可能会有高达一个完整时钟滴答声(即)的延迟,这可能会在1毫秒(1000 us)之后
平均而言,这是HZ
或500 us的一半
对于某些系统调用,当进程标记为runnable时,会立即执行重新调度。如果进程具有更高的优先级,它将立即运行
当我第一次看到这个[circa 2004]时,我查看了内核中的所有代码路径,唯一一个立即重新调度的系统调用是SysV IPC,用于msgsnd/msgrcv
。也就是说,当进程A执行msgsnd时,等待给定消息的任何进程B都将运行
但是,其他人没有(例如,futex)。他们会等待计时器的滴答声。从那时起,情况发生了很大变化,现在,更多的系统调用将立即进行重新调度。例如,我最近测量了futex
[通过pthread\u mutex\u*
]调用],它似乎可以快速重新调度
此外,内核调度程序也发生了更改。较新的调度程序可以在很短的时间内唤醒/运行某些事情
那么,对你来说,400
#include <stdio.h>
#include <time.h>
#define ITERMAX 10000
typedef long long tsc_t;
// tscget -- get time in nanoseconds
static inline tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
clock_gettime(CLOCK_MONOTONIC,&ts);
tsc = ts.tv_sec;
tsc *= 1000000000;
tsc += ts.tv_nsec;
return tsc;
}
// tscsec -- convert nanoseconds to fractional seconds
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= 1e9;
return sec;
}
tsc_t
calibrate(void)
{
tsc_t tscbeg;
tsc_t tscold;
tsc_t tscnow;
tsc_t tscdif;
tsc_t tscmin;
int iter;
tscmin = 1LL << 62;
tscbeg = tscget();
tscold = tscbeg;
for (iter = ITERMAX; iter > 0; --iter) {
tscnow = tscget();
tscdif = tscnow - tscold;
if (tscdif < tscmin)
tscmin = tscdif;
tscold = tscnow;
}
tscdif = tscnow - tscbeg;
printf("MIN:%.9f TOT:%.9f AVG:%.9f\n",
tscsec(tscmin),tscsec(tscdif),tscsec(tscnow - tscbeg) / ITERMAX);
return tscmin;
}
int
main(void)
{
calibrate();
return 0;
}
MIN:0.000000019 TOT:0.000254999 AVG:0.000000025
/sys/devices/system/cpu/cpuN/cpufreq
echo "performance" > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor