¿Hay alguna sugerencia del comstackdor para que GCC obligue a la predicción de bifurcación a ir siempre de cierta manera?

Para las architectures Intel, ¿hay alguna manera de instruir al comstackdor de GCC para que genere código que siempre fuerce la predicción de bifurcaciones de una manera particular en mi código? ¿El hardware de Intel incluso admite esto? ¿Qué pasa con otros comstackdores o hardwares?

Usaría esto en código C ++, donde sé el caso, deseo correr rápido y no me importa la desaceleración cuando se necesita tomar la otra twig, incluso cuando se ha tomado recientemente esa twig.

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 especificar una pista para la primera vez que el procesador encuentra la instrucción, todas las predicciones de bifurcación subsiguientes, funcionando normalmente?

La forma correcta de definir macros probables / improbables en C ++ 11 es la siguiente:

 #define LIKELY(condition) __builtin_expect(static_cast(condition), 1) #define UNLIKELY(condition) __builtin_expect(static_cast(condition), 0) 

Cuando estas macros se definen de esta manera:

 #define LIKELY(condition) __builtin_expect(!!(condition), 1) 

Eso puede cambiar el significado de las declaraciones if y romper el código. Considera el siguiente código:

 #include  struct A { explicit operator bool() const { return true; } operator int() const { return 0; } }; #define LIKELY(condition) __builtin_expect((condition), 1) int main() { A a; if(a) std::cout < < "if(a) is true\n"; if(LIKELY(a)) std::cout << "if(LIKELY(a)) is true\n"; else std::cout << "if(LIKELY(a)) is false\n"; } 

Y su resultado:

 if(a) is true if(LIKELY(a)) is false 

Como puede ver, ¡la definición de PROBABLEMENTE se usa !! como un elenco a bool rompe la semántica de if .

El punto aquí no es que el operator int() y el operator bool() deberían estar relacionados. Lo cual es una buena práctica.

Más bien, el uso de !!(x) lugar de static_cast(x) pierde el contexto para las conversiones contextuales de C ++ 11 .

GCC admite la función __builtin_expect(long exp, long c) para proporcionar este tipo de característica. Puede consultar la documentación aquí .

Donde exp es la condición utilizada y c es el valor esperado. Por ejemplo en tu caso querrías

 if (__builtin_expect(normal, 1)) 

Debido a la syntax incómoda, esto generalmente se usa al definir dos macros personalizadas como

 #define likely(x) __builtin_expect (!!(x), 1) #define unlikely(x) __builtin_expect (!!(x), 0) 

solo para facilitar la tarea.

Ten en cuenta que:

  1. esto no es estándar
  2. Es probable que un comstackdor / predictor de twig de CPU sea más hábil que usted para decidir tales cosas, por lo que podría tratarse de una micro-optimización prematura.

gcc tiene largo __builtin_expect (exp largo, c largo) (el énfasis es mío ):

Puede usar __builtin_expect para proporcionar al comstackdor información de predicción de bifurcación. En general, debería preferir utilizar comentarios de perfil reales para esto (-fprofile-arcs), ya que los progtwigdores son notoriamente malos a la hora de predecir cómo funcionan realmente sus progtwigs . Sin embargo, hay aplicaciones en las cuales estos datos son difíciles de recostackr.

El valor de retorno es el valor de exp, que debe ser una expresión integral. La semántica del built-in es que se espera que exp == c. Por ejemplo:

 if (__builtin_expect (x, 0)) foo (); 

indica que no esperamos llamar a foo, ya que esperamos que x sea cero. Como está limitado a expresiones integrales para exp, debe usar construcciones como

 if (__builtin_expect (ptr != NULL, 1)) foo (*ptr); 

al probar valores de puntero o punto flotante.

Como las notas de la documentación, usted debería preferir utilizar los comentarios del perfil real y este artículo muestra un ejemplo práctico de esto y cómo, en su caso, al menos termina siendo una mejora sobre el uso de __builtin_expect . Consulte también Cómo utilizar optimizaciones guiadas por perfil en g ++? .

También podemos encontrar un artículo sobre principiantes en kernel de Linux sobre las macros de kernel probable () y poco probable () que usan esta característica:

 #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 

Tenga en cuenta el !! utilizado 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 a partir de esta pregunta que recientemente respondí la diferencia entre el rendimiento de la función al pasar el parámetro como constante de tiempo de comstackción o variable que muchas técnicas de optimización de laminados a mano no funcionan en el caso general. Necesitamos perfilar el código cuidadosamente para comprender si una técnica es efectiva. Muchas técnicas antiguas pueden no ser relevantes con las optimizaciones del comstackdor modernas.

Tenga en cuenta que, aunque los builtins no son portátiles, clang también admite __builtin_expect .

También en algunas architectures puede no hacer la diferencia .

No no hay. (Al menos en los procesadores x86 modernos).

