结合仅姿势者和 Jitamaro 的答案,如果您假设输入和输出是 16 字节对齐的,并且如果您一次处理 4 个像素,则可以使用 shuffle、mask、ands 和 ors 的组合来存储使用对齐的商店。主要思想是生成四个中间数据集,然后或它们与掩码一起选择相关的像素值并写出3个16字节的像素数据集。请注意,我根本没有编译或尝试运行它。
EDIT2:关于底层代码结构的更多细节:
使用 SSE2,您可以通过 16 字节对齐的 16 字节读取和写入获得更好的性能。由于您的 3 字节像素每 16 个像素只能对齐到 16 个字节,因此我们一次使用 shuffle 和 mask 以及 16 个输入像素的 ors 组合来批量处理 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 表示选择右侧像素):
掩码(0)= 111 111 111 111 000 0
掩码 (1)= 11 111 111 000 000 00
掩码(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] 偏移量,它们只会以相反的顺序匹配姿势者的 shuffle 掩码。)
现在,我们可以生成一个 shuffle 掩码来将每个源字节映射到一个目标字节(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] 的 shuffle 掩码:
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
由于两个 shuffle 蒙版不重叠,我们可以将它们组合起来,并简单地屏蔽掉 combine_ 中不相关的像素,我们已经这样做了!以下代码执行所有这些优化(另外它假设源地址和目标地址是 16 字节对齐的)。此外,掩码以 MSB->LSB 顺序写在代码中,以防您对排序感到困惑。
编辑:将存储更改为_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));
}
}