为超线程创建友好的定时繁忙循环

Bee*_*ope 5 concurrency performance x86 multithreading hyperthreading

想象一下,我想让一个主线程和一个辅助线程作为两个超线程在同一物理核心上运行(可能通过强制它们的亲和力来大致确保这一点)。

线程将执行重要的高 IPC、CPU 密集型工作。除了定期更新主线程将定期读取的共享时间戳值之外,辅助线程不应该执行任何操作更新频率是可配置的,但可以快至 100 MHz 或更高。如此快速的更新或多或少排除了基于睡眠的方法,因为阻塞睡眠太慢,无法在 10 纳秒 (100 MHz) 周期内睡眠/唤醒。

所以我想要忙碌的等待。然而,繁忙等待应该对主线程尽可能友好:使用尽可能少的执行资源,从而给主线程增加尽可能少的开销。

我想这个想法将是一个长延迟指令,不使用很多资源,就像pause并且也有一个固定且已知的延迟。这将使我们能够校准“睡眠”周期,因此甚至不需要读取时钟(如果想要更新周期,P我们只需发出P/L这些指令以进行校准的忙睡眠。但pause不满足后一个标准,因为它的延迟各不相同很多1 .

第二种选择是使用长延迟指令,即使延迟未知,并且在每条指令之后执行一个rdtsc或一些其他时钟读取方法(clock_gettime等)来查看我们实际睡了多长时间。看起来它可能会大大减慢主线程的速度。

还有更好的选择吗?


1还有pause一些关于防止推测性内存访问的特定语义,这可能有利于也可能没有利于这个同级线程场景,因为我实际上并不处于自旋等待循环中。

Sur*_*urt 1

关于这个主题的一些随意的思考。

因此,您希望在 100 MHz 样本上有时间戳,这意味着在 4GHz cpu 上,每次调用之间有 40 个周期。

计时器线程忙于读取实时时钟(RTDSC???),但无法使用 cpuid 的 save 方法,因为这需要 100 个周期。旧的实时时钟的延迟约为 25(吞吐量为 1/25),可能有一个稍微更新、更准确、延迟稍多的计时器(32 个周期)。

  start:
  read time (25 cycles)
  tmp = time - last (1 cycle)
  if tmp < sample length goto start
  last += cycles between samples
  sample = time
  goto start
Run Code Online (Sandbox Code Playgroud)

在完美的世界中,分支预测器每次都会猜测正确,但实际上,由于读取时间周期的差异,它会错误预测随机添加 5-14 个周期到 26 个周期的循环。

当写入样本时,另一个线程将从该缓存行的第一个推测加载中取消其指令(请记住将样本位置对齐到 64 字节,这样就不会影响其他数据)。样本时间戳的加载会在大约 5-14 个周期的延迟后重新开始,具体取决于指令的来源、循环缓冲区、微操作高速缓存或 I 高速缓存。

因此,除了一半的 cpu 被其他线程使用外,还会损失至少 5->14 个周期/40 个周期的性能。

另一方面,读取主线程中的实时时钟将花费......

~1/4 个周期,延迟很可能会被其他指令覆盖。但这样你就无法改变频率。25 个周期的长延迟可能会成为一个问题,除非在它之前有其他长延迟指令。

使用 CAS 指令(lock exch???)可能会部分解决问题,因为加载不会导致指令重新发出,而是会导致所有后续读取和写入的延迟。