Pregunta

Esto es sólo un problema en GCC versiones anteriores a 4.4, esto fue corregido en GCC 4.5.

Es posible indicar al compilador que la variable que se utiliza en un interruptor que encaja en el caso de las declaraciones?En particular, si se trata de un pequeño intervalo y hay un salto de la tabla generada.

extern int a;
main()
{
        switch (a & 0x7) {   // 0x7  == 111  values are 0-7
        case 0: f0(); break;
        case 1: f1(); break;
        case 2: f2(); break;
        case 3: f3(); break;
        case 4: f4(); break;
        case 5: f5(); break;
        case 6: f6(); break;
        case 7: f7(); break;
        }
}

Traté de xor ing a bits bajos (como el ejemplo), el uso de las enumeraciones, el uso de gcc_unreachable() fue en vano.El código generado siempre comprueba si la variable está dentro de la gama, la adición de un sentido de la rama condicional y alejando el salto de la tabla de código de cálculo.

Nota:esto es en el bucle más profundo de un decodificador, que importa el rendimiento significativamente.

Parece que no soy el sólo uno.

No hay manera de decirle a gcc que la rama predeterminada es tomado nunca, aunque se omite la rama predeterminada si puede probar que la valor nunca está fuera de rango basándose en las comprobaciones de tipo condicional.

Así que, ¿cómo ayudar a gcc demostrar la variable se ajusta y no hay ninguna rama predeterminada en el ejemplo anterior?(Sin adición de una rama condicional, por supuesto).

Actualizaciones

  1. Esto fue en OS X 10.6 Snow Leopard con GCC 4.2 (predeterminado desde Xcode.) No sucedió con GCC 4.4/4.3 en linux (reportado por Nathon y Jens Gustedt.)

  2. Las funciones en el ejemplo están allí para mejorar la legibilidad, creo que esos son insertados o simplemente declaraciones.Hacer una llamada a una función en x86 es caro.

    También el ejemplo, como se menciona en la nota, pertenece dentro de un bucle de datos (big data.)

    El código generado por gcc 4.2/OS X es:

    [...]
    andl    $7, %eax
    cmpl    $7, %eax
    ja  L11
    mov %eax, %eax
    leaq    L20(%rip), %rdx
    movslq  (%rdx,%rax,4),%rax
    addq    %rdx, %rax
    jmp *%rax
    .align 2,0x90
    L20:
    .long   L12-L20
    .long   L13-L20
    .long   L14-L20
    .long   L15-L20
    .long   L16-L20
    .long   L17-L20
    .long   L18-L20
    .long   L19-L20
    L19:
    [...]
    

    El problema radica en cmp $7, %eax; ja L11;

  3. OK, me voy con la más fea y la adición de solución de un caso especial para el gcc por debajo de 4.4 uso de una versión diferente sin un interruptor y el uso de goto y de gcc &&etiqueta de extensiones.

    static void *jtb[] = { &&c_1, &&c_2, &&c_3, &&c_4, &&c_5, &&c_6, &&c_7, &&c_8 };
    [...]
    goto *jtb[a & 0x7];
    [...]
    while(0) {
    c_1:
    // something
    break;
    c_2:
    // something
    break;
    [...]
    }
    

    Nota: la matriz de etiquetas es estático, por lo que no computa cada llamada.

¿Fue útil?

Solución

He intentado compilar algo simple y comparable con -O5 y -fno-inline (mi f0-f7 funciones eran triviales) y que ha generado este:


 8048420:   55                      push   %ebp ;; function preamble
 8048421:   89 e5                   mov    %esp,%ebp ;; Yeah, yeah, it's a function.
 8048423:   83 ec 04                sub    $0x4,%esp ;; do stuff with the stack
 8048426:   8b 45 08                mov    0x8(%ebp),%eax ;; x86 sucks, we get it
 8048429:   83 e0 07                and    $0x7,%eax ;; Do the (a & 0x7)
 804842c:   ff 24 85 a0 85 04 08    jmp    *0x80485a0(,%eax,4) ;; Jump table!
 8048433:   90                      nop
 8048434:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048438:   8d 45 08                lea    0x8(%ebp),%eax
 804843b:   89 04 24                mov    %eax,(%esp)
 804843e:   e8 bd ff ff ff          call   8048400 
 8048443:   8b 45 08                mov    0x8(%ebp),%eax
 8048446:   c9                      leave  

Intenta jugar con la optimización de los niveles?

Otros consejos

Tal vez usted podría utilizar una matriz de punteros a función en lugar de un switch ?

#include <stdio.h>

typedef void (*func)(void);

static void f0(void) { printf("%s\n", __FUNCTION__); }
static void f1(void) { printf("%s\n", __FUNCTION__); }
static void f2(void) { printf("%s\n", __FUNCTION__); }
static void f3(void) { printf("%s\n", __FUNCTION__); }
static void f4(void) { printf("%s\n", __FUNCTION__); }
static void f5(void) { printf("%s\n", __FUNCTION__); }
static void f6(void) { printf("%s\n", __FUNCTION__); }
static void f7(void) { printf("%s\n", __FUNCTION__); }

int main(void)
{
    const func f[8] = { f0, f1, f2, f3, f4, f5, f6, f7 };
    int i;

    for (i = 0; i < 8; ++i)
    {
        f[i]();
    }
    return 0;
}

Has probado a declarar la switch variable como un campo de bits?

struct Container {
  uint16_t a:3;
  uint16_t unused:13;
};

struct Container cont;

cont.a = 5;  /* assign some value */
switch( cont.a ) {
...
}

Espero que esto funcione!

Yo no lo intente, pero no estoy seguro de que gcc_unreachable hace lo mismo que __builtin_unreachable.Googlear los dos, gcc_unreachable parece ser diseñada como una afirmación de la herramienta para el desarrollo de GCC en sí, tal vez con una rama de predicción de la pista incluido, mientras que __builtin_unreachable hace que el programa al instante indefinido — que suena como borrar el bloque básico, que es lo que quieres.

http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-g_t_005f_005fbuiltin_005funreachable-3075

Tal vez sólo tiene que utilizar un default etiqueta para el puño, o el último caso?

Esta pregunta es ciertamente interesante, desde el punto de vista de una pasada de optimización del compilador que aparentemente es obvio para nosotros, y me hizo pasar un tiempo considerable tratando de llegar a una solución sencilla, en gran parte de la curiosidad personal.

Dicho esto, tengo que admitir Yo soy muy escéptico de que esta instrucción adicional jamás va a resultar en un rendimiento medible diferencia en la práctica, especialmente en un mac nuevo.Si usted tiene cualquier cantidad significativa de datos, usted será dependiente de e/S, y una sola instrucción nunca será su cuello de botella.Si usted tiene una pequeña cantidad de datos, entonces usted necesitará para realizar una mucho mucho mucho de los cálculos varias veces antes de que una sola instrucción se convertirá en un cuello de botella.

Iba a publicar algo de código para mostrar que realmente hay una diferencia de rendimiento?O describir el código y los datos de su trabajo con?

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top