gpt4 book ai didi

c++ - 返回一个 2 元组是否比 std::pair 效率低?

转载 作者:行者123 更新时间:2023-12-02 01:11:55 45 4
gpt4 key购买 nike

考虑这个代码:

#include <utility>
#include <tuple>

std::pair<int, int> f1()
{
return std::make_pair(0x111, 0x222);
}

std::tuple<int, int> f2()
{
return std::make_tuple(0x111, 0x222);
}

Clang 3 和 4 在 x86-64 上为两者生成类似的代码:
f1():
movabs rax,0x22200000111
ret
f2():
movabs rax,0x11100000222 ; opposite packing order, not important
ret

但是 Clang 5 为 f2() 生成了不同的代码:
f2():
movabs rax,0x11100000222
mov QWORD PTR [rdi],rax
mov rax,rdi
ret

与 GCC 4 到 GCC 7 一样:
f2():
movabs rdx,0x11100000222
mov rax,rdi
mov QWORD PTR [rdi],rdx ; GCC 4-6 use 2 DWORD stores
ret

为什么返回 std::tuple 时生成的代码更糟适合单个寄存器,vs std::pair ?这似乎特别奇怪,因为 Clang 3 和 4 似乎是最佳的,但 5 不是。

在这里试试: https://godbolt.org/g/T2Yqrj

最佳答案

简短的回答是因为 libstc++ gcc 使用的标准库实现和 clang在 Linux 上实现 std::tuple具有非平凡的移动构造函数(特别是 _Tuple_impl 基类具有非平凡的移动构造函数)。另一方面, std::pair 的复制和移动构造函数都是默认的。

这反过来会导致从函数返回这些对象以及按值传递它们的调用约定中与 C++-ABI 相关的差异。

血腥细节

您在遵循 SysV x86-64 ABI 的 Linux 上运行测试。此 ABI 具有向函数传递或返回类或结构的特定规则,您可以阅读有关 here 的更多信息。 .我们感兴趣的具体案例是否是两个int这些结构中的字段将获得 INTEGER类或 MEMORY类(class)。

recent ABI 规范的版本是这样说的:

The classification of aggregate (structures and arrays) and union types works as follows:

  1. If the size of an object is larger than eight eightbytes, or it contains un- aligned fields, it has class MEMORY 12 .
  2. If a C++ object has either a non-trivial copy constructor or a non-trivial destructor 13 , it is passed by invisible reference (the object is replaced in the parameter list by a pointer that has class INTEGER) 14 .
  3. If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.
  4. Each field of an object is classified recursively so that always two fields are considered. The resulting class is calculated according to the classes of the fields in the eightbyte


这里适用的是条件(2)。请注意,它只提到了复制构造函数,而不是移动构造函数 - 但很明显,鉴于引入了移动构造函数,这可能只是规范中的一个缺陷,移动构造函数通常需要包含在任何分类算法中,其中之前包含复制构造函数.特别是 IA-64 cxx-abi,其中 gcc被记录为遵循 does include move constructors :

If the parameter type is non-trivial for the purposes of calls, the caller must allocate space for a temporary and pass that temporary by reference. Specifically:

  • Space is allocated by the caller in the usual manner for a temporary, typically on the stack.


然后是 definition非平凡的:

A type is considered non-trivial for the purposes of calls if:

  • it has a non-trivial copy constructor, move constructor, or destructor, or
  • all of its copy and move constructors are deleted.


所以因为 tuple从 ABI 的角度来看,它不被认为是微不足道的可复制的,它会得到 MEMORY处理,这意味着您的函数必须填充由 rdi 中的调用传入的堆栈分配对象。 . std::pair函数可以在 rax 中传回整个结构因为它适合一个 EIGHTBYTE并有类(class) INTEGER .

有关系吗?是的,严格来说,像您编译的这样的独立函数对于 tuple 来说效率较低因为这个 ABI 不同是“烘焙”的。

然而,编译器通常能够看到函数的主体并将其内联或执行过程间分析,即使没有内联。在这两种情况下,ABI 都不再重要,而且很可能两种方法都同样有效,至少有一个不错的优化器。例如 let's call your f1() and f2() functions and do some math on the result :
int add_pair() {
auto p = f1();
return p.first + p.second;
}

int add_tuple() {
auto t = f2();
return std::get<0>(t) + std::get<1>(t);
}

原则上 add_tuple方法从一个劣势开始,因为它必须调用 f2()这效率较低,它还必须在堆栈上创建一个临时元组对象,以便将其传递给 f2作为隐藏参数。没关系,这两个函数都经过了全面优化,可以直接返回正确的值:
add_pair():
mov eax, 819
ret
add_tuple():
mov eax, 819
ret

所以总的来说你可以说这个ABI问题的影响 tuple将相对静音:它为必须符合 ABI 的函数增加了一个小的固定开销,但这仅在相对意义上对于非常小的函数真正重要 - 但这些函数很可能在它们可以被声明的地方被声明内联(或者如果没有,您将把性能放在桌面上)。

libcs​​tc++ 与 libc+++

如上所述,这本身是 ABI 问题,而不是优化问题。 clang 和 gcc 都已经在 ABI 的约束下最大限度地优化了库代码——如果他们生成了像 f1() 这样的代码。为 std::tuple如果他们会破坏 ABI 兼容的调用者。

如果您切换到使用 libc++,您可以清楚地看到这一点。而不是 Linux 默认的 libstdc++ - 这个实现没有显式的移动构造函数(正如 Marc Glisse 在评论中提到的,为了向后兼容,他们坚持这个实现)。现在 clang (大概是 gcc,虽然我没有尝试过),生成 same optimal code在这两种情况下:
f1():                                 # @f1()
movabs rax, 2345052143889
ret
f2(): # @f2()
movabs rax, 2345052143889
ret

Clang 的早期版本

为什么版本 clang以不同的方式编译它?简直就是 a bug in clang或规范中的错误,取决于您如何看待它。在需要传递指向临时对象的隐藏指针的情况下,规范没有明确包含移动构造。不符合 IA-64 C++ ABI。例如,clang 过去使用的编译方式与 gcc 不兼容。或更新版本的 clang .规范是 eventually updated和 clang behavior changed in version 5.0 .

更新:马克·格利斯 mentions在评论中,最初对非平凡移动构造函数和 C++ ABI 的交互存在混淆,以及 clang在某些时候改变了他们的行为,这可能解释了这种转变:

The ABI specification for some argument passing cases involving move constructors were unclear, and when they were clarified, clang changed to follow the ABI. This is probably one of those cases.

关于c++ - 返回一个 2 元组是否比 std::pair 效率低?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46901697/

45 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com