在简单的加法任务中使用 ARM NEON 速度较慢
Using ARM NEON is slower in a simple Addition task
我尝试编写一段简单的 NEON 代码,但发现它比常规的 C++ 实现要慢。代码如下
float A[] = {1,2,3,4};
float B[] = {2,3,4,5};
float32x4_t v1;
float32x4_t v2;
int counter = 1000000;
while(counter--){
v1 = vld1q_f32(A);
v2 = vld1q_f32(B);
v = vaddq_f32(v1,v2);
vst1q_f32(A,v);
// A[0] = A[0]+B[0]; // regular implementation
// A[1] = A[1]+B[1]; // regular implementation
// A[2] = A[2]+B[2]; // regular implementation
// A[3] = A[3]+B[3]; // regular implementation
}
我搜索了原因,所以我猜是因为顺序管道和这个简单的任务导致 CPU 中的停顿?但是谁能帮忙详细解释一下?有什么方法可以提高这个 NEON 实现的性能吗?还是在面对这种简单任务时,使用常规实现比使用 NEON 更好?谢谢你。
如果您对 load/store 使用内在函数,编译器似乎不会理会它们。
如果没有内在函数,它可以优化掉 load/store 并仅使用寄存器来获取中间值。查看生成的程序集:https://www.godbolt.org/z/FvqKP3
此外,我猜想在另一个传输正在写入同一内存的同时将某些内容加载到 NEON 可能是一种非常罕见的用例,CPU 设计人员不会费心去实现直接旁路。您可能需要等到商店完全完成后才能开始加载。
您的测试例程完全有缺陷:
由于编译器在构建时可以清楚地看到所有输入,因此编译器将简单地生成类似于以下代码的机器代码:
A[0] = 3.0f;
A[1] = 5.0f;
A[2] = 7.0f;
A[3] = 9.0f;
为了防止编译器作弊,你必须隐藏输入:
void myFunc_c(float *pA, float *pB, uint32_t count)
{
if (count == 0) return;
do {
*pA++ += *pB++;
} while (--count);
}
void myFunc_neon(float *pA, float *pB, uint32_t count)
{
float32x4_t a, b;
count >>= 2;
if (count == 0) return;
do {
a = vld1q_f32(pA);
b = vld1q_f32(pB);
a = vaddq_f32(a, b);
vst1q_f32(pA, a);
pA += 4;
pB += 4;
} while (--count);
}
您需要做的就是为pA
和pB
分配足够的内存,根据需要初始化它们,然后调用上面的函数。
我认为 neon 版本大约快 3 倍。
我尝试编写一段简单的 NEON 代码,但发现它比常规的 C++ 实现要慢。代码如下
float A[] = {1,2,3,4};
float B[] = {2,3,4,5};
float32x4_t v1;
float32x4_t v2;
int counter = 1000000;
while(counter--){
v1 = vld1q_f32(A);
v2 = vld1q_f32(B);
v = vaddq_f32(v1,v2);
vst1q_f32(A,v);
// A[0] = A[0]+B[0]; // regular implementation
// A[1] = A[1]+B[1]; // regular implementation
// A[2] = A[2]+B[2]; // regular implementation
// A[3] = A[3]+B[3]; // regular implementation
}
我搜索了原因,所以我猜是因为顺序管道和这个简单的任务导致 CPU 中的停顿?但是谁能帮忙详细解释一下?有什么方法可以提高这个 NEON 实现的性能吗?还是在面对这种简单任务时,使用常规实现比使用 NEON 更好?谢谢你。
如果您对 load/store 使用内在函数,编译器似乎不会理会它们。
如果没有内在函数,它可以优化掉 load/store 并仅使用寄存器来获取中间值。查看生成的程序集:https://www.godbolt.org/z/FvqKP3
此外,我猜想在另一个传输正在写入同一内存的同时将某些内容加载到 NEON 可能是一种非常罕见的用例,CPU 设计人员不会费心去实现直接旁路。您可能需要等到商店完全完成后才能开始加载。
您的测试例程完全有缺陷:
由于编译器在构建时可以清楚地看到所有输入,因此编译器将简单地生成类似于以下代码的机器代码:
A[0] = 3.0f;
A[1] = 5.0f;
A[2] = 7.0f;
A[3] = 9.0f;
为了防止编译器作弊,你必须隐藏输入:
void myFunc_c(float *pA, float *pB, uint32_t count)
{
if (count == 0) return;
do {
*pA++ += *pB++;
} while (--count);
}
void myFunc_neon(float *pA, float *pB, uint32_t count)
{
float32x4_t a, b;
count >>= 2;
if (count == 0) return;
do {
a = vld1q_f32(pA);
b = vld1q_f32(pB);
a = vaddq_f32(a, b);
vst1q_f32(pA, a);
pA += 4;
pB += 4;
} while (--count);
}
您需要做的就是为pA
和pB
分配足够的内存,根据需要初始化它们,然后调用上面的函数。
我认为 neon 版本大约快 3 倍。