RMW指令在现代x86上被认为是有害的吗?



我记得在优化x86以提高速度时通常要避免使用读-修改-写指令。也就是说,您应该避免add [rsi], 10这样的东西,它会增加存储在rsi中的内存位置。通常的建议是将它分成一个read-modify指令,后面跟着一个store指令,比如:

mov rax, 10
add rax, [rsp]
mov [rsp], rax

或者,您可以使用显式加载和存储以及reg-reg添加操作:

mov rax, [esp]
add rax, 10
mov [rsp], rax

对于现代x86来说,这仍然是合理的建议吗?1 <一口>

当然,如果内存中的值不止一次被使用,那么RMW是不合适的,因为这会导致冗余的负载和存储。我感兴趣的是一个值只被使用一次的情况。

基于对Godbolt的探索,所有icc, clang和gcc都倾向于使用单个RMW指令来编译如下内容:

void Foo::f() {
  x += 10;
}

为:

Foo::f():
    add     QWORD PTR [rdi], 10
    ret

所以至少大多数编译器似乎认为RMW是好的,当这个值只被使用一次。

有趣的是,当增量值是全局变量而不是成员变量时,不同的编译器都不同意,例如:
int global;
void g() {
  global += 10;
}

在这种情况下,gccclang仍然是一个RMW指令,而icc更倾向于一个带有显式加载和存储的regg -reg添加:

g():
        mov       eax, DWORD PTR global[rip]                    #5.3
        add       eax, 10                                       #5.3
        mov       DWORD PTR global[rip], eax                    #5.3
        ret     

也许这与RIP相对寻址和微融合限制有关?然而,icc13仍然与-m32做同样的事情,所以也许它更多地与需要32位位移的寻址模式有关。


1我故意使用模糊的术语现代x86基本上是指英特尔和AMD的最后几代笔记本电脑/台式机/服务器芯片。

RMW指令在现代x86上被认为是有害的吗?

在现代x86/x64上,输入指令被翻译成up。
任何RMW指令都将被分解为许多部分;实际上是和单独的指令被分解成的一样。

通过使用'复杂' RMW指令而不是单独的'简单'读取,修改和写入指令,您将获得以下结果。

  1. 更少的指令解码。
  2. 更好地利用指令缓存
  3. 更好地利用可寻址寄存器
你可以在Agner Fog的指令表中清楚地看到这一点。

ADD [mem],const的延迟为5个周期。

MOV [mem],reg和反之,每个延迟为2个周期,ADD reg,const的延迟为1,总共为5个周期。

我检查了英特尔Skylake的时间,但AMD K10是相同的。

您需要考虑到编译器必须迎合许多不同的处理器,有些编译器甚至为不同的处理器系列使用相同的核心逻辑。这可能导致相当次优的策略。

RIP相对寻址
在X64上,RIP相对寻址需要额外的周期来解析旧处理器上的RIP。
Skylake没有这种延迟,我相信其他人也会消除延迟。
我确信您知道x86不支持EIP相对寻址;在X86上,您必须以迂回的方式完成此操作。

最新更新