web-dev-qa-db-fra.com

Quel est le plus rapide: if (bool) ou if (int)?

Quelle valeur est préférable d'utiliser? Boolean true ou Integer 1?

Le sujet ci-dessus m'a fait faire quelques expériences avec bool et int dans la condition if. Donc, juste par curiosité, j'ai écrit ce programme:

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 Génère le code asm pour chaque fonction comme suit:

  • code asm pour 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
    
  • code asm pour 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
    

Étonnamment, g(bool) génère plus d'instructions asm! Cela signifie-t-il que if(bool) est un peu plus lent que if(int)? Je pensais que bool est spécialement conçu pour être utilisé dans une instruction conditionnelle telle que if, donc je m'attendais à ce que g(bool) génère moins d'instructions asm, faisant ainsi de la fonction g(bool) plus efficace et plus rapide.

MODIFIER:

Je n'utilise aucun indicateur d'optimisation pour l'instant. Mais même en son absence, pourquoi génère-t-il plus d'asm pour g(bool) est une question pour laquelle je cherche une réponse raisonnable. Je dois également vous dire que l'indicateur d'optimisation -O2 Génère exactement le même asm. Mais ce n'est pas la question. La question est ce que j'ai demandé.


89
Nawaz

Ça a du sens pour moi. Votre compilateur définit apparemment un bool comme une valeur de 8 bits, et votre système ABI l'exige pour "promouvoir" de petits arguments entiers (<32 bits) en 32 bits lors de leur insertion dans la pile des appels. Ainsi, pour comparer un bool, le compilateur génère du code pour isoler l'octet le moins significatif de l'argument 32 bits que g reçoit, et le compare avec cmpb. Dans le premier exemple, l'argument int utilise les 32 bits complets qui ont été poussés sur la pile, il se compare donc simplement à l'ensemble avec cmpl.

98
Sherm Pendley

Compilation avec -03 me donne ce qui suit:

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

.. donc il compile essentiellement le même code, à l'exception de cmpl vs cmpb. Cela signifie que la différence, s'il y en a, n'a pas d'importance. Il n'est pas juste de juger à l'aide d'un code non optimisé.


Modifier pour clarifier mon point. Le code non optimisé sert au débogage simple et non à la vitesse. Comparer la vitesse du code non optimisé est insensé.

78
Alexander Gessler

Lorsque je compile cela avec un ensemble sain d'options (en particulier -O3), voici ce que j'obtiens:

Pour 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

Pour 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

Ils utilisent toujours des instructions différentes pour la comparaison (cmpb pour booléen vs cmpl pour int), mais sinon les corps sont identiques. Un rapide coup d'œil aux manuels Intel me dit: ... pas grand-chose. Il n'y a rien de tel que cmpb ou cmpl dans les manuels Intel. Ils sont tous cmp et je ne trouve pas les tables de synchronisation pour le moment. Je suppose, cependant, qu'il n'y a aucune différence d'horloge entre la comparaison d'un octet immédiat et la comparaison d'un long immédiat, donc à toutes fins pratiques, le code est identique.


modifié pour ajouter ce qui suit en fonction de votre ajout

La raison pour laquelle le code est différent dans le cas non optimisé est qu'il n'est pas optimisé. (Oui, c'est circulaire, je sais.) Lorsque le compilateur parcourt le AST et génère directement du code, il ne "sait" rien sauf ce qui se trouve au point immédiat du AST il est là. À ce stade, il manque toutes les informations contextuelles nécessaires pour savoir qu'à ce stade spécifique, il peut traiter le type déclaré bool comme un int. Un booléen est évidemment par défaut, traité comme un octet et lorsque vous manipulez des octets dans le monde Intel, vous devez faire des choses comme l'extension de signe pour l'amener à certaines largeurs pour le mettre sur la pile, etc. (vous ne pouvez pas pousser un octet.)

Lorsque l'optimiseur visualise le AST et fait sa magie, cependant, il regarde le contexte environnant et "sait" quand il peut remplacer le code par quelque chose de plus efficace sans changer la sémantique. Il le "sait" donc peut utiliser un entier dans le paramètre et ainsi perdre les conversions et l'élargissement inutiles.

26

Avec GCC 4.5 sur Linux et Windows au moins, sizeof(bool) == 1. Sur x86 et x86_64, vous ne pouvez pas passer moins que la valeur d'un registre à usage général à une fonction (que ce soit via la pile ou un registre selon la convention d'appel, etc.).

