![](/img/trans.png)
[英]Why is std::pair<int, int> constructible from a const std::pair<int, float>&?
[英]Why is the construction of std::optional<int> more expensive than a std::pair<int, bool>?
考虑这两种可以表示“可选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
为什么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
libstdc ++显然没有实现P0602“变体和可选应该传播复制/移动平凡” 。 您可以通过以下方式验证:
static_assert(std::is_trivially_copyable_v<std::optional<int>>);
对于libstdc ++来说失败了,并传递了libc ++和MSVC标准库 (它确实需要一个合适的名称,因此我们不必将其称为“C ++标准库的MSVC实现”或“MSVC STL”)。
当然,MSVC 仍然不会在寄存器中传递optional<int>
,因为MS ABI。
编辑:此问题已在GCC 8发布系列中修复。
为什么
get_std_optional_int()
需要三个mov
指令,而get_my_optional()
只需要一个movabs
?
直接原因是optional
通过隐藏指针返回,而pair
在寄存器中返回。 那为什么呢? SysV ABI规范,第3.2.3节参数传递说:
如果C ++对象具有非平凡的复制构造函数或非平凡的析构函数,则它由不可见的引用传递。
排除optional
的C ++混乱并不容易,但至少在我检查的实现的optional_base
类中似乎有一个非平凡的复制构造函数 。
在通过Agner Fog调用不同C ++编译器和操作系统的约定时,它表示复制构造函数或析构函数阻止在寄存器中返回结构。 这解释了为什么在寄存器中不返回optional
。
必须有其他东西阻止编译器进行商店合并( 将比单词更窄的立即值的连续存储合并到更少的更宽的存储中以减少指令数量 )... 更新: gcc bug 82434 - -fstore-merge不会工作可靠。
即使std::is_trivially_copyable_v<std::optional<int>>
为false, 技术上也允许优化 。 但是,编译器可能需要不合理程度的“聪明”才能找到。 此外,对于使用std::optional
作为函数的返回类型的特定情况,可能需要在链接时而不是编译时完成优化。
执行此优化不会影响任何(明确定义的)程序的可观察行为*,因此在as-if规则下隐式允许。 但是,由于其他答案中解释的原因,编译器尚未明确地意识到这一事实,需要从头开始推断。 行为静态分析本身就很困难 ,因此编译器可能无法证明此优化在所有情况下都是安全的。
假设编译器可以找到此优化,则需要更改此函数的调用约定(即更改函数返回给定值的方式),这通常需要在链接时完成,因为调用约定会影响所有调用站点。 或者,编译器可以完全内联函数,这可能在编译时可能也可能不可能。 对于易于复制的对象,这些步骤不是必需的,因此在这个意义上,标准确实会抑制优化并使其复杂化。
std::is_trivially_copyable_v<std::optional<int>>
应该是真的。 如果确实如此,编译器将更容易发现并执行此优化。 那么,回答你的问题:
这是QoI问题,还是
std::optional
的规范中存在阻止此优化的问题?
这两者都是。 该规范使得优化更难以找到,并且实现不够“智能”足以在这些约束下找到它。
*假设你没有做过一些非常奇怪的事情,比如#define int something_else
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.