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

时间:2017-10-03 11:55:05

标签: c++ performance assembly x86-64 c++17

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

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;

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

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

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}

... g ++ trunk clang ++ trunk (与-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

live example on 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;
}

...导致:

main:
        mov     DWORD PTR a[rip], 42
        xor     eax, eax
        mov     DWORD PTR b[rip], 42
        ret

4 个答案:

答案 0 :(得分:42)

libstdc ++显然没有实现P0602 "variant and optional should propagate copy/move triviality"。您可以通过以下方式验证:

static_assert(std::is_trivially_copyable_v<std::optional<int>>);

which fails for libstdc++, and passes for libc++ and the MSVC standard library(这确实需要一个正确的名称,因此我们不必将其称为&#34; C ++标准库的MSVC实现&#34;或者#34; MSVC STL& #34;。)

当然,由于MS ABI,MSVC 无法在寄存器中传递optional<int>

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

答案 1 :(得分:18)

  

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

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

  

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

排除optional的C ++混乱并不容易,但似乎有non-trivial copy constructor at least in the optional_base class of the implementation I checked

答案 2 :(得分:16)

Calling conventions for different C++ compilers and operating systems by Agner Fog中,它表示复制构造函数或析构函数阻止在寄存器中返回结构。这解释了为什么optional未在寄存器中返回。

必须有其他东西阻止编译器进行商店合并(将比单词更窄的直接值的连续存储合并到更少的更广泛的存储中以减少指令数)... 更新: gcc bug 82434 - -fstore-merging does not work reliably.

答案 3 :(得分:4)

技术上允许进行优化,即使std::is_trivially_copyable_v<std::optional<int>>为false也是如此。但是,编译器可能需要不合理程度的“聪明”才能找到。此外,对于使用std::optional作为函数的返回类型的特定情况,可能需要在链接时而不是编译时完成优化。

执行此优化不会影响任何(明确定义的)程序的可观察行为*,因此在as-if rule下隐式允许。但是,由于其他答案中解释的原因,编译器尚未明确地意识到这一事实,需要从头开始推断。行为静态分析是inherently difficult,因此编译器可能无法证明此优化在所有情况下都是安全的。

假设编译器可以找到此优化,则需要更改此函数的调用约定(即更改函数返回给定值的方式),这通常需要在链接时完成,因为调用约定会影响所有的呼叫站点。或者,编译器可以完全内联函数,这可能在编译时可能也可能不可能。对于一个易于复制的对象,这些步骤不是必需的,因此从这个意义上说,标准确实会抑制优化并使其复杂化。

std::is_trivially_copyable_v<std::optional<int>>应该是真的。如果确实如此,编译器将更容易发现并执行此优化。所以,回答你的问题:

  

这是QoI问题,还是std::optional规范中存在阻止此优化的内容?

两者都是。该规范使得优化更难以找到,并且实现不够“智能”足以在这些约束下找到它。

*假设你没有做过一些非常奇怪的事情,比如#define int something_else