web-dev-qa-db-ja.com

どちらが速いですか:if(bool)またはif(int)?

どの値を使用する方が良いですか?ブール値trueまたは整数1?

上記のトピックにより、bool条件でintおよびifを使用していくつかの実験を行うことができました。好奇心からこのプログラムを書きました。

_int f(int i) 
{
    if ( i ) return 99;   //if(int)
    else  return -99;
}
int g(bool b)
{
    if ( b ) return 99;   //if(bool)
    else  return -99;
}
int main(){}
_

_g++ intbool.cpp -S_は、次のように各関数のasmコードを生成します。

  • f(int)のasmコード

    ___Z1fi:
       LFB0:
             pushl  %ebp
       LCFI0:
              movl  %esp, %ebp
       LCFI1:
              cmpl  $0, 8(%ebp)
              je    L2
              movl  $99, %eax
              jmp   L3
       L2:
              movl  $-99, %eax
       L3:
              leave
       LCFI2:
              ret
    _
  • g(bool)のasmコード

    ___Z1gb:
       LFB1:
              pushl %ebp
       LCFI3:
              movl  %esp, %ebp
       LCFI4:
              subl  $4, %esp
       LCFI5:
              movl  8(%ebp), %eax
              movb  %al, -4(%ebp)
              cmpb  $0, -4(%ebp)
              je    L5
              movl  $99, %eax
              jmp   L6
       L5:
              movl  $-99, %eax
       L6:
              leave
       LCFI6:
              ret
    _

驚いたことに、g(bool)はさらにasm命令を生成します! if(bool)if(int)よりも少し遅いということですか? boolifなどの条件ステートメントで使用するように特に設計されていると思っていたので、g(bool)が生成するasm命令が少なくなり、g(bool)より効率的かつ高速。

編集:

現在、最適化フラグを使用していません。しかし、それがなくても、なぜg(bool)に対してより多くのasmを生成するのかは、合理的な答えを探している質問です。また、_-O2_最適化フラグがまったく同じasmを生成することも説明する必要があります。しかし、それは問題ではありません。質問は私が尋ねたことです。


89
Nawaz

私には理にかなっています。コンパイラは明らかにboolを8ビット値として定義しており、システムABIでは、コールスタックにプッシュするときに小さな(<32ビット)整数引数を32ビットに「昇格」する必要があります。したがって、boolを比較するために、コンパイラは、gが受け取る32ビット引数の最下位バイトを分離するコードを生成し、cmpbと比較します。最初の例では、int引数はスタックにプッシュされた32ビット全体を使用するため、cmplを使用して全体と単純に比較します。

98
Sherm Pendley

-03でコンパイルすると、次のことがわかります。

f:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

g:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

..したがって、cmpl vs cmpbを除き、本質的に同じコードにコンパイルされます。これは、差があれば、それは問題ではないことを意味します。最適化されていないコードによる判断は公平ではありません。


編集私のポイントを明確にします。最適化されていないコードは、単純なデバッグ用であり、速度ではありません。最適化されていないコードの速度を比較するのは無意味です。

78

これを適切なオプションセット(特に-O3)でコンパイルすると、次のようになります。

f()の場合:

_        .type   _Z1fi, @function
_Z1fi:
.LFB0:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpl    $1, %edi
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc
_

g()の場合:

_        .type   _Z1gb, @function
_Z1gb:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpb    $1, %dil
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc
_

それらはまだ比較のために異なる命令を使用します(ブール値の場合はcmpb、intの場合はcmpl)が、それ以外は本体は同一です。インテルのマニュアルをざっと見てみると、次のことがわかります。 Intelのマニュアルにはcmpbcmplのようなものはありません。それらはすべてcmpであり、現時点ではタイミングテーブルが見つかりません。ただし、バイト即値の比較とロング即値の比較にクロックの違いはないため、すべての実用的な目的でコードは同じです。


追加に基づいて以下を追加するように編集

最適化されていない場合とコードが異なるのは、最適化されていないためです。 (はい、それは循環的です、私は知っています。)コンパイラがASTを歩き、コードを直接生成するとき、ASTのすぐのところにあるもの以外は何も「知りません」その時点では、この特定の時点で、宣言されたタイプboolintとして扱うことができることを知るために必要なすべてのコンテキスト情報が不足しています。ブールは明らかにデフォルトでバイトとして扱われ、Intelの世界でバイトを操作するときは、スタックに置くために特定の幅にするために符号拡張などを行う必要があります(バイトをプッシュすることはできません)

ただし、オプティマイザーがASTを表示してその魔法をかけるとき、周囲のコンテキストを見て、セマンティクスを変更せずにコードをより効率的なものに置き換えることができるかを「認識」します。そのため、パラメータで整数を使用できるため、不必要な変換と拡張が失われることを「認識」しています。

少なくともLinuxおよびWindows上のGCC 4.5では、sizeof(bool) == 1。 x86およびx86_64では、汎用レジスターの価値よりも小さい値を関数に渡すことはできません(呼び出し規約に応じてスタックまたはレジスターを介して...)。

そのため、boolのコードは、最適化されていない場合、実際にはある程度の長さで引数スタックからそのbool値を抽出します(別のスタックスロットを使用してそのバイトを保存します)。ネイティブのレジスタサイズの変数を単にプルするよりも複雑です。

