web-dev-qa-db-ja.com

コンパイラの8ビットとしてのブール値。それらに対する操作は非効率的ですか?

Agner Fogの「 C++でソフトウェアを最適化する 」(Intel、AMD、およびVIAのx86プロセッサに固有)を読んでおり、34ページに記載されています

ブール変数は8ビット整数として格納され、値がfalseの場合は0、trueの場合は1になります。ブール変数は、入力としてブール変数を持つすべての演算子が入力に0または1以外の値があるかどうかをチェックするという意味で過剰決定されますが、出力としてブールを持つ演算子は0または1以外の値を生成できません。入力としてのブール変数は必要以上に効率的ではありません。

これは今日でも、どのコンパイラーでも当てはまりますか?例を挙げていただけますか?著者は述べています

オペランドが0と1以外の値を持たないことが確実にわかっている場合、ブール演算をはるかに効率的にすることができます。コンパイラがそのような仮定を行わない理由は、変数が初期化されていないか、不明なソースからのものです。

これは、たとえば関数ポインターbool(*)()を使用して呼び出した場合、その操作により非効率なコードが生成されることを意味しますか?それとも、ポインターを逆参照するか、参照から読み取ることによってブール値にアクセスしてから操作する場合ですか?

これは事実ではないと思います。

まず第一に、この推論はまったく受け入れられません。

コンパイラがこのような仮定を行わない理由は、変数が初期化されていないか、未知のソースからのものである場合、変数が他の値を持つ可能性があるためです。

いくつかのコードを確認しましょう(clang 6でコンパイルされていますが、GCC 7とMSVC 2017は同様のコードを生成します)。

ブール値または:

bool fn(bool a, bool b) {
    return a||b;
}

0000000000000000 <fn(bool, bool)>:
   0:   40 08 f7                or     dil,sil
   3:   40 88 f8                mov    al,dil
   6:   c3                      ret    

ご覧のとおり、ここでは0/1チェックなし、単純なor

boolをintに変換:

int fn(bool a) {
    return a;
}

0000000000000000 <fn(bool)>:
   0:   40 0f b6 c7             movzx  eax,dil
   4:   c3                      ret    

繰り返しますが、チェックは不要です。簡単に移動できます。

charをブールに変換:

bool fn(char a) {
    return a;
}

0000000000000000 <fn(char)>:
   0:   40 84 ff                test   dil,dil
   3:   0f 95 c0                setne  al
   6:   c3                      ret    

ここでは、charが0かどうかがチェックされ、それに応じてブール値が0または1に設定されます。

ですから、コンパイラはある意味でboolを使用し、常に0/1を含むと言っても安全だと思います。有効性をチェックすることはありません。

効率について:ブール値が最適だと思います。私が想像できる唯一のケースは、このアプローチが最適ではない場合、char-> bool変換です。 bool値が0/1に制限されない場合、その操作は単純なmovになります。他のすべての操作では、現在のアプローチは同等またはそれ以上です。


編集:Peter CordesがABIに言及した。 AMD64のSystem V ABIからの関連テキストを次に示します(i386のテキストも同様です)。

ブール値は、メモリオブジェクトに格納されると、シングルバイトオブジェクトとして格納され、その値は常に0(false)または1(true)です。整数レジスタに格納する場合(引数として渡す場合を除く)、レジスタの8バイトすべてが重要です。ゼロ以外の値はすべて真と見なされます

したがって、SysV ABIに準拠するプラットフォームでは、boolの値が0/1であることを確認できます。

MSVCのABIドキュメントを検索しましたが、残念ながらboolについて何も見つかりませんでした。

7
geza

次をclang ++ -O3 -Sでコンパイルしました

bool andbool(bool a, bool b)
{
    return a && b;
}

bool andint(int a, int b)
{
    return a && b;
}

.sファイルに含まれるもの:

andbool(bool, bool):                           # @andbool(bool, bool)
    andb    %sil, %dil
    movl    %edi, %eax
    retq

andint(int, int):                            # @andint(int, int)
    testl   %edi, %edi
    setne   %cl
    testl   %esi, %esi
    setne   %al
    andb    %cl, %al
    retq

明らかに、実行回数が少ないのはboolバージョンです。

0
Tony Delroy