c++ - form - if if c#
¿Cuál es más rápido: if(bool) o if(int)? (8)
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.
En su mayoría dependerá del compilador y la optimización. Hay una discusión interesante (sin sentido del idioma) aquí:
¿"If ([bool] == true)" requiere un paso más que "if ([bool])"?
Además, eche un vistazo a esta publicación: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/
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
.