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

Quelle valeur est préférable d’utiliser? Booléen vrai ou entier 1?

Le sujet ci-dessus m’a fait faire des expériences avec bool et int dans condition if . Donc, 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 ! Est-ce que cela signifie que if(bool) est un peu plus lent que if(int) ? J’avais l’habitude de penser que bool est spécialement conçu pour être utilisé dans des instructions conditionnelles comme if , donc je m’attendais à ce que g(bool) génère moins d’instructions asm, rendant ainsi g(bool) plus efficace et plus rapide.

MODIFIER:

Je n’utilise pas de drapeau d’optimisation dès maintenant. Mais même en l’absence, pourquoi cela génère-t-il plus d’asm pour g(bool) est une question pour laquelle je cherche une réponse raisonnable. Je devrais é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é.


Cela a du sens pour moi. Votre compilateur définit apparemment un bool comme une valeur de 8 bits, et votre ABI système exige qu’il “promeut” de petits arguments entiers (<32 bits) en 32 bits lors de leur insertion dans la pile d'appels. Donc, 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é insérés dans la stack. Il se compare donc simplement à l’ensemble avec cmpl .

Comstackr 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 comstack essentiellement le même code, sauf pour cmpl vs cmpb . Cela signifie que la différence, s’il en existe, n’a pas d’importance. Juger par code non optimisé n’est pas juste.


Modifier pour clarifier mon point. Le code non optimisé est pour le débogage simple, pas pour la vitesse. La comparaison de la vitesse du code non optimisé est insensée.

Quand je comstack ceci avec un ensemble sain d’options (spécifiquement -O3), voici ce que j’ai:

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 encore 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. cmpb ou cmpl dans les manuels Intel. Ils sont tous cmp et je ne trouve pas les tables de chronométrage pour le moment. Je suppose cependant qu’il n’y a pas de différence d’horloge entre la comparaison d’un octet immédiat et la comparaison d’un immédiat immédiat. Par conséquent, à toutes fins pratiques, le code est identique.


modifié pour append 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 l’AST et génère directement du code, il ne “sait” rien d’autre que ce qui se trouve au point immédiat de l’AST. pour savoir qu’à ce point précis, il peut traiter le type déclaré bool comme un int . Un booléen est évidemment traité par défaut comme un octet et lorsque vous manipulez des octets dans le monde Intel, vous devez faire des choses comme une extension de signe pour l’amener à une certaine largeur, etc. (Vous ne pouvez pas pousser un octet .)

Lorsque l’optimiseur visualise l’AST et effectue sa magie, cependant, il examine le contexte environnant et “sait” quand il peut remplacer le code par quelque chose de plus efficace sans modifier la sémantique. Donc, il “sait” qu’il peut utiliser un entier dans le paramètre et perdre ainsi les conversions et les élargissements inutiles.

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

Donc, le code pour bool, lorsqu’il n’est pas optimisé, va en fait assez loin pour extraire cette valeur bool de la stack des arguments (en utilisant un autre emplacement de stack pour enregistrer cet octet). C’est plus compliqué que de simplement tirer une variable native de la taille d’un registre.

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

Très peu d’architectures de jeux d’instructions définissent un 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 un des types scalaires ou une chaîne de caractères.

Un compilateur donné et un ABI donné devront choisir des tailles spécifiques pour int et bool et quand, comme dans votre cas, il s’agit de tailles différentes, ils peuvent générer un code légèrement différent et à certains niveaux d’optimisation, un peu plus rapide.

Pourquoi bool un octet sur de nombreux systèmes?

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

Mise à jour: par “plus sûr”, 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.

Oui, la discussion est amusante. Mais juste le tester:

Code de test:

 #include  #include  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 nombre entier:

 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 booléen / impression sans commentaire (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 identiques avec 1 affectation et 2 comparaisons chaque boucle de plus de 30 millions de boucles. Trouvez autre chose à optimiser. Par exemple, n’utilisez pas strcmp inutilement. 😉

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

Est-ce que “if ([bool] == true)” nécessite un pas de plus que “if ([bool])”?

Aussi, jetez un oeil à ce post: http://www.linuxquestions.org/questions/programming-9/c-comstackr-handling-of-boolean-variables-290996/

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

Si vous parlez spécifiquement de C ++ ou de tout autre langage de programmation qui produira du code d’assemblage, nous sums liés au code que le compilateur va générer dans ASM. Nous sums également liés à la représentation de true et false en c ++. Un entier devra être stocké dans 32 bits, et je pourrais simplement utiliser un octet pour stocker l’expression booléenne. Asm snippets 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 

Donc, la comparaison de vitesse est tellement dépendante de la compilation. Dans le cas ci-dessus, le bool serait légèrement rapide puisque cmp impliquerait une soustraction pour définir les drapeaux. Cela contredit également avec 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 s’inquiéter de la façon dont le compilateur traduira votre code, et je pense que cette façon de penser est 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 à un booléen d’un côté et à un entier dans un autre, le compilateur s’exécutera plus rapidement avec des expressions booléennes au niveau de la machine.

Je considère que c’est une question conceptuelle, alors je vais donner une réponse conceptuelle. Cette discussion me rappelle les discussions que j’ai souvent sur la question de savoir si l’efficacité du code se traduit par moins de lignes de code dans l’assemblage. 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 l’assemblage. Lorsque tel est le cas, la distinction entre les instructions 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 ++ renverra une valeur qui recevra ensuite une représentation. Dans l’assemblage, en revanche, les sauts et les comparaisons seront basés sur des valeurs numériques, quel que soit le type d’expression qui a été évalué sur votre instruction C ++ if. Sur ces questions, il est important de se rappeler que de telles déclarations purement logiques aboutissent à une surcharge de calcul considérable, même si un seul bit serait capable de la même chose.