Refiriéndose a la respuesta de @auselen aquí: Using ARM NEON intrinsics to add alpha and permute, parece que el compilador de armcc es mucho mejor que el compilador de gcc para las optimizaciones de NEON. ¿Es esto realmente cierto? Realmente no he probado el compilador de armcc. Pero obtuve un código bastante optimizado usando el compilador gcc con el indicador de optimización -O3. Pero ahora me pregunto si armcc realmente es tan bueno. Entonces, ¿cuál de los dos compiladores es mejor, teniendo en cuenta todos los factores?¿Cuál es mejor, gcc o armcc para las optimizaciones de NEON?
Respuesta
Los compiladores también son software, tienden a mejorar con el tiempo. Cualquier afirmación genérica como el brazo es mejor que GCC en NEON (o mejor dicho como vectorización) no puede ser verdad para siempre ya que un grupo de desarrolladores puede cerrar la brecha con suficiente atención. Sin embargo, inicialmente es lógico esperar que los compiladores desarrollados por las compañías de hardware sean superiores porque necesitan demostrar/comercializar estas características.
Un ejemplo reciente que vi fue aquí en Stack Overflow sobre un answer for branch prediction. Citando de la última línea de la sección actualizada "Esto demuestra que incluso los compiladores modernos maduros pueden variar enormemente en su capacidad para optimizar el código ...".
Soy un gran admirador de GCC, pero no apostaría a la calidad del código producido por él contra los compiladores de Intel o ARM.Espero que cualquier compilador comercial convencional produzca código al menos tan bueno como GCC.
Una respuesta empírica a esta pregunta podría ser el uso de hilbert-space's neon optimization example y ver cómo los diferentes compiladores optimizarlo.
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int n)
{
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
n/=8;
for (i=0; i<n; i++)
{
uint16x8_t temp;
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], rfac);
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], bfac);
result = vshrn_n_u16 (temp, 8);
vst1_u8 (dest, result);
src += 8*3;
dest += 8;
}
}
Ésta es armcc 5,01
20: f421140d vld3.8 {d1-d3}, [r1]!
24: e2822001 add r2, r2, #1
28: f3810c04 vmull.u8 q0, d1, d4
2c: f3820805 vmlal.u8 q0, d2, d5
30: f3830806 vmlal.u8 q0, d3, d6
34: f2880810 vshrn.i16 d0, q0, #8
38: f400070d vst1.8 {d0}, [r0]!
3c: e1520003 cmp r2, r3
40: bafffff6 blt 20 <neon_convert+0x20>
Esta es GCC 4.4.3-4.7.1
1e: f961 040d vld3.8 {d16-d18}, [r1]!
22: 3301 adds r3, #1
24: 4293 cmp r3, r2
26: ffc0 4ca3 vmull.u8 q10, d16, d19
2a: ffc1 48a6 vmlal.u8 q10, d17, d22
2e: ffc2 48a7 vmlal.u8 q10, d18, d23
32: efc8 4834 vshrn.i16 d20, q10, #8
36: f940 470d vst1.8 {d20}, [r0]!
3a: d1f0 bne.n 1e <neon_convert+0x1e>
que se ve muy similares, por lo que tenemos un empate. Después de ver esto intenté agregar alpha y permute nuevamente.
void neonPermuteRGBtoBGRA(unsigned char* src, unsigned char* dst, int numPix)
{
numPix /= 8; //process 8 pixels at a time
uint8x8_t alpha = vdup_n_u8 (0xff);
for (int i=0; i<numPix; i++)
{
uint8x8x3_t rgb = vld3_u8 (src);
uint8x8x4_t bgra;
bgra.val[0] = rgb.val[2]; //these lines are slow
bgra.val[1] = rgb.val[1]; //these lines are slow
bgra.val[2] = rgb.val[0]; //these lines are slow
bgra.val[3] = alpha;
vst4_u8(dst, bgra);
src += 8*3;
dst += 8*4;
}
}
compilación con gcc ...
$ arm-linux-gnueabihf-gcc --version
arm-linux-gnueabihf-gcc (crosstool-NG linaro-1.13.1-2012.05-20120523 - Linaro GCC 2012.05) 4.7.1 20120514 (prerelease)
$ arm-linux-gnueabihf-gcc -std=c99 -O3 -c ~/temp/permute.c -marm -mfpu=neon-vfpv4 -mcpu=cortex-a9 -o ~/temp/permute_gcc.o
00000000 <neonPermuteRGBtoBGRA>:
0: e3520000 cmp r2, #0
4: e2823007 add r3, r2, #7
8: b1a02003 movlt r2, r3
c: e92d01f0 push {r4, r5, r6, r7, r8}
10: e1a021c2 asr r2, r2, #3
14: e24dd01c sub sp, sp, #28
18: e3520000 cmp r2, #0
1c: da000019 ble 88 <neonPermuteRGBtoBGRA+0x88>
20: e3a03000 mov r3, #0
24: f460040d vld3.8 {d16-d18}, [r0]!
28: eccd0b06 vstmia sp, {d16-d18}
2c: e59dc014 ldr ip, [sp, #20]
30: e2833001 add r3, r3, #1
34: e59d6010 ldr r6, [sp, #16]
38: e1530002 cmp r3, r2
3c: e59d8008 ldr r8, [sp, #8]
40: e1a0500c mov r5, ip
44: e59dc00c ldr ip, [sp, #12]
48: e1a04006 mov r4, r6
4c: f3c73e1f vmov.i8 d19, #255 ; 0xff
50: e1a06008 mov r6, r8
54: e59d8000 ldr r8, [sp]
58: e1a0700c mov r7, ip
5c: e59dc004 ldr ip, [sp, #4]
60: ec454b34 vmov d20, r4, r5
64: e1a04008 mov r4, r8
68: f26401b4 vorr d16, d20, d20
6c: e1a0500c mov r5, ip
70: ec476b35 vmov d21, r6, r7
74: f26511b5 vorr d17, d21, d21
78: ec454b34 vmov d20, r4, r5
7c: f26421b4 vorr d18, d20, d20
80: f441000d vst4.8 {d16-d19}, [r1]!
84: 1affffe6 bne 24 <neonPermuteRGBtoBGRA+0x24>
88: e28dd01c add sp, sp, #28
8c: e8bd01f0 pop {r4, r5, r6, r7, r8}
90: e12fff1e bx lr
Compilar con armcc ...
$ armcc
ARM C/C++ Compiler, 5.01 [Build 113]
$ armcc --C99 --cpu=Cortex-A9 -O3 -c permute.c -o permute_arm.o
00000000 <neonPermuteRGBtoBGRA>:
0: e1a03fc2 asr r3, r2, #31
4: f3870e1f vmov.i8 d0, #255 ; 0xff
8: e0822ea3 add r2, r2, r3, lsr #29
c: e1a031c2 asr r3, r2, #3
10: e3a02000 mov r2, #0
14: ea000006 b 34 <neonPermuteRGBtoBGRA+0x34>
18: f420440d vld3.8 {d4-d6}, [r0]!
1c: e2822001 add r2, r2, #1
20: eeb01b45 vmov.f64 d1, d5
24: eeb02b46 vmov.f64 d2, d6
28: eeb05b40 vmov.f64 d5, d0
2c: eeb03b41 vmov.f64 d3, d1
30: f401200d vst4.8 {d2-d5}, [r1]!
34: e1520003 cmp r2, r3
38: bafffff6 blt 18 <neonPermuteRGBtoBGRA+0x18>
3c: e12fff1e bx lr
En este caso armcc produce mucho mejor código. Creo que esto justifica fgp's answer above. La mayoría de las veces, GCC producirá un código lo suficientemente bueno, pero debe vigilar las partes críticas o, lo que es más importante, primero debe medir/perfilar.
Si utiliza los intrínsecos de NEON, el compilador no debería importar demasiado. La mayoría (si no todos) los intrínsecos de NEON se traducen en una única instrucción NEON, por lo que lo único que le queda al compilador es la asignación de registros y la programación de instrucciones. En mi experiencia, tanto GCC 4.2 como Clang 3.1 funcionan razonablemente bien en esas tareas.
Tenga en cuenta, sin embargo, que las instrucciones NEON son un poco más expresivas que las instrinsics de NEON. Por ejemplo, las instrucciones de carga/almacenamiento de NEON tienen modos de direccionamiento de incremento previo y posterior que combinan una carga o almacenamiento con un incremento del registro de direcciones, lo que le ahorra una instrucción. Los intrínsecos de NEON no proporcionan una forma explícita de hacerlo, sino que confían en el compilador para combinar un regulador NEON load/store intrinsic y un incremento de dirección en una instrucción load/store con incremento posterior. De forma similar, algunas instrucciones de carga/almacenamiento le permiten especificar la alineación de la dirección de memoria y ejecutarla más rápido si especifica garantías de alineación más estrictas. Los intrínsecos de NEON, de nuevo, no le permiten especificar la alineación explícitamente, sino que confían en el compilador para deducir el especificador de alineación correcto. En teoría, se utiliza "align" atributos en sus punteros para ofrecer consejos adecuados para el compilador, pero al menos Clang parece ignorar los ...
En mi experiencia, ni tampoco Clang GCC son muy brillantes cuando se trata de ese tipo de optimizaciones. Afortunadamente, el beneficio de rendimiento adicional de este tipo de optimización generalmente no es tan alto: se parece más al 10% que al 100%.
Otra área donde esos dos compiladores no son especialmente inteligentes es evitar el derrame de la pila. Si el código utiliza más variables de valor vectorial que registros NEON, parece que ambos compiladores producen código horrible. Básicamente, lo que parecen hacer es programar instrucciones basadas en la suposición de que hay suficientes registros disponibles. La asignación de registros parece venir después, y parece simplemente derramar valores en la pila una vez que se ejecuta de registros. ¡Así que asegúrese de que el código tenga un conjunto de trabajo de menos de 16 vectores de 128 bits o 32 vectores de 64 bits en cualquier momento!
En general, obtuve muy buenos resultados tanto de GCC como de Clang, pero regularmente tuve que reorganizar un poco el código para evitar las idiosincrasias del compilador. Mi consejo sería que te quedes con GCC o Clang, pero revisa regularmente con el disensembler que elijas.
Así que, en general, diría que seguir con GCC está bien. Sin embargo, es posible que desee ver el desmontaje de las piezas críticas para el rendimiento y verificar si se ve razonable.
- 1. ¿Cuáles son las costosas optimizaciones de GCC?
- 2. Cómo deshabilito las optimizaciones de tailcall en gcc
- 3. ARM NEON: ¿Cuál es la diferencia entre vld4_f32 y vld4q_f32?
- 4. Cuál es mejor: mysql_connect o mysql_pconnect
- 5. Esquema DTD o XML. ¿Cuál es mejor?
- 6. min o gzip, ¿cuál es mejor?
- 7. Apache2: mod_wsgi o mod_python, ¿cuál es mejor?
- 8. ¿Cuál es la mejor práctica de codificación para las condiciones?
- 9. cuál es mejor ... GATE o RapidMiner
- 10. Cuál es mejor - Ext.get() o document.getElementById()
- 11. ¿Cuál es mejor H2 o HSQLDB?
- 12. ¿Cuál es la mejor clave principal para almacenar las URL
- 13. ¿Cómo inicializar const float32x4x4_t (ARM NEON intrinsic, GCC)?
- 14. ¿Cuál es la diferencia entre -ggdb gcc gcc -g y
- 15. ¿Cómo construir en modo de lanzamiento con optimizaciones en GCC?
- 16. matriz de enteros o matriz de estructuras: ¿cuál es mejor?
- 17. ¿Cuál es la diferencia entre glib gunichar y wchar_t y cuál es mejor para las soluciones multiplataforma?
- 18. ¿Cuál es la mejor de las aplicaciones de eventos django?
- 19. ¿Qué tan bueno es NVCC en las optimizaciones de código?
- 20. ¿Cuál es un método mejor? libsvm o svmclassify?
- 21. C: ¿Cuál es mejor? Malloc matriz de punteros a las estructuras, o una matriz de estructuras?
- 22. ¿Cuál es el mejor editor para AutoHotkey?
- 23. ¿Cuál es el mejor visor para NLog?
- 24. TR1 de Boost o VC10 - ¿Cuál es mejor?
- 25. ¿Optimizaciones CALayer?
- 26. ¿Cuál es el más utilizado? RSS o Atom? y de cuál es mejor construir?
- 27. ¿Cuál de las interpolaciones utf8 es la mejor?
- 28. ¿Evita la JVM las optimizaciones de cola?
- 29. Django-Socialauth o django-social-auth, ¿cuál es el mejor?
- 30. ¿Cuál es la mejor solución de VCS para Windows?
El soporte de neón en gcc es menos maduro que el apoyo número entero/fp escalar. Sin embargo, la comparación de auselen se basa en gcc 4.4.3, lanzado hace más de 2.5 años. Desde entonces, se ha invertido bastante trabajo en las mejoras de NEON. Al mismo tiempo, el brazo 5.01 tiene solo un año. Si bien aún esperaría que el brazo 5.02 estuviera adelante, una comparación más relevante sería entre él y un 4.7 gcc. – unixsmurf
@unixsmurf uno de +1 es por mí :) – auselen