__builtin_expect mencionado en otras respuestas influye en la forma en que gcc organiza el código de ensamblaje. No influye directamente en el predictor de bifurcación de la CPU. Por supuesto, habrá efectos indirectos en la predicción de bifurcación causados ​​por la reordenación del código. Pero en los procesadores x86 modernos no hay instrucciones que le digan a la CPU “suponer que esta twig no se toma”.

Consulte esta pregunta para obtener más detalles: Intel x86 0x2E / 0x3E ¿Prefijo de twig de prefijo realmente utilizado?

Para que quede claro, __builtin_expect y / o el uso de -fprofile-arcs pueden mejorar el rendimiento de tu código, tanto dando sugerencias al predictor de bifurcación a través del diseño del código (ver Optimizaciones de rendimiento del ensamblaje x86-64 – Alineación y predicción de bifurcación ) y también mejora el comportamiento del caché manteniendo el código “improbable” alejado del código “probable”.

Como todas las otras respuestas han sugerido adecuadamente, puede usar __builtin_expect para dar al comstackdor una pista sobre cómo organizar el código de ensamblado. Como lo 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 del CCG. Siempre es mejor usar datos de perfil reales para optimizar tu código, en lugar de adivinar.

A lo largo de líneas similares, pero aún no mencionadas, hay una forma específica de GCC de obligar al comstackdor a generar código en una ruta “fría”. Esto implica el uso de los atributos noinline y cold , que hacen exactamente lo que suenan como lo hacen. 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 se incluye en la categoría general de una microoptimización y, por lo tanto, se aplica el asesoramiento estándar, no lo adivine, creo que es más útil en general que __builtin_expect . Casi ninguna generación del procesador x86 usa pistas de predicción de bifurcación ( referencia ), por lo que lo único que podrá afectar de todos modos es el orden del código de ensamblaje. Como sabe qué es el código de manejo de errores o el caso de borde, puede usar esta anotación para asegurarse de que el comstackdor nunca pronosticará una bifurcación y la vinculará con el código “activo” al optimizar el tamaño.

Uso de muestra:

 void FooTheBar(void* pFoo) { if (pFoo == nullptr) { // Oh no! A null pointer is an error, but maybe this is a public-facing // function, so we have to be prepared for anything. Yet, we don't want // the error-handling code to fill up the instruction cache, so we will // force it out-of-line and onto a "cold" path. [&]() __attribute__((noinline,cold)) { HandleError(...); }(); } // Do normal stuff ⋮ } 

Aún mejor, GCC ignorará automáticamente esto a favor de la retroalimentación de perfil cuando esté disponible (por ejemplo, al comstackr con -fprofile-use ).

Consulte la documentación oficial aquí: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes

__builtin_expect se puede usar para decirle al comstackdor de qué manera espera que vaya una twig. Esto puede influir en cómo se genera el código. Los procesadores típicos ejecutan el código más rápido de forma secuencial. Entonces, si escribes

 if (__builtin_expect (x == 0, 0)) ++count; if (__builtin_expect (y == 0, 0)) ++count; if (__builtin_expect (z == 0, 0)) ++count; 

el comstackdor generará código como

 if (x == 0) goto if1; back1: if (y == 0) goto if2; back2: if (z == 0) goto if3; back3: ; ... if1: ++count; goto back1; if2: ++count; goto back2; if3: ++count; goto back3; 

Si su pista es correcta, esto ejecutará el código sin que se realicen realmente twigs. 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 twigs.

Los procesadores x86 más nuevos tienen instrucciones para las twigs que se espera que se tomen, o para las twigs 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 usa eso. No es muy útil, porque la predicción de bifurcación manejará esto muy bien. Así que no creo que puedas influir en la predicción de la twig.

Con respecto al OP, no, no hay manera en que GCC le diga al procesador que siempre asum que la twig se toma o no. Lo que tienes es __builtin_pect, que hace lo que otros dicen que hace. Además, creo que no quieres decirle al procesador si la twig está ocupada o no siempre . Los procesadores de hoy en día, como la architecture Intel, pueden reconocer patrones bastante complejos y adaptarse de manera efectiva.

Sin embargo, hay ocasiones en que desea asumir el control de si, de manera predeterminada, se prevé o no una derivación: cuando sepa que el código se llamará “frío” con respecto a las estadísticas de bifurcación.

Un ejemplo concreto: código de gestión de excepciones. Por definición, el código de gestión será excepcional, pero quizás cuando se produzca el máximo rendimiento sea deseable (puede haber un error crítico que solucionar tan pronto como sea posible), por lo tanto, 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 recostackr estadísticas pero las pierde porque la misma clasificación no ocurre lo suficientemente rápido y los recursos de predicción están dedicados al código llamado recientemente. Desearía que hubiera un primitivo que dijera al procesador “por favor no dedique recursos de predicción a este código” de la forma en que a veces puede decir “no almacenar en caché esto”.