哪个更快:if(bool)还是if(int)?

Naw*_*waz 90 c++ int assembly boolean

哪个值更好用?布尔值是真还是整数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(){}
Run Code Online (Sandbox Code Playgroud)

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
    
    Run Code Online (Sandbox Code Playgroud)
  • 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
    
    Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,g(bool)生成更多asm指令!这是否意味着if(bool)比它慢一点if(int)?我曾经认为bool特别设计用于条件语句,例如if,因此我期望g(bool)生成更少的asm指令,从而提高g(bool)效率和速度.

编辑:

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


She*_*ley 98

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

  • 我同意.这有助于说明在选择变量类型时,您选择它是为了两个潜在的竞争目的,存储空间与计算性能. (4认同)
  • 这是回答这个问题的好尝试.+1 (3认同)
  • 这是否也适用于64位进程,`__ int64`比`int`快?或者CPU分别处理32位整数和32位指令集? (3认同)
  • @CrendKing 也许值得提出另一个问题? (3认同)

Ale*_*ler 78

编译-03为我提供以下内容:

F:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret
Run Code Online (Sandbox Code Playgroud)

G:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret
Run Code Online (Sandbox Code Playgroud)

..所以它编译成基本上是相同的代码,除了cmplVS cmpb.这意味着差异(如果有的话)无关紧要.从未经优化的代码判断是不公平的.


编辑以澄清我的观点.未优化的代码用于简单的调试,而不是速度.比较未经优化的代码的速度是毫无意义的.

  • @jalf:因为`bool`是单个字节而``int`是4.我认为没有比这更特别的东西了. (21认同)
  • @Nathan:没有.C++没有位数据类型.最小的类型是`char`,它是按字节定义的字节,是最小的可寻址单元.`bool`的大小是实现定义的,可能是1,4或8,或者其他什么.但编译器倾向于使其成为一体. (9认同)
  • 尽管我同意你的结论,但我认为你正在跳过有趣的部分.*为什么*它使用`cmpl`为一个而`cmpb`用于另一个? (7认同)
  • 我认为其他回应更加注重原因:这是因为有问题的平台将`bool`视为8位类型. (7认同)
  • @Nathan:在Java中也很棘手.Java表示布尔表示的数据是一位的值,但该位的存储方式仍然是实现定义的.务实的计算机根本不解决比特问题. (6认同)
  • @Charles:是的,但是imo属于答案.如果找到差异并且没有提及它的含义是什么,你就不能真正理解"没有区别".:) (3认同)

JUS*_*ION 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
Run Code Online (Sandbox Code Playgroud)

用于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
Run Code Online (Sandbox Code Playgroud)

它们仍然使用不同的指令进行比较(cmpb对于boolean与cmplint),但是其他主体是相同的.快速浏览一下英特尔手册告诉我:......没什么特别的.有因为没有这样的事情cmpb还是cmpl在英特尔手册.他们都是cmp,我现在找不到时间表.然而,我猜测,比较一个立即字节与比较一个长立即数之间没有时钟差异,所以对于所有实际目的,代码是相同的.


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

代码在未经优化的情况下不同的原因是它未被优化.(是的,它是循环的,我知道.)当编译器遍历AST并直接生成代码时,它不会"知道"除了它所处的AST的直接点之外的任何内容.此时它缺少所需的所有上下文信息.要知道在这个特定点上它可以将声明的类型bool视为一个int.布尔值显然默认被视为一个字节,当在英特尔世界中操作字节时,你必须做一些事情,比如sign-extend将它带到一定的宽度以将它放在堆栈上等等.(你不能推送一个字节.)

然而,当优化器查看AST并发挥其魔力时,它会查看周围的上下文并"知道"何时可以在不改变语义的情况下用更高效的代码替换代码.因此它"知道"它可以在参数中使用整数,从而失去不必要的转换和扩展.

  • `l`和`b`是仅在AT&T语法中使用的后缀.它们只是分别使用4字节(长)和1字节(字节)操作数来引用`cmp`的版本.在intel语法中存在任何歧义的情况下,通常,内存操作数用"BYTE PTR","WORD PTR"或"DWORD PTR"标记,而不是在操作码上添加后缀. (7认同)

Mat*_*Mat 13

至少在Linux和Windows上使用GCC 4.5 sizeof(bool) == 1.在x86和x86_64上,您不能将少于通用寄存器的值传递给函数(无论是通过堆栈还是寄存器,取决于调用约定等...).

所以bool的代码,当未经优化时,实际上会花一些时间从参数堆栈中提取bool值(使用另一个堆栈槽来保存该字节).它比仅提取本地寄存器大小的变量更复杂.


Dig*_*oss 9

在机器级别没有布尔这样的东西

很少有指令集体系结构定义任何类型的布尔操作数类型,尽管通常有指令触发对非零值的操作.通常,对于CPU来说,一切都是标量类型之一或其中的一串.

给定的编译器和给定的ABI将需要选择特定的大小int,bool以及何时,在您的情况下,这些是不同的大小,它们可能会生成稍微不同的代码,并且在某些优化级别,可能会稍快一些.

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

char为bool 选择一个类型会更安全,因为有人可能会制作一个非常大的类型.

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

  • +1如果`bool`由位表示,想象一下x86上的开销; 所以在许多实现中,字节将是速度/数据紧凑性的一个很好的权衡. (2认同)

dan*_*uer 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;
}
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

布尔测试/打印未注释(和整数注释):

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
Run Code Online (Sandbox Code Playgroud)

它们与1个赋值相同,2个比较每个循环超过3000万个循环.找到其他优化的东西.例如,不要不必要地使用strcmp.;)