У меня есть две вкладки поплавков. Мне нужно умножить элементы из первой вкладки на соответствующие элементы из второй вкладки и сохранить результат в третьей вкладке.
Я хотел бы использовать NEON для распараллеливания умножений с плавающей точкой: четыре умножения с плавающей точкой одновременно вместо одной.
Я ожидал значительного ускорения, но достиг всего лишь 20% сокращения времени выполнения. Это мой код:
#include <stdlib.h>
#include <iostream>
#include <arm_neon.h>
const int n = 100; // table size
/* fill a tab with random floats */
void rand_tab(float *t) {
for (int i = 0; i < n; i++)
t[i] = (float)rand()/(float)RAND_MAX;
}
/* Multiply elements of two tabs and store results in third tab
- STANDARD processing. */
void mul_tab_standard(float *t1, float *t2, float *tr) {
for (int i = 0; i < n; i++)
tr[i] = t1[i] * t2[i];
}
/* Multiply elements of two tabs and store results in third tab
- NEON processing. */
void mul_tab_neon(float *t1, float *t2, float *tr) {
for (int i = 0; i < n; i+=4)
vst1q_f32(tr+i, vmulq_f32(vld1q_f32(t1+i), vld1q_f32(t2+i)));
}
int main() {
float t1[n], t2[n], tr[n];
/* fill tables with random values */
srand(1); rand_tab(t1); rand_tab(t2);// I repeat table multiplication function 1000000 times for measuring purposes:
for (int k=0; k < 1000000; k++)
mul_tab_standard(t1, t2, tr); // switch to next line for comparison:
//mul_tab_neon(t1, t2, tr);
return 1;
}
Я запускаю следующую команду для компиляции:
g ++ -mfpu = neon -ffast-math neon_test.cpp
Мой процессор: процессор ARMv7, версия 0 (v7l)
У вас есть идеи, как мне добиться более значительного ускорения?
Cortex-A8 и Cortex-A9 могут выполнять только два умножения SP FP за такт, поэтому вы можете максимально увеличить производительность этих (самых популярных) процессоров в два раза. На практике процессоры ARM имеют очень низкий IPC, поэтому желательно максимально развернуть циклы. Если вы хотите максимальной производительности, напишите в ассемблере: генератор кода gcc для ARM нигде не так хорош, как для x86.
Я также рекомендую использовать специфичные для CPU опции оптимизации: «-O3 -mcpu = cortex-a9 -march = armv7-a -mtune = cortex-a9 -mfpu = neon -mthumb» для Cortex-A9; для Cortex-A15, Cortex-A8 и Cortex-A5 замените -mcpu = -mtune = cortex-a15 / a8 / a5 соответственно. gcc не имеет оптимизации для процессоров Qualcomm, поэтому для Qualcomm Scorpion используйте параметры Cortex-A8 (а также разверните даже больше, чем вы обычно делаете), а для Qualcomm Krait попробуйте параметры Cortex-A15 (вам потребуется последняя версия gcc, которая поддерживает Это).
Один недостаток встроенных неоновых функций: вы не можете использовать автоинкремент при загрузке, что проявляется в виде дополнительных инструкций в вашей неоновой реализации.
Скомпилировано с gcc версии 4.4.3 и опциями -c -std = c99 -mfpu = неон -O3 и сбрасывается с помощью objdump, это часть цикла mul_tab_neon
000000a4 <mul_tab_neon>:
ac: e0805003 add r5, r0, r3
b0: e0814003 add r4, r1, r3
b4: e082c003 add ip, r2, r3
b8: e2833010 add r3, r3, #16
bc: f4650a8f vld1.32 {d16-d17}, [r5]
c0: f4642a8f vld1.32 {d18-d19}, [r4]
c4: e3530e19 cmp r3, #400 ; 0x190
c8: f3400df2 vmul.f32 q8, q8, q9
cc: f44c0a8f vst1.32 {d16-d17}, [ip]
d0: 1afffff5 bne ac <mul_tab_neon+0x8>
и это часть цикла mul_tab_standard
00000000 <mul_tab_standard>:
58: ecf01b02 vldmia r0!, {d17}
5c: ecf10b02 vldmia r1!, {d16}
60: f3410db0 vmul.f32 d16, d17, d16
64: ece20b02 vstmia r2!, {d16}
68: e1520003 cmp r2, r3
6c: 1afffff9 bne 58 <mul_tab_standard+0x58>
Как вы можете видеть в стандартном случае, компилятор создает гораздо более жесткий цикл.