我记得在优化x86速度时通常要避免使用读 - 修改 - 写指令。也就是说,您应该避免使用add [rsi], 10
之类的内容,这会添加到rsi
中存储的内存位置。建议通常是将其拆分为读取 - 修改指令,然后是商店,如下所示:
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 prefer都使用单个RMW指令来编译类似的东西:
void Foo::f() {
x += 10;
}
成:
Foo::f():
add QWORD PTR [rdi], 10
ret
因此,至少大多数编译器似乎认为RMW没问题,只需使用一次。
有趣的是,当增量值是全局值而不是成员时,各种编译器不同意,例如:
int global;
void g() {
global += 10;
}
在这种情况下,gcc
和clang
仍然是单个RMW指令,而icc
prefers一个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
也许这与RIP
相对寻址和微观融合限制有关?但是,icc13仍然与-m32
做同样的事情,所以可能更多的是与需要32位位移的寻址模式有关。
1 我使用故意模糊的术语现代x86 基本上意味着最后几代英特尔和AMD笔记本电脑/台式机/服务器芯片。
答案 0 :(得分:6)
对于现代x86,RMW指令是否被视为有害?
没有
在现代x86 / x64上,输入指令被翻译成uops 任何RMW指令都会被分解为多个uops;实际上进入相同的uops,单独的指令将被分解为。
使用'复合物' RMW指令而不是单独的简单'阅读,修改和编写获得以下内容的说明。
你可以在Agner Fog's instruction tables中清楚地看到这一点。
ADD [mem],const
的延迟为5个周期。
MOV [mem],reg
,反之亦然,每个延迟为2个周期,ADD reg,const
的延迟为1,总共为5个。
我检查了英特尔Skylake的时间,但AMD K10是一样的。
您需要考虑到编译器必须满足许多不同的处理器,并且一些编译器甚至为不同的处理器系列使用相同的核心逻辑。这可能导致非常不理想的策略。
RIP相对寻址
在X64上,RIP相对寻址需要一个额外的周期来解决旧处理器上的RIP问题
Skylake没有这种延迟,我相信其他人也会消除延迟
我确定你知道x86不支持EIP相对寻址;在X86上,你必须以圆润的方式做到这一点。