13
Mat

マシンレベルでは、boolなどはありません。

ゼロ以外の値でアクションをトリガーする命令が頻繁にありますが、任意の種類のブールオペランドタイプを定義する命令セットアーキテクチャはほとんどありません。 CPUにとっては、通常、すべてがスカラー型の1つまたはそれらの文字列です。

特定のコンパイラと特定のABIは、intboolに特定のサイズを選択する必要があり、あなたの場合のように、これらが異なるサイズである場合、わずかに異なるコードを生成し、いくつかのレベルで最適化の1つはわずかに速いかもしれません。

多くのシステムでboolが1バイトなのはなぜですか?

Boolにはchar型を選択する方が安全です。誰かがそれらの本当に大きな配列を作成する可能性があるからです。

更新: by "safer"、意味:コンパイラとライブラリの実装者向け人々がシステムタイプを再実装する必要があるとは言っていない。

9
DigitalRoss

ええ、ディスカッションは楽しいです。ただし、テストするだけです。

テストコード:

#include <stdio.h>
#include <string.h>

int testi(int);
int testb(bool);
int main (int argc, char* argv[]){
  bool valb;
  int  vali;
  int loops;
  if( argc < 2 ){
    return 2;
  }
  valb = (0 != (strcmp(argv[1], "0")));
  vali = strcmp(argv[1], "0");
  printf("Arg1: %s\n", argv[1]);
  printf("BArg1: %i\n", valb ? 1 : 0);
  printf("IArg1: %i\n", vali);
  for(loops=30000000; loops>0; loops--){
    //printf("%i: %i\n", loops, testb(valb=!valb));
    printf("%i: %i\n", loops, testi(vali=!vali));
  }
  return valb;
}

int testi(int val){
  if( val ){
    return 1;
  }
  return 0;
}
int testb(bool val){
  if( val ){
    return 1;
  }
  return 0;
}

64ビットUbuntu 10.10ラップトップ上でコンパイル:g ++ -O3 -o/tmp/test_i /tmp/test_i.cpp

整数ベースの比較:

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.203s
user    0m8.170s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.056s
user    0m8.020s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.116s
user    0m8.100s
sys 0m0.000s

ブールテスト/コメントなしの印刷(および整数コメント付き):

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.254s
user    0m8.240s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.028s
user    0m8.000s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m7.981s
user    0m7.900s
sys 0m0.050s

1つの割り当てと2つの比較で同じであり、各ループは3,000万ループを超えます。最適化する他の何かを見つけます。たとえば、strcmpを不必要に使用しないでください。 ;)

7
dannysauer

主にコンパイラと最適化に依存します。ここで興味深い議論(言語に依存しない)があります:

「if([bool] == true)」は「if([bool])」よりももう1ステップ必要ですか?

また、この投稿を見てください: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/

2
Aleadam

2つの異なる方法で質問にアプローチします。

特にC++またはアセンブリコードを生成するプログラミング言語について話している場合、コンパイラがASMで生成するコードに拘束されます。また、c ++のtrueとfalseの表現にも拘束されます。整数は32ビットで保存する必要がありますが、単純にバイトを使用してブール式を保存できます。条件ステートメントのasmスニペット:

整数の場合:

  mov eax,dword ptr[esp]    ;Store integer
  cmp eax,0                 ;Compare to 0
  je  false                 ;If int is 0, its false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

ブール値の場合:

  mov  al,1     ;Anything that is not 0 is true
  test al,1     ;See if first bit is fliped
  jz   false    ;Not fliped, so it's false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

そのため、速度の比較はコンパイルに依存する理由です。上記の場合、cmpはフラグを設定するための減算を意味するため、boolはわずかに高速になります。また、コンパイラが生成したものと矛盾します。

もっと簡単な別のアプローチは、式のロジックを独自に見て、コンパイラがコードをどのように変換するかを心配しないようにすることです。これははるかに健康的な考え方だと思います。最終的には、コンパイラーによって生成されるコードが実際に真実の解決を試みていると私は信じています。つまり、ifステートメントのテストケースを増やし、一方の側にブール値を、もう一方の側に整数値を使用すると、コンパイラーはそれを生成し、生成されたコードがマシンレベルのブール式でより速く実行されるようにします。

これは概念的な質問だと考えているので、概念的な答えをします。この議論は、コード効率がアセンブリのコード行の削減につながるかどうかについて私がよく持っている議論を思い起こさせます。この概念は一般に真実であると受け入れられているようです。 ALUが各ステートメントを処理する速度を追跡することは実行不可能であることを考慮すると、2番目のオプションは、アセンブリでのジャンプと比較に焦点を当てることです。その場合、提示したコード内のブール文または整数の区別はかなり代表的なものになります。 C++での式の結果は値を返し、その値に表現が与えられます。一方、アセンブリでは、C++ ifステートメントで評価された式のタイプに関係なく、ジャンプと比較は数値に基づいて行われます。これらの質問で重要なのは、これらのような純粋に論理的なステートメントは、たとえ1ビットでも同じことが可能であるとしても、膨大な計算オーバーヘッドが発生することを忘れないことです。

0
Artie