Optimization “比”小的指令;添加esp,4“;

Optimization “比”小的指令;添加esp,4“;,optimization,assembly,x86,Optimization,Assembly,X86,又是我 我的程序中有很多“添加esp,4”,我正试图缩小它的大小。是否有任何较小的指令可以替代“添加esp,4” 或者任何其他你不介意销毁的整数寄存器 (叮当声,有时是gcc),因为它通常是现代CPU上性能和代码大小的最佳选择 调用后的add esp,4将强制CPU的堆栈引擎在执行实际的add之前插入堆栈同步uop。如果在下一次push/pop/call/ret之前,除了使用堆栈指令(例如作为寻址模式的一部分)外,您不直接修改use ESP,则使用pop保存uop 如果最近运行了任何其他堆栈指令

又是我

我的程序中有很多“添加esp,4”,我正试图缩小它的大小。是否有任何较小的指令可以替代“添加esp,4”

或者任何其他你不介意销毁的整数寄存器

(叮当声,有时是gcc),因为它通常是现代CPU上性能和代码大小的最佳选择

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


如果最近运行了任何其他堆栈指令,堆栈内存的缓存线将在缓存中变得很热(使负载变得便宜)。

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

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

您是否同时在堆栈中移动对象?你能用
push
/
pop
代替吗

或者,您真的需要如此频繁地更新堆栈指针,还是可以在代码块开始时移动它一次,在堆栈上留出一些空间,然后在例程结束时恢复它一次


您真正想做什么?

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

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

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

如果这听起来很琐碎,那么很抱歉。。。但是,如果您设法对代码进行重新排序,使多个
add esp,4
指令是连续的,那么您当然可以将它们简化为,例如:

add esp, 8
甚至:

add esp, 12

只需确保移动的指令不引用
esp
或堆栈;或者,如果它们引用堆栈上的某个内容,则只能通过
ebp
寄存器进行引用。

popfd
将在一个字节内向
esp
添加4个,其副作用是随机化标记。它执行起来可能很慢;我不知道


当然,查看代码或了解您的实际需求会有所帮助。

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

PUSH EAX
CALL EBX (in this func, you use RETN 4)    
<<here the stack is already aligned>>
PUSH-EAX
调用EBX(在此函数中,使用RETN 4)

或者,使用
POPFD=x

这是程序集输出,还是用程序集编写的?您可以发布一个小示例,以便我们更准确地了解您实际要做的事情吗?应该有人向其添加“x86”标记。可以在不涉及其他寄存器的情况下完成吗?像“pop”@DavidH:x86家族中没有这样的指令。这种非正交结构的一个副作用是,假定每个指令都有特定的用途。像DEC处理器或带有
CMP(SP)+,(SP)+
的68K处理器这样的花哨把戏是不可能的,因为x86堆栈指针不是通用寄存器。@wallyk:ESP是8个通用整数寄存器之一。区别在于x86没有增量后寻址模式。(
lods
以这种方式隐式使用ESI,但我指的是可以用于任何寄存器的模式)。此外,x86不允许使用2个显式内存操作数的指令。(一个ModRM字节只能对一个寄存器和一个reg/mem操作数进行编码,因此大多数指令都有用于
add r,r/m
add r/m,r
的成对操作码,允许对内存src或dst进行编码,但不能同时对两者进行编码。)它们来自外部函数调用内部函数调用本身,需要对堆栈指针进行大量的小调整。是否调整堆栈指针以满足ABI对齐要求?作为调用后从堆栈中弹出参数的一部分?
popf
非常慢,比如在Nehalem上每14个周期1个,或者在Skylake上每20个周期1个()。它可以设置或清除DF,因此您还需要
cld
按照大多数ABI的要求维护DF=0。在内核代码中,其他代码是可修改的,包括IF(中断启用/禁用)和TF(陷阱=单步)。这些其他标志可能是
popf
必须进行微编码的原因;我想,它的使用频率不足以让解码器在用户空间中对其进行不同的解码。TL:DR Thread choice vs.
pop ecx
。这是
gcc使用的代码生成策略-facumulate outing args
popfd
非常慢;请看另一个答案
retimm16
在某些CPU上解码为一个额外的uop,因此实际上可能对性能没有帮助。与普通的
ret
相比,它需要额外2个字节的代码大小。(但仅在被叫方中,而不是在每个呼叫大小下)。当然,有时被叫人大会可能是个不错的选择。
PUSH EAX
CALL EBX (in this func, you use RETN 4)    
<<here the stack is already aligned>>