programacion producto optimización multiplicacion matrices ejemplo dinamica complejidad complejas analisis algoritmos algoritmo c++ optimization assembly arm neon

c++ - producto - Máxima optimización de la multiplicación sabia de elementos a través del ensamblaje ARM NEON



programacion dinamica (1)

Realmente no sé mucho sobre el NEON. Sin embargo, creo que tiene dependencias de datos que causan problemas de rendimiento. Le sugiero que prepare el ciclo con algunas cargas y luego las coloque entre el multiplicador y la tienda . Creo que la tienda probablemente esté bloqueando hasta que se complete la multiplicación .

asm volatile( "vld1.32 {q1}, [%[src1]:128]! /n/t" "vld1.32 {q2}, [%[src2]:128]! /n/t" ".loop: /n/t" "vmul.f32 q0, q1, q2 /n/t" "vld1.32 {q1}, [%[src1]:128]! /n/t" "vld1.32 {q2}, [%[src2]:128]! /n/t" "vst1.32 {q0}, [%[dst]:128]! /n/t" "subs %[lBound], %[lBound], $1 /n/t" "bge .loop /n/t" : :[dst] "r" (dst), [src1] "r" (src1), [src2] "r" (src2), [lBound] "r" (loopBound) :"memory", "d0", "d1", "d2", "d3", "d4", "d5 );

De esta manera, deberías poder hacer un paralelo de las cargas con el multiplicador. Tendrá que sobreasignar las matrices de origen o cambiar el índice del ciclo y hacer una multiplicación final y almacenar. Si las operaciones NEON no están afectando los códigos de condición, puede reordenar también los subs y colocarlos antes.

Editar: De hecho, el documento del motor de procesamiento de medios Cortex A-9 recomienda entrelazar las instrucciones ARM y NEON ya que pueden ejecutarse en paralelo. Además, las instrucciones NEON parecen establecer FPSCR y no el ARM CPSR, por lo que reordenar los subs disminuiría el tiempo de ejecución. También puede almacenar en caché el ciclo.

Estoy optimizando una multiplicación sabia de elementos de dos matrices unidimensionales para un procesador dual Cortex-A9. Linux se está ejecutando en el tablero y estoy usando el compilador GCC 4.5.2.

Entonces la siguiente es mi función de ensamblador en línea C ++. src1, src2 y dst están alineados en 16 bytes.

Actualización: Código Testable:

void Multiply( const float* __restrict__ src1, const float* __restrict__ src2, float* __restrict__ dst, const unsigned int width, const unsigned int height) { int loopBound = (width * height) / 4; asm volatile( ".loop: /n/t" "vld1.32 {q1}, [%[src1]:128]! /n/t" "vld1.32 {q2}, [%[src2]:128]! /n/t" "vmul.f32 q0, q1, q2 /n/t" "vst1.32 {q0}, [%[dst]:128]! /n/t" "subs %[lBound], %[lBound], $1 /n/t" "bge .loop /n/t" : :[dst] "r" (dst), [src1] "r" (src1), [src2] "r" (src2), [lBound] "r" (loopBound) :"memory", "d0", "d1", "d2", "d3", "d4", "d5 ); } //The following function describes how to test the element wise multiplication void Test() { const unsigned int width = 1024, height = 1024; float* src1 __attribute__((aligned(16))) = new float[width * height]; float* src2 __attribute__((aligned(16))) = new float[width * height]; float* dst __attribute__((aligned(16))) = new float[width * height]; for(unsigned int i = 0; i < (width * height); i++) { src1[i] = (float)rand(); src2[i] = (float)rand(); } Multiply(src1, src2, dst, width, height); std::cout << dst[0] << std::endl; }

El cálculo de 1024 * 1024 valores toma ~ 0.016 s. (Dos hilos: cada hilo calcula la mitad del conjunto). Interpretado ingenuamente, el cálculo de una iteración requiere 122 ciclos. Esto parece ser un poco lento. Pero, ¿dónde está el cuello de botella?

Incluso probé el comando pld para precargar elementos en la memoria caché L2, "desenrollando" el ciclo calculando hasta 20 valores por iteración y reordenando las instrucciones para asegurar que el procesador no esté esperando memoria. No obtuve tanta aceleración (máx. 0,001 s más rápido).

¿Tiene alguna sugerencia para acelerar el cálculo?