“enter”vs“push ebp; mov ebp,esp; sub esp,imm”和“leave”vs“mov esp,ebp; pop ebp”

时间:2011-05-11 05:59:05

标签: assembly stack

enter

之间有什么区别
push ebp
mov  ebp, esp
sub  esp, imm

指令?是否存在性能差异?如果是这样,哪个更快,为什么编译器总是使用后者?

leave

类似
mov  esp, ebp
pop  ebp

的指令。

4 个答案:

答案 0 :(得分:35)

性能存在差异,尤其是enter。在现代处理器上,这解码到大约10到20μs,而三个指令序列大约是4到6,具体取决于架构。有关详细信息,请参阅Agner Fog's指令表。

此外,enter指令通常具有相当高的延迟,例如core2上的8个时钟,而三个指令序列的3个时钟依赖链。

此外,编译器可以为调度目的分散三个指令序列,具体取决于周围的代码,以允许更多并行执行指令。

答案 1 :(得分:4)

使用它们中的任何一个都没有真正的速度优势,尽管长方法可能会运行得更好,因为现在CPU更加“优化”到更简单的使用更简单的指令(加上它允许饱和)如果你的幸运,执行端口)。

LEAVE(仍在使用,只看到窗户dll)的优点是它比手动拆除堆叠框架小,这在你的空间有限时会有很大的帮助。

英特尔指导手册(准确地说是第2A卷)将在说明书中包含更多细节,因此Dr Agner Fogs Optimization manuals

答案 2 :(得分:3)

在设计80286时,英特尔的CPU设计人员决定添加两条指令来帮助维护显示。

这里是CPU内部的微代码:

; ENTER Locals, LexLevel

push    bp              ;Save dynamic link.
mov     tempreg, sp     ;Save for later.
cmp     LexLevel, 0     ;Done if this is lex level zero.
je      Lex0

lp:
dec     LexLevel
jz      Done            ;Quit if at last lex level.
sub     bp, 2           ;Index into display in prev act rec
push    [bp]            ; and push each element there.
jmp     lp              ;Repeat for each entry.

Done:
push    tempreg         ;Add entry for current lex level.

Lex0:
mov     bp, tempreg     ;Ptr to current act rec.
sub     sp, Locals      ;Allocate local storage

ENTER的替代方案是:

在486上输入n,0; 14个周期

push    bp              ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

在486上输入n,1; 17个周期

push    bp              ;1 cycle on the 486
push    [bp-2]          ;4 cycles on the 486
mov     bp, sp          ;1 cycle on the 486
add     bp, 2           ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

在486上输入n,3; 23个周期

push    bp              ;1 cycle on the 486
push    [bp-2]          ;4 cycles on the 486
push    [bp-4]          ;4 cycles on the 486
push    [bp-6]          ;4 cycles on the 486
mov     bp, sp          ;1 cycle on the 486
add     bp, 6           ;1 cycle on the 486
sub     sp, n           ;1 cycle on the 486

等。漫长的路可能会增加您的文件大小,但速度会更快。

最后一点,程序员不再使用显示器,因为这是一个非常缓慢的工作,使得ENTER现在非常无用。

来源:https://courses.engr.illinois.edu/ece390/books/artofasm/CH12/CH12-3.html

答案 3 :(得分:1)

enter 在所有 CPU 上都慢得无法使用,除了可能以牺牲速度为代价进行代码大小优化之外,没有人使用它。 (如果根本需要一个帧指针,或者希望允许更紧凑的寻址模式来寻址堆栈空间。)

leave 足够快,值得使用,并且 GCC 确实使用它(如果 ESP / RSP 不是已经指向保存的 EBP/RBP;否则它只使用 pop ebp)。

leave 在现代 Intel CPU 上只有 3 个 uops(在某些 AMD 上只有 2 个)。 (https://agner.org/optimize/https://uops.info/)。

mov / pop 总共只有 2 uops(在现代 x86 上,“堆栈引擎”跟踪对 ESP/RSP 的更新)。所以 leave 只是比单独做事情多一个 uop。我已经在 Skylake 上对此进行了测试,将循环中的 call/ret 与设置传统帧指针并使用 mov/popleave 拆除其堆栈帧的函数进行了比较。当您使用 leave 时,perfuops_issued.any 计数器比 mov/pop 多显示一个前端 uop。 (我运行了我自己的测试,以防其他测量方法在其休假测量中计算堆栈同步 uop,但在实际功能控件中使用它。)

较旧的 CPU 可能在保持 mov / pop 分离方面受益更多的可能原因:

  • 在大多数没有 uop 缓存的 CPU(即 Sandybridge 之前的 Intel,Zen 之前的 AMD)中,多 uop 指令可能是解码瓶颈。它们只能在第一个(“复杂”)解码器中进行解码,因此可能意味着之前的解码周期产生的 uops 比正常情况少。

  • 一些 Windows 调用约定是 callee-pops 堆栈参数,使用 ret n。 (例如,ret 8 在弹出返回地址后执行 ESP/RSP += 8)。这是一个多 uop 指令,与现代 x86 上的 ret 附近的普通指令不同。所以上面的原因是双重的: leave 和 ret 12 不能在同一个循环中解码

  • 这些原因也适用于构建 uop 缓存条目的传统解码。

  • P5 Pentium 也更喜欢 x86 的类似 RISC 的子集,甚至无法将复杂的指令分解成单独的 uops

对于现代 CPUleave 在 uop 缓存中占用 1 个额外的 uop。并且所有 3 个都必须在 uop 缓存的同一行中,这可能导致仅部分填充前一行。因此,更大的 x86 代码大小可能实际上可以改善对 uop 缓存的打包。或不,取决于事情如何排列。

为每个函数节省 2 个字节(或 64 位模式下的 3 个字节)可能值得也可能不值得 1 个额外的 uop。

GCC 支持 leave,clang 和 MSVC 支持 mov/pop(即使以牺牲速度为代价进行了 clang -Oz 代码大小优化,例如做类似 { {1}}(3 个字节)而不是 5 个字节的 push 1 / pop rax)。

ICC 支持 mov/pop,但使用 mov eax,1 将使用 -Oshttps://godbolt.org/z/95EnP3G1f