如何解决为什么std :: tuple破坏了C ++中的小型结构调用约定优化?
C ++具有小型结构调用约定优化功能,其中,编译器通过函数参数传递小型结构的效率与传递原始类型(例如通过寄存器)的效率一样。例如:
class MyInt { int n; public: MyInt(int x) : n(x){} };
void foo(int);
void foo(MyInt);
void bar1() { foo(1); }
void bar2() { foo(MyInt(1)); }
bar1()
和bar2()
生成几乎相同的汇编代码,除了分别调用foo(int)
和foo(MyInt)
。特别是在x86_64上,它看起来像:
mov edi,1
jmp foo(MyInt) ;tail-call optimization jmp instead of call ret
但是如果我们测试std::tuple<int>
,它将有所不同:
void foo(std::tuple<int>);
void bar3() { foo(std::tuple<int>(1)); }
struct MyIntTuple : std::tuple<int> { using std::tuple<int>::tuple; };
void foo(MyIntTuple);
void bar4() { foo(MyIntTuple(1)); }
生成的汇编代码看起来完全不同,小型结构(std::tuple<int>
)通过指针传递:
sub rsp,24
lea rdi,[rsp+12]
mov DWORD PTR [rsp+12],1
call foo(std::tuple<int>)
add rsp,24
ret
我挖得更深一些,试图使我的int更加脏(这应该接近一个不完整的朴素元组impl):
class Empty {};
class MyDirtyInt : protected Empty,MyInt {public: using MyInt::MyInt; };
void foo(MyDirtyInt);
void bar5() { foo(MyDirtyInt(1)); }
但应用了调用约定优化:
mov edi,1
jmp foo(MyDirtyInt)
我尝试了GCC / Clang / MSVC,它们都表现出相同的行为。 (Godbolt link here)所以我猜这一定是C ++标准中的东西吗? (我相信C ++标准没有指定任何ABI约束吗?)
我知道,只要foo(std::tuple<int>)
的定义是可见的并且没有标记为noinline,编译器就应该能够对其进行优化。但是我想知道标准或实施的哪一部分导致此优化无效。
仅供参考,如果您对std::tuple
的使用感到好奇,我想创建一个包装器类(即 strong typedef ),并且不想我自己声明比较运算符(运算符在C ++ 20之前),并且不想打扰Boost,所以我认为std::tuple
是一个很好的基类,因为所有内容都在那里。
解决方法
这似乎与ABI有关。例如,Itanium C++ ABI reads:
如果出于调用目的参数类型非平凡,则调用方必须为临时变量分配空间并通过引用传递该临时变量。
然后,further:
如果类型具有非平凡的副本构造函数,移动构造函数或析构函数或所有副本,则出于调用目的将其视为非平凡的和移动构造函数被删除。
AMD64 ABI Draft 1.0中的要求相同。
例如,在 libstdc ++ 中,std::tuple
具有简单的移动构造函数:https://godbolt.org/z/4j8vds。该标准规定了both copy and move constructor as defaulted,在此满足。但是,同时tuple
inherits from _Tuple_impl
和_Tuple_impl
has a user-defined move constructor。因此,tuple
本身的move构造函数不能太琐碎。
相反,在 libc ++ 中,std::tuple<int>
的复制和移动构造函数都是微不足道的。因此,该参数在https://godbolt.org/z/WcTjM9的寄存器中传递。
对于 Microsoft STL ,std::tuple<int>
既不可复制构造,也不可移动构造。甚至似乎违反了C ++标准规则。 std::tuple
是递归定义的,在递归结束时,std::tuple<>
专业化定义了non-defaulted copy constructor。关于此问题有一条评论:// TRANSITION,ABI: should be defaulted
。由于tuple<>
没有move构造函数,因此tuple<class...>
的copy和move构造函数都是不平凡的。
如@StoryTeller所建议,它可能与std::tuple
中用户定义的move构造函数有关,从而导致此行为。
例如参见:https://godbolt.org/z/3M9KWo
具有用户定义的move构造函数会导致未优化程序集:
bar_my_tuple():
sub rsp,24
lea rdi,[rsp+12]
mov DWORD PTR [rsp+12],1
call foo(MyTuple<int>)
add rsp,24
ret
例如在libcxx中,复制和移动构造函数都被声明为默认for tuple_leaf
和for tuple
,并且您获得了小型结构调用约定优化for std::tuple<int>
但{{3} }持有一个不可微动的成员,因此自然就变得不可微动。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。