如何在x86-64上优化C和C ++中的函数返回值?

时间:2014-08-19 11:01:49

标签: c++ c performance x86-64 abi

x86-64 ABI指定两个返回寄存器:raxrdx,大小均为64位(8字节)。

假设x86-64是唯一的目标平台,这两个功能中的哪一个:

uint64_t f(uint64_t * const secondReturnValue) {
    /* Calculate a and b. */
    *secondReturnValue = b;
    return a;
}

std::pair<uint64_t, uint64_t> g() {
    /* Calculate a and b, same as in f() above. */
    return { a, b };
}
鉴于目前针对x86-64的C / C ++编译器的状态,

会产生更好的性能吗?使用一个版本或其他版本是否存在性能方面的任何陷阱?编译器(GCC,Clang)是否始终能够优化std::pairrax中要返回的rdx

UPDATE:通常,如果编译器优化std::pair方法(使用GCC 5.3.0Clang 3.8.0的二进制输出示例),则返回一对更快。如果未内联f(),则编译器必须生成将值写入内存的代码,例如:

movq b, (%rdi)
movq a, %rax
retq

但是在g()的情况下,编译器就足够了:

movq a, %rax
movq b, %rdx
retq

因为将值写入内存的指令通常比将值写入寄存器的指令慢,所以第二个版本应该更快。

2 个答案:

答案 0 :(得分:8)

由于ABI指定在某些特定情况下必须将两个寄存器用于2字结果,因此任何符合标准的编译器都必须遵守该规则。

但是,对于这些微小的功能,我猜大多数性能都来自内联。

您可能希望使用链接时优化来编译并使用g++ -flto -O2链接

我猜第二个函数(通过2个寄存器返回一对)可能会稍快一点,并且在某些情况下,GCC编译器可能会内联并优化第一个到第二个。

但如果你非常关心,你真的应该进行基准测试。

答案 1 :(得分:2)

请注意,ABI指定将任何小结构打包到寄存器中以进行传递/返回(如果它只包含整数类型)。这意味着返回std::pair<uint32_t, uint32_t>表示必须将值移至+ rax

这可能仍然比通过内存的往返更好,因为为指针设置空间,并将该指针作为额外的arg传递,会产生一些开销。 (除此之外,通过L1缓存的往返非常便宜,比如〜5c延迟。存储/加载几乎肯定会在L1缓存中命中,因为堆栈内存一直在使用。即使它未命中,商店转发仍然可能发生,因此执行不会停止,直到ROB填充因为商店无法退休。请参阅Agner Fog's microarch guide标签维基上的和其他内容。)

无论如何,here's the kind of code you get from gcc 5.3 -O2,使用带有args的函数而不是返回编译时常量值(这将导致movabs rax, 0x...):

#include <cstdint>
#include <utility>
#define type_t uint32_t

type_t f(type_t * const secondReturnValue, type_t x) {
    *secondReturnValue = x+4;
    return x+2;
}
    lea     eax, [rsi+4]           # LEA is an add-and-shift instruction that uses memory-operand syntax and encoding
    mov     DWORD PTR [rdi], eax
    lea     eax, [rsi+2]
    ret

std::pair<type_t, type_t> g(type_t x) { return {x+2, x+4}; }
    lea     eax, [rdi+4]
    lea     edx, [rdi+2]
    sal     rax, 32
    or      rax, rdx
    ret

type_t use_pair(std::pair<type_t, type_t> pair) {
    return pair.second + pair.first;
}
    mov     rax, rdi
    shr     rax, 32
    add     eax, edi
    ret

所以它真的不坏。调用者和被调用者中有两个或三个insn来打包和解包一对uint32_t值。但是,它远不及返回一对uint64_t值。

如果您专门针对x86-64进行了优化,并关注具有多个返回值的非内联函数会发生什么,那么更愿意返回std::pair<uint64_t, uint64_t> (或int64_t ,显然),即使你将这些对分配给调用者中较窄的整数。请注意,在x32 ABI(-mx32)中,指针只有32位。在优化x86-64时,不要假设指针是64位,如果你关心那个ABI。

如果该对中的任何一个成员是64位,则它们使用单​​独的寄存器。它没有做任何愚蠢的事情,比如在一个reg的高半部分和另一个的低半部分之间分割一个值。