作业题目
![](https://i-blog.csdnimg.cn/blog_migrate/8ad286a43d553dd1bb0c8719d1fc677e.png#pic_center)
提交内容一:改写代码
原代码编译运行结果
![](https://i-blog.csdnimg.cn/blog_migrate/05159c4c449164e2e6ac391e015a2ed2.png#pic_center)
使用avx进行向量化
![](https://i-blog.csdnimg.cn/blog_migrate/f41782ac20f957a9bb74067df46b3d0f.png#pic_center)
bool Convolve1D_Ks5_F64_cpp_AVX(double *__restrict__ y, const double * __restrict__ x, const double* __restrict__ kernel, int64_t num_pts) {
constexpr int64_t kernel_size = 5;
constexpr int64_t ks2 = kernel_size / 2;
if (num_pts < kernel_size) {
return false;
}
for (int64_t i = ks2; i < num_pts - ks2; i += 4) {
__m256d k2 = _mm256_set1_pd(kernel[ks2 - 2]);
__m256d k1 = _mm256_set1_pd(kernel[ks2 - 1]);
__m256d k0 = _mm256_set1_pd(kernel[ks2]);
__m256d kn1 = _mm256_set1_pd(kernel[ks2 + 1]);
__m256d kn2 = _mm256_set1_pd(kernel[ks2 + 2]);
__m256d x2 = _mm256_loadu_pd(x + i + 2);
__m256d x1 = _mm256_loadu_pd(x + i + 1);
__m256d x0 = _mm256_loadu_pd(x + i);
__m256d xn1 = _mm256_loadu_pd(x + i - 1);
__m256d xn2 = _mm256_loadu_pd(x + i - 2);
__m256d y0 = _mm256_mul_pd(k2, x2);
__m256d y1 = _mm256_mul_pd(k1, x1);
__m256d y2 = _mm256_mul_pd(k0, x0);
__m256d y3 = _mm256_mul_pd(kn1, xn1);
__m256d y4 = _mm256_mul_pd(kn2, xn2);
__m256d result = _mm256_add_pd(_mm256_add_pd(_mm256_add_pd(y0, y1), _mm256_add_pd(y2, y3)), y4);
_mm256_storeu_pd(y + i, result);
}
return true;
}
使用avx2进行向量化
- avx2在avx的基础上进行了一些扩展,新增了FMA(融合乘法和加法)、gather(从非连续内存位置加载数据到一个寄存器中)、permute(在寄存器内重新排列数据)等新的方法
- 因此我在代码中尝试融入fma方法,试试能不能再次降低运行时间
- 编译运行时间
![](https://i-blog.csdnimg.cn/blog_migrate/47d2720e73d825a7c705af2c082feaa3.png#pic_center)
bool Convolve1D_Ks5_F64_cpp_AVX2(double *__restrict__ y, const double * __restrict__ x, const double* __restrict__ kernel, int64_t num_pts) {
constexpr int64_t kernel_size = 5;
constexpr int64_t ks2 = kernel_size / 2;
if (num_pts < kernel_size) {
return false;
}
for (int64_t i = ks2; i < num_pts - ks2; i += 4) {
__m256d k2 = _mm256_set1_pd(kernel[ks2 - 2]);
__m256d k1 = _mm256_set1_pd(kernel[ks2 - 1]);
__m256d k0 = _mm256_set1_pd(kernel[ks2]);
__m256d kn1 = _mm256_set1_pd(kernel[ks2 + 1]);
__m256d kn2 = _mm256_set1_pd(kernel[ks2 + 2]);
__m256d x2 = _mm256_loadu_pd(x + i + 2);
__m256d x1 = _mm256_loadu_pd(x + i + 1);
__m256d x0 = _mm256_loadu_pd(x + i);
__m256d xn1 = _mm256_loadu_pd(x + i - 1);
__m256d xn2 = _mm256_loadu_pd(x + i - 2);
__m256d result = _mm256_setzero_pd();
result = _mm256_fmadd_pd(k2, x2, result);
result = _mm256_fmadd_pd(k1, x1, result);
result = _mm256_fmadd_pd(k0, x0, result);
result = _mm256_fmadd_pd(kn1, xn1, result);
result = _mm256_fmadd_pd(kn2, xn2, result);
_mm256_storeu_pd(y + i, result);
}
return true;
}
运行时间对比
| 原代码 | AVX | AVX2 |
---|
timing | 13241us | 10648us | 11179us |
- 在同样使用-O2优化级别的前提下,使用avx向量化内嵌指令修改后的代码运行时间提升了20%左右
- 使用avx2中的fma特性融合加法和乘法后,运行时间反而略微上涨
提交内容二:编译运行代码
![](https://i-blog.csdnimg.cn/blog_migrate/f7e8ff9ad30da7454a4f2a2bffa0fb0b.png#pic_center)
![](https://i-blog.csdnimg.cn/blog_migrate/46326e33c99385e6ee4a62c25fc2c1f8.png#pic_center)
总结分析
- 使用向量化内嵌指令可以大幅度提升代码的运行速度
- 使用avx2中的fma融合加法和乘法操作不一定能提升代码运行速度,反而可能产生不好的影响。在本次作业的代码里,原来的各个乘法之间没有依赖关系,
__m256d y0
到__m256d y4
这五个乘法运算可能并行地运行。但是如果使用fma将乘法操作和最后的加法操作进行融合,代码就需要进行一次乘法并累加到result
后才能进行下一次乘法,这样反而浪费了时间
自评分及理由
自评分
评分理由
- 完成提交内容一:使用avx和avx2的fma特性对原代码进行优化、比较代码运行时间,并对产生的现象进行了分析(7分)
- 完成提交内容二:编译运行
gather.cpp
和permute.cpp
(3分)