在intel x64程序集中执行功能的最快(CPU方式)方式?

时间:2015-10-26 01:57:37

标签: c assembly x86 stdcall

我一直在阅读有关汇编功能的内容,我对是否使用输入和退出或仅使用调用/返回指令进行快速执行感到困惑。一路快,另一路小吗?例如,在没有内联函数的情况下,在汇编中执行此操作的最快(stdcall)方法是什么:

static Int32 Add(Int32 a, Int32 b) {
   return a + b;
}

int main() {
    Int32 i = Add(1, 3);
}

1 个答案:

答案 0 :(得分:5)

使用call / ret,而无需使用enter / leavepush&pop rbp / mov rbp, rsp制作堆叠框架。 gcc(使用默认的-fomit-frame-pointer)仅在堆栈中执行可变大小分配的函数中生成堆栈帧。 This may make debugging slightly more difficult,因为gcc在使用-fomit-frame-pointer进行编译时通常会发出堆栈展开信息,但是您手写的asm将不会有这种情况。通常只在asm中编写leaf函数才有意义,或者至少在不调用许多其他函数的函数中编写函数。

堆栈帧意味着您不必跟踪堆栈指针自从函数入口访问堆栈中的内容(例如本地的函数args和溢出槽)以来已经改变了多少。 Windows和Linux / Unix 64位ABI都通过寄存器中的前几个args,并且通常有足够的regs,您不必将任何变量溢出到堆栈中。在大多数情况下,堆栈帧是浪费指令。在32位代码中,ebp可用(从6到7个GP,从不计算堆栈指针)产生的差异比从14到15更大。当然,你还需要push/pop rbp但是,如果你使用它,因为在两个ABI中它都是被调用者保存的寄存器,不允许函数被破坏。

如果您正在优化x86-64 asm,则应阅读Agner Fog's guides,并查看标记wiki中的其他一些链接。

您的功能的最佳实现可能是:

align 16
global Add
Add:
    lea   eax, [rdi + rsi]
    ret
    ; the high 32 of either reg doesn't affect the low32 of the result
    ; so we don't need to zero-extend or use a 32bit address-size prefix
    ; like  lea  eax, [edi, esi]
    ; even if we're called with non-zeroed upper32 in rdi/rsi.

align 16
global main
main:
    mov    edi, 1   ; 1st arg in SysV ABI
    mov    esi, 3   ; 2nd arg in SysV ABI
    call Add
    ; return value in eax in all ABIs
    ret

align 16
OPmain:  ; This is what you get if you don't return anything from main to use the result of Add
    xor   eax, eax
    ret

Add()对于return 4实际上是what gcc emits,但如果你return i,它仍会将主要变为空函数,或变为-fno-inline-functionsclang 3.7即使结果是编译时常量,也会尊重jmp。它通过尾部调用优化来胜过我的主题,Add到{{1}}。

请注意,Windows 64位ABI对函数args使用不同的寄存器。请参阅x86标记wiki中的链接或Agner Fog的ABI指南。 Assembler macros可能有助于在asm中编写函数,这些函数使用正确的寄存器作为其args,具体取决于您所针对的平台。