web-dev-qa-db-ja.com

std :: optional <int>の構築がstd :: pair <int、bool>よりも高いのはなぜですか?

「オプションの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 ++ trunkclang ++ 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
_

godbolt.orgでの実例


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
_
66
Vittorio Romeo

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リリースシリーズで修正されました。

43
Casey

なぜget_std_optional_int()には3つのmov命令が必要なのに、get_my_optional()には単一のmovabsしか必要ないのですか?

直接的な原因は、optionalがレジスターで返されている間に、pairが隠しポインターを介して返されることです。それはなぜですか? SysV ABI仕様のセクション3.2.3 Parameter Passingによると:

C++オブジェクトに非自明なコピーコンストラクターまたは非自明なデストラクターがある場合、不可視の参照によって渡されます。

optionalであるC++の混乱を整理するのは簡単ではありませんが、少なくともチェックした実装のoptional_baseクラスに 非自明なコピーコンストラクタがあるようです

18
Jester

Agner FogによるさまざまなC++コンパイラーおよびオペレーティングシステムの呼び出し規則 では、コピーコンストラクターまたはデストラクターがレジスター内の構造体を返すのを防ぐことを示しています。これは、optionalがレジスターで返されない理由を説明しています。

コンパイラーがストアのマージを実行するのを妨げる何か他のものが必要です(Wordよりも狭い即値の隣接するストアを、命令の数を減らすためにより少ないストアにマージする)...更新:gccバグ82434--fstore-mergingが確実に機能しない

16

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のような本当に変なことをしていないと仮定します。

4
Kevin