实现一个点乘函数,实现两个向量的对应元素相乘最后相加的功能。
c语言代码(逐个元素进行计算)
float dot(const float *src01,const float *src02,const int len){
float sum=0;
for(int i=0;i<len;i++){
sum+=src01[i]*src02[i];
}
}
C语言代码(针对循环展开)
float dot_(const float *src01,const float *src02,const int len){
float sum=0;
int l=len-len%4;
for(int i=0;i<l;i+=4){
sum+=src01[i]*src02[i];
sum+=src01[i+1]*src02[i+1];
sum+=src01[i+2]*src02[i+2];
sum+=src01[i+3]*src02[i+3];
}
for(int i=l;l<len;i++){
sum+=src01[i]*src02[i];
}
return sum;
}
使用neon intrinsic函数
float dot__(const float *src01,const float *src02,const int len){
float sum;
float32x4_t sum_vec=vdupq_n_f32(0); //dup表示复制,即将0复制给4个float
float32x4_t l_vec,r_vec;
int l=len-len%4; //满足整4个float的一起并行计算,比满足的需要逐个计算
for(int i=0;i<l;i+=4){
l_vec=vld1q_f32(src01+i);
r_vec=vld1q_f32(src02+i);
sum_vec=vmlaq_f32(sum_vec,l_vec,r_vec); //后两个向量对应元素相乘后与第一个向量对应元素相加
}
float32x2_t r=vadd_f32(vget_high_f32(sum_vec),vget_low_f32(sum_vec)); //取sum_vec高64位和低64位相加。vadd_f32是进行64位的加法,vaddq_f32才是进行128位的加法
sum+=vget_lane_f32(vpadd_f32(r,r),0); //vpadd_f32是堆某个向量内的元素逐个相加,这里参数都是r,所以是对r寄存器内的两个float相加。vget_lane_f32用来获取指定索引的元素值
for(int i=l;i<len;i++){
sum+=src01[i]*src02[i];
}
return sum;
}
三种方法时间对比,在arm64v8-a架构上,点乘向量均是2<<20个float数据。
总结:
vaddq_f32:实现128位,即包含4个float数据的寄存器之间的对应元素加法操作;
vadd_f32:实现64位,即包含2个float数据的寄存器之间的对应元素加法操作;
vpadd_f32:实现64位的,即包含2个float数据的寄存器的逐个元素相加;
vmlaq_f32:实现128位的,即包含4个float数据的寄存器之间的对应元素的相乘,然后与第三个寄存器相加。mla=mul+add,方便记忆。
vget_high_f32:获取寄存器的高64位包含的两个float数据,vget_low_f32同理;
vget_lane_f32:获取寄存器指定索引的元素。