我正在尝试为 RGB8 到 RGB32 图像转换找到一种程序集优化的方法。
源是一个 8 位灰度图像,目标应该是一个 32 位灰度图像 (BGRA),其中第 4 个 channel (alpha) 被忽略。源地址不保证 16 字节对齐,Count 是 16 的倍数,目标地址是 16 字节对齐。
这是我优化的汇编代码。有没有更快的转换方式?
void ConvertGreyToRgb32Assembler(__m128i* Source, __m128i* Destination, unsigned int Count) {
static unsigned int __declspec(align(64)) Masks[] = {
0x80000000, 0x80010101, 0x80020202, 0x80030303,
0x80040404, 0x80050505, 0x80060606, 0x80070707,
0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
};
__asm {
mov esi, Source
mov edi, Destination
mov edx, Count
xor ecx, ecx
movdqa xmm4, xmmword ptr [Masks + 0]
movdqa xmm5, xmmword ptr [Masks + 16]
movdqa xmm6, xmmword ptr [Masks + 32]
movdqa xmm7, xmmword ptr [Masks + 48]
l1:
movdqu xmm0, xmmword ptr [esi + ecx]
movdqa xmm1, xmm0
movdqa xmm2, xmm0
movdqa xmm3, xmm0
pshufb xmm0, xmm4
pshufb xmm1, xmm5
pshufb xmm2, xmm6
pshufb xmm3, xmm7
movntdq [edi + 0], xmm0
movntdq [edi + 16], xmm1
movntdq [edi + 32], xmm2
movntdq [edi + 48], xmm3
add edi, 64
add ecx, 16
cmp ecx, edx
jb l1
}
}
还有另一种使用几个 PUNPCKLBW 和 PUNPCKHBW 的方法,但这似乎有点慢。
更新:这是基本的非优化算法:
BGRA* Destination = ...
unsigned char* Source ...
for (unsigned int i = 0; i < Size; i++) {
Destination[i].Blue = Source[i];
Destination[i].Green = Source[i];
Destination[i].Red = Source[i];
}
PS:我还尝试将 C 代码与 MS VS2008 SSE 编译器内在函数一起使用。事实证明,编译器生成了大量不必要的内存移动,导致代码比纯汇编慢 10-20%。
更新 2:这是仅使用内部函数的相同代码。
void ConvertGreyToRgb32Assembler(__m128i* Source, __m128i* Destination, unsigned int Count) {
static const unsigned int __declspec(align(64)) Masks[] = {
0x80000000, 0x80010101, 0x80020202, 0x80030303,
0x80040404, 0x80050505, 0x80060606, 0x80070707,
0x80080808, 0x80090909, 0x800a0a0a, 0x800b0b0b,
0x800c0c0c, 0x800d0d0d, 0x800e0e0e, 0x800f0f0f
};
register __m128i m0 = _mm_load_si128((__m128i*) (Masks + 0));
register __m128i m1 = _mm_load_si128((__m128i*) (Masks + 4));
register __m128i m2 = _mm_load_si128((__m128i*) (Masks + 8));
register __m128i m3 = _mm_load_si128((__m128i*) (Masks + 12));
for (unsigned int i = 0; i < Count / 16; i++) {
__m128i r0 = _mm_load_si128(Source + i);
_mm_stream_si128(Destination + (i * 4) + 0, _mm_shuffle_epi8(r0, m0));
_mm_stream_si128(Destination + (i * 4) + 1, _mm_shuffle_epi8(r0, m1));
_mm_stream_si128(Destination + (i * 4) + 2, _mm_shuffle_epi8(r0, m2));
_mm_stream_si128(Destination + (i * 4) + 3, _mm_shuffle_epi8(r0, m3));
}
}
更新 3:这是编译器生成的代码(美化)(Visual Studio 2012,所有优化):
push ebp
mov ebp, esp
mov edx, dword ptr [ebp+8]
movdqa xmm1, xmmword ptr ds:[Masks + 0]
movdqa xmm2, xmmword ptr ds:[Masks + 16]
movdqa xmm3, xmmword ptr ds:[Masks + 32]
movdqa xmm4, xmmword ptr ds:[Masks + 48]
push esi
test ecx, ecx
je l2
lea esi, [ecx-1]
shr esi, 4
inc esi
l1:
mov ecx, edx
movdqu xmm0, xmmword ptr [ecx]
mov ecx, eax
movdqa xmm5, xmm0
pshufb xmm5, xmm1
movdqa xmmword ptr [ecx], xmm5
movdqa xmm5, xmm0
pshufb xmm5, xmm2
movdqa xmmword ptr [eax+10h], xmm5
movdqa xmm5, xmm0
pshufb xmm5, xmm3
movdqa xmmword ptr [eax+20h], xmm5
lea ecx, [eax+30h]
add edx, 10h
add eax, 40h
dec esi
pshufb xmm0, xmm4
movdqa xmmword ptr [ecx], xmm0
jne l1
l2:
pop esi
pop ebp
ret
好像是交错
movdqa
与 pshufb
是有些什么更快。更新 4:这似乎是最佳的手动优化代码:
__asm {
mov esi, Source
mov edi, Destination
mov ecx, Count
movdqu xmm0, xmmword ptr [esi]
movdqa xmm4, xmmword ptr [Masks + 0]
movdqa xmm5, xmmword ptr [Masks + 16]
movdqa xmm6, xmmword ptr [Masks + 32]
movdqa xmm7, xmmword ptr [Masks + 48]
l1:
dec ecx
lea edi, [ edi + 64 ]
lea esi, [ esi + 16 ]
movdqa xmm1, xmm0
movdqa xmm2, xmm0
movdqa xmm3, xmm0
pshufb xmm0, xmm4
movdqa [edi - 64], xmm0
pshufb xmm1, xmm5
movdqa [edi - 48], xmm1
pshufb xmm2, xmm6
movdqa [edi - 32], xmm2
pshufb xmm3, xmm7
movdqa [edi - 16], xmm3
movdqu xmm0, xmmword ptr [esi]
ja l1
}
更新 5:此转换算法使用
punpck
操作说明。然而,这个转换例程比使用掩码和 pushfb
慢一点。 .for (unsigned int i = 0; i < Count; i += 16) {
register __m128i r0 = _mm_load_si128(Source++);
register __m128i r1 = _mm_unpackhi_epi8(r0, r0);
register __m128i r2 = _mm_unpacklo_epi8(r0, r0);
register __m128i r3 = _mm_unpackhi_epi8(r1, r1);
register __m128i r4 = _mm_unpacklo_epi8(r1, r1);
register __m128i r5 = _mm_unpackhi_epi8(r2, r2);
register __m128i r6 = _mm_unpacklo_epi8(r2, r2);
_mm_store_si128(Destination++, r6);
_mm_store_si128(Destination++, r5);
_mm_store_si128(Destination++, r4);
_mm_store_si128(Destination++, r3);
}
更新 6:为了完整起见,这是从 32 位转换回 8 位灰度图像的逆方法。
static void ConvertRgb32ToGrey(const __m128i* Source, __m128i* Destination, unsigned int Count) {
static const unsigned char __declspec(align(64)) Masks[] = {
0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x04, 0x08, 0x0c,
};
register __m128i m0 = _mm_load_si128((__m128i*) (Masks + 0));
register __m128i m1 = _mm_load_si128((__m128i*) (Masks + 16));
register __m128i m2 = _mm_load_si128((__m128i*) (Masks + 32));
register __m128i m3 = _mm_load_si128((__m128i*) (Masks + 48));
for (unsigned int i = 0; i < Count / 64; i++) {
__m128i a = _mm_load_si128(Source + (i * 4) + 0);
__m128i b = _mm_load_si128(Source + (i * 4) + 1);
__m128i c = _mm_load_si128(Source + (i * 4) + 2);
__m128i d = _mm_load_si128(Source + (i * 4) + 3);
a = _mm_shuffle_epi8(a, m0);
b = _mm_shuffle_epi8(b, m1);
c = _mm_shuffle_epi8(c, m2);
d = _mm_shuffle_epi8(d, m3);
__m128i e = _mm_or_si128(a, b);
__m128i f = _mm_or_si128(c, d);
__m128i g = _mm_or_si128(e, f);
_mm_stream_si128(Destination + i, g);
}
}
最佳答案
会尝试:
__asm {
mov esi, 来源
mov edi, 目的地
mov ecx, 计数
movdqu xmm0, xmmword ptr [esi]
movdqa xmm4, xmmword ptr [面具 + 0]
movdqa xmm5, xmmword ptr [面具 + 16]
movdqa xmm6, xmmword ptr [面具 + 32]
movdqa xmm7, xmmword ptr [面具 + 48]
l1:
十二月
lea edi, [ edi + 64 ]
lea esi, [ esi + 16 ]
movdqa xmm1, xmm0
movdqa xmm2, xmm0
movdqa xmm3, xmm0
pshufb xmm0, xmm4
pshufb xmm1, xmm5
pshufb xmm2, xmm6
pshufb xmm3, xmm7
movntdq [edi - 64],xmm0
movntdq [edi - 48], xmm1
movntdq [edi - 32], xmm2
movntdq [edi - 16], xmm3
movdqu xmm0, xmmword ptr [esi]
ja l1
}
不过还没有对其进行基准测试;这些变化背后的假设:
movdqu xmm0,...
延迟可以在循环中隐藏得更多一些(您的代码的负载为 xmm0
,后跟直接使用该寄存器中的值的指令)add
操作两个 reg 以及 cmp
并不是所有必要的;地址生成( lea
)和隐式零测试 dec
/ja
可以使用。这样,就不会有 EFLAGS
由 ecx
上的操作引起的依赖关系/esi
/edi
因为循环中唯一的 ALU 操作是递减循环计数器。 最后,这在任何情况下都可能是加载/存储限制,因此算术是“免费游戏”;因此,即使有给定的论点,我也不希望有什么不同。
如果输入很大,那么去掉“未对齐的头/尾”是有意义的,即为第一个/最后一个
[0..15]
做一个 duff 的设备字节,以及使用 movdqa
的主循环.编辑:
通过
gcc -msse4.2 -O8 -c
运行您的内在源(GCC 4.7.1) 给出了以下程序集:.text 节的反汇编:
0000000000000000
0: 85 d2 测试 edx,edx
2: 74 76 je 7a
4: 66 0f 6f 2d 00 00 00 00 movdqa xmm5,XMMWORD PTR [rip+0x0]
# c
c: 48 89 f8 mov rax,rdi
f: 66 0f 6f 25 00 00 00 00 movdqa xmm4,XMMWORD PTR [rip+0x0]
# 17
17: 66 0f 6f 1d 00 00 00 00 movdqa xmm3,XMMWORD PTR [rip+0x0]
# 1f
1f: 66 0f 6f 15 00 00 00 00 movdqa xmm2,XMMWORD PTR [rip+0x0]
# 27
27: 66 0f 1f 84 00 00 00 00 00 nop WORD PTR [rax+rax*1+0x0]
30: f3 0f 6f 00 movdqu xmm0,XMMWORD PTR [rax]
34: 48 89 f1 mov rcx,rsi
37: 48 83 c0 10 添加 rax,0x10
3b: 66 0f 6f c8 movdqa xmm1,xmm0
3f: 66 0f 38 00 cd pshufb xmm1,xmm5
44: 66 0f e7 0e movntdq XMMWORD PTR [rsi],xmm1
48: 66 0f 6f c8 movdqa xmm1,xmm0
4c: 66 0f 38 00 cc pshufb xmm1,xmm4
51: 66 0f e7 4e 10 movntdq XMMWORD PTR [rsi+0x10],xmm1
56: 66 0f 6f c8 movdqa xmm1,xmm0
5a: 66 0f 38 00 c2 pshufb xmm0,xmm2
5f: 66 0f 38 00 cb pshufb xmm1,xmm3
64: 66 0f e7 4e 20 movntdq XMMWORD PTR [rsi+0x20],xmm1
69: 66 0f e7 41 30 movntdq XMMWORD PTR [rcx+0x30],xmm0
6e: 89 c1 mov ecx,eax
70: 29 f9 子 ecx,edi
72: 48 83 c6 40 添加 rsi,0x40
76: 39 ca cmp edx,ecx
78: 77 b6 ja 30
7a: f3 c3 repz ret
这让我非常强烈地想起你最初的汇编代码。如果 MSVC 产生的东西比这更糟糕,我会说这是您使用的编译器(版本)中的错误/限制。
关于visual-c++ - 更快的组装优化方式在 RGB8 和 RGB32 图像之间转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12004437/