使用 64 位 MASM 优化 C 函数调用
Optimizing a C function call using 64-bit MASM
目前正在使用此 64 位 MASM 代码调用 C 运行时函数,例如 memcmp(). I recall this convention was from a GoAsm article 优化。
memcmp PROTO;:QWORD,:QWORD,:QWORD
PUSH RSP
PUSH QWORD PTR [RSP]
AND SPL,0F0h
MOV R8,R11
MOV RDX,R10
MOV RCX,RAX
SUB RSP,32
CALL memcmp
LEA RSP,[RSP+40]
POP RSP
下面是有效的优化版本吗?
memcmp PROTO;:QWORD,:QWORD,:QWORD
PUSH RSP
PUSH QWORD PTR [RSP]
AND RSP,-16 ; new
MOV R8,R11
MOV RDX,R10
MOV RCX,RAX
LEA RSP,[RSP-32] ; new
CALL memcmp
LEA RSP,[RSP+40]
POP RSP
替换的理由
AND SPL,0F0h
和
AND RSP,-16
是它避免调用部分寄存器更新。
正在替换
SUB RSP,32
和
LEA RSP,[RSP-32]
后续指令不依赖于减法更新的标志
那么不更新标志也会更有效率。
Why does GCC emit "lea" instead of "sub" for subtraction?
在这种情况下,是否还有其他优化技巧?
AND
是的,原始代码很愚蠢,没有保存任何代码大小(SPL 也采用 REX 前缀,就像 64 位 ope运行d-size)。
LEA
- 毫无意义且浪费代码大小:x86 CPU 已经 ; that's necessary to efficiently run normal x86 code which is full of instructions like add
, sub
, and
, etc. Compilers would use lea
much more heavily if that wasn't the case. The answer on that linked Q&A is wrong and should be downvoted / deleted. The only danger is on a few less-common CPUs (Pentium 4 and Silvermont for different reasons) from instructions like inc
that only write some flags. ()。即使是 Silvermont 系列上 inc
的成本也很小,只是一个额外的 uop 但在解码期间 不是 ,因此它不会停止。
add
在任何 CPU 上都不比 lea
慢,无论是本身还是对后续指令的影响。 (除了有序的 Atom pre-Silvermont,其中 lea
运行 在管道中比 add
更早(在实际的 AGU 上),因此它可能更好或更差,具体取决于数据的位置来自/去往)。您只会在某些情况下使用 lea
,例如 adc
循环,您实际上需要保持 CF 不变,以便下一次迭代可以读取它。即不要弄乱真正的依赖关系(RAW),与避免错误的(WAW)输出依赖关系无关。 (请参阅 - note that cases where adc
/ inc
/ adc
creates a partial-flag stall 是 add
会导致正确性问题的情况,因此我没有将其视为 add
会使后续指令更快的情况。)
您可能不需要保存旧的 RSP; ABI 在调用之前需要 16 字节堆栈对齐,这包括你的调用者(除非你从不遵循 ABI 的代码中调用,所以你不知道相对于 16 字节边界的 RSP 对齐).
通常你会像编译器那样做 sub rsp, 40
,重新对齐 RSP 并为影子 space 保留 space。 (而且你会在函数的 top/bottom 处执行此操作,而不是在每次调用时,以及 saving/restoring 调用保留寄存器)。
(实际上 memcmp
不太可能关心堆栈对齐,除非它需要 save/restore 一些更多的 XMM regs。Windows x64 调用约定不明智 only has 6 call-clobbered x/ymm registers ,这可能会稍微紧一点,这取决于他们在手写的 (?) memcmp
.)
中展开了多少循环
即使您确实需要处理未知的传入 RSP 对齐,将 RSP 保存到两个不同的位置以供 pop rsp
仍然不是一种非常有效的方法。通常,您只需使用 RBP 来制作一个传统的帧指针,以使用 mov rsp, rbp
/ pop rbp
进行清理,无论对 RSP 进行未知调整如何工作。例如即使在使用 alloca
的函数中(或在 asm 中,执行未知数量的推送或可变大小的 sub rsp
,这实际上与 and rsp, -16
相同)。
目前正在使用此 64 位 MASM 代码调用 C 运行时函数,例如 memcmp(). I recall this convention was from a GoAsm article 优化。
memcmp PROTO;:QWORD,:QWORD,:QWORD
PUSH RSP
PUSH QWORD PTR [RSP]
AND SPL,0F0h
MOV R8,R11
MOV RDX,R10
MOV RCX,RAX
SUB RSP,32
CALL memcmp
LEA RSP,[RSP+40]
POP RSP
下面是有效的优化版本吗?
memcmp PROTO;:QWORD,:QWORD,:QWORD
PUSH RSP
PUSH QWORD PTR [RSP]
AND RSP,-16 ; new
MOV R8,R11
MOV RDX,R10
MOV RCX,RAX
LEA RSP,[RSP-32] ; new
CALL memcmp
LEA RSP,[RSP+40]
POP RSP
替换的理由
AND SPL,0F0h
和
AND RSP,-16
是它避免调用部分寄存器更新。
正在替换
SUB RSP,32
和
LEA RSP,[RSP-32]
后续指令不依赖于减法更新的标志
那么不更新标志也会更有效率。
Why does GCC emit "lea" instead of "sub" for subtraction?
在这种情况下,是否还有其他优化技巧?
AND
是的,原始代码很愚蠢,没有保存任何代码大小(SPL 也采用 REX 前缀,就像 64 位 ope运行d-size)。
LEA
- 毫无意义且浪费代码大小:x86 CPU 已经 add
, sub
, and
, etc. Compilers would use lea
much more heavily if that wasn't the case. The answer on that linked Q&A is wrong and should be downvoted / deleted. The only danger is on a few less-common CPUs (Pentium 4 and Silvermont for different reasons) from instructions like inc
that only write some flags. (inc
的成本也很小,只是一个额外的 uop 但在解码期间 不是 ,因此它不会停止。
add
在任何 CPU 上都不比 lea
慢,无论是本身还是对后续指令的影响。 (除了有序的 Atom pre-Silvermont,其中 lea
运行 在管道中比 add
更早(在实际的 AGU 上),因此它可能更好或更差,具体取决于数据的位置来自/去往)。您只会在某些情况下使用 lea
,例如 adc
循环,您实际上需要保持 CF 不变,以便下一次迭代可以读取它。即不要弄乱真正的依赖关系(RAW),与避免错误的(WAW)输出依赖关系无关。 (请参阅 adc
/ inc
/ adc
creates a partial-flag stall 是 add
会导致正确性问题的情况,因此我没有将其视为 add
会使后续指令更快的情况。)
您可能不需要保存旧的 RSP; ABI 在调用之前需要 16 字节堆栈对齐,这包括你的调用者(除非你从不遵循 ABI 的代码中调用,所以你不知道相对于 16 字节边界的 RSP 对齐).
通常你会像编译器那样做 sub rsp, 40
,重新对齐 RSP 并为影子 space 保留 space。 (而且你会在函数的 top/bottom 处执行此操作,而不是在每次调用时,以及 saving/restoring 调用保留寄存器)。
(实际上 memcmp
不太可能关心堆栈对齐,除非它需要 save/restore 一些更多的 XMM regs。Windows x64 调用约定不明智 only has 6 call-clobbered x/ymm registers ,这可能会稍微紧一点,这取决于他们在手写的 (?) memcmp
.)
即使您确实需要处理未知的传入 RSP 对齐,将 RSP 保存到两个不同的位置以供 pop rsp
仍然不是一种非常有效的方法。通常,您只需使用 RBP 来制作一个传统的帧指针,以使用 mov rsp, rbp
/ pop rbp
进行清理,无论对 RSP 进行未知调整如何工作。例如即使在使用 alloca
的函数中(或在 asm 中,执行未知数量的推送或可变大小的 sub rsp
,这实际上与 and rsp, -16
相同)。