编译带有活动霓虹灯标志的代码几乎没有任何改进(甚至恶化)
Compiling code with active neon flags leads to almost no improvement (and even deterioration)
我正在努力了解在gcc编译器中使用活动霓虹灯标志编译C++代码可能带来的好处。为此,我制作了一个小程序,可以遍历数组并进行简单的算术运算。
我更改了代码,以便任何人都可以编译和运行它。如果有人愿意执行此测试并分享结果,我将不胜感激:)
编辑:我真的请附近碰巧有Cortex-A9板的人进行这个测试,并检查结果是否相同。我真的很感激。
#include <ctime>
int main()
{
unsigned long long arraySize = 30000000;
unsigned short* arrayShort = new unsigned short[arraySize];
std::clock_t begin;
for (unsigned long long n = 0; n < arraySize; n++)
{
*arrayShort = rand() % 100 + 1;
arrayShort++;
}
arrayShort -= arraySize;
begin = std::clock();
for (unsigned long long n = 0; n < arraySize; n++)
{
*arrayShort += 10;
*arrayShort /= 3;
arrayShort++;
}
std::cout << "Time: " << (std::clock() - begin) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;
arrayShort -= arraySize;
delete[] arrayShort;
return 0;
}
基本上,我用1到100之间的随机数填充一个30000000大小的数组,然后遍历所有元素,求和10,除以3。我原以为用活动霓虹灯标志编译这段代码会有很大的改进,因为它一次可以进行多个数组操作。
我正在使用带有GCC 4.8.3的Linaro工具链编译此代码,以便在Cortex A9 ARM板上运行。我编译了带有和不带有以下标志的代码:
-O3 -mcpu=cortex-a9 -ftree-vectorize -mfloat-abi=hard -mfpu=neon
我还复制了用unsigned int、float和double类型的数组运行的代码,这些是以秒为单位的结果:
Array type unsigned short:
With NEON flags: 0.07s
Without NEON flags: 0.089s
Array type unsigned int:
With NEON flags: 0.524s
Without NEON flags: 0.529s
Array type float:
With NEON flags: 0.65s
Without NEON flags: 0.673s
Array type double:
With NEON flags: 0.955s
Without NEON flags: 0.927s
你可以看到,在大多数情况下,使用霓虹灯标志几乎没有任何改进,甚至在使用替身数组的情况下会导致更糟糕的结果。
我真的觉得我在这里做错了什么,也许你可以帮我解释这些结果。
我不得不用修复你的代码
#include <iostream>
#include <cstdlib>
之后,GCC 5.0自动向量化您的循环,如下所示:
.L7:
vld1.64 {d16-d17}, [r1:64]
adds r4, r4, #1
vadd.i16 q8, q8, q11
adc r5, r5, #0
cmp r3, r5
add r1, r1, #16
vmull.u16 q9, d16, d20
cmpeq r2, r4
vmull.u16 q8, d17, d21
add lr, lr, #16
vuzp.16 q9, q8
vshr.u16 q8, q8, #1
vstr d16, [lr, #-16]
vstr d17, [lr, #-8]
bhi .L7
所以,是的,编译器可以自动向量化代码,但这有什么好处吗?在我附近的Cortex-A7板上,我看到了以下时间:
g++ ~/foo.cpp -O3
./a.out
Time: 129.355 ms
g++ ~/foo.cpp -O3 -fno-tree-vectorize
./a.out
Time: 430.405 ms
它看起来像是您希望的4x矢量化因子(4x16位值)。
在这种情况下,我认为数据和生成的汇编代码不言自明,并驳斥了上面评论中的一些说法。编译器可以,也将执行自动向量化,您可以从中获得的性能是一个有意义的加速。
同样值得注意的是,编译器在评论中击败了一位专业的汇编程序员!
NEON不支持整数除法,因此没有什么可以矢量化。请尝试相乘。
一般情况下是这样,是的。但是,使用霓虹灯除以特定常数的有效序列是存在的,而"3"恰好是这些常数之一!
我的Linaro/UbuntuGCC 4.8.2系统编译器也对这些代码进行了矢量化,生成了与上面非常相似的代码,时间也相似。
我试图使用arm_neon.h内部函数重写这段代码,结果非常令人惊讶。。太多了,以至于我需要一些帮助来解读它们。
这是代码:
#include <ctime>
#include <stdio.h>
#include <cstdlib>
#include <arm_neon.h>
int main()
{
unsigned long long arraySize = 125000000;
std::clock_t begin;
unsigned short* arrayShort = new unsigned short[arraySize];
for (unsigned long long n = 0; n < arraySize; n++)
{
*arrayShort = rand() % 100 + 1;
arrayShort++;
}
arrayShort -= arraySize;
uint16x8_t vals;
uint16x8_t constant1 = {10, 10, 10, 10, 10, 10, 10, 10};
uint16x8_t constant2 = {3, 3, 3, 3, 3, 3, 3, 3};
begin = std::clock();
for (unsigned long long n = 0; n < arraySize; n+=8)
{
vals = vld1q_u16(arrayShort);
vals = vaddq_u16(vals, constant1);
vals = vmulq_u16(vals, constant2);
// std::cout << vals[0] << " " << vals[1] << " " << vals[2] << " " << vals[3] << " " << vals[4] << " " << vals[5] << " " << vals[6] << " " << vals[7] << std::endl;
arrayShort += 8;
}
std::cout << "Time: " << (std::clock() - begin) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;
arrayShort -= arraySize;
delete[] arrayShort;
return 0;
}
所以,现在我正在创建一个1.25亿元素长的无符号short数组。然后我一次遍历8个元素,加10,然后乘以3。
在cortex A9板上,该代码的纯C++版本需要270毫秒来处理该阵列,而该NEON代码只需要<strong]20毫秒>
现在,在看到结果之前,我的期望值并不高,但是,我脑海中最好的场景是减少8倍的时间。我无法解释这是如何导致执行时间减少13.5倍的。。我很乐意为您解释这些结果。
很明显,我已经看到了数学运算的结果输出,我可以保证代码是有效的,结果非常一致。
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 链表c++插入,所有情况都已检查,但没有任何工作
- 为什么瓦尔格林德在不释放恶意内存后没有报告任何问题?
- 代码编译没有任何输出,入门程序
- 对减去uint16_t值几乎没有困惑
- 可能我知道为什么这段代码没有给出任何输出吗?
- 如果用户没有输入任何内容或输入错误,如何重新输入用户的输入?
- write() 和 read() 中几乎没有混淆
- 为什么下面的Hello World程序在PowerShell上没有显示任何输出?同一程序在CMD上显示正确的输出
- 编译时检查以确保结构中的任何位置都没有填充
- 从旧的 C 样式指针移动到C++智能指针,代码几乎没有变化
- OpenMP:嵌套的 for 循环,执行时间几乎没有任何差异
- 编译带有活动霓虹灯标志的代码几乎没有任何改进(甚至恶化)
- 用于人脸识别的特征面算法.如何识别面部是否在任何类别中没有锥体
- 为什么我遇到此代码的分段错误?此代码几乎没有运行,因此构造函数或复制构造函数中可能存在错误
- 在C++中使用 I/O 文件几乎没有问题(战略客户没有帮助)
- 使用多个线程时性能几乎没有提高
- 类的类设计几乎没有什么不同
- 对于编译时的1.constructor和2.array定义,几乎没有什么疑问
- 指向对象的指针没有任何值(或者没有得到值)