Векторное сложение с расширением
Ну или используй интринсики. Примерно так. Ассемблер вроде похожий на правду получается, запускать не запускал.
#include <arm_neon.h>
void sum16(int size, void *dst, void *src1, void *src2,
void *src3, void *src4)
{
int i = size / 8;
uint16x8_t *d = (uint16x8_t *)dst;
uint8x8_t *s1 = (uint8x8_t *) src1;
uint8x8_t *s2 = (uint8x8_t *) src2;
uint8x8_t *s3 = (uint8x8_t *) src3;
uint8x8_t *s4 = (uint8x8_t *) src4;
while (i--) {
*d++ = vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++;
}
}
arm-none-linux-gnueabi-gcc -mfloat-abi=softfp -mfpu=neon -O3 -fno-unroll-loops vaddl.c -S -o vaddl.s
Получилось:
sum16:
.fnstart
.LFB1870:
@ args = 8, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
add ip, r0, cmp r0, movlt r0, ip
stmfd sp!, {r4, r5, r6, r7, r8, sl}
.save {r4, r5, r6, r7, r8, sl}
.LCFI0:
movs r0, r0, asr ldr r7, [sp, #24]
ldr r8, [sp, #28]
beq .L4
mov ip, L3:
add sl, r2, ip
add r6, r3, ip
add r5, r7, ip
add r4, r8, ip
fldd d18, [sl, #0]
fldd d19, [r6, #0]
fldd d17, [r5, #0]
fldd d16, [r4, #0]
subs r0, r0, vaddl.u8 q9, d18, d19
add ip, ip, vaddl.u8 q8, d17, d16
vadd.i16 q8, q9, q8
vstmia r1!, {d16-d17}
bne .L3
.L4:
ldmfd sp!, {r4, r5, r6, r7, r8, sl}
bx lr
http://gcc.gnu.org/projects/tree-ssa/vectorization.html
Возможно сложение с расширением не поддерживается в GCC, тогда интринсики зарешают.
Также не рекомендую пытаться как-то помогать с unroll и т.д. он сам сделает как ему надо, и в большинстве случаев это будет быстрее.
Чем проще написано на си, тем лучше в итоге всё будет векторизовано.
Всякие атрибуты типа vector_size тоже ни к чему.
Unroll вообще бывает сильно мешает - если в ffmpeg заменить пару развёрнутых вручную циклов, то он сразу на 5% шустрее становится, причём в случае с -O3 для этого ещё надо писать и -fno-unroll-loops.
Какие ошибки пишутся при -fdump-tree-vect-details?
Примеров пачка вот тут: Возможно сложение с расширением не поддерживается в GCC, тогда интринсики зарешают.
Также не рекомендую пытаться как-то помогать с unroll и т.д. он сам сделает как ему надо, и в большинстве случаев это будет быстрее.
Чем проще написано на си, тем лучше в итоге всё будет векторизовано.
Всякие атрибуты типа vector_size тоже ни к чему.
Unroll вообще бывает сильно мешает - если в ffmpeg заменить пару развёрнутых вручную циклов, то он сразу на 5% шустрее становится, причём в случае с -O3 для этого ещё надо писать и -fno-unroll-loops.
Какие ошибки пишутся при -fdump-tree-vect-details?
void sum16(unsigned int size, void *dst, const void *src1, const void *src2, const void *src3, const void *src4, const void *src5, const void *src6, const void *src7, const void *src8)
{
unsigned int i = size / 8; // vectorization 8 byte
i = i / 8 ; // unroll 8x
uint16x8_t *d = (uint16x8_t *) dst;
uint8x8_t *s1 = (uint8x8_t *) src1;
uint8x8_t *s2 = (uint8x8_t *) src2;
uint8x8_t *s3 = (uint8x8_t *) src3;
uint8x8_t *s4 = (uint8x8_t *) src4;
uint8x8_t *s5 = (uint8x8_t *) src5;
uint8x8_t *s6 = (uint8x8_t *) src6;
uint8x8_t *s7 = (uint8x8_t *) src7;
uint8x8_t *s8 = (uint8x8_t *) src8;
while (i--) {
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
*d++ = vaddq_u16( vaddq_u16(vaddl_u8(*s1++, *s2++ vaddl_u8(*s3++, *s4++
vaddq_u16(vaddl_u8(*s5++, *s6++ vaddl_u8(*s7++, *s8++;
#define PREFETCH 16
__builtin_prefetch(s1+PREFETCH,0);
__builtin_prefetch(s2+PREFETCH,0);
__builtin_prefetch(s3+PREFETCH,0);
__builtin_prefetch(s4+PREFETCH,0);
__builtin_prefetch(s5+PREFETCH,0);
__builtin_prefetch(s6+PREFETCH,0);
__builtin_prefetch(s7+PREFETCH,0);
__builtin_prefetch(s8+PREFETCH,0);
}
}
К вопросу ниже про unroll - его имеет смысл делать вручную, как минимум в моем случае, потому что за раз обрабатывается 8 байт, а линия кеша - 64 байт. Поэтому для максимальной скорости загрузки данных из памяти в кэш второго уровня на 8 сложений надо делать одну предзагрузку кеша. Причем насколько далеко стоит делать предзагрузку тоже надо подбирать.
Небольшой бенчмарк - 10 прогонов по 100 раз на массивах по 5 мбайт, время отработки в мс.
original - 171.3
cache-prefetch - 125.7
unroll 4x - 148.1
unroll 8x - 148.8
unroll 4x + cache prefetch - 81.5
unroll 8x + cache prefetch - 76.1
Оптимизировать дальше имеет мало смысла - это уже упирается в шину памяти. (8+2)*5мбайт / 0.076 с = 650 мб/с. Некий спец писал в инете что на этой железке (beagleboard) максимально оптимизированный memcpy у него давал где-то 300-400 мб/с, memset порядка 700-800 мб/с.
-fprefetch-loop-arrays не пробовал? В теории должен помочь делать "Причем насколько далеко стоит делать предзагрузку тоже надо подбирать", но этим пассом вроде как давно не занимались, плюс размер кэша там в параметрах может быть не тот, тогда надо руками ставить.
Оставить комментарий
kiracher
Помогите с проблемой - есть несколько массивов, надо их сложить поэлементно и записать в другой массив. Подточив примеры, легко разобрался с однобайтовыми и двухбайтовыми массивами, в случае когда тип исходных и результирующего массива одинаковый.Но мне надо другое - сложить байтовые массивы в массив двухбайтовых результатов. Работающий вариант (маскирование и сложение младших и старших бит по отдельности есть но это криво. Архитектура - arm neon, сложение с расширением типа поддерживается. Векторизация требуется, иначе тупо скорости проц и шины памяти не хватит.
По моему разумению, код считывает по 8 байтов из каждого массива и складывает в 8 же 16-битных ячеек в другой. Пробую разные cast и typedef, но получаю всякий раз разные ошибки.
Подскажите правильную комбинацию
PS Cache prefetch и небольшой unroll подбираю вручную, в примере не приводится чтобы не загромождать