¿Hay alguna sugerencia de compilador para que GCC fuerce la predicción de ramificaciones a ir siempre de cierta manera?


Para las arquitecturas Intel, ¿hay alguna forma de instruir al compilador GCC para que genere código que siempre fuerza la predicción de ramas de una manera particular en mi código? ¿El hardware Intel siquiera soporta esto? ¿Qué pasa con otros compiladores o hardwares?

Usaría esto en código C++ donde sé el caso en el que quiero correr rápido y no me importa la ralentización cuando la otra rama necesita ser tomada, incluso cuando recientemente ha tomado esa rama.

for (;;) {
  if (normal) { // How to tell compiler to always branch predict true value?
    doSomethingNormal();
  } else {
    exceptionalCase();
  }
}

Como continuación pregunta para Evdzhan Mustafa, puede la pista solo especificar una pista para la primera vez que el procesador se encuentra con la instrucción, toda la predicción de rama posterior, funcionando normalmente?

Author: Bergi, 2015-05-08

7 answers

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

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

Cuando estas macros se definen de esta manera:

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

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

#include <iostream>

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 salida:

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

Como puede ver, la definición de PROBABLE uso de !! como un molde para bool rompe la semántica de if.

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

En lugar de usar !!(x) en lugar de static_cast<bool>(x) pierde el contexto para C++11 conversiones contextuales.

 15
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-09 14:35:10

GCC soporta 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 caso de que desee

if (__builtin_expect(normal, 1))

Debido a la sintaxis incómoda, esto se usa generalmente definiendo dos macros personalizadas como

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

Solo para facilitar la tarea.

Tenga en cuenta que:

  1. esto no es estándar
  2. a el compilador / predictor de ramas de cpu es probablemente más hábil que usted para decidir tales cosas, por lo que esto podría ser una microoptimización prematura
 78
Author: Jack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-08 19:07:02

Gcc has long _ _ builtin _ exp (long exp, long c) (énfasis mío):

Puede usar _ _ builtin _ expect para proporcionar al compilador una rama información de predicción. En general, usted debe preferir utilizar real comentarios de perfil para esto (- fprofile-arcs), ya que los programadores son notoriamente malo en predecir cómo sus programas realmente funcionan . Sin embargo, hay aplicaciones en las que estos datos son difíciles de recopilar.

El retorno valor es el valor de exp, que debe ser una integral expresion. 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. Puesto que usted está limitado a expresiones integrales para exp, usted debe utilizar construcciones como

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

Al probar valores de puntero o coma flotante.

Como se indica en la documentación, debería preferir utilizar comentarios de perfil 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. Véase también ¿Cómo utilizar optimizaciones guiadas por perfil en g++?.

También podemos encontrar un artículo de novatos del kernel de Linux sobre las macros kernal likely() y unlikely () 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 su lugar de (condición)?.

El hecho de que esta técnica se utilice en el núcleo Linux no significa que siempre tenga sentido usarla. Podemos ver a partir de esta pregunta que recientemente respondí diferencia entre el rendimiento de la función al pasar el parámetro como constante de tiempo de compilación o variable que muchas técnicas de optimización hechas a mano no funcionan en el caso general. Necesitamos perfilar el código cuidadosamente para entender si una técnica es efectiva. Muchas técnicas antiguas pueden ni siquiera ser relevante con optimizaciones de compiladores modernos.

Nota, aunque los builtins no son portables clang también soporta __builtin_expect.

También en algunas arquitecturas puede que no haga una diferencia.

 41
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:41

No, no lo 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 directamente influye en el predictor de rama de la CPU. Por supuesto, habrá efectos indirectos en la predicción de ramas causados por reordenar el código. Pero en los procesadores x86 modernos no hay ninguna instrucción que le diga a la CPU "asumir que esta rama está/no está tomada".

Vea esta pregunta para más detalles: Intel x86 0x2E / 0x3E Prefijo Predicción de rama realmente utilizado?

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 ramas a través del diseño del código (ver Optimizaciones de rendimiento de la alineación de ensamblado x86-64 y predicción de ramas), como también mejorando el comportamiento de la caché al mantener el código "improbable" alejado del código "probable".

 38
Author: Artelius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:59

Como todas 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 construido en su cerebro no será tan bueno como el elaborado por el equipo del CCG. Siempre es mejor usar datos de perfil reales para optimizar tu código, en lugar de adivinar.

En una línea similar, pero aún no mencionada, hay una forma específica de GCC de forzar al compilador a generar código en un camino "frío". 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 cae en la categoría general de una microoptimización, y por lo tanto se aplica el consejo estándar-prueba no adivine-Siento que es más útil en general que __builtin_expect. Apenas cualquier generación del procesador x86 usa sugerencias de predicción de ramas ( referencia ), por lo que lo único que vas a poder afectar de todos modos es el orden del código ensamblador. Ya que sabe qué 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 la vinculará lejos del código "caliente" al optimizar el tamaño.

Uso de la 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á esto automáticamente en favorezca la retroalimentación del perfil cuando esté disponible (por ejemplo, al compilar con -fprofile-use).

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

 15
Author: Cody Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-09 10:07:54

__builtin_expect se puede usar 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 el código más rápido de forma secuencial. Así que si escribes

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

El compilador 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 sugerencia es correcta, esto ejecutará el código sin ninguna rama realmente realizada. Se ejecutará más rápido que la secuencia normal, donde cada instrucción if se ramificaría alrededor del código condicional y se ejecutaría tres ramas.

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 usa eso. No es muy útil, porque la predicción de ramas manejará esto muy bien. Así que no creo que realmente pueda influir en la rama predicción.

 3
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-08 23:22:20

Con respecto al OP, no, no hay forma en GCC de decirle al procesador que siempre asuma que la rama está o no está tomada. Lo que tienes es _ _ builtin _ expect, que hace lo que otros dicen que hace. Además, creo que no quieres 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 veces que desea asumir el control de si por defecto una rama se predice tomada o no: Cuando se sabe que el código se llamará "frío" con respecto a las estadísticas de ramificación.

Un ejemplo concreto: Código de administración de excepciones. Por definición, el código de administración ocurrirá excepcionalmente, pero tal vez cuando se produce se desea el máximo rendimiento (puede haber un error crítico para tener cuidado lo antes posible), por lo tanto, es posible que desee controlar la predicción por defecto.

Otro ejemplo: Puede clasificar su ingresa y salta al código que maneja el resultado de tu 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 llamado recientemente. Me gustaría que hubiera un primitivo para decirle al procesador "por favor, no dedique recursos de predicción a este código" de la manera en que a veces se puede decir "no almacenar en caché esto".

 0
Author: EdMaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 02:28:44