2010-01-11 9 views
13

Aquí hay dos maneras de configurar un bit individual en C en x86-64:Eficacia de optmization GCC en operaciones de bits

inline void SetBitC(long *array, int bit) { 
    //Pure C version 
    *array |= 1<<bit; 
} 

inline void SetBitASM(long *array, int bit) { 
    // Using inline x86 assembly 
    asm("bts %1,%0" : "+r" (*array) : "g" (bit)); 
} 

con gcc 4.3 con -O3 -march=core2 opciones, la versión C tarda aproximadamente 90% más de tiempo cuando se usa con una constante bit. (Ambas versiones compilan a exactamente el mismo código de montaje, excepto que la versión C utiliza una instrucción or [1<<num],%rax en vez de una instrucción de bts [num],%rax)

Cuando se utiliza con una variable bit, la versión C realiza mejor, pero es aún significativamente más lento que el inline montaje.

Restablecer, alternar y comprobar bits tienen resultados similares.

¿Por qué GCC se optimiza tan poco para una operación tan común? ¿Estoy haciendo algo mal con la versión C?

Edit: Disculpe por la larga espera, aquí está el código que solía comparar. En realidad comenzó como un simple problema de programación ...

int main() { 
    // Get the sum of all integers from 1 to 2^28 with bit 11 always set 
    unsigned long i,j,c=0; 
    for (i=1; i<(1<<28); i++) { 
     j = i; 
     SetBit(&j, 10); 
     c += j; 
    } 
    printf("Result: %lu\n", c); 
    return 0; 
} 

gcc -O3 -march=core2 -pg test.c 
./a.out 
gprof 
with ASM: 101.12  0.08  0.08        main 
with C: 101.12  0.16  0.16        main 

time ./a.out también da resultados similares.

+1

¿Es realmente una "operación común"? El momento más común en que veo que se usa la manipulación de bits es en la optimización prematura por parte de personas que piensan que ahorrarán memoria al agrupar un grupo de indicadores en un solo byte. –

+2

Hmm ... buen punto. Aún así, es una operación bastante simple y común en los controladores de hardware. De todos modos, el punto es la disminución del 90% del rendimiento. –

+1

En la programación del controlador, estoy bastante seguro de que el modismo "giro a la izquierda 1 para determinar nuestra máscara" no se usa demasiado. En su lugar, 'o' con un indicador predefinido. –

Respuesta

0

Creo que está pidiendo mucho de su optimizador.

