为什么构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 ++ 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
在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
。