GCC 4.4:Evitar la gama de verificación del switch/case en gcc?
-
15-09-2020 - |
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
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.)
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;
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.
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.
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?