metodo form español elseif else booleano bool c++ assembly int boolean

c++ - form - if if c#



¿Cuál es más rápido: if(bool) o if(int)? (8)

¿Qué valor es mejor usar? Boolean true o Integer 1?

El tema anterior me hizo hacer algunos experimentos con bool e int en if condition. Entonces, por curiosidad escribí este programa:

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 genera código asm para cada función de la siguiente manera:

  • código asm para 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

  • código asm para 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

Sorprendentemente, g(bool) genera más instrucciones asm ! ¿Significa que if(bool) es un poco más lento que if(int) ? Solía ​​pensar que bool está especialmente diseñado para ser usado en sentencias condicionales como if , así que esperaba g(bool) para generar menos instrucciones asm, haciendo g(bool) más eficiente y rápido.

EDITAR:

No estoy usando ninguna bandera de optimización a partir de ahora. Pero incluso la ausencia de ello, ¿por qué genera más asm para g(bool) es una pregunta para la que estoy buscando una respuesta razonable. También debo decirle que la bandera de optimización de -O2 genera exactamente la misma asm. Pero esa no es la pregunta. La pregunta es qué he preguntado.


A nivel de máquina no existe bool

Muy pocas arquitecturas de conjuntos de instrucciones definen cualquier tipo de tipo de operando booleano, aunque a menudo hay instrucciones que desencadenan una acción en valores distintos de cero. Para la CPU, por lo general, todo es uno de los tipos escalares o una cadena de ellos.

Un compilador determinado y un ABI dado necesitarán elegir tamaños específicos para int y bool y cuando, como en su caso, estos sean de diferentes tamaños, pueden generar códigos ligeramente diferentes, y en algunos niveles de optimización, uno puede ser ligeramente más rápido.

¿Por qué bool es un byte en muchos sistemas?

Es más seguro elegir un tipo de caracteres para bool porque alguien podría crear una gran variedad de ellos.

Actualización: por "más seguro", quiero decir: para el compilador y los implementadores de la biblioteca. No digo que las personas necesiten volver a implementar el tipo de sistema.


Abordando su pregunta de dos maneras diferentes:

Si está hablando específicamente de C ++ o de cualquier lenguaje de programación que produzca código ensamblador para ese asunto, estamos obligados a qué código generará el compilador en ASM. También estamos obligados a la representación de verdadero y falso en c ++. Un entero tendrá que ser almacenado en 32 bits, y podría simplemente usar un byte para almacenar la expresión booleana. Fragmentos de Asm para declaraciones condicionales:

Para el entero:

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

Para el 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

Entonces, esa es la razón por la cual la comparación de velocidad es tan dependiente de la compilación. En el caso anterior, el bool sería ligeramente rápido ya que cmp implicaría una resta para establecer las banderas. También contradice con lo que generó tu compilador.

Otro enfoque, mucho más simple, es mirar la lógica de la expresión por sí mismo y tratar de no preocuparse por cómo el compilador traducirá su código, y creo que esta es una forma de pensar mucho más saludable. Todavía creo, en última instancia, que el código generado por el compilador está tratando de dar una resolución veraz. Lo que quiero decir es que, tal vez si aumenta los casos de prueba en la sentencia if y se queda con boolean en un lado y en otro en enter, el compilador lo hará para que el código generado se ejecute más rápido con expresiones booleanas en el nivel de máquina.

Considero que esta es una pregunta conceptual, así que daré una respuesta conceptual. Esta discusión me recuerda las discusiones que comúnmente tengo sobre si la eficiencia del código se traduce a menos líneas de código en el ensamblaje. Parece que este concepto generalmente se acepta como verdadero. Teniendo en cuenta que mantener un registro de la rapidez con que la ALU manejará cada declaración no es viable, la segunda opción sería centrarse en los saltos y las comparaciones en el ensamblaje. Cuando ese es el caso, la distinción entre enunciados booleanos o enteros en el código que presenta se vuelve bastante representativa. El resultado de una expresión en C ++ devolverá un valor que luego tendrá una representación. En el ensamblaje, por otro lado, los saltos y las comparaciones se basarán en valores numéricos, independientemente del tipo de expresión que se esté evaluando en la declaración C ++ if. Es importante en estas preguntas recordar que las declaraciones puramente lógicas como estas terminan con una gran sobrecarga computacional, aunque un solo bit sería capaz de hacer lo mismo.


