[英]Converting an inline-asm x87 fsqrt function from C++ to C for x86-64
[英]How to optimize function return values in C and C++ on x86-64?
x86-64 ABI指定两个返回寄存器: rax
和rdx
,大小均为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)总是能够优化在rax
和rdx
返回的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字结果,因此任何符合标准的编译器都必须遵守该规则。
但是,对于这些微小的功能,我猜大多数性能都来自内联。
您可能希望使用链接时优化来编译和链接 g++ -flto -O2
。
我想第二个函数(通过2个寄存器返回一对)可能会稍快一些,并且在某些情况下,GCC编译器可能会内联并优化第一个到第二个。
但如果你关心的话,你真的应该做基准测试。
请注意,ABI指定将任何小结构打包到寄存器中以进行传递/返回(如果它只包含整数类型)。 这意味着返回std::pair<uint32_t, uint32_t>
意味着值必须移位+ ORed到rax
。
这可能仍然比通过内存的往返更好 ,因为为指针设置空间,并将该指针作为额外的arg传递,会产生一些开销。 (除此之外,通过L1缓存的往返非常便宜,比如〜5c延迟。存储/加载几乎肯定会在L1缓存中命中,因为堆栈内存一直在使用。即使它未命中存储转发仍然可能发生,因此执行不会停止,直到ROB填充因为商店无法退出。请参阅Agner Fog的微指南指南以及x86标签维基上的其他内容。)
无论如何, 这是从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
包一对uint32_t
值。 尽管如此, uint64_t
及返回一对uint64_t
值。
如果您专门针对x86-64进行了优化,并且关心具有多个返回值的非内联函数会发生什么,那么即使您分配了这些对,也更喜欢返回std::pair<uint64_t, uint64_t>
(或显然是int64_t
)。在呼叫者中缩小整数。 请注意,在x32 ABI( -mx32
)中,指针只有32位。 如果你关心那个ABI,那么在优化x86-64时不要假设指针是64位。
如果该对中的任何一个成员是64位,则它们使用单独的寄存器 。 它没有做任何愚蠢的事情,比如在一个reg的高半部分和另一个的低半部分之间分割一个值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.