为什么std :: optional <int>的构造比std :: pair <int,bool>更昂贵?

Vit*_*meo 65 c++ performance assembly x86-64 c++17

考虑这两种可以代表"可选int"的方法:

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;
Run Code Online (Sandbox Code Playgroud)

鉴于这两个功能......

auto get_std_optional_int() -> std_optional_int 
{
    return {42};
}

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}
Run Code Online (Sandbox Code Playgroud)

... g ++ trunkclang ++ trunk (with -std=c++17 -Ofast -fno-exceptions -fno-rtti)产生以下程序集:

get_std_optional_int():
        mov     rax, rdi
        mov     DWORD PTR [rdi], 42
        mov     BYTE PTR [rdi+4], 1
        ret

get_my_optional():
        movabs  rax, 4294967338 // == 0x 0000 0001 0000 002a
        ret
Run Code Online (Sandbox Code Playgroud)

godbolt.org上的实例


为什么get_std_optional_int()需要三个mov指令,而get_my_optional()只需要一个movabs这是QoI问题,还是说std::optional规范中存在阻止此优化的问题?

另请注意,无论如何,功能的用户可能会完全优化:

volatile int a = 0;
volatile int b = 0;

int main()
{
    a = get_std_optional_int().value();
    b = get_my_optional().first;
}
Run Code Online (Sandbox Code Playgroud)

...结果是:

main:
        mov     DWORD PTR a[rip], 42
        xor     eax, eax
        mov     DWORD PTR b[rip], 42
        ret
Run Code Online (Sandbox Code Playgroud)

Cas*_*sey 42

libstdc ++显然没有实现P0602"变体和可选应该传播复制/移动平凡".您可以通过以下方式验证:

static_assert(std::is_trivially_copyable_v<std::optional<int>>);
Run Code Online (Sandbox Code Playgroud)

对于libstdc ++来说失败了,并传递了libc ++和MSVC标准库(它确实需要一个合适的名称,因此我们不必将其称为"C++标准库的MSVC实现"或"MSVC STL").

当然,MSVC 仍然不会通过optional<int>寄存器,因为MS ABI.

编辑:此问题已在GCC 8发布系列中修复.

  • @MaximEgorushkin当T有一个时,它只需要一个析构函数. (6认同)

Jes*_*ter 17

为什么get_std_optional_int()需要三个mov指令,而 get_my_optional()只需要一个movabs

直接原因是在寄存器中optional返回时通过隐藏指针pair返回.那为什么呢?SysV ABI规范,第3.2.3参数传递说:

如果C++对象具有非平凡的复制构造函数或非平凡的析构函数,则它由不可见的引用传递.

排除C++混乱optional并不容易,但至少在optional_base我检查的实现类中似乎有一个非平凡的复制构造函数.

  • 不一定是,但可能是,这对编译器来说非常重要.所以你实际上必须看看实现. (3认同)

Max*_*kin 15

通过Agner Fog调用不同C++编译器和操作系统的约定时,它表示复制构造函数或析构函数阻止在寄存器中返回结构.这解释了为什么optional不在寄存器中返回.

必须有其他东西阻止编译器进行商店合并(将比单词更窄的立即值的连续存储合并到更少的更宽的存储中以减少指令数量)... 更新: gcc bug 82434 - -fstore-merge不会工作可靠.