c++ - realiza - variables booleanas en python




Valores booleanos como 8 bits en compiladores. ¿Las operaciones sobre ellos son ineficientes? (2)

Estoy leyendo " Optimizing software in C ++ " de Agner Fog (específico para procesadores x86 para Intel, AMD y VIA) y afirma en la página 34

Las variables booleanas se almacenan como enteros de 8 bits con el valor 0 para falso y 1 para verdadero. Las variables booleanas están sobredeterminadas en el sentido de que todos los operadores que tienen variables booleanas como entrada verifican si las entradas tienen cualquier otro valor que 0 o 1, pero los operadores que tienen booleanos como salida no pueden producir ningún otro valor que 0 o 1. Esto hace que las operaciones Las variables booleanas como entrada son menos eficientes de lo necesario.

¿Sigue siendo cierto hoy y en qué compiladores? ¿Puedes dar un ejemplo? El autor declara

Las operaciones booleanas se pueden hacer mucho más eficientes si se sabe con certeza que los operandos no tienen otros valores que 0 y 1. La razón por la cual el compilador no hace tal suposición es que las variables pueden tener otros valores si son sin inicializar o provienen de fuentes desconocidas.

¿Esto significa que si tomo un puntero de función bool(*)() por ejemplo y lo llamo, entonces las operaciones en él producen un código ineficiente? ¿O es el caso cuando accedo a un booleano desreferenciando un puntero o leyendo de una referencia y luego actúo sobre él?


Creo que este no es el caso.

En primer lugar, este razonamiento es completamente inaceptable:

La razón por la cual el compilador no hace tal suposición es que las variables pueden tener otros valores si no están inicializadas o provienen de fuentes desconocidas.

Revisemos algunos códigos (compilados con clang 6, pero GCC 7 y MSVC 2017 producen un código similar).

Booleano o:

bool fn(bool a, bool b) {
    return a||b;
}

0000000000000000 <fn(bool, bool)>:
   0:   40 08 f7                or     dil,sil
   3:   40 88 f8                mov    al,dil
   6:   c3                      ret    

Como se puede ver, no hay verificación 0/1 aquí, simple or .

Convierte bool a int:

int fn(bool a) {
    return a;
}

0000000000000000 <fn(bool)>:
   0:   40 0f b6 c7             movzx  eax,dil
   4:   c3                      ret    

De nuevo, sin control, movimiento simple.

Convierta char a bool:

bool fn(char a) {
    return a;
}

0000000000000000 <fn(char)>:
   0:   40 84 ff                test   dil,dil
   3:   0f 95 c0                setne  al
   6:   c3                      ret    

Aquí, char se verifica si es 0, o no, y el valor de bool se establece en 0 o 1 en consecuencia.

Así que creo que es seguro decir que el compilador usa bool de una manera que siempre contiene un 0/1. Nunca verifica su validez.

Acerca de la eficiencia: creo que bool es óptimo. El único caso que puedo imaginar, donde este enfoque no es óptimo es la conversión char-> bool. Esa operación podría ser un simple mov, si el valor de bool no estuviera restringido a 0/1. Para todas las demás operaciones, el enfoque actual es igualmente bueno o mejor.

EDITAR: Peter Cordes mencionó ABI. Aquí está el texto relevante del System V ABI para AMD64 (el texto para i386 es similar):

Los booleanos, cuando se almacenan en un objeto de memoria, se almacenan como objetos de un solo byte cuyo valor siempre es 0 (falso) o 1 (verdadero) . Cuando se almacenan en registros enteros (excepto para pasar como argumentos), los 8 bytes del registro son significativos; cualquier valor distinto de cero se considera verdadero

Entonces, para las plataformas que siguen a SysV ABI, podemos estar seguros de que un bool tiene un valor 0/1.

Busqué un documento ABI para MSVC, pero lamentablemente no encontré nada sobre bool .


Recopilé lo siguiente con clang ++ -O3 -S

bool andbool(bool a, bool b)
{
    return a && b;
}

bool andint(int a, int b)
{
    return a && b;
}

El archivo .s contiene:

andbool(bool, bool):                           # @andbool(bool, bool)
    andb    %sil, %dil
    movl    %edi, %eax
    retq

andint(int, int):                            # @andint(int, int)
    testl   %edi, %edi
    setne   %cl
    testl   %esi, %esi
    setne   %al
    andb    %cl, %al
    retq

Claramente, es la versión bool que está haciendo menos.





boolean