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

Bee*_*ope 6 optimization x86 assembly intel

我记得在优化x86速度时通常要避免使用读 - 修改 - 写指令.也就是说,你应该避免类似的东西add [rsi], 10,这会增加存储在其中的内存位置rsi.建议通常是将其拆分为读取 - 修改指令,然后是商店,如下所示:

mov rax, 10
add rax, [rsp]
mov [rsp], rax
Run Code Online (Sandbox Code Playgroud)

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

mov rax, [esp]
add rax, 10
mov [rsp], rax
Run Code Online (Sandbox Code Playgroud)

对于现代x86来说,这仍然是合理的建议(并且它曾经是吗?)?1

当然,在内存中的值被多次使用的情况下,RMW是不合适的,因为您将产生冗余的加载和存储.我对只使用一次值的情况感兴趣.

基于对Godbolt的探索,所有icc,clang和gcc都喜欢使用单个RMW指令来编译类似于:

void Foo::f() {
  x += 10;
}
Run Code Online (Sandbox Code Playgroud)

成:

Foo::f():
    add     QWORD PTR [rdi], 10
    ret
Run Code Online (Sandbox Code Playgroud)

因此,至少大多数编译器似乎认为RMW很好,当值仅使用一次.

有趣的是,当增量值是全局值而不是成员时,各种编译器同意,例如:

int global;

void g() {
  global += 10;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,gcc并且clang仍然是单个RMW指令,而icc更倾向于 一个REG-REG具有明确载入和存储地址:

g():
        mov       eax, DWORD PTR global[rip]                    #5.3
        add       eax, 10                                       #5.3
        mov       DWORD PTR global[rip], eax                    #5.3
        ret     
Run Code Online (Sandbox Code Playgroud)

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


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

Joh*_*ica 6

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

没有.

在现代x86/x64上,输入指令被转换为uops.
任何RMW指令都会被分解为多个uops; 实际上进入相同的uops,单独的指令将被分解为.

通过使用"复杂"RMW指令而不是单独的"简单"读取,修改和写入指令,您将获得以下内容.

  1. 解码的指令较少.
  2. 更好地利用指令缓存
  3. 更好地利用可寻址寄存器

您可以在Agner Fog的说明表中清楚地看到这一点.

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

MOV [mem],reg反之亦然,每个延迟为2个周期,ADD reg,const总延迟为1,延迟为1.

我检查了英特尔Skylake的时间,但AMD K10是一样的.

您需要考虑到编译器必须满足许多不同的处理器,而一些编译器甚至为不同的处理器系列使用相同的核心逻辑.这可能导致非常不理想的策略.

RIP相对寻址
在X64上,RIP相对寻址需要一个额外的周期来解决旧处理器上的RIP问题.
Skylake没有这种延迟,我相信其他人也会消除延迟.
我相信你知道x86不支持EIP相对寻址; 在X86上,你必须以圆润的方式做到这一点.