问题描述
限时送ChatGPT账号..我编写了一个测试应用程序来比较 C++ 实现和霓虹灯优化实现,用于两个包含复数的向量的乘法.
I have written a test app to compare c++ implementation and neon optimized implementation for multiplication of two vectors containing complex numbers.
neon 实现比 cpp 快约 3 倍.(代码 1)
The neon implementation is ~3x faster than cpp. (Code 1)
但是,如果我用乘法运算符 *
替换用于乘法的内在霓虹灯 - vmulq_f32
以将两个霓虹灯寄存器相乘,我将获得约 4 倍的速度.
But if I replace neon intrinsic for multiplication - vmulq_f32
with multiplication operator *
to multiply two neon registers, I am getting a ~4x speed.
然后,如果我还用 +
/-
替换了用于加/减 - vaddq_f32
/vsubq_f32
的内在霓虹灯> 添加/减去两个霓虹灯寄存器,我获得了大约 5 倍的速度.(代码 2)
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. (Code 2)
我不明白发生了什么?为什么 Neon 内在函数比常规运算符慢?
I don't understand what's going on? Why are neon intrinsics slower than regular operators?
代码 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)
Tools used - clang, ndk 16, Samsung S6 (AT&T)
编辑 - 按照建议添加反汇编
所以我查看了代码 1 和代码 2 的反汇编 -
So I looked at disassembly for code 1 and code 2 -
代码1的反汇编(仅复制ld2
和st2
之间的相关部分)-
Disassembly for code 1 (copied only the relevant portion between ld2
and 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]
反汇编代码 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]
反汇编确实解释了加速的原因.注意在第一个代码中,它们在 fmul
和 fmul
之间有很多(看似不必要的)ldr
和 str
命令/fadd
.
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
.
现在的问题是,为什么同一个编译器会为代码 1 生成如此糟糕的汇编?所有这些不必要的 ldr
和 str
的原因是什么?
Now the question is why does same compiler produce such poor assembly for code 1? What is the reason for all these unnecessary ldr
and 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.
你为什么不发布你的拆解?我的可能略有不同,因为我必须将参数转换为简单类型.(float *)
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.
更新:
在这种情况下,排除一切不必要的:
In this case, rule out everything unnecessary:
像我一样将所有参数更改为简单的 float *
.
Change all arguments to simple float *
like I did.
这篇关于为什么用于乘法、加法的霓虹内在函数比运算符慢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论