Compilar con -03 da lo siguiente:

F:

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

gramo:

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

.. por lo que compila esencialmente el mismo código, a excepción de cmpl vs cmpb . Esto significa que la diferencia, si hay alguna, no importa. A juzgar por el código no optimizado no es justo.

Editar para aclarar mi punto. El código no optimizado es para la depuración simple, no para la velocidad. Comparar la velocidad del código no optimizado no tiene sentido.


Con GCC 4.5 en Linux y Windows al menos, sizeof(bool) == 1 . En x86 y x86_64, no se puede pasar menos de un valor de registro de propósito general a una función (ya sea a través de la pila o un registro dependiendo de la convención de llamadas, etc ...).

Por lo tanto, el código de bool, cuando no está optimizado, en realidad sirve para extraer ese valor de bool de la pila de argumentos (usando otra ranura de pila para guardar ese byte). Es más complicado que solo tirar de una variable nativa de tamaño de registro.


Cuando compilo esto con un conjunto de opciones sensatas (específicamente -O3), esto es lo que obtengo:

Para 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

Para 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

Todavía usan instrucciones diferentes para la comparación ( cmpb para boolean vs. cmpl para int), pero de lo contrario los cuerpos son idénticos. Una rápida mirada a los manuales de Intel me dice: ... no mucho de nada. No hay tal cosa como cmpb o cmpl en los manuales de Intel. Todos son cmp y no puedo encontrar las tablas de tiempo en este momento. Supongo, sin embargo, que no hay diferencia de reloj entre comparar un byte inmediato y comparar un inmediato largo, por lo que para todos los propósitos prácticos, el código es idéntico.

editado para agregar lo siguiente basado en su adición

La razón por la cual el código es diferente en el caso no optimizado es que no está optimizado. (Sí, es circular, lo sé.) Cuando el compilador recorre el AST y genera el código directamente, no "sabe" nada, excepto lo que está en el punto inmediato del AST en el que se encuentra. En ese punto, carece de toda la información contextual necesaria saber que en este punto específico puede tratar el tipo declarado bool como un int . Obviamente, un booleano se trata como un byte y cuando se manipulan bytes en el mundo de Intel, hay que hacer cosas como sign-extend para llevarlo a ciertos anchos para ponerlo en la pila, etc. (No se puede presionar un byte .)

Sin embargo, cuando el optimizador ve el AST y hace su magia, observa el contexto circundante y "sabe" cuándo puede reemplazar el código por algo más eficiente sin cambiar la semántica. Por lo tanto, "sabe" que puede usar un número entero en el parámetro y perder así las conversiones y ampliaciones innecesarias.



Sí, la discusión es divertida. Pero solo pruebalo:

Código de prueba:

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

Compilado en una computadora portátil Ubuntu 10.10 de 64 bits con: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp

Comparación basada en enteros:

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

Prueba booleana / impresión sin comentario (y entero comentado):

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

Son lo mismo con 1 asignación y 2 comparaciones en cada ciclo de más de 30 millones de bucles. Encuentre algo más para optimizar. Por ejemplo, no use strcmp innecesariamente. ;)


Tiene sentido para mi. Su compilador aparentemente define un bool como un valor de 8 bits, y su sistema ABI requiere que "promueva" argumentos enteros pequeños (<32 bits) a 32 bits cuando los empuja hacia la pila de llamadas. Entonces para comparar un bool , el compilador genera código para aislar el byte menos significativo del argumento de 32 bits que recibe g, y lo compara con cmpb . En el primer ejemplo, el argumento int usa los 32 bits completos que se insertaron en la pila, por lo que simplemente se compara con todo el conjunto con cmpl .