如何启动两个CPU内核以同时运行指令?

时间:2019-07-08 09:17:14

标签: linux linux-kernel x86-64 thread-synchronization microbenchmark

例如,在X86中,两个CPU内核正在运行不同的软件线程。
同时,这两个线程需要同时在其CPU内核上运行。
有没有办法同步这2个CPU内核/线程,或类似的方法使它们在同一时间(在指令级别)开始同时运行(几乎)?

2 个答案:

答案 0 :(得分:5)

使用共享变量在两个线程之间传递基于rdtsc的截止日期。例如,设置一个截止日期,例如说当前的rdtsc值加上10,000。

然后让两个线程都在rdtsc上旋转,直到当前rdtsc值和阈值之间的 gap 小于阈值T(T = 100应该可以)。最后,使用最终间隔值(即,截止日期rdtsc值减去最后读取的rdtsc值)跳入相关的相加指令序列,以使相加指令的数量等于间隔

最后一步弥补了以下事实:每个芯片相对于其rdtsc自旋环路通常不会“同相”。例如,假设rdtsc读数有30个周期的背对背吞吐量,则一个芯片可能会获得890、920、950等的读数,而另一个芯片可能会读取880、910、940的读数,因此会有10如果仅使用rdtsc,则为20个循环错误。使用加法滑动补偿,如果最后期限为1,000,且阈值为100,则第一个线程将在rdtsc == 920处触发并执行80次添加,而第二个线程将在rdtsc == 910处触发并执行90次添加。原则上,两个内核都将大致同步。

一些注意事项:

  • 以上假设CPU频率等于标称rdtsc的频率-如果不是这种情况,则在计算跳转到添加幻灯片的位置时,您必须基于标称值至真实的频率比来应用补偿因子。
  • 不要期望您的CPU长时间保持同步:诸如中断,可变延迟操作(如高速缓存未命中)之类的操作,或许多其他事情都可能使它们失去同步。
  • 您希望所有的有效负载代码以及附加幻灯片在每个内核的icache中都很热,否则它们很可能立即不同步。您可以通过在同步之前对此代码进行一次或多次虚拟运行来预热icache。
  • 您希望T足够大,以使间隙始终为正,因此比背对背rdtsc的延迟要大一些,但不要太大,以免增加发生类似事件的机会在添加幻灯片期间中断。
  • 您可以通过在同步之后在“有效负载”代码的各个点处发出rdtscrdtscp来检查“同步”的有效性,并查看记录值在线程之间的接近程度。

一个完全不同的选择是使用Intel TSX:事务扩展。为两个要进行协调的线程进行组织,以同时读取事务区域内的共享线然后旋转,并让第三个线程写入共享线。这将导致两个等待线程中止。根据内核间的拓扑,两个等待线程可能会收到无效消息,因此随后的TSX几乎同时终止。从中止处理程序中调用要同步运行的代码。

答案 1 :(得分:3)

根据您对“(几乎同时)”的定义,从微体系结构上来讲,这是一个非常棘手的问题。

如果您担心计时到周期,即使“运行”的定义也不够明确。您的意思是从前端到无序后端的问题吗?执行? (派遣到执行单位?还是无需重播就可以成功完成执行?)还是退休?

我倾向于使用Execute 1 ,因为那是像rdtsc这样的指令对时间戳计数器进行采样的时候。这是您可以实际记录其时间,然后稍后进行比较的一个。

脚注1:在正确的道路上,不要误以为是,除非您对没有退休的处决还满意。

但是,当您关心的指令执行时,如果两个内核具有不同的ROB / RS状态,则它们将不会以锁步方式继续。 (有序的x86-64 CPU很少,例如一些Silverver之前的Atom和Xeon Phi早期的产品:Knight's Corner。今天的x86-64 CPU都是乱序的,并且在低功耗Silvermont之外。 -家庭积极,因此使用大型ROB +调度程序。)


x86 asm技巧:

我还没有使用过它,但是x86 asm monitor / mwait 可以让两个CPU监视并等待对给定内存位置的写操作。我不知道唤醒如何同步。我猜想睡眠越深,延迟的变化就越小。

总是可以在写之前将中断从唤醒中唤醒。除非您禁用中断,否则您将不可能100%地做到这一点。希望您只需要在合理的成功机会下实现它,并能够在事后告诉您是否实现了它。

(在最近的低功耗Intel CPU(Tremont)上,这些可用的用户空间可用版本是:umonitor / umwait。但是在内核中,您可能只使用{{ 1}} / monitor

如果mwait / umonitor可用,则意味着您具有 WAITPKG CPU功能,该功能还包括tpause :与umwait类似,但请稍候直到给定的TSC时间戳。

在现代x86 CPU上,TSC 通过硬件在所有内核之间同步,因此对多个内核使用相同的唤醒时间就变得很简单。

否则,您可以在pause截止日期之前等待,并且在Skylake上最糟糕的情况下可能会花费约25个周期。

rdtsc在Skylake(https://agner.org/optimize/)上每25个周期有一个吞吐量,因此您希望每个线程平均晚12.5个周期,离开旋转等待循环+ -12.5。我假设两个线程的branch-mispredict成本是相同的。这些是核心时钟周期,而不是rdtsc计数的参考周期。 RDTSC通常接近最大非涡轮时钟。有关来自C的RDTSC的更多信息,请参见How to get the CPU cycle count in x86_64 from C++?

请参见 How much delay is generated by this assembly code in linux,以获取在rdtsc上旋转的asm函数,以等待截止日期。您可以很容易地用C编写此代码。


在首次启动后保持同步:

在多核Xeon上,每个核可以独立更改频率,您需要将CPU频率固定为某个值,最大非涡轮增压可能是个不错的选择。否则,如果内核使用不同的时钟速度,它们显然会立即不同步。

在台式机上,您可能还是想这样做,以防万一暂停时钟来更改CPU频率会丢掉这些东西。


分支错误预测,缓存未命中甚至ROB / RS的初始状态的任何差异都可能导致严重的不同步。

更重要的是,与在已经运行的任务中再运行一条指令相比,中断是巨大的并且花费很长的时间。甚至可能导致调度程序将上下文切换到另一个线程。或为该任务进行CPU迁移,显然会花费很多周期。