首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么std::tuple破坏了C++中的小规模结构调用约定优化?

为什么std::tuple破坏了C++中的小规模结构调用约定优化?
EN

Stack Overflow用户
提问于 2020-09-03 07:58:33
回答 2查看 1.5K关注 0票数 29

C++有一个小规模的结构调用约定优化,编译器在函数参数中传递一个小型结构的效率与传递原始类型(例如通过寄存器)的效率一样高。例如:

代码语言:javascript
复制
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上,它看起来像是:

代码语言:javascript
复制
        mov     edi, 1
        jmp     foo(MyInt) ;tail-call optimization jmp instead of call ret

但是如果我们测试std::tuple<int>,它将是不同的:

代码语言:javascript
复制
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>)通过指针传递:

代码语言:javascript
复制
        sub     rsp, 24
        lea     rdi, [rsp+12]
        mov     DWORD PTR [rsp+12], 1
        call    foo(std::tuple<int>)
        add     rsp, 24
        ret

我做了更深入的研究,试图让我的int更脏一些(这应该接近于一个不完整的幼稚元组的推进):

代码语言:javascript
复制
class Empty {};
class MyDirtyInt : protected Empty, MyInt {public: using MyInt::MyInt; };
void foo(MyDirtyInt);
void bar5() { foo(MyDirtyInt(1)); }

但是应用了调用约定优化:

代码语言:javascript
复制
        mov     edi, 1
        jmp     foo(MyDirtyInt)

我试过GCC/Clang/MSVC,他们都表现出了相同的行为。(天栓链接)所以我想这一定是C++标准中的东西吧?(我认为C++标准没有指定任何ABI约束?)

我知道编译器应该能够优化这些输出,只要foo(std::tuple<int>)的定义是可见的,并且没有标记为noinline。但是,,我想知道标准或实现的哪一部分导致了这个优化的失效。

FYI,如果您对我正在使用std::tuple做什么感到好奇,我想创建一个包装类(即强myself ),并且不想自己声明比较操作符(操作符<==‘>在C++20之前),也不想费心使用Boost,所以我认为std::tuple是一个很好的基类,因为所有东西都在那里。

EN

回答 2

Stack Overflow用户

发布于 2020-09-03 12:31:48

这似乎是ABI的问题。例如,Itanium C++ ABI读取

如果参数类型是,对于调用来说并不简单,调用方必须为临时调用分配空间,并通过引用传递临时空间。

和,进一步

如果一个类型具有一个非平凡的复制构造函数、移动构造函数或析构函数,或者它的所有复制和移动构造函数都被删除,则在调用时,该类型被认为是非平凡的。

AMD64 ABI草案1.0中也有同样的要求。

例如,在libstdc++中,std::tuple具有重要的移动构造函数:https://godbolt.org/z/4j8vds。该标准规定了在默认情况下复制和移动构造函数,这一点在这里得到了满足。然而,与此同时,tuple _Tuple_impl具有用户定义的移动构造函数。。因此,移动tuple本身的构造函数不可能是一件简单的事情。

相反,在libc++中,std::tuple<int>的复制和移动构造函数都是微不足道的。因此,参数将在一个寄存器中传递:https://godbolt.org/z/WcTjM9

至于Microsoft STLstd::tuple<int>并不是可复制的,也不是可移动的.它甚至似乎违反了C++的标准规则。std::tuple是递归定义的,在递归结束时,std::tuple<>专门化定义了非默认复制构造函数。有一个关于这个问题的评论:// TRANSITION, ABI: should be defaulted。由于tuple<>没有移动构造函数,所以tuple<class...>的复制和移动构造函数都是非平凡的.

票数 12
EN

Stack Overflow用户

发布于 2020-09-03 11:39:44

正如@StoryTeller所建议的那样,它可能与导致此行为的std::tuple中的用户定义的move构造函数有关。

参见例如:https://godbolt.org/z/3M9KWo

拥有用户定义的移动构造函数将导致非优化程序集:

代码语言:javascript
复制
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中,复制和移动构造函数被声明为默认的tuple_leaftuple,您将得到小规模的struct调用约定优化std::tuple,但是包含非琐碎可移动成员的std::tuple本身就自然变得不可移动。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63719249

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档