【问题标题】:Fastest (CPU-wise) way to do functions in intel x64 assembly?在 intel x64 程序集中执行功能的最快(CPU 方面)方法?
【发布时间】:2015-10-26 01:57:36
【问题描述】:

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

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

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

【问题讨论】:

    标签: c assembly x86 stdcall


    【解决方案1】:

    使用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 中编写叶函数才有意义,或者至少是那些不调用许多其他函数的函数。

    堆栈帧意味着您不必跟踪自函数进入后堆栈指针发生了多少变化以访问堆栈上的内容(例如,函数参数和局部变量的溢出槽)。 Windows 和 Linux/Unix 64 位 ABI 都在寄存器中传递前几个 args,并且通常有足够的 regs,您不必将任何变量溢出到堆栈中。在大多数情况下,堆栈帧是对指令的浪费。在 32 位代码中,ebp 可用(从 6 到 7 个 GP regs,不计算堆栈指针)比从 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()what gcc emits,但它仍然把main 变成一个空函数,或者如果你return i 变成一个return 4clang 3.7 尊重 -fno-inline-functions 即使结果是编译时常量。它通过尾调用优化和 jmping 到 Add 击败了我的 asm。

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

    【讨论】:

    • 我认为main函数不正确。对于另一个函数,没有显式返回 return 将是一个错误,main 是特殊的。从main 隐式返回等效于返回值0。在这里,您将返回 eax 中发现的任何垃圾,即 4,这是您不应该返回的。
    • @JensGustedt:我的 asm 匹配我修改后的返回 i 的 C,以使优化编译器仍然调用 Add(使用 -fno-inline-functions 时)。正如您正确指出的那样,OP 的 main 仅编译为 xor eax, eax / ret
    猜你喜欢
    • 1970-01-01
    • 2017-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    相关资源
    最近更新 更多