7

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

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

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

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

当前的

我当前的代码可以在这里找到作为 Gist

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

我现在正在做 3 件事

将进程设置为实时优先级(@Spudd86在这里指出了一些)

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. 使用外部嵌入式设备触发(无法想象为什么会更好)

为什么

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

因此,例如,每秒 10 个请求,我们可能会在以下时间发出请求:t = 0.02、0.04、0.05、0.056、0.09 秒

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

4

2 回答 2

3

您已经非常接近 vanilla Linux 为您提供的限制,而且已经超出了它可以保证的范围。将实时补丁添加到您的内核并调整完全抢占将有助于在负载下为您提供更好的保证。我还将从您的时间关键代码中删除任何动态内存分配,如果它必须从 i/ o 缓存。我还会考虑从那台机器上删除交换以帮助保证性能。将处理器专用于您的任务将有助于防止上下文切换时间,但同样不能保证。

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

于 2013-11-12T11:06:25.050 回答
3

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

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

当我需要高度准确的事件调度时,我使用混合方法。首先,我测量了最坏情况下的延迟——即我请求睡眠的时间与睡眠后的实际时钟时间之间的最大差异。我们称这种差异为“D”。(实际上,您可以在正常跑步期间即时执行此操作,通过每次睡觉时跟踪“D”,使用“D = (D*7 + lastD) / 8”之类的东西来产生时间平均值)。

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

这会消耗更多的 CPU 周期,但根据您需要的精度,您可能可以在自旋循环中使用“sched_yield()”,这对您的系统更友好。

于 2014-05-16T17:28:16.400 回答