RMW 指令是否被认为对现代 x86 有害?
Are RMW instructions considered harmful on modern x86?
我记得在优化 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 很好,当值只使用一次时。
有趣的是,各种编译器 not 同意增量值是全局的,而不是成员的,例如:
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 的最后几代 laptop/desktop/server筹码。
Are RMW instructions considered harmful on modern x86?
没有
在现代 x86/x64 中,输入指令被翻译成 uops。
任何 RMW 指令都将分解为多个微指令;事实上,单独的指令将被分解成相同的微指令。
通过使用 'complex' RMW 指令而不是单独的 'simple' 读取、修改和写入指令,您可以获得以下结果。
- 需要解码的指令更少。
- 更好地利用指令缓存
- 更好地利用可寻址寄存器
您可以在 Agner Fog's instruction tables 中清楚地看到这一点。
ADD [mem],const
有 5 个周期的延迟。
MOV [mem],reg
反之亦然,每个延迟为 2 个周期,ADD reg,const
的延迟为 1,总共为 5。
我检查了 Intel Skylake 的时间,但 AMD K10 是一样的。
您需要考虑到编译器必须迎合许多不同的处理器,有些编译器甚至为不同的处理器系列使用相同的核心逻辑。这可能会导致非常次优的策略。
RIP 相对寻址
在 X64 RIP 上,相对寻址需要一个额外的周期来解析旧处理器上的 RIP。
Skylake 没有这种延迟,我相信其他人也会消除这种延迟。
我相信您知道 x86 不支持 EIP 相对寻址;在 X86 上,您必须以一种迂回的方式执行此操作。
我记得在优化 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 很好,当值只使用一次时。
有趣的是,各种编译器 not 同意增量值是全局的,而不是成员的,例如:
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 的最后几代 laptop/desktop/server筹码。
Are RMW instructions considered harmful on modern x86?
没有
在现代 x86/x64 中,输入指令被翻译成 uops。
任何 RMW 指令都将分解为多个微指令;事实上,单独的指令将被分解成相同的微指令。
通过使用 'complex' RMW 指令而不是单独的 'simple' 读取、修改和写入指令,您可以获得以下结果。
- 需要解码的指令更少。
- 更好地利用指令缓存
- 更好地利用可寻址寄存器
您可以在 Agner Fog's instruction tables 中清楚地看到这一点。
ADD [mem],const
有 5 个周期的延迟。
MOV [mem],reg
反之亦然,每个延迟为 2 个周期,ADD reg,const
的延迟为 1,总共为 5。
我检查了 Intel Skylake 的时间,但 AMD K10 是一样的。
您需要考虑到编译器必须迎合许多不同的处理器,有些编译器甚至为不同的处理器系列使用相同的核心逻辑。这可能会导致非常次优的策略。
RIP 相对寻址
在 X64 RIP 上,相对寻址需要一个额外的周期来解析旧处理器上的 RIP。
Skylake 没有这种延迟,我相信其他人也会消除这种延迟。
我相信您知道 x86 不支持 EIP 相对寻址;在 X86 上,您必须以一种迂回的方式执行此操作。