Ainsi, le code de bool, lorsqu'il n'est pas optimisé, va en fait à une certaine longueur pour extraire cette valeur bool de la pile d'arguments (en utilisant un autre emplacement de pile pour enregistrer cet octet). C'est plus compliqué que de simplement extraire une variable native de la taille d'un registre.

13
Mat

Au niveau de la machine, il n'y a pas de bool

Très peu d'architectures de jeux d'instructions définissent une sorte de type d'opérande booléen, bien qu'il existe souvent des instructions qui déclenchent une action sur des valeurs non nulles. Pour le CPU, généralement, tout est l'un des types scalaires ou une chaîne d'entre eux.

Un compilateur donné et un ABI donné devront choisir des tailles spécifiques pour int et bool et quand, comme dans votre cas, ce sont des tailles différentes, ils peuvent générer du code légèrement différent, et à certains niveaux d'optimisation on peut être légèrement plus rapide.

Pourquoi bool un octet sur de nombreux systèmes?

Il est plus sûr de choisir un type char pour bool car quelqu'un pourrait en créer un très grand nombre.

pdate: by "safer", Je veux dire: pour les implémenteurs du compilateur et de la bibliothèque. Je ne dis pas que les gens doivent réimplémenter le type de système .

9
DigitalRoss

Ouais, la discussion est amusante. Mais testez-le:

Code de test:

#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;
}

Compilé sur un ordinateur portable Ubuntu 10.10 64 bits avec: g ++ -O3 -o/tmp/test_i /tmp/test_i.cpp

Comparaison basée sur des nombres entiers:

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

Test/impression booléen non commenté (et entier commenté):

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

Ils sont les mêmes avec 1 affectation et 2 comparaisons chaque boucle sur 30 millions de boucles. Trouvez autre chose à optimiser. Par exemple, n'utilisez pas inutilement strcmp. ;)

7
dannysauer

Cela dépendra principalement du compilateur et de l'optimisation. Il y a une discussion intéressante (indépendante de la langue) ici:

Est-ce que "if ([bool] == true)" nécessite une étape de plus que "if ([bool])"?

Jetez également un œil à cet article: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/

2
Aleadam

Aborder votre question de deux manières différentes:

Si vous parlez spécifiquement de C++ ou de tout langage de programmation qui produira du code Assembly d'ailleurs, nous sommes liés au code que le compilateur générera dans ASM. Nous sommes également liés à la représentation du vrai et du faux en c ++. Un entier devra être stocké sur 32 bits, et je pourrais simplement utiliser un octet pour stocker l'expression booléenne. Extraits de code Asm pour les instructions conditionnelles:

Pour l'entier:

  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

Pour le bool:

  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

C'est pourquoi la comparaison de vitesse dépend tellement de la compilation. Dans le cas ci-dessus, le booléen serait légèrement rapide puisque cmp impliquerait une soustraction pour définir les drapeaux. Cela contredit également ce que votre compilateur a généré.

Une autre approche, beaucoup plus simple, consiste à examiner la logique de l'expression seule et à ne pas se soucier de la façon dont le compilateur traduira votre code, et je pense que c'est une façon de penser beaucoup plus saine. Je crois toujours, en fin de compte, que le code généré par le compilateur essaie en fait de donner une résolution véridique. Ce que je veux dire, c'est que, peut-être si vous augmentez les cas de test dans l'instruction if et que vous vous en tenez à booléen d'un côté et entier dans un autre, le compilateur fera en sorte que le code généré s'exécute plus rapidement avec des expressions booléennes au niveau de la machine.

Je considère que c'est une question conceptuelle, je vais donc donner une réponse conceptuelle. Cette discussion me rappelle les discussions que j'ai couramment sur la question de savoir si l'efficacité du code se traduit ou non par moins de lignes de code dans Assembly. Il semble que ce concept soit généralement accepté comme étant vrai. Étant donné que le suivi de la vitesse à laquelle l'ALU traitera chaque instruction n'est pas viable, la deuxième option serait de se concentrer sur les sauts et les comparaisons dans Assembly. Lorsque c'est le cas, la distinction entre les déclarations booléennes ou les entiers dans le code que vous avez présenté devient plutôt représentative. Le résultat d'une expression en C++ retournera une valeur qui recevra alors une représentation. Dans Assembly, d'un autre côté, les sauts et les comparaisons seront basés sur des valeurs numériques, quel que soit le type d'expression qui a été évalué en retour sur votre instruction C++ if. Il est important sur ces questions de se rappeler que des déclarations purement logiques telles que celles-ci se retrouvent avec une énorme charge de calcul, même si un seul bit serait capable de la même chose.

0
Artie