上記のトピックにより、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)
よりも少し遅いということですか? bool
はif
などの条件ステートメントで使用するように特に設計されていると思っていたので、g(bool)
が生成するasm命令が少なくなり、g(bool)
より効率的かつ高速。
編集:
現在、最適化フラグを使用していません。しかし、それがなくても、なぜg(bool)
に対してより多くのasmを生成するのかは、合理的な答えを探している質問です。また、_-O2
_最適化フラグがまったく同じasmを生成することも説明する必要があります。しかし、それは問題ではありません。質問は私が尋ねたことです。
私には理にかなっています。コンパイラは明らかにbool
を8ビット値として定義しており、システムABIでは、コールスタックにプッシュするときに小さな(<32ビット)整数引数を32ビットに「昇格」する必要があります。したがって、bool
を比較するために、コンパイラは、gが受け取る32ビット引数の最下位バイトを分離するコードを生成し、cmpb
と比較します。最初の例では、int
引数はスタックにプッシュされた32ビット全体を使用するため、cmpl
を使用して全体と単純に比較します。
-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
を除き、本質的に同じコードにコンパイルされます。これは、差があれば、それは問題ではないことを意味します。最適化されていないコードによる判断は公平ではありません。
編集私のポイントを明確にします。最適化されていないコードは、単純なデバッグ用であり、速度ではありません。最適化されていないコードの速度を比較するのは無意味です。
これを適切なオプションセット(特に-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のマニュアルにはcmpb
やcmpl
のようなものはありません。それらはすべてcmp
であり、現時点ではタイミングテーブルが見つかりません。ただし、バイト即値の比較とロング即値の比較にクロックの違いはないため、すべての実用的な目的でコードは同じです。
追加に基づいて以下を追加するように編集
最適化されていない場合とコードが異なるのは、最適化されていないためです。 (はい、それは循環的です、私は知っています。)コンパイラがASTを歩き、コードを直接生成するとき、ASTのすぐのところにあるもの以外は何も「知りません」その時点では、この特定の時点で、宣言されたタイプbool
をint
として扱うことができることを知るために必要なすべてのコンテキスト情報が不足しています。ブールは明らかにデフォルトでバイトとして扱われ、Intelの世界でバイトを操作するときは、スタックに置くために特定の幅にするために符号拡張などを行う必要があります(バイトをプッシュすることはできません)
ただし、オプティマイザーがASTを表示してその魔法をかけるとき、周囲のコンテキストを見て、セマンティクスを変更せずにコードをより効率的なものに置き換えることができるかを「認識」します。そのため、パラメータで整数を使用できるため、不必要な変換と拡張が失われることを「認識」しています。
少なくともLinuxおよびWindows上のGCC 4.5では、sizeof(bool) == 1
。 x86およびx86_64では、汎用レジスターの価値よりも小さい値を関数に渡すことはできません(呼び出し規約に応じてスタックまたはレジスターを介して...)。
そのため、boolのコードは、最適化されていない場合、実際にはある程度の長さで引数スタックからそのbool値を抽出します(別のスタックスロットを使用してそのバイトを保存します)。ネイティブのレジスタサイズの変数を単にプルするよりも複雑です。
ゼロ以外の値でアクションをトリガーする命令が頻繁にありますが、任意の種類のブールオペランドタイプを定義する命令セットアーキテクチャはほとんどありません。 CPUにとっては、通常、すべてがスカラー型の1つまたはそれらの文字列です。
特定のコンパイラと特定のABIは、int
とbool
に特定のサイズを選択する必要があり、あなたの場合のように、これらが異なるサイズである場合、わずかに異なるコードを生成し、いくつかのレベルで最適化の1つはわずかに速いかもしれません。
Boolにはchar
型を選択する方が安全です。誰かがそれらの本当に大きな配列を作成する可能性があるからです。
更新: by "safer"、意味:コンパイラとライブラリの実装者向け人々がシステムタイプを再実装する必要があるとは言っていない。
ええ、ディスカッションは楽しいです。ただし、テストするだけです。
テストコード:
#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を不必要に使用しないでください。 ;)
主にコンパイラと最適化に依存します。ここで興味深い議論(言語に依存しない)があります:
「if([bool] == true)」は「if([bool])」よりももう1ステップ必要ですか?
また、この投稿を見てください: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/
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ビットでも同じことが可能であるとしても、膨大な計算オーバーヘッドが発生することを忘れないことです。