哪个更快:if (bool) 或 if(int)?
Posted
技术标签:
【中文标题】哪个更快:if (bool) 或 if(int)?【英文标题】:Which is faster : if (bool) or if(int)? 【发布时间】:2011-08-11 12:28:03 【问题描述】:Which value is better to use? Boolean true or Integer 1?
上述主题让我在if
条件下对bool
和int
做了一些实验。所以出于好奇,我写了这个程序:
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。但这不是问题。问题就是我问的。
【问题讨论】:
这也是一个不公平的测试,除非您将它们与启用的合理优化进行比较。 @Daniel:我没有使用任何优化标志。但即使没有它,为什么它会为g(bool)
生成更多的 asm 是我正在寻找合理答案的问题。
你为什么要费心去阅读汇编,而不是仅仅运行程序和计时结果?指令的数量并没有真正说明性能。您不仅需要考虑指令长度,还需要考虑依赖关系和指令类型(其中一些是使用较慢的微码路径解码的,它们需要哪些执行单元,指令的延迟和吞吐量是多少,是分支?内存访问?
@user 未知,@Malvolio:很明显;我不是为生产代码做所有这些。正如我在文章开头已经提到的那样,“所以出于好奇,我编写了这个程序”。所以是的,它纯属假设。
这是一个合理的问题。它们要么相等,要么更快。 ASM 的发布可能是为了提供帮助或大声思考,因此与其将其用作回避问题并说“只需编写可读代码”的方式,不如直接回答问题或 STFU,如果您不知道或没什么好说的;)我的贡献是这个问题是可以回答的,“只写可读的代码”只不过是回避这个问题。
【参考方案1】:
对我来说很有意义。您的编译器显然将 bool
定义为 8 位值,并且您的系统 ABI 要求它在将小(bool,编译器生成代码以隔离 g 接收的 32 位参数的最低有效字节,并将其与 cmpb
进行比较。在第一个示例中,int
参数使用了全部压入堆栈的 32 位,因此它只是将整个事物与 cmpl
进行比较。
【讨论】:
我同意。这有助于说明在选择变量类型时,您选择它是为了两个可能相互竞争的目的,即存储空间与计算性能。 这是否也适用于 64 位进程,__int64
比 int
快?还是 CPU 分别处理 32 位整数和 32 位指令集?
@CrendKing 也许值得再问一个问题?
我希望您在回答中提供更多信息;所以你的意思是编译器会执行更多指令来将一个字节bool
强制转换到堆栈,而不是一次性将 32 位单元推入堆栈,不需要强制转换,这就是你的意思吗?在这个演员阵容中使用int
更快?【参考方案2】:
使用-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
和cmpb
。
这意味着差异(如果有的话)并不重要。以未优化的代码来判断是不公平的。
编辑来澄清我的观点。未优化的代码是为了简单的调试,而不是为了速度。比较未优化代码的速度是没有意义的。
【讨论】:
尽管我同意你的结论,但我认为你跳过了有趣的部分。 为什么它使用cmpl
和cmpb
另一个?
@jalf:因为bool
是一个字节,int
是四个。我认为没有什么比这更特别的了。
我认为其他回复更关注原因:这是因为该平台将bool
视为8位类型。
@Nathan:不。C++ 没有位数据类型。最小的类型是char
,按定义是一个字节,是最小的可寻址单元。 bool
的大小是实现定义的,可以是 1、4 或 8,或其他任何值。不过,编译器倾向于将其合二为一。
@Nathan:这在 Java 中也很棘手。 Java 表示布尔值表示的数据是一位的值,但如何存储该位仍然是实现定义的。实用计算机根本不处理位。【参考方案3】:
当我使用一组健全的选项(特别是 -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
。默认情况下,布尔值显然被视为一个字节,并且在 Intel 世界中处理字节时,您必须执行符号扩展等操作以将其带到特定宽度以将其放入堆栈等。(您不能推送一个字节.)
然而,当优化器查看 AST 并施展魔法时,它会查看周围的上下文并“知道”何时可以在不改变语义的情况下用更高效的代码替换代码。所以它“知道”它可以在参数中使用整数,从而失去不必要的转换和扩大。
【讨论】:
哈哈,我喜欢编译器如何简单地返回 99,或者 99+58 = 157 = -99(有符号 8 位溢出)...有趣。 @Daniel:即使我也喜欢这样。起初,我说“-99 在哪里”,然后我立即意识到它在做一些非常古怪的事情。l
和 b
是仅在 AT&T 语法中使用的后缀。它们只是分别使用 4 字节(长)和 1 字节(字节)操作数来引用 cmp
的版本。如果 intel 语法有任何歧义,通常会将内存操作数标记为 BYTE PTR
、WORD PTR
或 DWORD PTR
,而不是在操作码上添加后缀。
时序表:agner.org/optimizecmp
的两个操作数大小具有相同的性能,并且对于 读取 %dil
没有部分寄存器惩罚。 (但这并不能阻止 clang 通过在 AL 上使用字节大小 and
作为 99 和 -99 之间的无分支大小写翻转的一部分来有趣地创建部分寄存器停顿。)【参考方案4】:
至少在 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
是除非您谈论的是特定编译器的特定版本,否则并不完全正确。【参考方案5】:
在机器级别没有 bool 之类的东西
很少有指令集架构定义任何类型的布尔操作数类型,尽管经常有指令触发对非零值的操作。对于 CPU,通常,一切都是标量类型之一或它们的字符串。
给定的编译器和给定的 ABI 将需要为 int
和 bool
选择特定的大小,当像您的情况一样,这些大小不同时,它们可能会生成稍微不同的代码,并且在某些优化级别上可能会稍微快一点。
为什么 bool 在许多系统上都是一个字节?
为 bool 选择 char
类型更安全,因为有人可能会创建一个非常大的数组。
更新: “更安全”, 我的意思是:对于编译器和库实现者。 我并不是说人们需要重新实现系统类型。
【讨论】:
+1 想象一下如果bool
用位表示,x86 上的开销;所以在许多实现中,字节将是速度/数据紧凑性的一个很好的折衷。
@Billy:我认为他不是说“使用char
而不是bool
”,而是简单地使用“char
type”来表示“1 字节”的大小编译器选择 bool
对象。
哦,当然,我并不是说每个程序都应该选择,我只是在为系统 bool 类型为 1 字节的原因提出一个理由。
@Dennis:啊,有道理。【参考方案6】:
是的,讨论很有趣。但只是测试它:
测试代码:
#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
它们是相同的,每个循环超过 3000 万次循环 1 次分配和 2 次比较。找到其他要优化的东西。例如,不要不必要地使用 strcmp。 ;)
【讨论】:
【参考方案7】:这主要取决于编译器和优化。这里有一个有趣的讨论(与语言无关):
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/
【讨论】:
【参考方案8】:以两种不同的方式处理您的问题:
如果您具体谈论的是 C++ 或任何会为此生成汇编代码的编程语言,那么我们将受限于编译器将在 ASM 中生成的代码。我们也受制于 c++ 中真假的表示。整数必须以 32 位存储,我可以简单地使用一个字节来存储布尔表达式。 asm sn-ps 用于条件语句:
对于整数:
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
意味着设置标志的减法。它也与您的编译器生成的内容相矛盾。
另一种方法,一种更简单的方法,是自己查看表达式的逻辑,尽量不要担心编译器将如何翻译您的代码,我认为这是一种更健康的思维方式。归根结底,我仍然相信编译器生成的代码实际上是在试图给出一个真实的解决方案。我的意思是,也许如果你在 if 语句中增加测试用例并在一侧坚持使用布尔值,而在另一侧坚持使用整数,编译器会这样做,因此生成的代码将在机器级别使用布尔表达式更快地执行。
我认为这是一个概念性问题,所以我将给出一个概念性的答案。这个讨论让我想起了我经常讨论的关于代码效率是否会转化为更少的汇编代码行数的讨论。似乎这个概念被普遍接受为正确的。考虑到跟踪 ALU 处理每条语句的速度是不可行的,第二种选择是专注于汇编中的跳转和比较。在这种情况下,您提供的代码中布尔语句或整数之间的区别变得相当具有代表性。 C++ 中表达式的结果将返回一个值,该值将被赋予一个表示。另一方面,在汇编中,跳转和比较将基于数值,而不管在 C++ if 语句中评估的是什么类型的表达式。在这些问题上,重要的是要记住,诸如此类的纯逻辑语句最终会产生巨大的计算开销,即使一个比特也能完成同样的事情。
【讨论】:
以上是关于哪个更快:if (bool) 或 if(int)?的主要内容,如果未能解决你的问题,请参考以下文章
分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)