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


94

どちらの値を使用する方が良いですか?ブール値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コードを生成します。

  • asmコード f(int)

    __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
  • asmコード g(bool)

    __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)ですか?以前boolはのような条件付きステートメントで使用するように特別に設計されていたと思っifていg(bool)たので、asm命令の生成を減らして、g(bool)より効率的で高速にすることを期待していました。

編集:

現在、最適化フラグは使用していません。しかし、それがなくても、なぜそれがより多くg(bool)の意見を生み出すのかは、私が合理的な答えを探している質問です。また、-O2最適化フラグがまったく同じasmを生成することもお伝えしておきます。しかし、それは問題ではありません。問題は私が尋ねたことです。



32
また、妥当な最適化を有効にして比較しない限り、これは不当なテストです。
Daniel Pryden、2011

9
@ダニエル:どちらの最適化フラグも使用していません。しかし、それがない場合でも、なぜそれがより多くのasmを生成するのかg(bool)は、私が合理的な答えを探している質問です。
Nawaz、2011

8
なぜプログラムを実行して結果のタイミングをとるだけなく、asmを読み取る問題に行くのですか?命令の数は実際にはパフォーマンスについてあまり言いません。命令の長さだけでなく、依存関係と命令のタイプも考慮に入れる必要があります(それらの一部は、より低速なマイクロコードパスを使用してデコードされますが、実行ユニットに必要なのは、命令のレイテンシとスループットは何ですか?ブランチ?メモリアクセス?
jalf

2
@user unknown、および@Malvolio:明らかにそうです。私はこれらすべてを量産コードのために行っているわけではありません。私はすでに、というのが私の記事の冒頭で述べたように、「だから好奇心のうち、私はこのプログラムを書きました」。ええ、それは純粋に仮説的なものです。
Nawaz、2011

3
それは正当な質問です。それらは同等か、どちらかが高速です。ASMはおそらく役立つか大声で考えて投稿されたので、質問をかわして「ただ読むことができるコードを書く」方法として使用するのではなく、質問に答えるか、知らない場合はSTFUに答えるか、言いたいことは何もありません;)私の貢献は、質問に答えることができることであり、「単に読み取り可能なコードを書く」ことは、質問を回避することに他なりません。
Triynko

回答:


99

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


4
同意する。これは、変数の型を選択するときに、ストレージスペースと計算パフォーマンスの2つの潜在的に競合する目的にそれを選択していることを明らかにするのに役立ちます。
Triynko

3
これはまた、64ビットプロセスに適用されない__int64よりも高速ですかint?または、CPUは32ビット整数セットと32ビット命令セットを別々に扱いますか?
Crend King 2011

1
@CrendKing多分それは別の質問を転がす価値がありますか?
表示名

81

でコンパイルすると-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

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


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


8
あなたの結論に同意する限り、興味深い部分をスキップしていると思います。なぜそれcmplを一方とcmpb他方に使用するのですか?
jalf

22
@jalf:boolは1バイトで、intは4 バイトなので。それ以上特別なものはないと思います。
CBベイリー

7
他の応答は理由にもっと注意を払ったと思います:それは問題のプラットフォームがbool8ビットタイプとして扱うためです。
Alexander Gessler、2011

9
@ネイサン:いいえ。C++にはビットデータ型がありません。最小の型はcharであり、これは定義によりバイトであり、アドレス可能な最小単位です。boolのサイズは実装によって定義され、1、4、8、またはその他のサイズになります。ただし、コンパイラはそれを1つにする傾向があります。
GManNickG 2011

6
@ネイサン:Javaでもそれはトリッキーです。Javaは、ブール値が表すデータは1ビットの値であると述べていますが、そのビットがどのように格納されるかは、依然として実装定義です。実用的なコンピュータは単にビットをアドレス指定しません。
GManNickG 2011

26

これを正しいオプションセット(具体的には-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ブール値cmplとintの場合)、それ以外は本体は同一です。Intelのマニュアルをざっと見ると、次のことがわかります。以下のようなものはありませんcmpbか、cmplインテルのマニュアルでは。それらはすべてでcmpあり、現時点ではタイミングテーブルを見つけることができません。ただし、バイトの即時比較と長い即時比較の間にクロックの違いはないので、すべての実用的な目的でコードは同一であると思います。


あなたの追加に基づいて以下を追加するように編集されました

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

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


1
はは、私はコンパイラが単に99、または99 + 58 = 157 = -99(符号付き8ビットのオーバーフロー)を返した方法が好きです...興味深いです。
deceleratedcaviar

@ダニエル:私もそれが好きだった。最初は「-99はどこですか」と言ったところ、すぐに、それが非常に異常なことをしていることに気付きました。
Nawaz

7
lおよびbは、AT&T構文でのみ使用される接尾辞です。これらはcmp、それぞれ4バイト(ロング)と1バイト(バイト)のオペランドを使用するバージョンを示しています。インテルの構文のいずれかのあいまいさがある場合、従来のメモリオペランドがでタグ付けされBYTE PTRWORD PTRまたはDWORD PTRその代わりオペコードに接尾辞を置きます。
CBベイリー

タイミングテーブル:agner.org/optimizeの 両方のオペランドサイズはcmp同じパフォーマンスを持ち、読み取りの ための部分的なレジスターのペナルティはありません%dil。(ただし、clang andがALでバイトサイズを使用して99と-99の間のブランチなしのケースフリッピングの一部として部分レジスターストールを面白いように作成することを止めることはできません。)
Peter Cordes

13

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

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


C ++ 03標準から、§5.3.3/ 1:「sizeof(bool)そしてsizeof(wchar_t)実装定義です。」したがってsizeof(bool) == 1、特定のコンパイラの特定のバージョンについて話しているのでない限り、言うことは厳密には正しくありません。
ildjarn、2011

9

マシンレベルではブールなどはありません

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

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

多くのシステムでブールが1バイトになるのはなぜですか?

char誰かが実際に大きな配列を作成する可能性があるため、boolのタイプを選択する方が安全です。

更新:によって「より安全」、私は意味:コンパイラとライブラリ実装者のために。私は人々がシステムタイプを再実装する必要があると言っているのではありません。


2
+1 boolビットで表された場合のx86のオーバーヘッドを想像してください。そのため、多くの実装では、バイトは速度とデータのコンパクトさのトレードオフになります。
hardmath

1
@ビリー:コンパイラがオブジェクト用に選択するサイズを参照するとき、彼は「char代わりにbool使用する」ではなく、単に「charタイプ」を使用して「1バイト」を意味すると言っていたと思いboolます。
Dennis Zickefoose

ああ、確かに、各プログラムが選択する必要があるという意味ではありませんでした。システムbool型が1バイトである理由を説明しているだけです。
DigitalRoss

@デニス:ああ、それは理にかなっています。
ビリーONeal

7

ええ、議論は楽しいです。しかし、それをテストしてください:

テストコード:

#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つの比較があり、各ループは3000万ループを超えます。最適化する他の何かを見つけてください。たとえば、不必要にstrcmpを使用しないでください。;)



0

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

boolの場合:

  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、フラグを設定するための減算を意味するため、ブール値はわずかに高速になります。また、コンパイラが生成したものとも矛盾します。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.