【问题标题】:Optimizing a C function call using 64-bit MASM使用 64 位 MASM 优化 C 函数调用
【发布时间】:2021-10-17 14:35:21
【问题描述】:

当前使用此 64 位 MASM 代码调用 C 运行时函数,例如 memcmp()。我记得这个约定来自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
              

是它避免调用部分寄存器更新。 Understanding fastcall stack frame

更换

              SUB             RSP,32
              

              LEA             RSP,[RSP-32]

接下来的指令不依赖于减法更新的标志
那么不更新标志也会更有效。
Why does GCC emit "lea" instead of "sub" for subtraction?

在这种情况下,还有其他优化技巧吗?

【问题讨论】:

  • AND 是的,原始代码很愚蠢,没有节省任何代码大小(SPL 采用 REX 前缀)。 LEA - 毫无意义且浪费代码大小:x86 CPU 已经通过寄存器重命名避免了对 FLAGS 的错误依赖。该链接问答的答案是错误的。
  • @PeterCordes 旁注: 感谢您看到这个。我打算在您的一个回复下链接评论以提醒您注意这一点,但决定稍等一下,因为我认为您会根据您的正常 M/O 找到它。

标签: c assembly x86-64 micro-optimization


【解决方案1】:

AND 是的,原始代码很傻,没有节省任何代码大小(SPL 也需要一个 REX 前缀,如 64 位操作数大小)。

LEA - 毫无意义且浪费代码大小:x86 CPU 已经 avoid false dependencies on FLAGS via register renaming;这对于有效运行包含addsuband 等指令的普通 x86 代码是必要的。如果不是这种情况,编译器会更频繁地使用lea。该链接问答的答案是错误的,应该被否决/删除。唯一的危险是一些不太常见的 CPU(Pentium 4 和 Silvermont 出于不同的原因)来自像 inc 这样的指令,它们只写 some 标志。 (INC instruction vs ADD 1: Does it matter?)。即使在 Silvermont-family 上 inc 的成本也很小,只是一个额外的微指令,但在解码期间不是,所以它不会停止。

add 在任何 CPU 上都不会比 lea 慢,无论是它本身还是它对后续指令的影响。 (除了有序的 Atom pre-Silvermont,lea 在管道中运行的时间比add 更早(在实际的 AGU 上),因此它可能会更好或更糟,具体取决于数据来自/去往的位置)。在某些情况下,您只会使用lea,例如adc 循环,您实际上需要保持CF 不变,以便下一次迭代可以读取它。即不要弄乱真正的依赖关系(RAW),与避免错误的(WAW)输出依赖关系无关。 (请参阅Problems with ADC/SBB and INC/DEC in tight loops on some CPUs - 请注意adc / inc / adc 创建partial-flag stall 的情况是add 会导致正确性问题的情况,所以我不认为这是@ 987654345@ 会使后面的指令更快。)


您可能不需要保存旧的 RSP; ABI 需要在调用之前进行 16 字节堆栈对齐,这包括您的调用者(除非您从不遵循 ABI 的代码中调用,因此您不知道 RSP 相对于 16 字节边界的对齐方式)。

通常您只需像编译器一样执行sub rsp, 40 来重新对齐 RSP 并为影子空间保留空间。 (并且您可以在函数的顶部/底部执行此操作,而不是围绕每个调用,以及保存/恢复调用保留的寄存器)。


(实际上memcmp 不太可能关心堆栈对齐,除非它需要保存/恢复更多 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 相同)。

【讨论】:

  • 编辑了问题以显示原始汇编代码的修改位置。 GoAsm 也使用 PUSH [RSP] ;在堆栈上保留另一个副本,所以我认为保留它是有充分理由的。 (不知道为什么我自己)
  • @vengy:我认为只是pop rsp 会弹出原始 RSP,无论 and rsp, -16 将 RSP 更改为 8 还是 0。即将 RSP 的副本放置在它可能重新加载的两个地方从。就像我说的,似乎效率低下。 (特别是 push [rsp] 正在从内存中重新加载,不像 mov rax, rsp / push rax / push rax 会不那么糟糕。根据 Agner Fog 的测试,即使 push rsp 在 AMD Zen CPU 上是 2 uop,在 Intel 上也是 3 (agner.org/optimize/).)
  • 好点。另外,我记得使用了 LEA RSP,[RSP+40],因为我遇到了一个问题,即在 CALL TheAPI 之后,有一个 SUB RSP, 40 但这会影响一些标志并导致下游问题。在您的回答中,您能否展示最终建议的 ASM 代码?
  • @vengy:这应该是更大函数的一部分,但我不知道它是否需要奇数或偶数的堆栈空间,或者保存了哪些/多少调用保留寄存器/恢复。调用memcpy 的实际顺序根本不应该涉及堆栈,除了call 本身(并且可以是删除堆栈操作后代码中剩下的内容),所有推送/弹出和RSP 操作都在开始时发生/end 包含这个的整个函数,为本地人保留足够的堆栈空间,为被调用者保留影子空间。
  • @vengy: SUB RSP,40 之后调用会明显出错;使用 add rsp, 40 代替您的 LEA 正在做的事情。除非 API 在 FLAGS 中返回您需要保留的值,否则您可以使用 add,即使它会覆盖 FLAGS。必须假定函数调用会覆盖 FLAGS,因此您不能 cmp eax, ecx / call foo / je。但就像我说的,在包含call 的函数结束之前不要对堆栈执行任何操作,而不是在call 之后。
猜你喜欢
  • 2015-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-13
相关资源
最近更新 更多