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

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

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

 uint64_t f(uint64_t * const secondReturnValue) { /* Calculate a and b. */ *secondReturnValue = b; return a; } std::pair g() { /* Calculate a and b, same as in f() above. */ return { a, b }; } 

考虑到针对x86-64的C / C ++编译器的当前状态,会产生更好的性能吗? 使用一个版本或其他版本在性能方面是否有任何陷阱? 编译器(GCC,Clang)总是能够优化在raxrdx返回的std::pair吗?

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

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

但是在g()情况下,编译器就可以:

 movq a, %rax movq b, %rdx retq 

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

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

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

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

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

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

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

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

无论如何, 这是你从gcc 5.3 -O2获得的代码类型 ,使用的函数采用args而不是返回编译时常量值(这将导致movabs rax, 0x... ):

 #include  #include  #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 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 pair) { return pair.second + pair.first; } mov rax, rdi shr rax, 32 add eax, edi ret 

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

如果您专门针对x86-64进行了优化,并且关心具有多个返回值的非内联函数会发生什么,那么即使您分配了这些对,也更喜欢返回std::pair (或显然是int64_t )。在呼叫者中缩小整数。 请注意,在x32 ABI( -mx32 )中,指针只有32位。 如果您关心ABI,那么在优化x86-64时不要假设指针是64位。

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