Комбинируя только ответы позера и Джитамаро, если вы предполагаете, что входы и выходы выровнены по 16 байтам, и если вы обрабатываете пиксели по 4 за раз, вы можете использовать комбинацию тасов, масок и / или для сохраненияс использованием выровненных магазинов.Основная идея состоит в том, чтобы сгенерировать четыре промежуточных набора данных, затем или их вместе с масками, чтобы выбрать соответствующие значения пикселей и записать 3 16-байтовых набора данных пикселей.Обратите внимание, что я не компилировал это или пытался запустить его вообще.
EDIT2: Более подробно о базовой структуре кода:
С SSE2 вы получаете лучшую производительность с 16-байтовыми выровненными чтениямии пишет 16 байтов.Поскольку ваш 3-байтовый пиксель может быть выровнен только по 16 байтов для каждых 16 пикселей, мы объединяем 16 пикселов за раз, используя комбинацию тасов, масок и / или 16 входных пикселей за раз.
Из LSBдля MSB входные данные выглядят так, игнорируя определенные компоненты:
s[0]: 0000 0000 0000 0000
s[1]: 1111 1111 1111 1111
s[2]: 2222 2222 2222 2222
s[3]: 3333 3333 3333 3333
, а выходные данные выглядят так:
d[0]: 000 000 000 000 111 1
d[1]: 11 111 111 222 222 22
d[2]: 2 222 333 333 333 333
Итак, чтобы сгенерировать эти выходные данные, необходимо выполнитьследующее (я укажу фактические преобразования позже):
d[0]= combine_0(f_0_low(s[0]), f_0_high(s[1]))
d[1]= combine_1(f_1_low(s[1]), f_1_high(s[2]))
d[2]= combine_2(f_1_low(s[2]), f_1_high(s[3]))
Теперь, как должен выглядеть combine_<x>
?Если мы предположим, что d
просто s
сжаты вместе, мы можем объединить два s
с маской и или:
combine_x(left, right)= (left & mask(x)) | (right & ~mask(x))
где (1 означает выбор левого пикселя, 0означает выбор правильного пикселя): mask (0) = 111 111 111 111 000 0 mask (1) = 11 111 111 000 000 00 mask (2) = 1 111 000 000 000 000
Но фактические преобразования(f_<x>_low
, f_<x>_high
) на самом деле не все так просто.Поскольку мы обращаем и удаляем байты из исходного пикселя, фактическое преобразование (для первого пункта назначения для краткости):
d[0]=
s[0][0].Blue s[0][0].Green s[0][0].Red
s[0][1].Blue s[0][1].Green s[0][1].Red
s[0][2].Blue s[0][2].Green s[0][2].Red
s[0][3].Blue s[0][3].Green s[0][3].Red
s[1][0].Blue s[1][0].Green s[1][0].Red
s[1][1].Blue
Если вы переведете вышеприведенное в смещения байтов от источника к месту назначения, вы получите:d [0] = & s [0] +3 & s [0] +2 & s [0] + 1
& s [0] +7 & s [0] +6 & s [0] +5 & s [0] +11& s [0] +10 & s [0] +9 & s [0] +15 & s [0] +14 & s [0] + 13
& s [1] +3 & s [1] +2 & s [1] +1
& s [1] + 7
(Если вы посмотрите на все смещения s [0], они соответствуют просто маске тасовки позера в обратном порядке.)
Сейчасмы можем сгенерировать маску тасования, чтобы сопоставить каждый исходный байт с целевым байтом (X
означает, что нам все равно, что это за значение):
f_0_low= 3 2 1 7 6 5 11 10 9 15 14 13 X X X X
f_0_high= X X X X X X X X X X X X 3 2 1 7
f_1_low= 6 5 11 10 9 15 14 13 X X X X X X X X
f_1_high= X X X X X X X X 3 2 1 7 6 5 11 10
f_2_low= 9 15 14 13 X X X X X X X X X X X X
f_2_high= X X X X 3 2 1 7 6 5 11 10 9 15 14 13
Мы можем дополнительно оптимизировать это, посмотрев маскимы используем для каждого исходного пикселя.Если вы посмотрите на маски тасования, которые мы используем для s [1]:
f_0_high= X X X X X X X X X X X X 3 2 1 7
f_1_low= 6 5 11 10 9 15 14 13 X X X X X X X X
Поскольку две маски тасования не перекрываются, мы можем объединить их и просто замаскировать нерелевантные пиксели в объединении_что мы уже сделали!Следующий код выполняет все эти оптимизации (плюс предполагается, что адреса источника и назначения выровнены по 16 байтов).Кроме того, маски записываются в коде в порядке MSB-> LSB, на случай, если вы запутаетесь с порядком.
EDIT: изменил хранилище на _mm_stream_si128
, так как вы, вероятно, делаете много записей имы не хотим обязательно очищать кеш.Кроме того, он должен быть выровнен в любом случае, так что вы получите бесплатный перф!
#include <assert.h>
#include <inttypes.h>
#include <tmmintrin.h>
// needs:
// orig is 16-byte aligned
// imagesize is a multiple of 4
// dest has 4 trailing scratch bytes
void convert(uint8_t *orig, size_t imagesize, uint8_t *dest) {
assert((uintptr_t)orig % 16 == 0);
assert(imagesize % 16 == 0);
__m128i shuf0 = _mm_set_epi8(
-128, -128, -128, -128, // top 4 bytes are not used
13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3); // bottom 12 go to the first pixel
__m128i shuf1 = _mm_set_epi8(
7, 1, 2, 3, // top 4 bytes go to the first pixel
-128, -128, -128, -128, // unused
13, 14, 15, 9, 10, 11, 5, 6); // bottom 8 go to second pixel
__m128i shuf2 = _mm_set_epi8(
10, 11, 5, 6, 7, 1, 2, 3, // top 8 go to second pixel
-128, -128, -128, -128, // unused
13, 14, 15, 9); // bottom 4 go to third pixel
__m128i shuf3 = _mm_set_epi8(
13, 14, 15, 9, 10, 11, 5, 6, 7, 1, 2, 3, // top 12 go to third pixel
-128, -128, -128, -128); // unused
__m128i mask0 = _mm_set_epi32(0, -1, -1, -1);
__m128i mask1 = _mm_set_epi32(0, 0, -1, -1);
__m128i mask2 = _mm_set_epi32(0, 0, 0, -1);
uint8_t *end = orig + imagesize * 4;
for (; orig != end; orig += 64, dest += 48) {
__m128i a= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig), shuf0);
__m128i b= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 1), shuf1);
__m128i c= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 2), shuf2);
__m128i d= _mm_shuffle_epi8(_mm_load_si128((__m128i *)orig + 3), shuf3);
_mm_stream_si128((__m128i *)dest, _mm_or_si128(_mm_and_si128(a, mask0), _mm_andnot_si128(b, mask0));
_mm_stream_si128((__m128i *)dest + 1, _mm_or_si128(_mm_and_si128(b, mask1), _mm_andnot_si128(c, mask1));
_mm_stream_si128((__m128i *)dest + 2, _mm_or_si128(_mm_and_si128(c, mask2), _mm_andnot_si128(d, mask2));
}
}