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

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

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

鉴于这两个function…

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

g ++ trunkclang ++ 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 

在godbolt.org上的实例


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

另外请注意,function的用户可能会完全优化,无论:

 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 

libstdc ++显然没有实现P0602“变体和可选应该传播复制/移动的小事” 。 你可以用下面的方法来validation

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

这对libstdc ++来说是失败的,并且传递给libc ++和MSVC标准库 (它确实需要一个正确的名称,所以我们不必将其称为“C ++标准库的MSVC实现”或“MSVC STL”)。

当然MSVC 仍然不会传递一个optional<int>在一个寄存器中,因为MS ABI。

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

直接的原因是optional是通过一个隐藏的指针返回,而pair返回一个寄存器。 那为什么呢? SysV ABI规范, 3.2.3parameter passing说:

如果一个C ++对象有一个非平凡的拷贝构造函数或者一个非平凡的析构函数,它将被不可见的引用传递。

optional的C ++混乱进行sorting并不容易,但似乎至less在我检查的实现的optional_base类中有一个不平凡的拷贝构造函数 。

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

必须有别的东西来阻止编译器进行存储合并( 将小于一个字的立即数值的连续存储区合并到更less的存储区中以减less指令数更新: gcc bug 82434 – -store-merge不可靠地工作。

优化在技术上是允许的 ,即使std::is_trivially_copyable_v<std::optional<int>>为false。 然而,编译器可能需要一个不合理的“巧妙”程度。 此外,对于使用std::optional作为函数的返回types的特定情况,优化可能需要在链接时而不是编译时完成。

执行此优化对任何(定义明确的)程序的可观察行为都没有影响,因此在if规则下隐式允许。 但是,由于其他答案中解释的原因,编译器还没有明确地意识到这一事实,并需要从头开始推断。 行为静态分析本质上是困难的 ,所以编译器可能无法certificate这种优化在任何情况下都是安全的。

假设编译器能够find这个优化,那么就需要改变这个函数的调用约定(即改变函数返回给定值的方式),这通常需要在链接时完成,因为调用约定会影响所有的调用点。 或者,编译器可以完全内联该函数,这在编译时可能或不可能完成。 这些步骤对于一个可以复制的对象来说是不必要的,所以在这个意义上,标准确实会抑制和复杂化优化。

std::is_trivially_copyable_v<std::optional<int>>应该是true。 如果这是真的,那么编译器就可以更容易地发现和执行这种优化。 所以,要回答你的问题:

这是一个QoI问题,还是有一些在std::optional的规范,防止这种优化?

这是两个。 规范使得优化很难find,而且在这些约束条件下,实现并不“足够聪明”。


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