SIMD / SSE Newbie: простая фильтрация изображения
-
01-10-2019 - |
Вопрос
Я очень новый для SIMD / SSE, и я пытаюсь сделать простую фильтрацию изображений (размытие). Код ниже фильтрует каждый пиксель 8-битной серой растровой карты с простым [1 2 1] взвешиванием в горизонтальном направлении. Я создаю суммы 16 пикселей за раз.
Что кажется очень плохим об этом коде, по крайней мере для меня, состоит в том, что в нем много вставки / экстракта, что не очень элегантно и, вероятно, замедляет все вниз. Есть ли лучший способ обернуть данные из одного ввода в другое при смене?
BUF - это данные изображения, 16-байт выравниваются. W / H - ширина и высота, кратные 16.
__m128i *p = (__m128i *) buf;
__m128i cur1, cur2, sum1, sum2, zeros, tmp1, tmp2, saved;
zeros = _mm_setzero_si128();
short shifted, last = 0, next;
// preload first row
cur1 = _mm_load_si128(p);
for (x = 1; x < (w * h) / 16; x++) {
// unpack
sum1 = sum2 = saved = cur1;
sum1 = _mm_unpacklo_epi8(sum1, zeros);
sum2 = _mm_unpackhi_epi8(sum2, zeros);
cur1 = tmp1 = sum1;
cur2 = tmp2 = sum2;
// "middle" pixel
sum1 = _mm_add_epi16(sum1, sum1);
sum2 = _mm_add_epi16(sum2, sum2);
// left pixel
cur2 = _mm_slli_si128(cur2, 2);
shifted = _mm_extract_epi16(cur1, 7);
cur2 = _mm_insert_epi16(cur2, shifted, 0);
cur1 = _mm_slli_si128(cur1, 2);
cur1 = _mm_insert_epi16(cur1, last, 0);
sum1 = _mm_add_epi16(sum1, cur1);
sum2 = _mm_add_epi16(sum2, cur2);
// right pixel
tmp1 = _mm_srli_si128(tmp1, 2);
shifted = _mm_extract_epi16(tmp2, 0);
tmp1 = _mm_insert_epi16(tmp1, shifted, 7);
tmp2 = _mm_srli_si128(tmp2, 2);
// preload next row
cur1 = _mm_load_si128(p + x);
// we need the first pixel of the next row for the "right" pixel
next = _mm_extract_epi16(cur1, 0) & 0xff;
tmp2 = _mm_insert_epi16(tmp2, next, 7);
// and the last pixel of last row for the next "left" pixel
last = ((uint16_t) _mm_extract_epi16(saved, 7)) >> 8;
sum1 = _mm_add_epi16(sum1, tmp1);
sum2 = _mm_add_epi16(sum2, tmp2);
// divide
sum1 = _mm_srli_epi16(sum1, 2);
sum2 = _mm_srli_epi16(sum2, 2);
sum1 = _mm_packus_epi16(sum1, sum2);
mm_store_si128(p + x - 1, sum1);
}
Решение
Я предлагаю держать соседние пиксели на реестре SSE. То есть сохранить результат _mm_slli_si128 / _mm_srli_si128 в переменной SSE и устранить всю вставку и экстракт. Мои рассуждения заключаются в том, что у более старых процессоров инструкции вставки / экстракта требуют связи между единицами SSE и единицами общего назначения, что намного медленнее, чем сохранение расчета в SSE, даже если он проникает в кэш L1.
Когда это сделано, там должны быть только четыре 16-битные сдвиги (_mm_slli_si128, _mm_srli_si128, не считая смещение подразделения ). Мое предложение состоит в том, чтобы сделать ориентир с вашим кодом, потому что к тому времени ваш код, возможно, уже ударил предел пропускной способности памяти .. что означает, что вы больше не можете оптимизировать.
Если изображение большое (больше, чем размер L2), и вывод скоро не будет прочитан, попробуйте использовать movntdq (_mm_stream_si128) для записи обратно. Согласно нескольким веб-сайтам это в SSE2, хотя вы можете удалить проверить.
Учебник Simd:
- http://www.tommesani.com/docs.html.
- http://en.wikipedia.org/wiki/x86_instruction_listings#sse2_instructions.
Некоторые веб-сайты Simd Guru:
Другие советы
Этот вид операции соседства всегда был болью с SSE, до тех пор, пока SSE3.5 (AKA SSSE3) пришел, а Палинр (_mm_m_alignr_epi8) был введен.
Если вам нужна обратная совместимость с SSE2 / SSE3, хотя вы можете написать эквивалентную функцию макроса или встроенной функции, которая эмулирует _mm_alignr_epi8 для sse2 / sse3 и которая падает до _mm_alignr_epi8 при нацелении _mm_.5 / sse4.
Другой подход - использовать смещенные нагрузки для получения смещенных данных - это относительно дорого на более старых процессорах (примерно в два раза задержки и половину пропускной способности выровненных нагрузок), но это может быть приемлемым в зависимости от гораздо больших вычислений, которые вы делаете за нагрузку. Он также имеет преимущество в том, что на текущих процессорах Intel (CORE I7) не имеет штрафных нагрузок по сравнению с выровненными нагрузками, поэтому ваш код будет довольно эффективным на Core i7 и другие.