Para las arquitecturas Intel, ¿hay alguna forma de instruir al compilador GCC para que genere código que siempre fuerce la predicción de ramas de una manera particular en mi código? ¿El hardware de Intel incluso es compatible con esto? ¿Qué pasa con otros compiladores o hardware?
Usaría esto en código C ++ donde sé el caso en el que deseo correr rápido y no me importa la ralentización cuando la otra rama debe tomarse incluso cuando ha tomado esa rama recientemente.
for (;;) {
if (normal) { // How to tell compiler to always branch predict true value?
doSomethingNormal();
} else {
exceptionalCase();
}
}
Como pregunta de seguimiento para Evdzhan Mustafa, ¿puede la sugerencia simplemente especificar una sugerencia por primera vez que el procesador encuentra la instrucción, todas las predicciones de bifurcaciones posteriores, funcionando normalmente?
Respuestas:
A partir de C ++ 20, los atributos probables e improbables deben estandarizarse y ya son compatibles con g ++ 9 . Entonces, como se discutió aquí , puede escribir
Por ejemplo, en el siguiente código, el bloque else se inserta gracias al
[[unlikely]]
bloque in the ifenlace godbolt comparando la presencia / ausencia del atributo
fuente
[[unlikely]]
enif
vs[[likely]]
en elelse
?GCC admite la función
__builtin_expect(long exp, long c)
para proporcionar este tipo de característica. Puedes consultar la documentación aquí .Donde
exp
se usa la condición yc
es el valor esperado. Por ejemplo, en tu caso querríasDebido a la sintaxis incómoda, esto se usa generalmente para definir dos macros personalizadas como
solo para facilitar la tarea.
Tenga en cuenta que:
fuente
constexpr
función?constexpr
función pueda reemplazar esta macro. Tiene que estarif
directamente en la declaración, creo. La misma razónassert
nunca podría ser unaconstexpr
función.constexpr
solo habla de semántica de valor, no de la inserción del ensamblado específico de implementación); la interpretación sencilla (no en línea) del código no tiene sentido. No hay ninguna razón para usar una función para esto.__builtin_expect
sí mismo es una sugerencia de optimización, por lo que argumentar que un método que simplifica su uso depende de la optimización no es ... convincente. Además, no agregué elconstexpr
especificador para que funcione en primer lugar, sino para que funcione en expresiones constantes. Y sí, hay razones para usar una función. Por ejemplo, no quisiera contaminar todo mi espacio de nombres con un lindo nombre comolikely
. Tendría que usarLIKELY
, por ejemplo , para enfatizar que es una macro y evitar colisiones, pero eso es simplemente feo.gcc tiene long __builtin_expect (long exp, long c) ( énfasis mío ):
Como señala la documentación, debería preferir usar comentarios de perfil reales y este artículo muestra un ejemplo práctico de esto y cómo, en su caso, al menos termina siendo una mejora con respecto al uso
__builtin_expect
. Consulte también ¿Cómo utilizar optimizaciones guiadas por perfiles en g ++? .También podemos encontrar un artículo para principiantes del kernel de Linux sobre las macros del kernel probables () e improbables () que usan esta función:
Tenga
!!
en cuenta el uso en la macro, podemos encontrar la explicación para esto en ¿Por qué usar !! (condición) en lugar de (condición) .El hecho de que esta técnica se use en el kernel de Linux no significa que siempre tenga sentido usarla. Podemos ver en esta pregunta que recientemente respondí la diferencia entre el rendimiento de la función al pasar el parámetro como constante o variable de tiempo de compilación que muchas técnicas de optimización hechas a mano no funcionan en el caso general. Necesitamos perfilar el código cuidadosamente para comprender si una técnica es efectiva. Es posible que muchas técnicas antiguas ni siquiera sean relevantes con las optimizaciones modernas del compilador.
Tenga en cuenta que, aunque los elementos integrados no son portátiles, el clang también admite __builtin_expect .
Además, en algunas arquitecturas puede que no suponga una diferencia .
fuente
No no hay. (Al menos en procesadores x86 modernos).
__builtin_expect
mencionado en otras respuestas influye en la forma en que gcc organiza el código ensamblador. No influye directamente en el predictor de rama de la CPU.Por supuesto, habrá efectos indirectos en la predicción de rama causados por reordenar el código. Pero en los procesadores x86 modernos no hay instrucción que le diga a la CPU "suponga que esta rama está / no está tomada".Consulte esta pregunta para obtener más detalles: ¿Se utilizó realmente la predicción de rama de prefijo Intel x86 0x2E / 0x3E?
Para ser claros,
__builtin_expect
y / o el uso de-fprofile-arcs
puede mejorar el rendimiento de su código, tanto dando pistas al predictor de rama a través del diseño del código (consulte Optimizaciones de rendimiento del ensamblaje x86-64: predicción de alineación y rama ) como también mejorando el comportamiento de la caché manteniendo el código "improbable" lejos del código "probable".fuente
__builtin_expect
.__builtin_expect
. Así que esto debería ser solo un comentario. Pero no es falso, por lo que he eliminado mi voto negativo.__builtin_expect
para crear trivialmente un caso de prueba con el que pueda medirperf stat
que tendrá una tasa de predicción errónea de rama muy alta. Solo afecta el diseño de la rama . Y por cierto, Intel desde Sandybridge o al menos Haswell no usa mucho o nada la predicción estática; siempre hay alguna predicción en el BHT, ya sea un alias obsoleto o no. xania.org/201602/bpu-part-twoLa forma correcta de definir macros probables / improbables en C ++ 11 es la siguiente:
Este método es compatible con todas las versiones de C ++, a diferencia de
[[likely]]
, pero se basa en una extensión no estándar__builtin_expect
.Cuando estas macros se definieron de esta manera:
Eso puede cambiar el significado de las
if
declaraciones y romper el código. Considere el siguiente código:Y su salida:
Como puede ver, la definición de PROBABLE usar
!!
como un elencobool
rompe la semántica deif
.El punto aquí no es eso
operator int()
yoperator bool()
debería estar relacionado. Cuál es una buena práctica.Más bien, usar en
!!(x)
lugar destatic_cast<bool>(x)
pierde el contexto para las conversiones contextuales de C ++ 11 .fuente
switch
, gracias. La conversión contextual involucrada aquí es particular para el tipobool
y los cinco contextos específicos enumerados allí , que no incluyen elswitch
contexto.(_Bool)(condition)
, porque C no tiene una sobrecarga de operadores.(condition)
, no!!(condition)
. Ambos estántrue
después de cambiar eso (probado con g ++ 7.1). ¿Puede construir un ejemplo que realmente demuestre el problema del que está hablando cuando usa!!
booleanizar?Como las otras respuestas han sugerido adecuadamente, puede usar
__builtin_expect
para darle al compilador una pista sobre cómo organizar el código ensamblador. Como señalan los documentos oficiales , en la mayoría de los casos, el ensamblador integrado en su cerebro no será tan bueno como el creado por el equipo de GCC. Siempre es mejor usar datos de perfil reales para optimizar su código, en lugar de adivinar.En líneas similares, pero aún no mencionadas, hay una forma específica de GCC para forzar al compilador a generar código en una ruta "fría". Esto implica el uso de los atributos
noinline
ycold
, que hacen exactamente lo que parecen. Estos atributos solo se pueden aplicar a funciones, pero con C ++ 11, puede declarar funciones lambda en línea y estos dos atributos también se pueden aplicar a funciones lambda.Aunque esto todavía cae en la categoría general de una microoptimización y, por lo tanto, se aplica el consejo estándar (prueba, no adivines), creo que es más útil en general que
__builtin_expect
. Casi ninguna generación del procesador x86 usa sugerencias de predicción de rama ( referencia ), por lo que lo único que podrá afectar de todos modos es el orden del código ensamblador. Ya que sabe lo que es el código de manejo de errores o "caso de borde", puede usar esta anotación para asegurarse de que el compilador nunca predecirá una rama y lo vinculará fuera del código "caliente" al optimizar el tamaño.Uso de muestra:
Aún mejor, GCC automáticamente ignorará esto a favor de la retroalimentación del perfil cuando esté disponible (por ejemplo, al compilar con
-fprofile-use
).Consulte la documentación oficial aquí: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
fuente
__builtin_expect
hace. No es en absoluto inútil. Tienes razón en que elcold
atributo también es útil, pero__builtin_expect
creo que subestimas la utilidad de .__builtin_expect puede usarse para decirle al compilador en qué dirección espera que vaya una rama. Esto puede influir en cómo se genera el código. Los procesadores típicos ejecutan código secuencialmente más rápido. Entonces si escribes
el compilador generará código como
Si su sugerencia es correcta, esto ejecutará el código sin que se realice ninguna rama. Se ejecutará más rápido que la secuencia normal, donde cada instrucción if se ramificaría alrededor del código condicional y ejecutaría tres ramificaciones.
Los procesadores x86 más nuevos tienen instrucciones para las ramas que se espera que se tomen, o para las ramas que se espera que no se tomen (hay un prefijo de instrucción; no estoy seguro de los detalles). No estoy seguro si el procesador lo usa. No es muy útil, porque la predicción de rama manejará esto bien. Así que no creo que puedas influir en la predicción de la rama .
fuente
Con respecto al OP, no, no hay forma en GCC de decirle al procesador que siempre asuma que la rama está o no tomada. Lo que tienes es __builtin_expect, que hace lo que otros dicen que hace. Además, creo que no querrá decirle al procesador si la rama está tomada o no siempre . Los procesadores actuales, como la arquitectura Intel, pueden reconocer patrones bastante complejos y adaptarse eficazmente.
Sin embargo, hay ocasiones en las que desea asumir el control de si, de forma predeterminada, se predice que una rama se tomará o no: cuando sepa que el código se llamará "frío" con respecto a las estadísticas de ramificación.
Un ejemplo concreto: código de gestión de excepciones. Por definición, el código de administración ocurrirá de manera excepcional, pero tal vez cuando ocurra se desee el máximo rendimiento (puede haber un error crítico que debe solucionarse lo antes posible), por lo que es posible que desee controlar la predicción predeterminada.
Otro ejemplo: puede clasificar su entrada y saltar al código que maneja el resultado de su clasificación. Si hay muchas clasificaciones, el procesador puede recopilar estadísticas pero perderlas porque la misma clasificación no ocurre lo suficientemente pronto y los recursos de predicción se dedican al código recientemente llamado. Me gustaría que hubiera una primitiva para decirle al procesador "por favor, no dedique recursos de predicción a este código" de la manera en que a veces puede decir "no almacenar en caché esto".
fuente