在自旋锁的实现中,有什么好的替代 PAUSE 的方法?

Zac*_*dom 3 c++ concurrency x86-64 energy spinlock

我正在为我的最新项目制作一个基于光纤的作业系统,该系统将依赖于使用自旋锁来实现正确的功能。我本来打算使用 PAUSE 指令,因为这似乎是普通现代自旋锁等待部分的黄金标准。然而,在对实现我自己的光纤进行一些研究时,我发现最近机器上的暂停周期持续时间已增加到不利的程度。

我从这里发现了这一点,其中引用了英特尔优化手册,“上一代微架构中 PAUSE 指令的延迟约为 10 个周期,而在 Skylake 微架构上它已扩展到多达 140 个周期,”和“由于 PAUSE 延迟显着增加,对 PAUSE 延迟敏感的工作负载将遭受一些性能损失。”

因此,我想找到 PAUSE 指令的替代方案以在我自己的自旋锁中使用。我读过,在过去,暂停一直是首选,因为它以某种方式节省了能源使用,我猜测这是由于另一个经常引用的事实,即使用暂停以某种方式向处理器发出信号,表明它处于自旋锁之中。我还猜测,这是在功率范围的另一端,为所需的周期数进行一些虚拟计算。

鉴于此,是否有一种最佳情况的解决方案能够接近 PAUSE 的表观能源效率,同时具有作为重复“丢弃”计算的灵活性和低周期计数?

Pet*_*des 6

我猜测这是由于另一个经常被引用的事实,即使用 PAUSE 以某种方式向处理器发出信号,表明它处于自旋锁之中。

是的,pause让 CPU 在离开只读旋转等待循环时避免内存顺序错误推测,这就是您应该旋转以避免为尝试解锁该位置的线程创建争用的方式。(不要发送垃圾邮件xchg)。也可以看看:

如果您必须等待另一个核心更改内存中的某些内容,那么比核心间延迟更频繁地检查并没有多大帮助,特别是当您最终看到所做的更改时,您会立即停止等待。


处理pause慢(较新的英特尔)或快(AMD 和旧的英特尔)

如果您的代码pause在检查共享值之间使用多个指令,则应该更改代码以减少执行次数。

另请参阅为什么 AMD-CPU 具有如此愚蠢的暂停计时,以及 Brendan 建议检查rdtsc自旋循环以更好地适应pause不同 CPU 上的未知延迟。

基本上尝试让您的工作负载对延迟pause那么敏感。这也可能意味着尽量避免等待来自其他线程的数据,或者在锁可用之前执行其他有用的工作。


替代方案,如果这比旋转唤醒延迟更低,我不知道pause

在足够新的 CPU 上,具有 WAITPKG 扩展(Tremont 或 Alder Lake / Sapphire Rapids),umonitor/umwait可以在用户空间中等待内存位置更改,例如旋转,但 CPU 在看到缓存一致性流量时会自行唤醒关于改变,或者类似的事情。尽管这可能比pause必须进入睡眠状态要慢。

(您可以要求umwait仅进入 C0.1,而不是 C0.2,指定寄存器的位 0,并将 EDX:EAX 作为 TSC 截止时间。Intel 表示 C0.2 睡眠状态可提高其他超线程的性能,所以大概这意味着切换回单核活动模式,对存储缓冲区、ROB 等进行取消分区,并且必须等待重新分区才能唤醒该核心。但 C0.1 状态不会去做。)


即使在最坏的情况下,pause也只有大约 140 个核心时钟周期。 这仍然比现代 x86-64 上的 Linux 系统调用快得多,尤其是在 Spectre/Meltdown 缓解措施下。(数千到数万个时钟周期,而syscall+则需要几百个sysret时钟周期,更不用说调用schedule()或者运行其他东西了。)

因此,如果您的目标是最大限度地减少唤醒延迟,但代价是浪费 CPU 时间来旋转更长的时间,那么nanosleep这不是一个选择。pause不过,如果作为旋转几次后的后备,这可能对其他用例有好处。

或者用于futex在值更改或来自另一个进程的通知时休眠。(它不能保证内核将使用monitor/mwait进入睡眠状态,直到发生更改,它会让其他任务运行。因此,futex如果任何服务员已经进入睡眠状态,您确实需要让解锁线程进行系统调用futex。但是您可以仍然创建一个轻量级互斥体,如果没有争用并且没有线程进入睡眠状态,则可以避免锁定器和解锁器中的任何系统调用。)

但到那时,futex您可能正在重现 glibc 的pthread_mutex功能。


归档时间:

查看次数:

533 次

最近记录:

3 年,1 月 前