【问题标题】:Smaller instruction than "add esp, 4"比“add esp, 4”更小的指令
【发布时间】:2010-01-10 20:09:14
【问题描述】:

又是我。

我的程序中有很多“add esp, 4”,我正在尝试减小它的大小。有没有更小的指令可以代替 "add esp, 4" ?

【问题讨论】:

  • 这个汇编输出,还是用汇编写的?
  • 您能否发布一个小示例,以便我们更准确地了解您实际想要做什么?
  • 有人应该在其中添加“x86”标签。

标签: optimization assembly x86


【解决方案1】:
pop edx  

或者您不介意破坏的任何其他整数寄存器。

This is what modern compilers actually do(clang,有时是 gcc),因为它通常最适合现代 CPU 上的性能和代码大小。

call 之后的 add esp,4 将强制 CPU 的堆栈引擎在执行实际的 add 之前插入堆栈同步微指令。如果您在下一次 push/pop/call/ret 之前不直接修改使用 ESP,除非使用堆栈指令(例如,作为寻址模式的一部分),那么您使用 pop 保存了一个 uop。

如果最近运行了任何其他堆栈指令,则堆栈内存的高速缓存行将在高速缓存中变热(降低负载)。

【讨论】:

  • 可以在不涉及其他寄存器的情况下完成吗?像“pop
  • @DavidH:x86 系列中没有这样的指令。这种非正交结构的副作用是假设每条指令都有特定的用途。像 DEC 处理器或带有CMP (SP)+,(SP)+ 的 68K 处理器这样的花哨技巧是不可能的,因为 x86 堆栈指针不是通用寄存器。
  • @wallyk:ESP 8 个通用整数寄存器之一。不同之处在于 x86 没有后增量寻址模式。 (lods 以这种方式隐式使用 ESI,但我说的是可以与任何寄存器一起使用的模式)。此外,x86 不允许具有 2 个显式内存操作数的指令。 (一个 ModRM 字节只能编码一个寄存器和一个 reg/mem 操作数,因此大多数指令都有一对操作码,用于 add r, r/madd r/m, r,允许对内存 src 或 dst 进行编码,但不能同时编码。)跨度>
【解决方案2】:

一个更好的问题可能是:“为什么你有这么多 add esp, 4 指令,你能做些什么来减少它们?”像这样对堆栈指针进行大量小增量有点不寻常。

您是否同时将东西移入/移出堆栈?你能改用push/pop吗?

或者,您真的需要如此频繁地更新堆栈指针吗,或者您是否可以在代码块的开头移动一次以在堆栈上腾出一些空间,然后在结束时恢复一次?例行公事?

你真正想做什么?

【讨论】:

  • 它们来自外部函数调用
  • 外部函数调用本身不需要对堆栈指针进行大量小调整。您是否正在调整堆栈指针以满足 ABI 对齐要求?作为调用后从堆栈中弹出参数的一部分?
【解决方案3】:

对不起,如果这听起来微不足道...但是如果您设法重新排序代码以使多个 add esp, 4 指令是连续的,您当然可以将它们简化为例如:

add esp, 8

甚至:

add esp, 12

只要确保移动的指令不引用esp 或堆栈即可;或者如果他们确实引用了堆栈上的某些东西,他们只能通过ebp 寄存器进行。

【讨论】:

    【解决方案4】:

    如果您有多个函数调用,一种方法:

    sub esp, 4
    mov 0(esp), param
    call ...
    ...
    mov 0(esp), param2
    call ...
    ...
    add esp, 4
    

    也就是说,在多个函数调用中重用为第一个参数分配的堆栈。

    【讨论】:

    • 这是gcc -faccumulate-outgoing-args使用的代码生成策略。
    【解决方案5】:

    尝试使用pop eax

    【讨论】:

      【解决方案6】:

      如果您在调用后对齐堆栈,更好的方法是使用RETN X,其中X 是要添加到ESP 的字节数...

      PUSH EAX
      CALL EBX (in this func, you use RETN 4)    
      <<here the stack is already aligned>>
      

      或者,使用POPFD =x

      【讨论】:

      • popfd 很慢;请参阅 my comment 另一个答案。 ret imm16 在某些 CPU 上解码为额外的微指令,因此它实际上可能对性能没有帮助。与普通ret 相比,它花费了 2 个额外字节的代码大小。 (但仅在被调用者中,而不是在每个调用大小处)。但可以肯定的是,callee-pops 约定有时可能是一个不错的选择。
      【解决方案7】:

      如果您正在管理堆栈(我假设您是),您可以使用push eaxpop eax 向堆栈添加值并维护esp。您还可以使用 pusha/popa 等指令将所有 GPR 推入/弹出堆栈,并使用 pushf/popf 将 EFLAGS 寄存器推入/弹出堆栈。

      【讨论】:

        【解决方案8】:

        popfd 将在一个字节内将 4 加到 esp 上,其副作用是随机化您的标志。执行起来可能很慢;我不知道。

        当然,查看代码或了解您的需求真正是什么会有所帮助。

        【讨论】:

        • popf 非常慢,例如在 Nehalem 上每 14 个周期 1 个,或者在 Skylake (agner.org/optimize) 上每 20 个周期 1 个。它可以设置或清除 DF,因此您还需要cld 来按照大多数 ABI 的要求保持 DF=0。在内核代码中,其他important bits in EFLAGS 是可修改的,包括IF(中断启用/禁用)和TF(陷阱=单步)。这些其他标志可能 为什么 popf 必须进行微编码;我猜,它的使用频率不足以让解码器在用户空间中以不同的方式对其进行解码。 TL:DR 糟糕的选择与pop ecx.
        猜你喜欢
        • 1970-01-01
        • 2011-05-16
        • 1970-01-01
        • 2011-09-13
        • 1970-01-01
        • 2014-03-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多