; 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的替代方法是: ;输入n,0 ;486上的14个周期
push bp ;1 cycle on the 486
sub sp, n ;1 cycle on the 486
;输入n,1 ;486上的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
;输入n,3 ;486上的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
4条答案
按热度按时间uz75evzq1#
这两者在性能上存在差异,尤其是
enter
。在现代处理器上,这解码为大约10到20 µ op,而三指令序列大约为4到6,具体取决于架构。有关详细信息,请参阅Agner Fog's指令表。此外,与三指令序列的3时钟相关性链相比,
enter
指令通常具有相当高的等待时间,例如在核心2上为8时钟。此外,为了调度的目的,三个指令序列可以由编译器展开,当然这取决于周围的代码,以允许指令的更多并行执行。
ubof19bj2#
在设计80286时,英特尔的CPU设计师决定添加两条指令来帮助维护显示器。
下面是CPU内部的微代码:
ENTER的替代方法是:
;输入n,0 ;486上的14个周期
;输入n,1 ;486上的17个周期
;输入n,3 ;486上的23个周期
长的路可能会增加你的文件大小,但更快。
最后一点注意,程序员不再使用显示器了,因为这是一个非常缓慢的工作,使回车现在相当无用。
来源:https://courses.engr.illinois.edu/ece390/books/artofasm/CH12/CH12-3.html
ulydmbyx3#
使用这两种方法都没有真实的的速度优势,尽管长方法可能会运行得更好,因为现在的CPU更“优化”到更短更简单的指令,这些指令在使用中更通用(加上如果你幸运的话,它允许执行端口饱和)。
LEAVE
(现在仍在使用,请参阅windows dll)的优点是它比手动拆除堆栈框架要小,当空间有限时,这会有很大帮助。英特尔的操作手册(准确地说是2A卷)将有更多关于指令的细节,Dr Agner Fogs Optimization manuals也应该如此
piwo6bdm4#
**
enter
在所有CPU上都非常慢,**除了以牺牲速度为代价优化代码大小之外,没有人使用它。(如果需要帧指针,或者希望允许更紧凑的寻址模式来寻址堆栈空间。)**
leave
足够快,值得使用,并且GCC * 确实 * 使用它(如果ESP / RSP还没有指向保存的EBP/RBP;否则它只使用pop ebp
)。leave
在现代Intel CPU上仅为3个uop(在某些AMD上为2个)。mov / pop总共只有2个微指令(在现代的x86上,一个“堆栈引擎”跟踪ESP/RSP的更新)。所以
leave
只是比单独做事情多一个uop。我已经在Skylake上测试过了,将循环中的call/ret与使用mov
/pop
或leave
设置传统帧指针并拆除其堆栈帧的函数进行比较。uops_issued.any
的perf
计数器在使用leave时比mov/pop多显示一个前端uop。(我运行了自己的测试,以防其他测量方法在leave测量中计数堆栈同步uop,但在真实的函数中使用它会对此进行控制。)旧CPU保持mov / pop分离可能会受益更多的可能原因:
ret n
的被调用方弹出堆栈参数(例如,ret 8
在弹出返回地址后执行ESP/RSP += 8)。这是一条多微指令,不像现代x86上ret
附近的普通指令。因此,上述原因有两个:leave和ret 12
无法在同一周期解码对于现代CPU,
leave
占用uop缓存中的1个额外uop。并且所有3个都必须在uop缓存的同一行中,这可能导致仅部分填充前一行。因此,更大的x86代码大小 * 可以 * 实际上改善装入uop缓存的情况。或者不改善,取决于如何排列。节省2个字节(或在64位模式下节省3个字节)可能值得也可能不值得每个函数多执行1个uop。
GCC支持
leave
,clang和MSVC支持mov
/pop
(即使是以牺牲速度为代价的clang -Oz
代码大小优化,例如做push 1 / pop rax
(3字节)而不是5字节的mov eax,1
)。ICC支持移动/弹出,但对于
-Os
,将使用leave
。https://godbolt.org/z/95EnP3G1f