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 &gt;&gt; 16

->x * 656 &gt;&gt; 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 缩放;您不想将两半相加。

现在,spixdpix 包含两个像素。不需要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 慢的主要内容,如果未能解决你的问题,请参考以下文章

为啥此 SIMD 代码运行速度比等效标量慢?

为啥 SSE scalar sqrt(x) 比 rsqrt(x) * x 慢?

使用 Vector<T> 的带有 SIMD 的矢量化 C# 代码运行速度比经典循环慢

CUDA Scalar 和 SIMD 视频指令的效率

Cortex-M的比较

为啥 SIMD 比蛮力慢