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

时间:2017-08-07 22:40:17

标签: multithreading performance concurrency x86 hyperthreading

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

主线程将执行重要的高IPC,CPU绑定工作。除了定期更新主线程将定期读取的共享时间戳值之外,帮助程序线程除此之外什么都不做。更新频率是可配置的,但可以快至100 MHz或更高。这种快速更新或多或少地排除了基于睡眠的方法,因为在10纳秒(100 MHz)的时间段内阻塞睡眠太慢而无法睡眠/唤醒。

所以我想要一个忙碌的等待。但是,繁忙的等待应该尽可能地与主线程友好:尽可能少地使用执行资源,因此尽可能少地增加主线程的开销。

我想这个想法将是一个长延迟指令,它不会使用许多资源,例如pause,并且还具有固定且已知的延迟。这将让我们校准睡眠"句点因此甚至不需要时钟读取(如果想要更新时段P,我们只需发出这些指令的P/L以进行校准忙碌睡眠。pause不会遇到后一个标准,因为它的潜伏期变化很大 1

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

有更好的选择吗?

1 同样pause有一些特定的语义来防止推测性内存访问,这可能会或可能不会对这个兄弟线程场景有益,因为我不在旋转 - 等待循环真的。

1 个答案:

答案 0 :(得分: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

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

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

因此,除了另一个线程使用的cpu的一半之外,最小的5> 14个周期/ 40个周期的性能将会丢失。

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

~1 / 4周期,延迟最有可能被其他指令覆盖。但是,你无法改变频率。除非在其之前有一些其他长延迟指令,否则25个周期的长延迟可能是个问题。

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