上述主题让我在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)
__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)
__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。但这不是问题。问题是我问过的问题。
答案 0 :(得分:97)
对我有意义。您的编译器显然将bool
定义为8位值,并且系统ABI要求它在将小型(< 32位)整数参数推送到调用堆栈时将其“提升”为32位。因此,为了比较bool
,编译器生成代码以隔离g接收的32位参数的最低有效字节,并将其与cmpb
进行比较。在第一个示例中,int
参数使用推入堆栈的完整32位,因此它只是将整个事物与cmpl
进行比较。
答案 1 :(得分:77)
使用-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
之外,它编译为基本相同的代码。
这意味着差异(如果有的话)无关紧要。从未经优化的代码判断是不公平的。
修改以澄清我的观点。未经优化的代码用于简单的调试,而不是速度。比较未经优化的代码的速度是毫无意义的。
答案 2 :(得分: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),但是其他正文是相同的。快速浏览一下英特尔手册告诉我:......没什么特别的。英特尔手册中没有cmpb
或cmpl
这样的内容。他们都是cmp
,我现在找不到时间表。然而,我猜测,比较一个立即字节与比较一个长立即数之间没有时钟差异,因此对于所有实际目的,代码是相同的。
已编辑,可根据您的添加内容添加以下内容
在未经优化的情况下代码不同的原因是它未被优化。 (是的,它是循环的,我知道。)当编译器遍历AST并直接生成代码时,它不会“知道”除了它所处的AST的直接点之外的任何内容。此时它缺少所需的所有上下文信息。要知道在这个特定点上它可以将声明的类型bool
视为int
。布尔值显然默认被视为一个字节,当在英特尔世界中操作字节时,你必须做一些事情,比如sign-extend将它带到一定的宽度以将它放在堆栈上等等。(你不能推送一个字节。)
当优化器查看AST并发挥其魔力时,它会查看周围的上下文并“知道”何时可以用更高效的代码替换代码而不更改语义。所以它“知道”它可以在参数中使用整数,从而失去不必要的转换和扩展。
答案 3 :(得分:13)
至少在Linux和Windows上使用GCC 4.5,sizeof(bool) == 1
。在x86和x86_64上,您不能将少于通用寄存器的值传递给函数(无论是通过堆栈还是寄存器,取决于调用约定等...)。
所以bool的代码,当未经优化时,实际上会花一些时间从参数堆栈中提取bool值(使用另一个堆栈槽来保存该字节)。它比仅提取本地寄存器大小的变量更复杂。
答案 4 :(得分:9)
很少有指令集架构定义任何类型的布尔操作数类型,尽管通常有指令触发对非零值的操作。通常,对于CPU来说,一切都是标量类型之一或其中的一串。
给定的编译器和给定的ABI需要选择int
和bool
的特定大小,并且在您的情况下,这些大小不同时,它们可能会生成稍微不同的代码,并且在某些情况下优化水平可能稍快一些。
为bool选择char
类型会更安全,因为有人可能会为它们制作一个非常大的数组。
答案 5 :(得分: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。 ;)
答案 6 :(得分:2)
它主要取决于编译器和优化。这里有一个有趣的讨论(语言不可知):
Does "if ([bool] == true)" require one more step than "if ([bool])"?
另外,请看一下这篇文章:http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/
答案 7 :(得分:0)
以两种不同的方式处理您的问题:
如果您专门讨论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
那么,这就是为什么速度比较依赖于编译的原因。在上面的例子中,bool会稍微快一些,因为cmp
意味着设置标志的减法。它也与编译器生成的内容相矛盾。
另一种方法,一个更简单的方法,是查看它自己的表达式的逻辑,并尽量不要担心编译器将如何翻译你的代码,我认为这是一种更健康的方式思考。最后,我仍然相信编译器生成的代码实际上是在试图给出真实的解决方案。我的意思是,也许如果你在if语句中增加测试用例并在一侧坚持使用boolean而在另一侧坚持使用整数,编译器将使其生成,因此生成的代码将在机器级别使用布尔表达式执行得更快。 / p>
我认为这是一个概念性的问题,所以我会给出一个概念性的答案。这个讨论让我想起了我常常讨论的代码效率是否转化为汇编中较少的代码行。似乎这个概念被普遍认为是真实的。考虑到跟踪ALU处理每个语句的速度是不可行的,第二个选择是关注跳转和组装比较。在这种情况下,您呈现的代码中的布尔语句或整数之间的区别变得相当具有代表性。 C ++中表达式的结果将返回一个值,然后给出一个表示。另一方面,在汇编中,跳转和比较将基于数值,而不管在C ++ if语句中评估的表达式是什么类型。在这些问题上重要的是要记住,像这样的纯逻辑语句最终会产生巨大的计算开销,即使单个位能够做同样的事情。