[英]Why are neon intrinsics for multiplication, addition slower than operators?
I have written a test app to compare c++ implementation and neon optimized implementation for multiplication of two vectors containing complex numbers. 我编写了一个测试应用程序,以比较c ++实现和霓虹优化的实现,以比较两个包含复数的向量的乘法。
The neon implementation is ~3x faster than cpp. 霓虹灯实现比cpp快3倍。 (Code 1)
(代码1)
But if I replace neon intrinsic for multiplication - vmulq_f32
with multiplication operator *
to multiply two neon registers, I am getting a ~4x speed. 但是,如果我用乘法运算符
*
替换neon内在函数乘法vmulq_f32
以将两个neon寄存器相乘,则速度约为4倍。
And then if I also replace neon intrinsic for add/subtract - vaddq_f32
/ vsubq_f32
with +
/ -
to add/subtract two neon registers, I am getting a ~5x speed. 然后,如果我也用
+
/ -
替换加/减的霓虹灯内在函数vaddq_f32
/ vsubq_f32
来加/减两个霓虹灯寄存器,则速度约为5倍。 (Code 2) (代码2)
I don't understand what's going on? 我不明白发生了什么事? Why are neon intrinsics slower than regular operators?
为什么霓虹灯本征比常规运算符慢?
code 1 (~3x faster than cpp) - 代码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;
}
}
code 2 (~5x faster than cpp) - 代码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 code - 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;
}
}
Tools used - clang, ndk 16, Samsung S6 (AT&T) 使用的工具-Clang,NDK 16,三星S6(AT&T)
EDIT - Adding disassembly as suggested 编辑-根据建议添加反汇编
So I looked at disassembly for code 1 and code 2 - 所以我查看了代码1和代码2的反汇编-
Disassembly for code 1 (copied only the relevant portion between ld2
and st2
) - 拆卸代码1(仅复制
ld2
和st2
之间的相关部分)-
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]
Disassembly for code 2 - 代码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]
Disassembly does explain the reason for speed up. 拆卸确实说明了加速的原因。 Notice how in first code, their are so many (seemingly unnecessary)
ldr
and str
commands between fmul
and fmul
/ fadd
. 注意在第一个代码中,它们在
fmul
和fmul
/ fadd
之间有那么多(看似不必要)的ldr
和str
命令。
Now the question is why does same compiler produce such poor assembly for code 1? 现在的问题是,为什么同一个编译器为代码1产生如此糟糕的汇编? What is the reason for all these unnecessary
ldr
and str
? 所有这些不必要的
ldr
和str
的原因是什么?
I checked the disassembly since you seem to have the same develop environment I have: 我检查了反汇编,因为您似乎拥有与我相同的开发环境:
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
Both generate the same bad machine codes. 两者都生成相同的错误机器代码。
Why don't you post the disassembly of yours? 您为什么不发布自己的拆卸书? Mine might be slightly different since I had to convert the parameters to simple types.
我的可能会略有不同,因为我不得不将参数转换为简单类型。
(float *)
If your disassembly looks the same, it must be benchmarking failure. 如果您的拆卸看起来相同,则必须是基准测试失败。 There is no other explanation.
没有其他解释。
update: 更新:
In this case, rule out everything unnecessary: 在这种情况下,请排除所有不必要的内容:
Change all arguments to simple float *
like I did. 像我一样将所有参数更改为简单的
float *
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.