performance - 从堆栈中弹出不需要的值,或者在 386+ CPU 上向 SP 添加一个立即常量是否更快?

标签 performance assembly stack x86-16 micro-optimization

我的代码目标是 386+(通常是 DOSBox,偶尔是 Pentium MMX)CPU,但我只使用 8086 特性集来实现兼容性。我的代码是为非多任务环境(MS-DOS 或 DOSBox)编写的。

在嵌套循环中,我经常发现自己将 CX 重新用于更深层次的循环计数器。我将它PUSH 放在嵌套循环的顶部,然后在执行LOOP 之前将它POP

有时 CX 以外的条件达到 0 会终止这些内部循环。然后我留下了不必要的循环计数器,有时还有更多的变量,坐在我需要清理的堆栈上。

是直接给SP加一个常量更快,还是POP这些不需要的值?

我知道最快的方法是将 CX 存储在循环顶部的备用寄存器中,然后在 LOOP 执行之前恢复它,前述堆栈完全,但我经常没有备用寄存器。

这是一段代码,我在其中添加了一个常量到 SP 以避免一些 POP 指令:

FIND_ENTRY PROC

;SEARCHES A SINGLE SECTOR OF A DIRECTORY LOADED INTO secBuff FOR A 
;SPECIFIED FILE/SUB DIRECTORY ENTRY

;IF FOUND, RETURNS THE FILE/SUB DIRECTORY'S CLUSTER NUMBER IN BX
;IF NOT FOUND, RETURNS 0 IN BX

;ALTERS BX

;EXPECTS A FILE NAME STRING INDEX NUMBER IN BP
;EXPECTS A SECTOR OF A DIRECTORY (ROOT, OR SUB) TO BE LOADED INTO secBuff
;EXPECTS DS TO BE LOADED WITH varData


    push ax
    push cx
    push es
    push si
    push di




    lea si, fileName             ;si -> file name strings 
    mov ax, 11d                  ;ax -> file name length in bytes/characters
    mul bp                       ;ax -> offset to file name string
    add si, ax                   ;ds:si -> desired file name as source input
                                 ;for CMPS
    mov di, ds
    mov es, di
    lea di, secBuff              ;es:di -> first entry in ds:secBuff as 
                                 ;destination input for CMPS


    mov cx, 16d                  ;outer loop cntr -> num entries in a sector
ENTRY_SEARCH:                    
    push cx                      ;store outer loop cntr
    push si                      ;store start of the file name
    push di                      ;store start of the entry


    mov cx, 11d                  ;inner loop cntr -> length of file name
    repe cmpsb                   ;Do the strings match?
    jne NOT_ENTRY                ;If not, test next entry.

    pop di                       ;di -> start of the entry
    mov bx, WORD PTR [di+26]     ;bx -> entry's cluster number

    add sp, 4                    ;discard unneeded stack elements
    pop di
    pop si
    pop es
    pop cx
    pop ax
    ret

NOT_ENTRY:                       
    pop di                       ;di -> start of the entry
    add di, 32d                  ;di -> start of next entry
    pop si                       ;si -> start of file name
    pop cx                       ;restore the outer loop cntr
    loop ENTRY_SEARCH            ;loop till we've either found a match, or
                                 ;have tested every entry in the sector 
                                 ;without finding a match.

    xor bx, bx                   ;if we're here no match was found. 
                                 ;return 0.




    pop di
    pop si
    pop es
    pop cx
    pop ax
    ret


FIND_ENTRY ENDP

最佳答案

如果您想编写高效的代码,与reducing the amount of saving/restoring you need to do 相比,pop 与add 是一个非常小的问题。 ,并优化其他一切(见下文)。


如果需要超过 1 个pop,请始终使用add sp, imm。或者 sub sp, -128 仍然使用 imm8 来节省代码大小。或者某些 CPU 可能更喜欢 lea 而不是 add/sub。 (例如,gcc 尽可能使用 LEA 和 -mtune=atom)。当然,这需要 16 位模式的地址大小前缀,因为 [sp+2] 不是有效的寻址模式。


除此之外,没有一个答案同时适用于实际的 386 和像 Haswell 或 Skylake 这样的现代 x86!它们之间有很多的微架构变化CPU。现代 CPU 将 x86 指令解码为内部类似 RISC 的微指令。有一段时间,使用简单的 x86 指令很重要,但现在现代 CPU 可以在一条指令中处理大量工作,因此更复杂的 x86 指令(如 pushadd 带有内存源操作数)是单 uop 指令。

现代 CPU(从 Pentium-M 开始)有一个堆栈引擎,不需要单独的 uop 来实际更新乱序内核中的 RSP/ESP/SP。当您使用非堆栈指令(除 push/pop/call/ret 之外的任何指令)读/写 RSP 时,Intel 的实现需要一个堆栈同步 uop,这就是 pop 有用的原因,尤其是如果你在推送或调用后执行此操作。

当需要单个 8 字节偏移量时,clang 使用 push/pop 对齐 x86-64 代码中的堆栈。 Why does this function push RAX to the stack as the first operation? .


但是如果你关心性能, loop is slow and should be avoided in the first place ,更不用说循环计数器的 push/pop 了! 对内/外循环使用不同的 regs。

基本上,就优化而言,您在错误的道路上走得太远了,所以真正的答案只是指向您 http://agner.org/optimize/ ,以及 the x86 tag wiki 中的其他性能链接.由于对现代 CPU 的所有部分寄存器错误依赖性,16 位代码很难获得良好的性能,但对代码大小有一些影响,您可以在必要时使用 32 位操作数大小来打破这些。 (例如对于 xor ebx,ebx)


当然,如果您针对 DOSBOX 进行优化,它就不是真正的 CPU 而是模拟的。所以 loop 可能很快!如果有人分析过或编写过 DOSBOX 的 CPU 模拟器的优化指南,请 IDK。但我建议学习在真正的现代硬件上什么是快速的;那更有趣。

关于performance - 从堆栈中弹出不需要的值,或者在 386+ CPU 上向 SP 添加一个立即常量是否更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48512040/

相关文章:

c# - Convert.TryToInt64 而不是 Convert.ToInt64?

c - 如何从汇编指令到 C 代码

assembly - 哪里可以获得所有版本的 x86 又名 IA32 指令集架构手册

c - Qt 原子操作使用我的 MIPS 24k 核心中不存在的条件代码寄存器

c# - 声明变量时的性能问题

c++ - 快速整数矩阵乘法与 bit-twiddling hacks

python - Pandas 检查平等性太慢而无法使用

c - (在 C 中)使用数组实现堆栈 - 将数组大小时加倍时出现运行时错误

r - 自动堆叠数据框的每第 n 列

performance - Go 语言指针性能