48

pause指令常用于测试自旋锁的循环中,当其他线程拥有自旋锁时,以缓解紧密循环。据说相当于一些NOP指令。有人能告诉我它对自旋锁优化到底是如何工作的吗?在我看来,即使是 NOP 指令也是在浪费 CPU 时间。它们会降低 CPU 使用率吗?

另一个问题是我可以将暂停指令用于其他类似目的吗?例如,我有一个繁忙的线程,它不断扫描一些地方(例如队列)以检索新节点;但是,有时队列是空的,线程只是在浪费 CPU 时间。休眠线程并由其他线程唤醒它可能是一种选择,但是线程很关键,所以我不想让它休眠。

可以暂停指令工作以减轻 CPU 使用率吗?目前它使用物理核心的 100% cpu?

4

4 回答 4

37

PAUSE通知 CPU 这是一个自旋锁等待循环,因此可以优化内存和缓存访问。有关在离开自旋循环时避免内存顺序错误推测的更多详细信息,另请参阅x86 中的暂停指令。

PAUSE 实际上可能会停止 CPU 一段时间以节省电力。较旧的 CPU 将其解码为 REP NOP,因此您不必检查它是否受支持。较旧的 CPU 将尽可能快地什么都不做 (NOP)。

另请参阅https://software.intel.com/en-us/articles/benefing-power-and-performance-sleep-loops


更新:我认为在队列检查中使用 PAUSE 不是一个好主意,除非您要使队列类似于自旋锁(并且没有明显的方法)。

旋转很长时间仍然很糟糕,即使有 PAUSE。

于 2011-01-18T15:14:43.063 回答
18

处理器在退出循环时会遭受严重的性能损失,因为它检测到可能的内存顺序违规。PAUSE 指令向处理器提示代码序列是一个自旋等待循环。处理器在大多数情况下使用此提示来避免内存顺序冲突,从而大大提高处理器性能。因此,建议在所有自旋等待循环中放置一条 PAUSE 指令。PAUSE 指令的一个附加功能是降低 Intel 处理器的功耗。

[来源:英特尔手册]

于 2011-10-09T11:29:52.053 回答
5

基于暂停的自旋等待循环

正如我从您的问题中了解到的那样,您的案件的等待时间很长。在这种情况下,根本不推荐使用自旋等待循环。但是,如果您使用的是不断检查内存中的值的自旋循环(例如,字节大小的同步变量),请使用PAUSE. 请参阅Intel 64 和 IA-32 架构优化参考手册的第 11.4.2 节“短周期同步” 。

您写道,您有一个“不断扫描某些地方(例如队列)以检索新节点的线程”。

在这种情况下(即长时间等待),英特尔建议使用您操作系统的同步 API 函数。例如,您可以在队列中出现新节点时创建一个事件,然后使用WaitForSingleObject(Handle, INFINITE). 每当出现新节点时,队列都会触发此事件。

根据英特尔优化参考手册,第 2.3.4 节“Skylake 客户端微架构中的暂停延迟”,

PAUSE 指令通常与在位于同一处理器内核的两个逻辑处理器上执行的软件线程一起使用,等待释放锁。如此短的等待循环往往会持续数十到数百个周期,因此在性能方面,在占用 CPU 的同时等待比屈服于操作系统要好。

通过上述引用的“数十和数百个周期”,我理解为 20 到 500 个 CPU 周期。

在 4500 MHz 英特尔酷睿 i7 7700K 处理器(2017 年 1 月发布,基于 Kaby-Lake-S 微架构)上 500 个 CPU 周期是 0.0000001 秒,即 1/10000000 秒:CPU 每秒可以进行 1000 万次这 500 -CPU 周期循环。

英特尔推荐的这个 500 周期限制是理论上的,并且完全取决于特定的用例,即需要通过自旋等待循环同步的代码逻辑。根据基准测试,某些场景(例如Delphi 的 FastMM4-AVX 内存管理器)在值为 5000 时效果更好。尽管如此,这些基准并不总是反映真实世界的场景,应该测量真实的程序用例。

如您所见,这个PAUSE基于自旋等待的循环的时间非常

另一方面,对像 Sleep() 这样的 API 函数的每次调用都会经历昂贵的上下文切换成本,可能是 10000 多个周期;它还承受从环 3 到环 0 转换的成本,可能是 1000 多个周期。

如果有更多线程,则处理器内核(如果存在,则乘以超线程功能)可用,并且一个线程将在临界区中间切换到另一个线程,等待另一个线程的临界区可能真的需要很长时间,至少 10000+ 个周期,因此PAUSE基于 - 的自旋等待循环将是徒劳的。

除了英特尔优化参考手册的相关章节,更多信息请参见以下文章:

当等待循环预计持续数千个周期或更多时,最好通过调用操作系统同步 API 函数之一来让步给操作系统,例如Windows 操作系统上的WaitForSingleObjectSwitchToThread

作为结论:在您的场景中,PAUSE基于 - 的 spin-wait 循环不会是最佳选择,因为您的等待时间很长,而 spin-wait 循环适用于非常短的循环。

PAUSE在基于 Skylake 微架构的处理器或更高版本的处理器上,该指令需要大约 140 个 CPU 周期。例如,在 2015 年 8 月发布的 Intel Core i7-6700K CPU (4GHz) 上仅为 35.10ns,或在 2020 年 9 月发布的用于移动设备的 Intel Core i7-1165G7 CPU 上为 49.47ns。在早期处理器上(Skylake 之前) ,和那些基于 Haswell 微架构的一样,它有大约 9 个周期。在 2013 年 6 月发布的 Intel Core i5-4430 (3GHz) 上为 2.81ns。因此,对于长循环,最好使用 OS 同步 API 函数将控制权交给其他线程,而不是用PAUSE循环占用 CPU,而不管微架构。

测试,测试和设置

请注意,旋转等待循环也必须正确实施。英特尔推荐使用所谓的“测试、测试和设置”技术(参见英特尔 64 和 IA-32 架构优化参考手册的第 11.4.3 节“使用自旋锁进行优化”)来确定同步变量的可用性. 根据这种技术,第一个“测试”是通过正常(非锁定)内存负载完成的,以防止在自旋等待循环期间过度的总线锁定;xchg如果变量在第一步(“测试”)的非锁定内存加载时可用,则继续通过总线锁定原子指令完成的第二步(“测试和设置”) 。

但请注意,与仅单步“测试和设置”相比,在“测试和设置”之前使用“测试”的这种两步方法可能会增加非竞争案例的成本。最初的只读访问可能只会获得处于共享状态的缓存行,因此像 test-and-set ( xchg) 或 compare-and-swap ( cmpxchg) 这样的原子操作仍然需要“Read For Ownership” (RFO) 操作获得缓存行的独占所有权。此操作由试图写入处于共享状态的高速缓存行的处理器发出。

于 2017-07-05T04:22:31.753 回答
1

PAUSE 指令似乎也用于超线程处理器中,以减轻对其他超线程的性能影响,大概是通过放弃更多的 CPU 时间给它们。

以下英特尔文章概述了这一点,并且毫不奇怪地建议避免在此类处理器上出现繁忙的等待循环: https ://software.intel.com/en-us/articles/long-duration-spin-wait-loops-on-hyper-threading -技术支持的英特尔处理器

于 2014-10-15T18:50:04.637 回答