1. 目标
对数组求和,对比ARM(neon)向量化优化(SIMD)和循环展开,还有 O0 O1优化的效率对比。
2. 测试代码
#include <arm_neon.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 1024000
int32_t neonSum(int32_t *arr,int n)
{
//保存计算元素向量
int32x4_t vec={0};
//声明保存中间计算结果向量
int32x4_t tempSum={0};
//声明标量保存最终计算结果变量
int32_t sum=0;
for(int i=0;i<n/4*4;i+=4)
{
//加载4个元素到向量寄存器
vec = vld1q_s32(&arr[i]);
//累加每轮元素之和
//或者使用接口 int32_t vaddvq_s32(int32x4_t a);
tempSum = vaddq_s32(tempSum,vec);
}
//处理结尾数据
for(int i=n/4*4;i<n;i++)
sum += arr[i];
//分别计算 01 23 元素的和,能保存2组求和结果 sum0 sum1 sum0 sum1
tempSum = vpaddq_s32(tempSum,tempSum);
//计算 01 元素的和
tempSum = vpaddq_s32(tempSum,tempSum);
//获取计算结果
sum += vgetq_lane_s32(tempSum,0);
return sum;
}
void main()
{
clock_t start,end;
int32_t arr[N] = {0};
int32_t sum=0;
//初始化
for(int i=0;i<N;i++)
arr[i] = (int32_t)(rand()%11);
start = clock();
#if 1 //neon向量化
sum = neonSum(arr,N);
#elif 0 //源码
for(int i=0;i<N;i++)
sum+=arr[i];
#else //循环展开
for(int i=0;i<N;i+=4)
{
sum+=arr[i];
sum+=arr[i+1];
sum+=arr[i+2];
sum+=arr[i+3];
}
#endif
end = clock();
// for(int i=0;i<N;i++)
// printf("%d\n",arr[i]);
printf("sum %d useSec %f\n",sum,(double)(end-start)/CLOCKS_PER_SEC);
}
3. 编译命令
-fopt-info-all :显示编译优化信息;
O1 O2 默认不开启向量化优化,需要参数指定;O3默认开启向量化优化。
gcc sum_simd.c -o sum_simd_0 -O0 -fopt-info-all
gcc sum_simd.c -o sum_simd_1 -O1 -ftree-vectorize -fopt-info-all
gcc sum_simd.c -o sum_simd_2 -O2 -ftree-vectorize -fopt-info-all
gcc sum_simd.c -o sum_simd_3 -O3 -fopt-info-all
测试脚本
#!/bin/bash
# 编译
dir=out
# 清空文件夹
> "$dir"
echo "start $(date)" >> out
# 要测试的编译优化等级
opt=(0 1 2 3)
# 要测试的函数 0:源码 1:循环展开 2:NEON Intrinsic 函数
func=(0 1 2)
for lev in "${opt[@]}"; do
for f in "${func[@]}"; do
for((i=0;i<4;i++)); do
yhrun -N1 -n1 -pthcp1 ./sum_simd_"$lev" "$f" | tee -a "$dir"
# echo "yhrun -N1 -n1 -pthcp1 ./sum_simd_$lev "$f" | tee -a "$dir"" >> "$dir"
done
done
done
echo "end $(date)" >> out
4. 测试数据
优化函数\优化参数 |
O0 |
O1 向量化 |
O2 向量化 |
O3 |
源码 |
0.004388 |
0.001618 |
0.001641 |
0.001591 |
循环展开 |
0.003876 |
0.001675 |
0.001610 |
0.001642 |
NEON Intrinsic SIMD |
0.002840 |
0.001667 |
0.001596 |
0.001593 |
5. 结果分析
(1) O0 优化:neon > 循环展开 > 源码。只有NEON Intrinsic函数在开启了向量化,所以效率最高;循环展开减少了for循环次数效率次之。neon比源码效率提升了 54% ;
(2) O1/O2/O3 编译器向量化优时,源码、循环展开、neon效率差不多;
(3) 向量化优化可以通过 NEON Intrinsic 函数实现,或者使用编译优化选项;但是复杂的循环结构,需要自己手动实现向量化优化代码。
备注:当前数据规模为1024000,预计数据规模的变化几乎没有影响。