Me encontré con una #define
en la que usan __builtin_expect
.
La documentación dice:
Función incorporada:
long __builtin_expect (long exp, long c)
Puede usar
__builtin_expect
para proporcionar al compilador información de predicción de rama. En general, debe preferir usar comentarios de perfil reales para esto (-fprofile-arcs
), ya que los programadores son notoriamente malos para predecir cómo funcionan realmente sus programas. Sin embargo, hay aplicaciones en las que estos datos son difíciles de recopilar.El valor de retorno es el valor de
exp
, que debería ser una expresión integral. La semántica del incorporado es que se espera que eso sea asíexp == c
. Por ejemplo:if (__builtin_expect (x, 0)) foo ();
indicaría que no esperamos llamar
foo
, ya que esperamosx
ser cero.
Entonces, ¿por qué no usar directamente:
if (x)
foo ();
en lugar de la sintaxis complicada con __builtin_expect
?
if ( x == 0) {} else foo();
... o simplemente loif ( x != 0 ) foo();
que es equivalente al código de la documentación de GCC.Respuestas:
Imagine el código de ensamblaje que se generaría a partir de:
Supongo que debería ser algo como:
Puede ver que las instrucciones están organizadas en un orden tal que el
bar
caso precede alfoo
caso (a diferencia del código C). Esto puede utilizar mejor la canalización de la CPU, ya que un salto agita las instrucciones ya obtenidas.Antes de ejecutar el salto, las instrucciones a continuación (el
bar
caso) se envían a la tubería. Dado que elfoo
caso es improbable, el salto también es improbable, por lo tanto, es poco probable que golpee la tubería.fuente
x = 0
que la barra se dé primero. Y foo, se define más tarde ya que sus posibilidades (más bien usar la probabilidad) son menores, ¿verdad?Vamos a descompilar para ver qué hace GCC 4.8 con él
Blagovest mencionó la inversión de ramas para mejorar la tubería, pero ¿los compiladores actuales realmente lo hacen? ¡Vamos a averiguar!
Sin
__builtin_expect
Compile y descompile con GCC 4.8.2 x86_64 Linux:
Salida:
El orden de las instrucciones en la memoria no cambió: primero el
puts
y luego elretq
retorno.Con
__builtin_expect
Ahora reemplace
if (i)
con:y obtenemos:
Se
puts
movió al final de la función, ¡elretq
regreso!El nuevo código es básicamente el mismo que:
Esta optimización no se realizó con
-O0
.Pero buena suerte al escribir un ejemplo que funciona más rápido con
__builtin_expect
que sin, las CPU son realmente inteligentes en esos días . Mis ingenuos intentos están aquí .C ++ 20
[[likely]]
y[[unlikely]]
C ++ 20 ha estandarizado esas funciones integradas de C ++: Cómo usar el atributo probable / improbable de C ++ 20 en la declaración if-else Probablemente (¡un juego de palabras!) Harán lo mismo.
fuente
La idea
__builtin_expect
es decirle al compilador que generalmente encontrará que la expresión se evalúa como c, para que el compilador pueda optimizar para ese caso.Supongo que alguien pensó que estaban siendo inteligentes y que estaban acelerando las cosas al hacer esto.
Desafortunadamente, a menos que la situación se entienda muy bien (es probable que no hayan hecho tal cosa), puede haber empeorado las cosas. La documentación incluso dice:
En general, no debe usar a
__builtin_expect
menos que:fuente
__builtin_expect
o no . Por otro lado, el compilador puede realizar muchas optimizaciones basadas en la probabilidad de ramificación, como organizar el código para que la ruta de acceso sea contigua, mover el código sea poco probable que se optimice más lejos o reducir su tamaño, tomando decisiones sobre qué ramificaciones vectorizar, mejor programación del camino caliente, y así sucesivamente.Bueno, como dice en la descripción, la primera versión agrega un elemento predictivo a la construcción, diciéndole al compilador que la
x == 0
rama es la más probable, es decir, es la rama que su programa tomará con más frecuencia.Con eso en mente, el compilador puede optimizar el condicional para que requiera la menor cantidad de trabajo cuando se cumple la condición esperada, a expensas de tal vez tener que hacer más trabajo en caso de una condición inesperada.
Eche un vistazo a cómo se implementan los condicionales durante la fase de compilación, y también en el ensamblaje resultante, para ver cómo una rama puede tener menos trabajo que la otra.
Sin embargo, solo esperaría que esta optimización tenga un efecto notable si el condicional en cuestión es parte de un ciclo interno ajustado que se llama mucho , ya que la diferencia en el código resultante es relativamente pequeña. Y si lo optimiza al revés, bien puede disminuir su rendimiento.
fuente
compiler design - Aho, Ullmann, Sethi
:-)No veo ninguna de las respuestas que aborden la pregunta que creo que estaban haciendo, parafraseada:
El título de su pregunta me hizo pensar en hacerlo de esta manera:
Si el compilador supone que 'verdadero' es más probable, podría optimizar para no llamar
foo()
.El problema aquí es que, en general, no sabes lo que asumirá el compilador, por lo que cualquier código que use este tipo de técnica debería medirse cuidadosamente (y posiblemente monitorearse con el tiempo si el contexto cambia).
fuente
else
quedó fuera del cuerpo de la publicación.Lo pruebo en Mac según @Blagovest Buyukliev y @Ciro. Las asambleas se ven claras y agrego comentarios;
Los comandos son
gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
Cuando uso -O3, se ve igual sin importar que el __builtin_expect (i, 0) exista o no.
Cuando se compila con -O2, se ve diferente con y sin __builtin_expect (i, 0)
Primero sin
Ahora con __builtin_expect (i, 0)
Para resumir, __builtin_expect funciona en el último caso.
fuente