Usted puede ser capaz de ayudarla a salir un poco a hacer un `registro a largo z = 1L < < bits;.", A continuación, o-ción de que con su gama

Sin embargo, supongo que en un 90% más tiempo, quiere decir que la versión C toma 10 ciclos y la versión asm toma 5 ciclos, ¿cómo se compara el rendimiento en -O2 o -O1?

+0

"el registro" será ignorado por el compilador. –

+0

@Laurynas Biveinis: "compilar" puede o no ser ignorado por el compilador, es una pista, después de todo – Hasturkun

+0

La idea era que hacer 'long | = register long' podría alentar al compilador a optimizar :) – Seth

2

Puede publicar el código que está usando para ¿el tiempo? Este tipo de operación puede ser difícil de cronometrar con precisión.

En teoría, las dos secuencias de código deben ser igualmente rápido, por lo que la explicación más probable (en mi opinión) es que algo está causando que su código de tiempo dé resultados falsos.

+0

Sí. En todas las CPU x86 que conozco, una operación básica de ALU como 'OR 'es al menos tan rápida como operaciones' exóticas 'como' BTS'. Una posibilidad es que la prueba de incremento + en su ciclo de temporización utilice la misma unidad de ejecución de CPU que el 'OR ', lo que lleva a la contención, mientras que un' BTS' usa una unidad diferente. –

+0

En el hardware x86 reciente, 'o' se puede enviar a más de una unidad de ejecución, por lo que no debería haber ninguna contención allí. –

+0

Publicado - gracias por su ayuda. –

13

¿Por qué GCC se optimiza tan poco para una operación tan común?

Preludio: Desde finales de 1980, se centran en la optimización del compilador se ha alejado de microbenchmarks que se centran en las operaciones individuales y hacia macrobenchmarks que se centran en aplicaciones cuya velocidad de la gente se preocupa. En estos días, la mayoría de los escritores de compiladores se centran en macrobenchmarks, y el desarrollo de buenas suites de referencia es algo que se toma en serio.

respuesta: Nadie en el gcc está utilizando un punto de referencia donde la diferencia entre or y bts cuestiones al tiempo de ejecución de un programa real. Si puede producir tal programa, es posible que pueda llamar la atención de las personas en gcc-land.

¿Estoy haciendo algo mal con la versión C?

No, esto es perfectamente bueno estándar C. Muy legible e idiomático, de hecho.

2

Para tal código:

#include <stdio.h> 
#include <time.h> 

int main() { 
    volatile long long i = 0; 
    time_t start = time (NULL); 
    for (long long n = 0; n < (1LL << 32); n++) { 
    i |= 1 << 10; 
    } 
    time_t end = time (NULL); 
    printf("C took %ds\n", (int)(end - start)); 
    start = time (NULL); 
    for (long long n = 0; n < (1LL << 32); n++) { 
    __asm__ ("bts %[bit], %[i]" 
        : [i] "=r"(i) 
        : "[i]"(i), [bit] "i" (10)); 
    } 
    end = time (NULL); 
    printf("ASM took %ds\n", (int)(end - start)); 
} 

el resultado fue:

C took 12s 
ASM took 10s 

Mi bandera era (-std=gnu99 -O2 -march=core2). Sin lo volátil, el ciclo se optimizó. gcc 4.4.2.

No se diferencia fue con:

__asm__ ("bts %[bit], %[i]" 
       : [i] "+m"(i) 
       : [bit] "r" (10)); 

Así que, probablemente, la respuesta fue - a nadie le importa. En microbenchmark la única diferencia es la que existe entre esos dos métodos, pero en la vida real creo que dicho código no requiere mucha CPU.

Además para tal código:

#include <stdio.h> 
#include <time.h> 

int main() { 
    volatile long long i = 0; 
    time_t start = time (NULL); 
    for (long long n = 0; n < (1L << 32); n++) { 
    i |= 1 << (n % 32); 
    } 
    time_t end = time (NULL); 
    printf("C took %ds\n", (int)(end - start)); 
    start = time (NULL); 
    for (long long n = 0; n < (1L << 32); n++) { 
    __asm__ ("bts %[bit], %[i]" 
        : [i] "+m"(i) 
        : [bit] "r" (n % 32)); 
    } 
    end = time (NULL); 
    printf("ASM took %ds\n", (int)(end - start)); 
} 

El resultado fue:

C took 9s 
ASM took 10s 

Ambos resultados fueron 'estable'. Prueba CPU 'Intel (R) Core (TM) 2 Duo CPU T9600 @ 2.80GHz'.

+0

el sufijo 'long long' es' LL', no 'L' –

+1

@ LưuVĩnhPhúc buen punto. Sin embargo, se corrigió que no debería afectar el resultado ya que Linux es una plataforma LP64 (podría afectar a Windows u otras plataformas LLP). –

+0

En cuanto a su segunda prueba, esto no parece una comparación "justa". Está forzando que 'i' esté en la memoria en lugar de en un registro, lo que probablemente el código C no haga. ¿Qué tal '__asm__ (" bts% [bit],% [i] ": [i]" + r "(i): [bit]" r "(n% 32));'? –

1

Esta es una operación muy común en los sistemas embebidos que generalmente están limitados por los recursos. 10 ciclos vs 5 ciclos es una mala actuación en estos sistemas. Hay muchos casos en los que uno desea acceder a los puertos IO o usar registros de 16 o 32 bits como indicadores de bits booleanos para guardar la memoria.

El hecho es que if(bit_flags& 1<<12) es mucho más legible [y portátil cuando se implementa con una biblioteca] que el equivalente de ensamblaje. Del mismo modo para IO_PINS|= 1<<5; Desafortunadamente, estas son muchas más lentas, por lo que las torpes macros de asm permanecen activas.

En muchos sentidos, los objetivos de las aplicaciones integradas y del espacio de usuario son opuestos. La capacidad de respuesta de las comunicaciones externas (a una interfaz de usuario o interfaz de máquina) es de menor importancia, al tiempo que garantiza que un ciclo de control (es decir, una micro-bechmark) se complete en un tiempo mínimo es absolutamente crítico y puede hacer o deshacer un procesador o control elegido estrategia.

Obviamente, si uno puede pagar una CPU de varios GHz y todos los periféricos asociados, conjuntos de chips, etc. necesarios para respaldar eso, uno realmente no necesita preocuparse por la optimización de bajo nivel en absoluto. Un microcontrolador más lento de 1000 veces en un sistema de control en tiempo real significa que el ahorro de ciclos de reloj es 1000 veces más importante.

Cuestiones relacionadas