Optimización de funciones de montaje de neón

Estoy trabajando en una aplicación androide nativa que debería ejecutarse en un dispositivo de procesador ARMv7. Por algunas razones necesito hacer un cierto cálculo pesado en vectores (corto y / o flotador). Implementé alguna función de ensamblaje usando comandos NEON para aumentar la computación. He ganado un factor de velocidad 1.5 que no está mal. Me pregunto si puedo mejorar estas funciones para ir aún más rápido.

Así que la pregunta es: ¿qué cambios puedo hacer para mejorar estas funciones?

//add to float vectors. //the result could be put in scr1 instead of dst void add_float_vector_with_neon3(float* dst, float* src1, float* src2, int count) { asm volatile ( "1: \n" "vld1.32 {q0}, [%[src1]]! \n" "vld1.32 {q1}, [%[src2]]! \n" "vadd.f32 q0, q0, q1 \n" "subs %[count], %[count], #4 \n" "vst1.32 {q0}, [%[dst]]! \n" "bgt 1b \n" : [dst] "+r" (dst) : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count) : "memory", "q0", "q1" ); } //multiply a float vector by a scalar. //the result could be put in scr1 instead of dst void mul_float_vector_by_scalar_with_neon3(float* dst, float* src1, float scalar, int count) { asm volatile ( "vdup.32 q1, %[scalar] \n" "2: \n" "vld1.32 {q0}, [%[src1]]! \n" "vmul.f32 q0, q0, q1 \n" "subs %[count], %[count], #4 \n" "vst1.32 {q0}, [%[dst]]! \n" "bgt 2b \n" : [dst] "+r" (dst) : [src1] "r" (src1), [scalar] "r" (scalar), [count] "r" (count) : "memory", "q0", "q1" ); } //add to short vector -> no problem of coding limits //the result should be put in in a dest different from src1 and scr2 void add_short_vector_with_neon3(short* dst, short* src1, short* src2, int count) { asm volatile ( "3: \n" "vld1.16 {q0}, [%[src1]]! \n" "vld1.16 {q1}, [%[src2]]! \n" "vadd.i16 q0, q0, q1 \n" "subs %[count], %[count], #8 \n" "vst1.16 {q0}, [%[dst]]! \n" "bgt 3b \n" : [dst] "+r" (dst) : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count) : "memory", "q0", "q1" ); } //multiply a short vector by a float vector and put the result bach into a short vector //the result should be put in in a dest different from src1 void mul_short_vector_by_float_vector_with_neon3(short* dst, short* src1, float* src2, int count) { asm volatile ( "4: \n" "vld1.16 {d0}, [%[src1]]! \n" "vld1.32 {q1}, [%[src2]]! \n" "vmovl.s16 q0, d0 \n" "vcvt.f32.s32 q0, q0 \n" "vmul.f32 q0, q0, q1 \n" "vcvt.s32.f32 q0, q0 \n" "vmovn.s32 d0, q0 \n" "subs %[count], %[count], #4 \n" "vst1.16 {d0}, [%[dst]]! \n" "bgt 4b \n" : [dst] "+r" (dst) : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count) : "memory", "d0", "q0", "q1" ); } 

Gracias por adelantado !

Puedes intentar desenrollar tu bucle para procesar más elementos por bucle.

Su código para add_float_vector_with_neon3 toma 10 ciclos (debido al bloqueo) por 4 elementos, mientras que desenrollar a 16 elementos consume 21 ciclos. http://pulsar.webshaker.net/ccc/sample-34e5f701

Aunque hay una sobrecarga porque es necesario procesar el resto (o puede pad sus datos para ser múltiples de 16), pero si tiene un montón de datos, la sobrecarga debe ser bastante bajo en comparación con la suma real.

Este es un ejemplo de cómo se puede codificar con instrinsics de neón.

La ventaja es que puede utilizar el compilador para optimizar la asignación de registros y la programación de instrucciones mientras restringe el uso de instrucciones.

El inconveniente es que, GCC no parece capaz de combinar la aritmética del puntero en la instrucción load / store para que se emitan instrucciones adicionales de la ALU para hacerlo. O tal vez estoy equivocado y GCC tiene una buena razón para hacerlo de esa manera.

Con GCC y CFLAGS=-std=gnu11 -O3 -fgcse-lm -fgcse-sm -fgcse-las -fgcse-after-reload -mcpu=cortex-a9 -mfloat-abi=hard -mfpu=neon -fPIE -Wall Código compila código de objeto muy decente. El bucle se desenrolla e intercala para ocultar el retardo largo antes de que el resultado de una carga esté disponible. Y también es legible, también.

 #include <arm_neon.h> #define ASSUME_ALIGNED_FLOAT_128(ptr) ((float *)__builtin_assume_aligned((ptr), 16)) __attribute__((optimize("unroll-loops"))) void add_float_vector_with_neon3( float *restrict dst, const float *restrict src1, const float *restrict src2, size_t size) { for(int i=0;i<size;i+=4){ float32x4_t inFloat41 = vld1q_f32(ASSUME_ALIGNED_FLOAT_128(src1)); float32x4_t inFloat42 = vld1q_f32(ASSUME_ALIGNED_FLOAT_128(src2)); float32x4_t outFloat64 = vaddq_f32 (inFloat41, inFloat42); vst1q_f32 (ASSUME_ALIGNED_FLOAT_128(dst), outFloat64); src1+=4; src2+=4; dst+=4; } } 

OK, he comparado entre el código dado en el post inicial y una nueva función propuesta por Josejulio:

 void add_float_vector_with_neon3(float* dst, float* src1, float* src2, int count) { asm volatile ( "1: \n" "vld1.32 {q0,q1}, [%[src1]]! \n" "vld1.32 {q2,q3}, [%[src2]]! \n" "vadd.f32 q0, q0, q2 \n" "vadd.f32 q1, q1, q3 \n" "vld1.32 {q4,q5}, [%[src1]]! \n" "vld1.32 {q6,q7}, [%[src2]]! \n" "vadd.f32 q4, q4, q6 \n" "vadd.f32 q5, q5, q7 \n" "subs %[count], %[count], #16 \n" "vst1.32 {q0, q1}, [%[dst]]! \n" "vst1.32 {q4, q5}, [%[dst]]! \n" "bgt 1b \n" : [dst] "+r" (dst) : [src1] "r" (src1), [src2] "r" (src2), [count] "r" (count) : "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" ); } 

Mientras que en la herramienta (pulsar.webshaker.net/ccc/index.php), hay una gran diferencia en CPU cylindcles / float, no veo mucha diferencia en la verificación de latencia:

Mediana, firstQuartile, thirdQuartile, minVal, maxVal (micro-sec, 1000 medidas)

Original: 3564, 3206, 5126, 1761, 12144

Desenrollado: 3567, 3080, 4877, 3018, 11683

Así que no estoy seguro de que el desenrollado sea tan eficiente …

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.