为啥乘法、加法的霓虹内在函数比运算符慢?

Posted

技术标签:

【中文标题】为啥乘法、加法的霓虹内在函数比运算符慢?【英文标题】:Why are neon intrinsics for multiplication, addition slower than operators?为什么乘法、加法的霓虹内在函数比运算符慢? 【发布时间】:2018-04-30 07:18:57 【问题描述】:

我编写了一个测试应用程序来比较 c++ 实现和霓虹灯优化实现,以将两个包含复数的向量相乘。

neon 实现比 cpp 快约 3 倍。 (代码 1)

但是,如果我用乘法运算符 * 替换用于乘法的 neon 固有函数 - vmulq_f32 以将两个霓虹灯寄存器相乘,我将获得约 4 倍的速度。

然后,如果我还用 +/- 替换用于加/减的霓虹灯固有的 vaddq_f32/vsubq_f32 以添加/减去两个霓虹灯寄存器,我将获得约 5 倍的速度。 (代码 2)

我不明白发生了什么?为什么 neon 内在函数比常规运算符慢?

代码 1(比 cpp 快约 3 倍)-

// (a + ib) * (c + id) = (ac - bd) + i(ad + bc)
void complex_mult_neon(
    std::vector<float>& inVec1,
    std::vector<float>& inVec2,
    std::vector<float>& outVec)

    float* src1 = &inVec1[0];
    float* src2 = &inVec2[0];
    float* dst = &outVec[0];

    float32x4x2_t reg_s1, reg_s2;
    float32x4_t reg_p1, reg_p2;
    float32x4x2_t reg_r;

    for (auto count = inVec1.size(); count > 0; count -= 8)
    
        reg_s1 = vld2q_f32(src1);
        src1 += 8;

        reg_s2 = vld2q_f32(src2);
        src2 += 8;

        // ac
        reg_p1 = vmulq_f32(reg_s1.val[0], reg_s2.val[0]);

        // bd
        reg_p2 = vmulq_f32(reg_s1.val[1], reg_s2.val[1]);

        // ac - bd
        reg_r.val[0] = vsubq_f32(reg_p1, reg_p2);

        // ad
        reg_p1 = vmulq_f32(reg_s1.val[0], reg_s2.val[1]);

        // bc
        reg_p2 = vmulq_f32(reg_s1.val[1], reg_s2.val[0]);

        // ad + bc
        reg_r.val[1] = vaddq_f32(reg_p1, reg_p2);

        vst2q_f32(dst, reg_r);
        dst += 8;
    

代码 2(比 cpp 快约 5 倍)-

void complex_mult_neon(...)

    // same as above ...

    for (auto count = inVec1.size(); count > 0; count -= 8)
    
        reg_s1 = vld2q_f32(src1);
        src1 += 8;

        reg_s2 = vld2q_f32(src2);
        src2 += 8;

        // ac
        reg_p1 = reg_s1.val[0] * reg_s2.val[0];

        // bd
        reg_p2 = reg_s1.val[1] * reg_s2.val[1];

        // ac - bd
        reg_r.val[0] = reg_p1 - reg_p2;

        // ad
        reg_p1 = reg_s1.val[0] * reg_s2.val[1];

        // bc
        reg_p2 = reg_s1.val[1] * reg_s2.val[0];

        // ad + bc
        reg_r.val[1] = reg_p1 + reg_p2;

        vst2q_f32(dst, reg_r);
        dst += 8;
    

cpp 代码 -

void complex_mult_cpp(
    std::vector<float>& inVec1,
    std::vector<float>& inVec2,
    std::vector<float>& outVec)

    float p1, p2;

    for (auto i = 0; i < inVec1.size(); i += 2)
    
        // ac
        p1 = inVec1[i] * inVec2[i];

        // bd
        p2 = inVec1[i + 1] * inVec2[i + 1];

        // ac - bd
        outVec[i] = p1 - p2;

        // ad
        p1 = inVec1[i] * inVec2[i + 1];

        // bc
        p2 = inVec1[i + 1] * inVec2[i];

        // ad + bc
        outVec[i + 1] = p1 + p2;
    

使用的工具 - clang、ndk 16、Samsung S6 (AT&T)

编辑 - 按照建议添加反汇编

所以我查看了代码 1 和代码 2 的反汇编 -

