Inf*_*ite 37 x86 multithreading spinlock
暂停指令通常用于测试自旋锁的循环,当一些其他线程拥有自旋锁时,以缓解紧密循环.据说这相当于一些NOP指令.有人能告诉我它是如何适用于自旋锁优化的吗?在我看来,即使是NOP指令也浪费了CPU时间.它们会降低CPU使用率吗?
另一个问题是我可以将暂停指令用于其他类似目的.例如,我有一个忙线程,它不断扫描一些地方(例如队列)以检索新节点; 但是,有时队列是空的,线程只是在浪费cpu时间.睡眠线程并通过其他线程唤醒它可能是一个选项,但线程是关键的,所以我不想让它睡觉.可以暂停指令工作以减轻CPU使用率吗?目前它使用100%cpu的物理核心?
谢谢.
bla*_*aze 28
PAUSE通知CPU这是一个自旋锁等待循环,因此可以优化内存和缓存访问.另请参阅x86中的暂停指令,了解有关在离开自旋循环时避免内存顺序误推测的更多详细信息.
PAUSE实际上可能会停止CPU一段时间以节省电量.较旧的CPU将其解码为REP NOP,因此您无需检查其是否受支持.较旧的CPU将尽可能快地执行任何操作(NOP).
另请参见https://software.intel.com/en-us/articles/benefitting-power-and-performance-sleep-loops
更新:我不认为在队列检查中使用PAUSE是个好主意,除非你要使你的队列像spinlock一样(并且没有明显的方法).
即使使用PAUSE,旋转很长时间仍然非常糟糕.
Nit*_*nal 14
处理器在退出循环时会受到严重的性能损失,因为它检测到可能的内存顺序违规.PAUSE指令向处理器提供代码序列为自旋等待循环的提示.在大多数情况下,处理器使用此提示来避免内存顺序违规,从而大大提高了处理器性能.因此,建议在所有自旋等待循环中放置PAUSE指令.PAUSE指令的另一个功能是降低英特尔处理器的功耗.
[来源:英特尔手册]
正如我从您的问题中了解到的,您的案件中的等待时间是很长的。在这种情况下,根本不推荐自旋等待循环。但是,如果您使用的自旋循环不断检查内存中的值(例如字节大小的同步变量),请使用PAUSE. 请参阅Intel 64 and IA-32 Architectures Optimization Reference Manual的第 11.4.2 节“短期同步” 。
您写道,您有一个“不断扫描某些地方(例如队列)以检索新节点的线程”。
在这种情况下(即长时间等待),英特尔建议使用操作系统的同步 API 函数。例如,您可以在队列中出现新节点时创建一个事件,然后使用WaitForSingleObject(Handle, INFINITE). 每当出现新节点时,队列都会触发此事件。
根据英特尔优化参考手册,第 2.3.4 节“Skylake 客户端微架构中的暂停延迟”,
PAUSE 指令通常与在位于同一处理器内核中的两个逻辑处理器上执行的软件线程一起使用,等待锁定被释放。如此短的等待循环往往会持续数十到数百个周期,因此在性能方面最好在占用 CPU 时等待而不是让步给操作系统。
通过上面引用的“几十个和几百个周期”,我理解为 20 到 500 个 CPU 周期。
在 4500 MHz Intel Core i7 7700K 处理器(2017 年 1 月发布,基于 Kaby-Lake-S 微架构)上的 500 个 CPU 周期为 0.0000001 秒,即 1/10000000 秒:CPU 可以在这 500 秒内每秒执行 1000 万次-CPU 周期循环。
Intel 推荐的这个 500 周期限制是理论上的,并且完全取决于特定的用例,即需要由自旋等待循环同步的代码的逻辑。根据基准测试,某些场景(如用于 Delphi 的 FastMM4-AVX 内存管理器)在值为 5000 时效果更好。尽管如此,这些基准并不总是反映真实世界的场景,应该衡量真实的程序用例。
如您所见,这个PAUSE基于自旋等待循环的时间非常短。
另一方面,对像 Sleep() 这样的 API 函数的每次调用都会经历上下文切换的昂贵成本,可能是 10000 多个周期;它还遭受环 3 到环 0 转换的成本,这可能是 1000 多个周期。
如果有更多线程,那么处理器内核(增加到超线程功能,如果存在)可用,并且一个线程将在临界区中间切换到另一个线程,等待来自另一个线程的临界区可能真的需要很长时间,至少 10000+ 个周期,因此PAUSE基于 - 的自旋等待循环将是徒劳的。
除了英特尔优化参考手册的相关章节外,更多信息请参见以下文章:
当等待循环预计持续成千上万个循环以上的,优选的是通过调用OS同步的API函数,诸如一个,以产生对操作系统WaitForSingleObject或SwitchToThreadWindows操作系统。
作为结论:在您的场景中,PAUSE基于 - 的自旋等待循环将不是最佳选择,因为您的等待时间很长,而自旋等待循环用于非常短的循环。
该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 ,无论微架构。
请注意,自旋等待循环也必须正确实现。Intel 建议使用所谓的“测试、测试和设置”技术(请参阅 Intel 64 和 IA-32 架构优化参考手册的第 11.4.3 节“使用自旋锁优化”)来确定同步变量的可用性. 根据这种技术,第一个“测试”是通过正常(非锁定)内存负载完成的,以防止在自旋等待循环期间过度锁定总线;如果变量在第一步(“测试”)的非锁定内存加载时可用,则继续通过总线锁定原子xchg指令完成的第二步(“测试并设置”)。
但请注意,与单步“测试并设置”相比,这种在“测试和设置”之前使用“测试”的两步方法会增加非竞争案例的成本。最初的只读访问可能只获取处于共享状态的缓存行,因此像 test-and-set ( xchg) 或 compare-and-swap ( cmpxchg)这样的原子操作仍然需要“读取所有权” (RFO) 操作获得缓存行的独占所有权。此操作由试图写入处于共享状态的缓存行的处理器发出。