Cortex-M4 SIMD 比 Scalar 慢
Posted
技术标签:
【中文标题】Cortex-M4 SIMD 比 Scalar 慢【英文标题】:Cortex-M4 SIMD slower than Scalar 【发布时间】:2014-08-21 16:27:58 【问题描述】:我的代码中有几个地方确实可以加快速度,当我尝试使用 CM4 SIMD 指令时,结果总是比标量版本慢,例如,这是我正在使用的 alpha 混合函数很多,它不是很慢,但它可以作为一个例子:
for (int y=0; y<h; y++)
i=y*w;
for (int x=0; x<w; x++)
uint spix = *srcp++;
uint dpix = dstp[i+x];
uint r=(alpha*R565(spix)+(256-alpha)*R565(dpix))>>8;
uint g=(alpha*G565(spix)+(256-alpha)*G565(dpix))>>8;
uint b=(alpha*B565(spix)+(256-alpha)*B565(dpix))>>8;
dstp[i+x]= RGB565(r, g, b);
R565、G565、B565、RGB565是分别提取和打包RGB565的宏,请忽略
现在我尝试使用 __SMUAD
并查看是否有任何变化,结果变慢(或与原始代码相同的速度)甚至尝试循环展开,但没有运气:
uint v0, vr, vg, vb;
v0 = (alpha<<16)|(256-alpha);
for (int y=0; y<h; y++)
i=y*w;
for (int x=0; x<w; x++)
spix = *srcp++;
dpix = dstp[i+x];
uint vr = R565(spix)<<16 | R565(dpix);
uint vg = G565(spix)<<16 | G565(dpix);
uint vb = B565(spix)<<16 | B565(dpix);
uint r = __SMUAD(v0, vr)>>8;
uint g = __SMUAD(v0, vg)>>8;
uint b = __SMUAD(v0, vb)>>8;
dstp[i+x]= RGB565(r, g, b);
我知道以前有人问过这个问题,但考虑到架构上的差异,而且没有一个答案能真正解决我的问题,我又问了一次。谢谢!
更新
标量反汇编:
Disassembly of section .text.blend:
00000000 <blend>:
0: e92d 0ff0 stmdb sp!, r4, r5, r6, r7, r8, r9, sl, fp
4: 6846 ldr r6, [r0, #4]
6: 68c4 ldr r4, [r0, #12]
8: b086 sub sp, #24
a: 199e adds r6, r3, r6
c: 9601 str r6, [sp, #4]
e: 9200 str r2, [sp, #0]
10: 68ca ldr r2, [r1, #12]
12: f89d 5038 ldrb.w r5, [sp, #56] ; 0x38
16: 9204 str r2, [sp, #16]
18: 9a01 ldr r2, [sp, #4]
1a: 426e negs r6, r5
1c: 4293 cmp r3, r2
1e: b2f6 uxtb r6, r6
20: da5b bge.n da <blend+0xda>
22: 8809 ldrh r1, [r1, #0]
24: 6802 ldr r2, [r0, #0]
26: 9102 str r1, [sp, #8]
28: fb03 fb01 mul.w fp, r3, r1
2c: 9900 ldr r1, [sp, #0]
2e: 4411 add r1, r2
30: 9103 str r1, [sp, #12]
32: 0052 lsls r2, r2, #1
34: 9205 str r2, [sp, #20]
36: 9903 ldr r1, [sp, #12]
38: 9a00 ldr r2, [sp, #0]
3a: 428a cmp r2, r1
3c: fa1f fb8b uxth.w fp, fp
40: da49 bge.n d6 <blend+0xd6>
42: 4610 mov r0, r2
44: 4458 add r0, fp
46: f100 4000 add.w r0, r0, #2147483648 ; 0x80000000
4a: 9a04 ldr r2, [sp, #16]
4c: f8dd a014 ldr.w sl, [sp, #20]
50: 3801 subs r0, #1
52: eb02 0040 add.w r0, r2, r0, lsl #1
56: 44a2 add sl, r4
58: f834 1b02 ldrh.w r1, [r4], #2
5c: 8842 ldrh r2, [r0, #2]
5e: f3c1 07c4 ubfx r7, r1, #3, #5
62: f3c2 09c4 ubfx r9, r2, #3, #5
66: f001 0c07 and.w ip, r1, #7
6a: f3c1 2804 ubfx r8, r1, #8, #5
6e: fb07 f705 mul.w r7, r7, r5
72: 0b49 lsrs r1, r1, #13
74: fb06 7709 mla r7, r6, r9, r7
78: ea41 01cc orr.w r1, r1, ip, lsl #3
7c: f3c2 2904 ubfx r9, r2, #8, #5
80: f002 0c07 and.w ip, r2, #7
84: fb08 f805 mul.w r8, r8, r5
88: 0b52 lsrs r2, r2, #13
8a: fb01 f105 mul.w r1, r1, r5
8e: 097f lsrs r7, r7, #5
90: fb06 8809 mla r8, r6, r9, r8
94: ea42 02cc orr.w r2, r2, ip, lsl #3
98: fb06 1202 mla r2, r6, r2, r1
9c: f007 07f8 and.w r7, r7, #248 ; 0xf8
a0: f408 58f8 and.w r8, r8, #7936 ; 0x1f00
a4: 0a12 lsrs r2, r2, #8
a6: ea48 0107 orr.w r1, r8, r7
aa: ea41 3142 orr.w r1, r1, r2, lsl #13
ae: f3c2 02c2 ubfx r2, r2, #3, #3
b2: 430a orrs r2, r1
b4: 4554 cmp r4, sl
b6: f820 2f02 strh.w r2, [r0, #2]!
ba: d1cd bne.n 58 <blend+0x58>
bc: 9902 ldr r1, [sp, #8]
be: 448b add fp, r1
c0: 9901 ldr r1, [sp, #4]
c2: 3301 adds r3, #1
c4: 428b cmp r3, r1
c6: fa1f fb8b uxth.w fp, fp
ca: d006 beq.n da <blend+0xda>
cc: 9a00 ldr r2, [sp, #0]
ce: 9903 ldr r1, [sp, #12]
d0: 428a cmp r2, r1
d2: 4654 mov r4, sl
d4: dbb5 blt.n 42 <blend+0x42>
d6: 46a2 mov sl, r4
d8: e7f0 b.n bc <blend+0xbc>
da: b006 add sp, #24
dc: e8bd 0ff0 ldmia.w sp!, r4, r5, r6, r7, r8, r9, sl, fp
e0: 4770 bx lr
e2: bf00 nop
SIMD 反汇编:
sassembly of section .text.blend:
00000000 <blend>:
0: e92d 0ff0 stmdb sp!, r4, r5, r6, r7, r8, r9, sl, fp
4: 6846 ldr r6, [r0, #4]
6: 68c4 ldr r4, [r0, #12]
8: b086 sub sp, #24
a: 199e adds r6, r3, r6
c: 9601 str r6, [sp, #4]
e: 9200 str r2, [sp, #0]
10: 68ca ldr r2, [r1, #12]
12: f89d 5038 ldrb.w r5, [sp, #56] ; 0x38
16: 9204 str r2, [sp, #16]
18: 9a01 ldr r2, [sp, #4]
1a: f5c5 7680 rsb r6, r5, #256 ; 0x100
1e: 4293 cmp r3, r2
20: ea46 4505 orr.w r5, r6, r5, lsl #16
24: da5d bge.n e2 <blend+0xe2>
26: 8809 ldrh r1, [r1, #0]
28: 6802 ldr r2, [r0, #0]
2a: 9102 str r1, [sp, #8]
2c: fb03 fb01 mul.w fp, r3, r1
30: 9900 ldr r1, [sp, #0]
32: 4411 add r1, r2
34: 9103 str r1, [sp, #12]
36: 0052 lsls r2, r2, #1
38: 9205 str r2, [sp, #20]
3a: 9903 ldr r1, [sp, #12]
3c: 9a00 ldr r2, [sp, #0]
3e: 428a cmp r2, r1
40: fa1f fb8b uxth.w fp, fp
44: da4b bge.n de <blend+0xde>
46: 4610 mov r0, r2
48: 4458 add r0, fp
4a: f100 4000 add.w r0, r0, #2147483648 ; 0x80000000
4e: 9a04 ldr r2, [sp, #16]
50: f8dd a014 ldr.w sl, [sp, #20]
54: 3801 subs r0, #1
56: eb02 0040 add.w r0, r2, r0, lsl #1
5a: 44a2 add sl, r4
5c: f834 2b02 ldrh.w r2, [r4], #2
60: 8841 ldrh r1, [r0, #2]
62: f3c2 07c4 ubfx r7, r2, #3, #5
66: f3c1 06c4 ubfx r6, r1, #3, #5
6a: ea46 4707 orr.w r7, r6, r7, lsl #16
6e: fb25 f707 smuad r7, r5, r7
72: f001 0907 and.w r9, r1, #7
76: ea4f 3c51 mov.w ip, r1, lsr #13
7a: f002 0607 and.w r6, r2, #7
7e: ea4f 3852 mov.w r8, r2, lsr #13
82: ea4c 0cc9 orr.w ip, ip, r9, lsl #3
86: ea48 06c6 orr.w r6, r8, r6, lsl #3
8a: ea4c 4606 orr.w r6, ip, r6, lsl #16
8e: fb25 f606 smuad r6, r5, r6
92: f3c1 2104 ubfx r1, r1, #8, #5
96: f3c2 2204 ubfx r2, r2, #8, #5
9a: ea41 4202 orr.w r2, r1, r2, lsl #16
9e: fb25 f202 smuad r2, r5, r2
a2: f3c6 260f ubfx r6, r6, #8, #16
a6: 097f lsrs r7, r7, #5
a8: f3c6 01c2 ubfx r1, r6, #3, #3
ac: f007 07f8 and.w r7, r7, #248 ; 0xf8
b0: 430f orrs r7, r1
b2: f402 52f8 and.w r2, r2, #7936 ; 0x1f00
b6: ea47 3646 orr.w r6, r7, r6, lsl #13
ba: 4316 orrs r6, r2
bc: 4554 cmp r4, sl
be: f820 6f02 strh.w r6, [r0, #2]!
c2: d1cb bne.n 5c <blend+0x5c>
c4: 9902 ldr r1, [sp, #8]
c6: 448b add fp, r1
c8: 9901 ldr r1, [sp, #4]
ca: 3301 adds r3, #1
cc: 428b cmp r3, r1
ce: fa1f fb8b uxth.w fp, fp
d2: d006 beq.n e2 <blend+0xe2>
d4: 9a00 ldr r2, [sp, #0]
d6: 9903 ldr r1, [sp, #12]
d8: 428a cmp r2, r1
da: 4654 mov r4, sl
dc: dbb3 blt.n 46 <blend+0x46>
de: 46a2 mov sl, r4
e0: e7f0 b.n c4 <blend+0xc4>
e2: b006 add sp, #24
e4: e8bd 0ff0 ldmia.w sp!, r4, r5, r6, r7, r8, r9, sl, fp
e8: 4770 bx lr
ea: bf00 nop
【问题讨论】:
尝试一次加载 32 位而不是 16 位,然后自己处理循环中的两个像素(有点展开)。 @auselen 展开 2 个像素,没有显着增益 你可以在反汇编中看到它是ldrh
指令而不是ldr
。我的意思不是展开,而是加载 32 位。
这可能会帮助您加快算法link
【参考方案1】:
如果你想真正优化一个函数,你应该检查编译器的汇编输出。然后,您将了解它如何转换您的代码,然后您可以学习如何编写代码以帮助编译器产生更好的输出,或编写必要的汇编程序。
您在 Alpha 混合循环中快速获得的一个轻松胜利是除法很慢。
不要使用x / 100
,而是使用
x * 65536 / 65536 / 100
->x * (65536 / 100) / 65536
->x * 655.36 >> 16
->x * 656 >> 16
一个更好的选择是使用 0 -> 256 之间的 alpha 值,这样您就可以对结果进行位移,甚至不需要做这个技巧。
smuad 可能没有任何好处的一个原因是您必须将数据移动到专门用于此命令的格式。
我不确定总体上你是否能做得更好,但我想我会指出一种方法来避免你的示例程序中的分裂。此外,如果您检查程序集,您可能会发现有一些您不希望可以消除的代码生成。
【讨论】:
谢谢你的提示,我会用256,但是这个函数只是一个例子,我的目标是找出为什么到目前为止我尝试过的几乎所有SIMD指令都比较慢,以及我是否是不是做错了什么。 我建议最重要的是检查程序集。然后你就会知道 CPU 被告知要做什么。而且通常,你可能会发现编译器没有你那么聪明希望:) 上述循环的问题是乘法的成本几乎肯定与除法的成本相比相形见绌,因此您所做的任何改进都不会明显。 用 >>8 替换了 div 并没有太大区别,在标量版本中节省了一些我们 ... 我很惊讶这并没有太大的区别(我们几个人的区别是多少?);但再一次,汇编代码是关键。【参考方案2】:社区维基答案
适应 SIMD 类型指令的主要变化是转换加载。 SMUAD 指令可以看作是“C”指令,例如,
/* a,b are register vectors/arrays of 16 bits */
SMUAD = a[0] * b[0] + a[1] * b[1];
转换这些非常容易。而不是,
u16 *srcp, dstp;
uint spix = *srcp++;
uint dpix = dstp[i+x];
使用全总线,一次获取 32 位,
uint *srcp, *dstp; /* These are two 16 bit values. */
uint spix = *srcp++;
uint dpix = dstp[i+x];
/* scale `dpix` and `spix` by alpha */
spix /= (alpha << 16 | alpha); /* Precompute, reduce strength, etc. */
dpix /= (1-alpha << 16 | 1-alpha); /* Precompute, reduce strength, etc. */
/* Hint, you can use SMUAD here? or maybe not.
You could if you scale at the same time*/
看起来SMUL
非常适合alpha 缩放;您不想将两半相加。
现在,spix
和 dpix
包含两个像素。不需要vr
合成。您可以同时进行两项操作。
uint rb = (dpix + spix) & ~GMASK; /* GMASK is x6xx6x bits. */
uint g = (dpix + spix) & GMASK;
/* Maybe you don't care about overflow?
A dual 16bit add helps, if the M4 has it? */
dstp[i+x]= rb | g; /* write 32bits or two pixels at a time. */
主要是通过一次加载 32 位来更好地利用 BUS,这肯定会加快您的日常工作。如果您注意范围并且不要将较低的 16 位值溢出到较高的值中,那么标准的 32 位整数数学可能在大多数情况下都有效。
对于 blitter 代码,Bit blog 和 Bit hacks 可用于提取和处理 RGB565 值;无论是 SIMD 还是直接 Thumb2 代码。
主要是,使用 SIMD 绝不是简单的重新编译。转换算法可能需要数周的时间。如果处理得当,当算法不受内存带宽限制且不涉及许多条件时,SIMD 的速度提升非常显着。
【讨论】:
我明白你的意思了,我试试看,谢谢。 是的,但你在这里做得更好:) 支持你的答案。【参考方案3】:现在发布反汇编:您会看到标量和 simd 版本都有 29 条指令,而 SIMD 版本实际上占用了更多的代码空间。 (内部循环的标量为 0x58 -> 0xba,而 SIMD 为 (0x5c -> 0xc2))
您可以看到很多指令用于将数据转换为两个循环的正确格式。也许您可以通过处理 RGB 位解包/重新打包而不是 alpha 混合计算来进一步提高性能!
编辑:您可能还想考虑一次处理成对的像素。
【讨论】:
但是在两个版本中都进行了解包/打包,所以它取消了,我预计 SIMD 会有一些改进,或者至少是相同的性能,每条指令都替换了 2 个乘法和加法。也尝试过如上所述展开。 内循环具有相同数量的指令,因此 2 次乘法和加法被 更多位移动和 smuad 指令代替。如果您可以避免/解决钻头移动,那么您可能会从 SIMD 中受益。 我现在明白你的意思了,检查装配确实有很大帮助,我在这个答案和另一个答案之间左右为难,他们都帮了我很多,但你的 cmets 说得很清楚,所以接受你的并支持每个人:) 我不确定你的 RGB 宏是什么,但我尝试用自己的 RGB 宏编译代码,内部循环只有 21 条 ARM 指令以上是关于Cortex-M4 SIMD 比 Scalar 慢的主要内容,如果未能解决你的问题,请参考以下文章
为啥 SSE scalar sqrt(x) 比 rsqrt(x) * x 慢?