代码 1 的反汇编(仅复制 ld2st2 之间的相关部分)-

      88:   00 89 40 4c     ld2  v0.4s, v1.4s , [x8]
      8c:   22 1c a1 4e     mov     v2.16b, v1.16b
      90:   03 1c a0 4e     mov     v3.16b, v0.16b
      94:   e8 07 40 f9     ldr x8, [sp, #8]
      98:   03 55 80 3d     str q3, [x8, #336]
      9c:   02 59 80 3d     str q2, [x8, #352]
      a0:   02 55 c0 3d     ldr q2, [x8, #336]
      a4:   02 5d 80 3d     str q2, [x8, #368]
      a8:   02 59 c0 3d     ldr q2, [x8, #352]
      ac:   02 61 80 3d     str q2, [x8, #384]
; outVec[i] = p1 - p2;
      b0:   02 5d c0 3d     ldr q2, [x8, #368]
      b4:   02 75 80 3d     str q2, [x8, #464]
      b8:   02 61 c0 3d     ldr q2, [x8, #384]
      bc:   02 79 80 3d     str q2, [x8, #480]
      c0:   e9 2b 40 f9     ldr x9, [sp, #80]
      c4:   29 81 00 91     add x9, x9, #32
      c8:   e9 2b 00 f9     str x9, [sp, #80]
      cc:   e9 27 40 f9     ldr x9, [sp, #72]
      d0:   20 89 40 4c     ld2  v0.4s, v1.4s , [x9]
; p1 = inVec1[i] * inVec2[i + 1];
      d4:   22 1c a1 4e     mov     v2.16b, v1.16b
      d8:   03 1c a0 4e     mov     v3.16b, v0.16b
      dc:   03 45 80 3d     str q3, [x8, #272]
      e0:   02 49 80 3d     str q2, [x8, #288]
      e4:   02 45 c0 3d     ldr q2, [x8, #272]
      e8:   02 4d 80 3d     str q2, [x8, #304]
      ec:   02 49 c0 3d     ldr q2, [x8, #288]
      f0:   02 51 80 3d     str q2, [x8, #320]
      f4:   02 4d c0 3d     ldr q2, [x8, #304]
      f8:   02 6d 80 3d     str q2, [x8, #432]
      fc:   02 51 c0 3d     ldr q2, [x8, #320]
     100:   02 71 80 3d     str q2, [x8, #448]
     104:   e9 27 40 f9     ldr x9, [sp, #72]
     108:   29 81 00 91     add x9, x9, #32
     10c:   e9 27 00 f9     str x9, [sp, #72]
; p2 = inVec1[i + 1] * inVec2[i];
     110:   02 75 c0 3d     ldr q2, [x8, #464]
     114:   03 6d c0 3d     ldr q3, [x8, #432]
     118:   e2 27 80 3d     str q2, [sp, #144]
     11c:   e3 23 80 3d     str q3, [sp, #128]
     120:   e2 27 c0 3d     ldr q2, [sp, #144]
     124:   e3 23 c0 3d     ldr q3, [sp, #128]
     128:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     12c:   e2 1f 80 3d     str q2, [sp, #112]
     130:   e2 1f c0 3d     ldr q2, [sp, #112]
     134:   e2 0f 80 3d     str q2, [sp, #48]
     138:   02 79 c0 3d     ldr q2, [x8, #480]
     13c:   03 71 c0 3d     ldr q3, [x8, #448]
     140:   02 39 80 3d     str q2, [x8, #224]
     144:   03 35 80 3d     str q3, [x8, #208]
     148:   02 39 c0 3d     ldr q2, [x8, #224]
; outVec[i + 1] = p1 + p2;
     14c:   03 35 c0 3d     ldr q3, [x8, #208]
     150:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     154:   02 31 80 3d     str q2, [x8, #192]
     158:   02 31 c0 3d     ldr q2, [x8, #192]
     15c:   e2 0b 80 3d     str q2, [sp, #32]
     160:   e2 0f c0 3d     ldr q2, [sp, #48]
     164:   e3 0b c0 3d     ldr q3, [sp, #32]
     168:   02 2d 80 3d     str q2, [x8, #176]
     16c:   03 29 80 3d     str q3, [x8, #160]
     170:   02 2d c0 3d     ldr q2, [x8, #176]
     174:   03 29 c0 3d     ldr q3, [x8, #160]
     178:   42 d4 a3 4e     fsub    v2.4s, v2.4s, v3.4s
; for (auto i = 0; i < inVec1.size(); i += 2)
     17c:   02 25 80 3d     str q2, [x8, #144]
     180:   02 25 c0 3d     ldr q2, [x8, #144]
     184:   02 65 80 3d     str q2, [x8, #400]
     188:   02 75 c0 3d     ldr q2, [x8, #464]
; 
     18c:   03 71 c0 3d     ldr q3, [x8, #448]
     190:   02 21 80 3d     str q2, [x8, #128]
     194:   03 1d 80 3d     str q3, [x8, #112]
     198:   02 21 c0 3d     ldr q2, [x8, #128]
     19c:   03 1d c0 3d     ldr q3, [x8, #112]
     1a0:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     1a4:   02 19 80 3d     str q2, [x8, #96]
     1a8:   02 19 c0 3d     ldr q2, [x8, #96]
     1ac:   e2 0f 80 3d     str q2, [sp, #48]
     1b0:   02 79 c0 3d     ldr q2, [x8, #480]
     1b4:   03 6d c0 3d     ldr q3, [x8, #432]
     1b8:   02 15 80 3d     str q2, [x8, #80]
     1bc:   03 11 80 3d     str q3, [x8, #64]
     1c0:   02 15 c0 3d     ldr q2, [x8, #80]
     1c4:   03 11 c0 3d     ldr q3, [x8, #64]
     1c8:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     1cc:   02 0d 80 3d     str q2, [x8, #48]
     1d0:   02 0d c0 3d     ldr q2, [x8, #48]
     1d4:   e2 0b 80 3d     str q2, [sp, #32]
     1d8:   e2 0f c0 3d     ldr q2, [sp, #48]
     1dc:   e3 0b c0 3d     ldr q3, [sp, #32]
     1e0:   02 09 80 3d     str q2, [x8, #32]
     1e4:   03 05 80 3d     str q3, [x8, #16]
     1e8:   02 09 c0 3d     ldr q2, [x8, #32]
     1ec:   03 05 c0 3d     ldr q3, [x8, #16]
     1f0:   42 d4 23 4e     fadd    v2.4s, v2.4s, v3.4s
     1f4:   02 01 80 3d     str     q2, [x8]
     1f8:   02 01 c0 3d     ldr     q2, [x8]
     1fc:   02 69 80 3d     str q2, [x8, #416]
     200:   02 65 c0 3d     ldr q2, [x8, #400]
     204:   02 3d 80 3d     str q2, [x8, #240]
     208:   02 69 c0 3d     ldr q2, [x8, #416]
     20c:   02 41 80 3d     str q2, [x8, #256]
     210:   e9 23 40 f9     ldr x9, [sp, #64]
     214:   02 3d c0 3d     ldr q2, [x8, #240]
     218:   03 41 c0 3d     ldr q3, [x8, #256]
     21c:   40 1c a2 4e     mov     v0.16b, v2.16b
     220:   61 1c a3 4e     mov     v1.16b, v3.16b
     224:   20 89 00 4c     st2  v0.4s, v1.4s , [x9]

代码 2 的反汇编 -

      88:   00 89 40 4c     ld2  v0.4s, v1.4s , [x8]
      8c:   22 1c a1 4e     mov     v2.16b, v1.16b
      90:   03 1c a0 4e     mov     v3.16b, v0.16b
      94:   e8 07 40 f9     ldr x8, [sp, #8]
      98:   03 11 80 3d     str q3, [x8, #64]
      9c:   02 15 80 3d     str q2, [x8, #80]
      a0:   02 11 c0 3d     ldr q2, [x8, #64]
      a4:   02 19 80 3d     str q2, [x8, #96]
      a8:   02 15 c0 3d     ldr q2, [x8, #80]
      ac:   02 1d 80 3d     str q2, [x8, #112]
; outVec[i] = p1 - p2;
      b0:   02 19 c0 3d     ldr q2, [x8, #96]
      b4:   02 31 80 3d     str q2, [x8, #192]
      b8:   02 1d c0 3d     ldr q2, [x8, #112]
      bc:   02 35 80 3d     str q2, [x8, #208]
      c0:   e9 2b 40 f9     ldr x9, [sp, #80]
      c4:   29 81 00 91     add x9, x9, #32
      c8:   e9 2b 00 f9     str x9, [sp, #80]
      cc:   e9 27 40 f9     ldr x9, [sp, #72]
      d0:   20 89 40 4c     ld2  v0.4s, v1.4s , [x9]
; p1 = inVec1[i] * inVec2[i + 1];
      d4:   22 1c a1 4e     mov     v2.16b, v1.16b
      d8:   03 1c a0 4e     mov     v3.16b, v0.16b
      dc:   e3 27 80 3d     str q3, [sp, #144]
      e0:   02 05 80 3d     str q2, [x8, #16]
      e4:   e2 27 c0 3d     ldr q2, [sp, #144]
      e8:   02 09 80 3d     str q2, [x8, #32]
      ec:   02 05 c0 3d     ldr q2, [x8, #16]
      f0:   02 0d 80 3d     str q2, [x8, #48]
      f4:   02 09 c0 3d     ldr q2, [x8, #32]
      f8:   02 29 80 3d     str q2, [x8, #160]
      fc:   02 0d c0 3d     ldr q2, [x8, #48]
     100:   02 2d 80 3d     str q2, [x8, #176]
     104:   e9 27 40 f9     ldr x9, [sp, #72]
     108:   29 81 00 91     add x9, x9, #32
     10c:   e9 27 00 f9     str x9, [sp, #72]
; p2 = inVec1[i + 1] * inVec2[i];
     110:   02 31 c0 3d     ldr q2, [x8, #192]
     114:   03 29 c0 3d     ldr q3, [x8, #160]
     118:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     11c:   e2 0f 80 3d     str q2, [sp, #48]
     120:   02 35 c0 3d     ldr q2, [x8, #208]
     124:   03 2d c0 3d     ldr q3, [x8, #176]
     128:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     12c:   e2 0b 80 3d     str q2, [sp, #32]
     130:   e2 0f c0 3d     ldr q2, [sp, #48]
     134:   e3 0b c0 3d     ldr q3, [sp, #32]
     138:   42 d4 a3 4e     fsub    v2.4s, v2.4s, v3.4s
     13c:   02 21 80 3d     str q2, [x8, #128]
     140:   02 31 c0 3d     ldr q2, [x8, #192]
     144:   03 2d c0 3d     ldr q3, [x8, #176]
     148:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
; outVec[i + 1] = p1 + p2;
     14c:   e2 0f 80 3d     str q2, [sp, #48]
     150:   02 35 c0 3d     ldr q2, [x8, #208]
     154:   03 29 c0 3d     ldr q3, [x8, #160]
     158:   42 dc 23 6e     fmul    v2.4s, v2.4s, v3.4s
     15c:   e2 0b 80 3d     str q2, [sp, #32]
     160:   e2 0f c0 3d     ldr q2, [sp, #48]
     164:   e3 0b c0 3d     ldr q3, [sp, #32]
     168:   42 d4 23 4e     fadd    v2.4s, v2.4s, v3.4s
     16c:   02 25 80 3d     str q2, [x8, #144]
     170:   02 21 c0 3d     ldr q2, [x8, #128]
     174:   e2 1f 80 3d     str q2, [sp, #112]
     178:   02 25 c0 3d     ldr q2, [x8, #144]
; for (auto i = 0; i < inVec1.size(); i += 2)
     17c:   e2 23 80 3d     str q2, [sp, #128]
     180:   e9 23 40 f9     ldr x9, [sp, #64]
     184:   e2 1f c0 3d     ldr q2, [sp, #112]
     188:   e3 23 c0 3d     ldr q3, [sp, #128]
; 
     18c:   40 1c a2 4e     mov     v0.16b, v2.16b
     190:   61 1c a3 4e     mov     v1.16b, v3.16b
     194:   20 89 00 4c     st2  v0.4s, v1.4s , [x9]

反汇编确实解释了加速的原因。请注意,在第一个代码中,fmulfmul/fadd 之间的 ldrstr 命令太多了(似乎没有必要)。

现在的问题是,为什么同一个编译器会为代码 1 生成如此糟糕的汇编?所有这些不必要的ldrstr 的原因是什么?

【问题讨论】:

为什么不检查反汇编? @Jake'Alquimista'LEE 添加了反汇编。您可以阅读更新。但问题仍然存在.. 您尝试过使用-O2 或-O3 吗?否则结果是贫血。 @Surt 我试过 -O1, -O2, -O3 。但这似乎并没有影响结果。不确定我是否错误地传递了标志,或者 cmake 忽略了它。 @VinayakGarg 嗨,你找到原因了吗? 【参考方案1】:

我检查了反汇编,因为您似乎拥有与我相同的开发环境:


LD2             V0.4S-V1.4S, [src1],#0x20
LD2             V2.4S-V3.4S, [src2],#0x20
SUB             W8, W8, #8
CMP             W8, #8
FMUL            V4.4S, V3.4S, V1.4S
FNEG            V4.4S, V4.4S
FMLA            V4.4S, V0.4S, V2.4S
FMUL            V5.4S, V2.4S, V1.4S
FMLA            V5.4S, V0.4S, V3.4S
ST2             V4.4S-V5.4S, [dst],#0x20
B.GT            loc_4C

两者都生成相同的错误机器代码。

你为什么不发布你的反汇编?我的可能略有不同,因为我必须将参数转换为简单类型。 (float *)

如果你的反汇编看起来一样,那一定是基准测试失败。没有其他解释。


更新:

在这种情况下,排除一切不必要的东西:

像我一样将所有参数更改为简单的float *

【讨论】:

以上是关于为啥乘法、加法的霓虹内在函数比运算符慢?的主要内容,如果未能解决你的问题,请参考以下文章

实现多项式的加法和乘法运算

为啥“OR”运算符比 oracle 中的 union 慢

为啥我的传感器功能比使用 ->> 运算符慢?

PHP在数据输出时进行乘法和加法运算

ARM中乘法和存储的霓虹灯优化

02-线性结构2 一元多项式的乘法与加法运算