在64位C++代码中如何使用暂停汇编指令?

13

由于VC++ 2010在64位代码中不支持内联汇编,那么我该如何将 pause x86-64指令添加到我的代码中?似乎没有像其他常见的汇编指令(例如__rdtsc()__cpuid()等)那样的内置函数。

为什么要使用这个指令呢?我想将其用于繁忙等待的情况下,这样(超线程的)CPU就可以为运行在该CPU上的其他线程提供服务(请参阅:intel.com上的性能见解)。pause指令对于这种用例以及自旋锁实现非常有帮助,我无法理解微软为什么没有将其作为内置函数。

谢谢

2个回答

15

哇,这个问题很难追踪,但如果有其他人需要x86-64 pause 指令,可以参考以下内容:

windows.h 中的 YieldProcessor() 宏会展开为未记录的 _mm_pause 内置函数,该函数最终会在32位和64位代码中展开为 pause 指令。

顺便提一句,这完全没有记录,部分记录(对于 VC++ 2010 文档不正确)可在 MSDN 的 YieldProcessor() 中找到。

以下是一段使用 YieldProcessor() 宏编译的示例代码:

    19:     ::YieldProcessor();
000000013FDB18A0 F3 90                pause  
    20:     ::YieldProcessor();
000000013FDB18A2 F3 90                pause  
    21:     ::YieldProcessor();
000000013FDB18A4 F3 90                pause  
    22:     ::YieldProcessor();
000000013FDB18A6 F3 90                pause  
    23:     ::YieldProcessor();
000000013FDB18A8 F3 90                pause  

顺便提一下,在 Nehalem 架构上,每个暂停指令平均会导致约 9 个时钟周期的延迟(即在 3.3 GHz 的 CPU 上为 3 ns)。


2
不幸的是,微软“忘记”记录了相当多的函数,这非常令人恼火(特别是当__yield被记录时)。有时候,最好只是查找intrin.h中与您所需名称类似的名称(这就是我发现_mm_pause的方式),尽管您的宏似乎更适合可移植性,+1。 - Necrolis
yield 可能已经被记录,但它只在 IA64 上工作(也就是说,人们实际使用的任何架构都不支持 :))。当我发现它时,我其实很高兴,但后来发现它在 x86/x86-64 构建中被 #ifdefed 掉了。 - Michael Goldshteyn

11
_mm_pause()内置函数已经被英特尔充分文档化,并且在跨操作系统的所有主要x86编译器中得到支持。我不知道微软过去的文档是否有所欠缺,或者你只是错过了7年前的内容。

#include <immintrin.h>并使用它。(或对于古老的编译器,使用#include <emmintrin.h>进行SSE2)。

#include <immintrin.h>

void test() {
    _mm_pause();
    _mm_pause();
}

在gcc/clang/ICC/MSVC上编译后,会生成以下汇编代码(在Godbolt编译器探索器上):

test():                               # @test()
    pause
    pause
    ret

在没有SSE2的CPU上,它被解码为rep nop,只是一个nop跨平台实现x86暂停指令 Gcc甚至知道这一点,在使用-mno-sse编译时仍然接受_mm_pause()。(通常,与MSVC不同,gcc和clang会拒绝未启用的指令的内部函数。)有趣的是,gcc甚至在其汇编输出中发出rep nop,而其他三个则发出pause。它们当然组装成相同的机器代码。

在Sandybridge系列到Skylake之前,暂停会使超线程的前端空闲约5个周期。 在Skylake上,Intel将其增加到大约100个周期,以在自旋等待循环中节省更多电力并增加总吞吐量,可能以延迟为代价,特别是在超线程核心上。

在所有CPU上,当退出自旋循环时,它还可以避免内存序错误推测。 因此,它确实在最终再次重要时减少了延迟。

另请参见x86中“PAUSE”指令的目的是什么?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接