Новичок SIMD / SSE: простая фильтрация изображений - PullRequest
7 голосов
/ 28 июня 2010

Я очень новичок в 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);
}

Ответы [ 2 ]

2 голосов
/ 30 июня 2010

Я предлагаю сохранить соседние пиксели в регистре 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 учебник:

Некоторые сайты гуру SIMD:

2 голосов
/ 28 июня 2010

Этот вид операции соседства всегда был проблемой с SSE, пока не появился SSE3.5 (он же SSSE3), и не был представлен PALIGNR (_mm_alignr_epi8).

Если вам нужна обратная совместимость с SSE2 / SSE3Вы можете написать эквивалентный макрос или встроенную функцию, которая эмулирует _mm_alignr_epi8 для SSE2 / SSE3 и которая переходит на _mm_alignr_epi8 при нацеливании на SSE3.5 / SSE4.

Другой подход заключается в использовании смещенных нагрузок для получения смещенных данных -это относительно дорого на старых процессорах (примерно вдвое больше задержки и половина пропускной способности выровненных нагрузок), но это может быть приемлемо в зависимости от того, сколько вычислений вы выполняете для каждой нагрузки.Это также дает преимущество, заключающееся в том, что на современных процессорах Intel (Core i7) не выровненные нагрузки не имеют штрафов по сравнению с выровненными нагрузками, поэтому ваш код будет достаточно эффективным на Core i7 и др. .

...