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

Multithreading 为超线程创建友好的定时忙循环,multithreading,performance,concurrency,x86,hyperthreading,Multithreading,Performance,Concurrency,X86,Hyperthreading,想象一下,我想让一个主线程和一个辅助线程作为同一物理核心上的两个超线程运行(可能是通过强制它们的关联来确保这一点) 主线程将执行重要的高IPC、CPU限制的工作。helper线程除了定期更新主线程将定期读取的共享时间戳值之外,不应该做任何事情。更新频率是可配置的,但可以快至100 MHz或更高。这种快速更新或多或少地排除了基于睡眠的方法,因为阻塞睡眠太慢,无法在10纳秒(100 MHz)周期内睡眠/醒来 所以我想要一个忙碌的等待。然而,繁忙的等待应该对主线程尽可能友好:使用尽可能少的执行资源,从

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

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

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

我想这个想法应该是一个长延迟指令,它不使用很多资源,比如
pause
,而且它还有一个固定的已知延迟。这将允许我们校准“睡眠”周期,因此甚至不需要读取时钟(如果要使用周期
P
进行更新,我们只需为校准的繁忙睡眠发出
P/L
这些指令。
pause
不符合后一个标准,因为其延迟变化为lot1

第二种选择是使用长延迟指令,即使延迟未知,并且在每条指令之后执行
rdtsc
或其他一些时钟读取方法(
clock\u gettime
,等等),以查看我们实际睡眠的时间。但这可能会大大降低主线程的速度

还有更好的选择吗



1另外,
pause
有一些特定的语义,用于防止推测性内存访问,这可能对兄弟线程场景有利,也可能对兄弟线程场景不利,因为我实际上并不是在旋转等待循环中。

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

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

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

开始:
读取时间(25个周期)
tmp=时间-最后一次(1个周期)
如果tmp<样本长度,则转到开始
最后+=样本之间的周期
样本=时间
开始
在一个完美的世界中,分支预测器每次都会猜对,实际上,由于读取时间周期的变化,它会在循环中随机添加5-14个周期26个周期,从而导致预测失误

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

因此,除了另一个线程使用的一半cpu外,至少5->14个周期/40个周期的性能将丢失

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

约1/4个周期,延迟很可能会被其他指令覆盖。但是,您不能改变频率。25个周期的长延迟可能是个问题,除非之前有其他长延迟指令


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

平方根可能?单个µop,延迟很长,但会消耗一些能量并影响主线程执行的平方根(它是否执行任何操作?)。我想知道故意预测失误的分支机构会如何行动。我有理由相信,只有FU被阻止(如果它没有管道化的话)而且这个端口只用于发布/写回。因此,例如一些
movd
,只是偶尔会被
sqrtsd
稍微减慢。实验很好,只是玩得开心很好,但出于实用的原因以及可移植性和兼容性,这是我会避免的。回答你的问题,我也有不知道计时是什么,但我敢打赌(相当严重)与CPU密集型任务中所做的任何其他事情相比,它都是微不足道的。如果你有一个紧密的内部循环,用一个较慢的循环(例如1:10000)来包装它在那里进行查找。这是我的2美分。对于mul或div,使用FP非规范化,或者使用NaN/Infinity的x87慢化,都会很有趣。这些延迟非常高,但是如果“FP-assist”生成以处理大量微码UOP,或者如果它是在执行单元内完成的。可能是微码,这使得它对于低HT干扰线程不是特别有用。
movinti
store/reload将使用内存资源而不是ALU。这肯定是可变延迟,因此只能在
rdtsc
(非校准),但将休眠约500个周期,因此它的重量相当轻。@PeterCordes-显著的可变性或多或少是可以的,尤其是当它与主线程所做的事情不相关时。想象一下,它用于低开销方法计时:如果不相关,可变性基本上是可以的。似乎至少某些类型的指令不会受到影响竞争太激烈,至少在某些情况下(例如,在主代码运行整数代码时,浮点指令用于休眠)……但Skylake
pause
之类的东西似乎非常接近理想。在Skylake之前,它不太清晰。您好,Surt。您所说的“旧实时时钟”指的是什么?如果有硬件clo,那就太好了
  start:
  read time (25 cycles)
  tmp = time - last (1 cycle)
  if tmp < sample length goto start
  last += cycles between samples
  sample = time
  goto start