哪个更快:if(bool)或if(int)?


94

使用哪个值更好?布尔值是true还是Integer 1?

上面的话题让我做一些实验与boolintif状态。因此,出于好奇,我编写了以下程序:

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)效率和速度。

编辑:

到目前为止,我没有使用任何优化标志。但是即使没有它,为什么它会生成更多的asm g(bool),这也是我正在寻找一个合理答案的问题。我还应该告诉您,-O2优化标志会生成完全相同的asm。但这不是问题。问题是我所问的。



32
除非您将它们与启用的合理优化进行比较,否则这也是一个不公平的测试。
Daniel Pryden 2011年

9
@Daniel:我没有对任何一个使用任何优化标志。但是,即使没有它,为什么它会生成更多的asm g(bool),这也是我正在寻找一个合理答案的问题。
Nawaz

8
为什么要麻烦阅读asm 而不是仅运行程序并计时结果?讲课的数量并没有真正说明性能。您不仅需要考虑指令长度,还需要考虑指令的依赖性和类型(其中一些指令是使用较慢的微码路径进行解码的,它们需要哪些执行单元,指令的等待时间和吞吐量是多少?分支吗?内存访问吗?
jalf

2
@用户未知,和@Malvolio:显然是这样;我没有为生产代码做所有这些事情。正如我在文章开头提到的那样:“出于好奇,我编写了该程序”。是的,这纯粹是假设的
Nawaz

3
这是一个合理的问题。它们相等或更快。发布ASM可能是为了寻求帮助或大声思考,因此与其将其用作避开问题并说“只写可读代码”的方法,否则请回答问题或STFU(如果您不知道或不知道)没什么可说的;)我的贡献是,这个问题是可以回答的,“只是编写可读的代码”不过是对这个问题的回避。
Triynko 2011年

Answers:


99

我感觉合理。您的编译器显然将a定义bool为8位值,并且系统ABI要求它在将小(<32位)整数参数推入调用堆栈时将其“提升”为32位。因此,为了比较a bool,编译器生成代码以隔离g接收的32位参数的最低有效字节,并将其与进行比较cmpb。在第一个示例中,该int参数使用了被压入堆栈的完整32位,因此它只是简单地与进行了比较cmpl


4
我同意。这有助于说明在选择变量类型时,您出于两个潜在的竞争目的(存储空间与计算性能)选择它。
Triynko 2011年

3
这是否也适用于__int64速度比64位更快的64位进程int?还是CPU用32位指令集分别处理32位整数?
Crend King

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:因为a bool是一个字节,而an int是四个。我认为没有什么比这更特别的了。
CB Bailey

7
我认为其他回应会更加关注这些原因:这是因为相关平台被视为bool8位类型。
亚历山大·盖斯勒

9
@Nathan:否。C++没有位数据类型。最小的类型是char,根据定义,它是一个字节,并且是最小的可寻址单元。bool的大小是由实现定义的,可以为1、4或8,或其他任何值。但是,编译器倾向于使其成为一体。
GManNickG 2011年

6
@Nathan:在Java中,这也很棘手。Java说布尔值表示的数据是一位的值,但是该位的存储方式仍由实现定义。实用计算机根本不处理位。
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对于布尔值对cmplint),但是在其他方面它们是相同的。快速浏览一下英特尔手册可以告诉我:...没什么。有因为没有这样的事情cmpb还是cmpl在英特尔手册。它们全都是cmp,我现在找不到时间表。但是,我猜测比较立即字节与比较立即字节之间没有时钟差异,因此对于所有实际目的,代码是相同的。


根据您的添加进行了编辑以添加以下内容

在未优化的情况下,代码不同的原因是未优化。(是的,这是循环的,我知道。)当编译器遍历AST并直接生成代码时,除了所处的AST的即时点外,它“不知道”任何东西。那时,它缺少所需的所有上下文信息。知道在特定的时刻它可以将声明的类型bool当作一个int。布尔值显然默认情况下被视为一个字节,并且在Intel世界中操作字节时,您必须执行诸如符号扩展之类的操作以将其调整为一定的宽度以将其放入堆栈等。(您不能将字节压入)

但是,当优化器查看AST并执行其魔术时,它会查看周围的上下文并“知道”何时可以在不更改语义的情况下用更高效的代码替换代码。因此,它“知道”它可以在参数中使用整数,从而丢失不必要的转换和扩展。


1
哈哈,我喜欢编译器如何简单地返回99或99 + 58 = 157 = -99(有符号8位溢出)...有趣。
deceleratedcaviar

@Daniel:甚至我都喜欢。刚开始,我说“ -99在哪里”,然后我立即意识到它的作用非常奇怪。
Nawaz

7
lb仅在AT&T语法中使用的后缀。它们仅指分别cmp使用4字节(长)和1字节(字节)操作数的版本。如果intel语法有任何歧义,通常以标记操作内存操作数BYTE PTRWORD PTR或者DWORD PTR在操作码上添加后缀。
CB Bailey

时序表: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

在机器级别上没有bool这样的东西

很少有指令集体系结构定义任何类型的布尔操作数类型,尽管经常有指令触发对非零值的操作。对于CPU,通常,所有内容都是标量类型之一或它们的字符串。

一个给定的编译器和一个给定的ABI需要选择特定的尺寸intbool和时,像你的情况,这些都是不同的规格,它们可能会产生略有不同的代码,并且在一个优化的一些水平可能稍快。

为什么在许多系统上bool都是一个字节?

选择char布尔类型比较安全,因为有人可能会制作很多类型的布尔值。

更新:通过“安全”的,我的意思是:编译器和库的实现者。我并不是说人们需要重新实现系统类型。


2
+1想象一下x86的开销(如果bool用位表示);因此在许多实现中,字节将是速度/数据紧凑性的不错折衷。
hardmath 2011年

1
@Billy:我认为他不是在指代编译器为对象选择的大小时说“使用char而不是bool”,而是简单地使用“ char类型”来表示“ 1字节” bool
丹尼斯·齐克福斯

哦,当然,我并不是说每个程序都应该选择,我只是在提出为什么系统布尔类型为1字节的理由。
DigitalRoss

@丹尼斯:啊,这很有道理。
Billy 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

以两种不同的方式处理您的问题:

如果您是专门谈论C ++或将为此生成汇编代码的任何编程语言,那么我们将受限于编译器将在ASM中生成的代码。我们还必须约束c ++中true和false的表示。一个整数必须存储在32位中,我可以简单地使用一个字节来存储布尔表达式。条件语句的汇编代码段:

对于整数:

  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语句正在评估哪种表达式类型。在这些问题上要记住,重要的是要记住,诸如此类的纯逻辑语句最终会带来巨大的计算开销,即使一点点就能完成相同的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.