「オプションのint
」を表すことができるこれらの2つのアプローチを検討してください。
_using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;
_
これら2つの機能を考えると...
_auto get_std_optional_int() -> std_optional_int
{
return {42};
}
auto get_my_optional() -> my_optional_int
{
return {42, true};
}
_
...g ++ trunkとclang ++ 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
_
get_std_optional_int()
が3つの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「バリアントとオプションはcopy/move trivialityを伝播する必要があります」 。これを確認するには:
static_assert(std::is_trivially_copyable_v<std::optional<int>>);
これはlibstdc ++で失敗し、libc ++およびMSVC標準ライブラリに渡されます (実際には適切な名前が必要なので、「C++標準ライブラリのMSVC実装」または「 MSVC STL」)。
もちろん、MSVCstillは、MS ABIであるため、レジスタでoptional<int>
を渡しません。
編集:この問題はGCC 8リリースシリーズで修正されました。
なぜ
get_std_optional_int()
には3つのmov
命令が必要なのに、get_my_optional()
には単一のmovabs
しか必要ないのですか?
直接的な原因は、optional
がレジスターで返されている間に、pair
が隠しポインターを介して返されることです。それはなぜですか? SysV ABI仕様のセクション3.2.3 Parameter Passingによると:
C++オブジェクトに非自明なコピーコンストラクターまたは非自明なデストラクターがある場合、不可視の参照によって渡されます。
optional
であるC++の混乱を整理するのは簡単ではありませんが、少なくともチェックした実装のoptional_base
クラスに 非自明なコピーコンストラクタがあるようです 。
Agner FogによるさまざまなC++コンパイラーおよびオペレーティングシステムの呼び出し規則 では、コピーコンストラクターまたはデストラクターがレジスター内の構造体を返すのを防ぐことを示しています。これは、optional
がレジスターで返されない理由を説明しています。
コンパイラーがストアのマージを実行するのを妨げる何か他のものが必要です(Wordよりも狭い即値の隣接するストアを、命令の数を減らすためにより少ないストアにマージする)...更新:gccバグ82434--fstore-mergingが確実に機能しない
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
のような本当に変なことをしていないと仮定します。