在POSIX中以微秒粒度调度事件

时间:2013-11-12 10:53:09

标签: c timer posix real-time poisson

我正在尝试确定可以在C / C ++中准确计划任务的粒度。目前,我可以可靠地安排任务每5微秒发生一次,但我试图看看是否可以进一步降低这一点。

关于如何实现这一目标的任何建议/如果可能的话,将不胜感激。

因为我知道计时器粒度通常可以依赖于操作系统:我目前在Linux上运行,但是如果时间粒度更好的话会使用Windows(虽然我不相信它,基于我发现的QueryPerformanceCounter的)

我在裸机上执行所有测量(无VM)。 /proc/timer_info确认了我的CPU的纳秒定时器分辨率(但我知道这不会转换为纳秒警报分辨率)

当前

My current code can be found as a Gist here

目前,我能够每5微秒(5000纳秒)执行一次请求,迟到的次数少于1%。当迟到确实发生时,它们通常只落后一个周期(5000纳秒)。

我现在正做三件事

将流程设置为实时优先级(某些人指出@ Spudd86 here

struct sched_param schedparm;
memset(&schedparm, 0, sizeof(schedparm));
schedparm.sched_priority = 99; // highest rt priority
sched_setscheduler(0, SCHED_FIFO, &schedparm);

最小化计时器松弛

prctl(PR_SET_TIMERSLACK, 1);

使用timerfds(2.6 Linux内核的一部分)

int timerfd = timerfd_create(CLOCK_MONOTONIC,0);
struct itimerspec timspec;
bzero(&timspec, sizeof(timspec));
timspec.it_interval.tv_sec = 0;
timspec.it_interval.tv_nsec = nanosecondInterval;
timspec.it_value.tv_sec = 0;
timspec.it_value.tv_nsec = 1;

timerfd_settime(timerfd, 0, &timspec, 0);

可能的改进

  1. 将处理器专用于此过程?
  2. 使用非阻塞timerfd,这样我就可以创建一个紧密循环,而不是阻塞(紧密循环会浪费更多CPU,但也可能更快响应警报)
  3. 使用外部嵌入式设备进行触发(无法想象为什么会更好)
  4. 为什么

    我目前正在为基准测试引擎创建工作负载生成器。工作负载生成器使用泊松过程模拟到达率(X请求/秒等)。从泊松过程中,我可以确定必须从基准测试引擎发出请求的相对时间。

    例如,在每秒10次请求时,我们可能会提出以下请求: t = 0.02,0.04,0.05,0.056,0.09秒

    这些请求需要提前安排然后执行。随着每秒请求数的增加,调度这些请求所需的粒度也会增加(每秒数千个请求需要亚毫秒精度)。因此,我正试图弄清楚如何进一步扩展这个系统。

2 个答案:

答案 0 :(得分:3)

你非常接近vanilla Linux将为你提供的限制,并且它已经超越了它可以保证的范围。将real-time patches添加到内核并进行完全抢占调整将有助于在负载下提供更好的保证。我还会删除你的时间关键代码中的任何动态内存分配,malloc和朋友可以(并且将会)停止一段时间(在实时意义上)如果必须从i /中回收内存的话。 o缓存。我也会考虑从该机器中删除交换以帮助保证性能。将处理器专用于您的任务将有助于防止上下文切换时间,但同样不能保证。

我还建议你小心那个级别的sched_priority,你在那里的Linux的各个重要部分之上,这会导致非常奇怪的效果。

答案 1 :(得分:3)

从构建实时内核中获得的是内核处理的IO /计时器事件与作为响应传递给您的应用程序的控件之间的时间更可靠的保证(即更低的最大延迟)。这是以较低吞吐量为代价的,您可能会注意到最佳延迟时间的增加。

但是,使用OS计时器以高精度计划事件的唯一原因是,如果您在等待下一个到期事件时害怕在循环中烧掉CPU周期。操作系统计时器(MS Windows中的尤其是)对于高粒度计时事件不可靠,并且非常依赖于系统中可用的计时/ HPET硬件类型。

当我需要高度准确的事件调度时,我使用混合方法。首先,我测量最坏情况下的延迟 - 即我请求睡眠的时间与睡眠后的实际时钟时间之间的最大差异。我们称这个差异为“D”。 (实际上你可以在正常运行期间通过每次睡眠时跟踪“D”来实现这一点,例如“D =(D * 7 + lastD)/ 8”以产生时间平均值。) p>

然后永远不要求超出“N-D * 2”睡觉,其中“N”是下一个事件的时间。在下一个事件的“D * 2”时间内,进入旋转循环并等待“N”发生。

这会占用更多的CPU周期,但根据您所需的准确性,您可以在旋转循环中使用“sched_yield()”,这对您的系统更为友好。