#c #sse
#c #sse
Вопрос:
Я хочу уменьшить масштаб изображений так быстро, как только смогу на c . В этой статье описывается, как эффективно уменьшить среднее значение 32-битных изображений rgb на 50%. Это быстро и выглядит хорошо.
Я попытался изменить этот подход, используя встроенные функции sse. Приведенный ниже код работает независимо от того, включен SSE или нет. Удивительно, однако, что ускорение незначительно.
Кто-нибудь может увидеть способ улучшить код SSE. Две строки, создающие переменные shuffle1 и shuffle2, кажутся двумя кандидатами (с использованием некоторого умного сдвига или подобного).
/*
* Calculates the average of two rgb32 pixels.
*/
inline static uint32_t avg(uint32_t a, uint32_t b)
{
return (((a^b) amp; 0xfefefefeUL) >> 1) (aamp;b);
}
/*
* Calculates the average of four rgb32 pixels.
*/
inline static uint32_t avg(const uint32_t a[2], const uint32_t b[2])
{
return avg(avg(a[0], a[1]), avg(b[0], b[1]));
}
/*
* Calculates the average of two rows of rgb32 pixels.
*/
void average2Rows(const uint32_t* src_row1, const uint32_t* src_row2, uint32_t* dst_row, int w)
{
#if !defined(__SSE)
for (int x = w; x; --x, dst_row , src_row1 = 2, src_row2 = 2)
* dst_row = avg(src_row1, src_row2);
#else
for (int x = w; x; x-=4, dst_row =4, src_row1 = 8, src_row2 = 8)
{
__m128i left = _mm_avg_epu8(_mm_load_si128((__m128i const*)src_row1), _mm_load_si128((__m128i const*)src_row2));
__m128i right = _mm_avg_epu8(_mm_load_si128((__m128i const*)(src_row1 4)), _mm_load_si128((__m128i const*)(src_row2 4)));
__m128i shuffle1 = _mm_set_epi32( right.m128i_u32[2], right.m128i_u32[0], left.m128i_u32[2], left.m128i_u32[0]);
__m128i shuffle2 = _mm_set_epi32( right.m128i_u32[3], right.m128i_u32[1], left.m128i_u32[3], left.m128i_u32[1]);
_mm_store_si128((__m128i *)dst_row, _mm_avg_epu8(shuffle1, shuffle2));
}
#endif
}
Ответ №1:
Передача данных между регистрами общего назначения и регистрами SSE происходит очень медленно, поэтому вам следует воздержаться от таких действий, как :
__m128i shuffle1 = _mm_set_epi32( right.m128i_u32[2], right.m128i_u32[0], left.m128i_u32[2], left.m128i_u32[0]);
__m128i shuffle2 = _mm_set_epi32( right.m128i_u32[3], right.m128i_u32[1], left.m128i_u32[3], left.m128i_u32[1]);
Перетасуйте значения в регистрах SSE с помощью соответствующих операций перетасовки.
Это должно быть то, что вы ищете :
__m128i t0 = _mm_unpacklo_epi32( left, right ); // right.m128i_u32[1] left.m128i_u32[1] right.m128i_u32[0] left.m128i_u32[0]
__m128i t1 = _mm_unpackhi_epi32( left, right ); // right.m128i_u32[3] left.m128i_u32[3] right.m128i_u32[2] left.m128i_u32[2]
__m128i shuffle1 = _mm_unpacklo_epi32( t0, t1 ); // right.m128i_u32[2] right.m128i_u32[0] left.m128i_u32[2] left.m128i_u32[0]
__m128i shuffle2 = _mm_unpackhi_epi32( t0, t1 ); // right.m128i_u32[3] right.m128i_u32[1] left.m128i_u32[3] left.m128i_u32[1]
Комментарии:
1. Прекрасно, вы решили эту проблему. Эти две строки увеличили скорость на 125%. Теперь алгоритм ограничен только пропускной способностью.
Ответ №2:
Основная проблема заключается в использовании _mm_set_epi32
для выполнения перетасовки — в отличие от большинства встроенных функций SSE, это не сопоставляется напрямую с одной инструкцией SSE — в таких случаях, как этот, генерируется много скалярного кода под капотом и заставляет данные перемещаться между памятью, регистрами общего назначения и регистрами SSE. Вместо этого посмотрите на использование соответствующих встроенных функций SSE shuffle.
Вторичная проблема заключается в том, что вы выполняете очень мало вычислений относительно количества загрузок и хранилищ. Это, как правило, приводит к коду, ограниченному пропускной способностью, а не вычислениями, и вы можете не увидеть значительного улучшения производительности даже при идеальном коде SSE. Посмотрите на объединение большего количества операций в вашем цикле, чтобы вы могли больше работать со своими данными, пока они находятся в кэше.
Ответ №3:
Если встроенные функции SSE практически ничего не меняют, то код, вероятно, ограничен пропускной способностью памяти.
В вашем коде есть много загрузок и хранилищ ( _mm_set_epi32
это нагрузка, а также очевидные) для небольшой фактической работы.
Если загрузка / сохранение занимает все время выполнения, то никакое количество сложных инструкций вас не спасет. На современных процессорах с высокой степенью конвейеризации и переупорядочения инструкций, вероятно, выполняется довольно хорошая работа по обеспечению занятости всего процессора в версии вашего кода, отличной от SSE.
Вы можете убедиться, что это так, несколькими способами. Вероятно, проще всего измерить, какова фактическая пропускная способность вашего алгоритма по сравнению со скоростью загрузки / сохранения вашей памяти. Вы также можете заметить некоторую разницу, изменяя не только реализацию, но и размер входных данных с резким увеличением, когда входные данные превышают размер каждого уровня кэша процессора.