2012-02-16 18 views
8

El vector extensions de GCC ofrece una forma agradable y razonablemente portátil de acceder a algunas instrucciones SIMD en diferentes arquitecturas de hardware sin recurrir a hardware specific intrinsics (o auto-vectorización).Cargando datos para las extensiones vectoriales de GCC

Un caso de uso real, es el cálculo de una suma de comprobación aditiva simple. Lo único que no está claro es cómo cargar datos de forma segura en un vector.

typedef char v16qi __attribute__ ((vector_size(16))); 

static uint8_t checksum(uint8_t *buf, size_t size) 
{ 
    assert(size%16 == 0); 
    uint8_t sum = 0; 

    vec16qi vec = {0}; 
    for (size_t i=0; i<(size/16); i++) 
    { 
     // XXX: Yuck! Is there a better way? 
     vec += *((v16qi*) buf+i*16); 
    } 

    // Sum up the vector 
    sum = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5] + vec[6] + vec[7] + vec[8] + vec[9] + vec[10] + vec[11] + vec[12] + vec[13] + vec[14] + vec[15]; 

    return sum; 
} 

Al lanzar un puntero al tipo de vector parece funcionar, pero me preocupa que esto podría explotar de una manera horrible si el hardware SIMD espera que los tipos de vectores para estar alineados correctamente.

La única otra opción que he pensado es utilizar un vector temporal y cargar explícitamente los valores (mediante una asignación memcpy o element-wise), pero al probar esto se contrarrestan la mayor parte de la aceleración del uso de las instrucciones SIMD. Idealmente, me imagino que esto sería algo así como una función genérica __builtin_load(), pero ninguna parece existir.

¿Cuál es una forma más segura de cargar datos en un vector arriesgando problemas de alineación?

+2

La ejecución de este en la memoria no alineada en x86_64 GCC causará una SIGSEGV cuando la CPU intenta cargar la memoria desalineada en un registro SSE. Una opción razonable parece ser solo la memoria de suma de comprobación o utilizar un ciclo normal para sumar los bytes hasta el primer límite de 16 bytes. – dcoles

+0

En su código actual, la carga de los datos se compila muy bien si el compilador conoce la entrada (pero la suma es mala): https://godbolt.org/g/DeR3Qv. No es tan lindo sin el conocimiento de la entrada: https: // godbolt.org/g/LxEkhp – ZachB

Respuesta

0

Se podría utilizar un inicializador para cargar los valores, es decir, hacer

const vec16qi e = { buf[0], buf[1], ... , buf[15] } 

y esperar que GCC convierte esto en una instrucción de carga ESS. Sin embargo, lo verificaría con un dissassembler ;-). Además, para un mejor rendimiento, intente alinear buf de 16 bytes e informe a ese compilador a través de un atributo aligned. Si puede garantizar que el búfer de entrada estará alineado, trátelo de forma continua hasta que haya alcanzado un límite de 16 bytes.

+0

No creo que sea necesario alinear buf. Sería, si estuviéramos lidiando con punteros. – user1095108

+0

@ user1095108 Desea que el compilador lo convierta en una instrucción de carga SSE, que es equivalente a 'e = * buf' (pero no puede escribirlo de esa manera porque los tipos no coinciden). Así que usted ESTÁ lidiando con punteros aquí, en realidad. Si el compilador puede inferir que buf está alineado en 16 bytes, puede usar una carga alineada, que (al menos, preventa puente) es más rápida que una carga desalineada. – fgp

+0

No, estarías lidiando con punteros si tuvieras que lanzar 'buf' a' vec16qi' de mi experiencia. – user1095108

1

Editar (gracias Peter Cordes) Usted puede lanzar punteros:

typedef char v16qi __attribute__ ((vector_size (16), aligned (16))); 

v16qi vec = *(v16qi*)&buf[i]; // load 
*(v16qi*)(buf + i) = vec; // store whole vector 

Esto compila a vmovdqa para cargar y vmovups de almacenar. Si no se sabe que los datos están alineados, configure aligned (1) para generar vmovdqu. (godbolt)

Tenga en cuenta que también hay varios órdenes internas de propósito especial para la carga y descarga de estos registros (Editar 2):

v16qi vec = _mm_loadu_si128((__m128i*)&buf[i]); // _mm_load_si128 for aligned 
_mm_storeu_si128((__m128i*)&buf[i]), vec); // _mm_store_si128 for aligned 

Parece ser necesario el uso de -flax-vector-conversions pasar de char s a v16qi con esta función.

Consulte también: C - How to access elements of vector using GCC SSE vector extension
Consulte también: SSE loading ints into __m128

(Consejo:. La mejor frase para Google es algo así como "__m128i gcc carga")

+1

Aparentemente, la forma recomendada de cargar datos no alineados en vectores GNU C es con un atributo 'aligned (1)' al declarar un tipo de vector, y lanzar punteros a ese tipo de vector no alineado. p.ej. 'typedef char __attribute__ ((vector_size (16), aligned (1))) unaligned_byte16;'. Ver [el final de mi respuesta aquí] (http://stackoverflow.com/a/39115055/224132), y los comentarios de Marc Glisse al respecto. –

+0

@PeterCordes gracias! Respuesta editada, mucho más simple. – ZachB

+0

Para extraer, creo que deberías estar usando 'vec [0]'. Tal como lo entiendo, aliasar punteros escalares en tipos de vectores es * no * correcto. Funciona con 'char *' porque 'char *' es especial y permite alias cualquier cosa. Lanzar un 'int *' a un 'v4si *' ni siquiera cuenta como alias, porque v4si se define en términos de 'int'. Los tipos intrínsecos de Intel ('__m128i') también pueden alias en otras cosas, debido a un atributo adicional:' typedef long long __m128i __attribute__ ((__vector_size__ (16), __may_alias __)); 'Sin may_alias, no se puede 'v4si' con seguridad ivec = * (v4si) short_pointer'. Lo dejé antes de –

Cuestiones relacionadas