Я изучаю возможности SIMD, переписывая свою личную библиотеку обработки изображений с использованием векторных встроенных функций. Одной из основных функций является простой «массив +=
», т.е.SIMD array add для произвольной длины массива
void arrayAdd(unsigned char* A, unsigned char* B, size_t n) {
for(size_t i=0; i < n; i++) { B[i] += A[i] };
}
Для произвольной длины массива, очевидный SIMD код (предполагая, что выравниваются 16) что-то вроде:
size_t i = 0;
__m128i xmm0, xmm1;
n16 = n - (n % 16);
for (; i < n16; i+=16) {
xmm0 = _mm_load_si128((__m128i*) (A + i));
xmm1 = _mm_load_si128((__m128i*) (B + i));
xmm1 = _mm_add_epi8(xmm0, xmm1);
_mm_store_si128((__m128i*) (B + i), xmm1);
}
for (; i < n; i++) { B[i] += A[i]; }
Но это возможно do все дополнение с инструкциями SIMD? Я думал про это:
__m128i mask = (0x100<<8*(n - n16))-1;
_mm_maskmoveu_si128(xmm1, mask, (__m128i*) (B + i));
для дополнительных элементов, но приведет ли это к неопределенному поведению? mask
должен гарантировать, что на самом деле доступ не проходит через границы массива (я думаю). Альтернативой является сначала выполнить дополнительные элементы, но затем массив должен быть выровнен по n-n16
, что кажется неправильным.
Есть ли другой, более оптимальный узор, такой векторизованный цикл?
вы могли убедиться, что в вашем коде длина массива всегда кратна 16 байт (хотя, возможно, меньше элементов фактически используются), так что это эпилог никогда не приходит. Но эпилог действительно не важен с точки зрения скорости. – Walter