【问题标题】:How to start two CPU cores to run instructions at the same time?如何启动两个 CPU 内核同时运行指令?
【发布时间】:2019-11-17 18:47:20
【问题描述】:

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

【问题讨论】:

  • 即使有可能,它们也会很快失去同步。
  • @tkausl:是的,下一个缓存未命中、中断或分支错误预测都会这样做。但是,如果您设法避免这种情况,并且 ROB / RS 的状态在两个内核上是相似的,那么它们可以在很长一段时间内以接近锁步的方式运行是合理的。 (当然这非常困难,尤其是避免中断,或者在没有缓存未命中或分支未命中的情况下做任何有用的事情。)
  • @PeterCordes it's plausible that they could run in close to lock-step for a good while. 取决于“good while”的定义。几千条指令?可能是。两秒?不太可能。他们需要被安排在同一时间进出,这不太可能发生。 (OP 没有提到任何关于实时系统的内容,所以我只是假设他们谈论的是常规的非实时系统)
  • @tkausl:如果没有中断,调度程序将无法运行。 (禁用,或者只是在足够短的时间间隔内没有任何中断,或者在您已与中断隔离的内核上,除了定时器中断。)我的“好时机”是数百或数千个周期是合理的,而不是数十个数百万(对于 10 毫秒的时间片)!
  • 使用变量来同步两个线程可能会导致一个人看到更新的值比第二个稍早(除非环形总线可以同时处理多个请求或线程在同一个核心中)。使用中断可能更精确(IPI、通常的 IRQ 或 TXT 唤醒),因为信号路径旨在到达多个目标(但如果它使用环形总线到达目标内核,不同的内核可能会有不同的延迟) .如果 TSC 同步,umwait 可以提供最佳结果。

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


【解决方案1】:

根据您对“(几乎)同时”的定义,这在微架构上是一个非常困难的问题。

如果您关心周期的计时,即使“运行”的定义也不够具体。你的意思是从前端到无序后端的问题?执行? (分派到执行单元?还是无需重播就成功完成执行?)还是退役?

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

脚注 1:在正确的道路上,而不是处于错误推测的阴影下,除非您也可以接受未达到退休的处决。

但是如果两个内核在执行您关心的指令时具有不同的 ROB / RS 状态,它们将不会继续锁步。 (很少有有序的 x86-64 CPU,比如一些前 Silvermont Atoms,以及早期的 Xeon Phi: Knight's Corner。今天的 x86-64 CPU 都是无序的,并且在低功耗 Silvermont 之外-family 是积极的,所以对于大型 ROB + 调度程序。)


x86 asm 技巧:

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

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

(在最近的低功耗 Intel CPU (Tremont) 上,有一个用户空间可用版本可用:umonitor / umwait。但在内核中你可能只使用 monitor/@987654331 @)

如果umonitor/umwait 可用,则意味着您具有WAITPKG CPU 功能,其中还包括tpause:类似于pause,但要等到给定的TSC 时间戳。 p>

在现代 x86 CPU 上,TSC 通过硬件在所有内核之间同步,因此对多个内核使用相同的唤醒时间使这变得微不足道。

否则,您可能会在 rdtsc 截止日期前进行旋转等待,并且在 Skylake 上最坏的情况可能会在大约 25 个周期内完成。

rdtsc 在 Skylake (https://agner.org/optimize/) 上有一个每 25 个周期的吞吐量,因此您希望每个线程平均延迟 12.5 个周期离开自旋等待循环,+-12.5。我假设两个线程的分支错误预测成本是相同的。这些是核心时钟周期,而不是 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 频率固定到某个值,可能 max non-turbo 是一个不错的选择。否则,如果内核具有不同的时钟速度,它们显然会立即不同步。

在台式机上,您可能还是想这样做,以防暂停时钟以更改 CPU 频率。


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

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

【讨论】:

    【解决方案2】:

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

    然后让两个线程在rdtsc 上旋转,等待当前rdtsc 值与阈值之间的间隙 小于阈值T(T = 100 应该没问题) .最后,使用最终的gap值(即deadlinerdtsc值减去最后一次读取rdtsc值)跳转到一个依赖add指令序列,使得add指令的数量等于gap。

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

    一些注意事项:

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

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

    【讨论】:

      猜你喜欢
      • 2015-03-18
      • 1970-01-01
      • 2021-07-03
      • 1970-01-01
      • 2019-12-10
      • 1970-01-01
      • 2018-08-09
      • 2022-01-08
      • 2013-08-09
      相关资源
      最近更新 更多