2012-04-16 9 views
6

Estoy aprendiendo a usar las capacidades de SIMD volviendo a escribir mi biblioteca de procesamiento de imagen personal utilizando vectores intrínsecos. Una función básica es una simple "array +=", es decirAgregación de matriz SIMD para longitudes de matriz arbitrarias

void arrayAdd(unsigned char* A, unsigned char* B, size_t n) { 
    for(size_t i=0; i < n; i++) { B[i] += A[i] }; 
} 

Para longitudes de matriz arbitrarias, el código SIMD obvio (suponiendo alineado por 16) es algo como:

size_t i = 0; 
__m128i xmm0, xmm1; 
n16 = n - (n % 16); 
for (; i < n16; i+=16) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
} 
for (; i < n; i++) { B[i] += A[i]; } 

Pero es posible hacer todos las adiciones con instrucciones SIMD? Pensé en probar esto:

__m128i mask = (0x100<<8*(n - n16))-1; 
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i)); 

para los elementos extra, pero ¿dará lugar a un comportamiento indefinido? El mask debería garantizar que no se realiza ningún acceso más allá de los límites de la matriz (creo). La alternativa es hacer los elementos adicionales primero, pero luego la matriz debe estar alineada por n-n16, lo que no parece correcto.

¿Hay algún otro patrón más óptimo como bucles vectorizados?

+0

se podría asegurar que en el código de las longitudes de matriz no son siempre múltiplos de 16 bytes (aunque posiblemente menos elementos se utilizan en realidad), por lo que este epílogo no aparece. Pero el epílogo realmente no es importante en términos de velocidad. – Walter

Respuesta

4

Una opción es rellenar su matriz a un múltiplo de 16 bytes. Entonces puede hacer 128 bit load/add/store y simplemente ignorar los resultados siguiendo el punto que le importa.

Para grandes arreglos, aunque la sobrecarga del byte por byte "epilog" va a ser muy pequeña. Desenrollar el bucle puede mejorar el rendimiento más, algo así como:

for (; i < n32; i+=32) { 
    xmm0 = _mm_load_si128((__m128i*) (A + i)); 
    xmm1 = _mm_load_si128((__m128i*) (B + i)); 
    xmm2 = _mm_load_si128((__m128i*) (A + i + 16)); 
    xmm3 = _mm_load_si128((__m128i*) (B + i + 16)); 
    xmm1 = _mm_add_epi8(xmm0, xmm1); 
    xmm3 = _mm_add_epi8(xmm2, xmm3); 
    _mm_store_si128((__m128i*) (B + i), xmm1); 
    _mm_store_si128((__m128i*) (B + i + 16), xmm3); 
} 
// Do another 128 bit load/add/store here if required 

Pero es difícil decir sin hacer algunos perfiles.

También podría hacer una carga/almacenamiento desalineado al final (suponiendo que tenga más de 16 bytes), aunque esto probablemente no suponga una gran diferencia. P.ej. si usted tiene 20 bytes que se lleve a cabo una carga/almacenamiento a la posición 0 y la otra carga no alineados/añadir/tienda (_mm_storeu_si128, __mm_loadu_si128) para compensar 4.

Usted podría utilizar _mm_maskmoveu_si128 pero que necesita para obtener la máscara en un registro XMM , y su código de muestra no va a funcionar. Probablemente quiera establecer el registro de máscara en todos los FF y luego usar un shift para alinearlo. Al final del día, probablemente será más lento que el load/add/store desalineado.

Esto sería algo así como:

mask = _mm_cmpeq_epi8(mask, mask); // Set to all FF's 
mask = _mm_srli_si128(mask, 16-(n%16)); // Align mask 
_mm_maskmoveu_si128(xmm, mask, A + i); 
+0

En la práctica, pondría las máscaras en una tabla de búsqueda. ¿Crees que todavía sería más lento que el ciclo "epilog"? –

+0

@reve_etrange: Probablemente no sea más lento, pero es difícil saberlo sin medir las dos soluciones. Darle una oportunidad. –

+0

Lo intentaré. ¿Pero es un acceso legal a la memoria? Dado que * algún * valor de 'máscara' podría causar una violación de límites de matriz. –

Cuestiones relacionadas