向量化实现矩阵运算优化(一)

打印 上一主题 下一主题

主题 893|帖子 893|积分 2689

xsimd简介

  xsimd是C++的一个开源simd库,实现了对常见simd指令的封装,从而使得simd的操作更为简单。接下来先从两个简单的例子来入门xsimd。
  1. void average(const std::vector<double>& v1, const std::vector<double>& v2, std::vector<double>& v) {
  2.     int n = v.size();
  3.     int size = xsimd::batch<double, xsimd::avx>::size;
  4.     int loop = n - n % size;
  5.     for (int i = 0; i < loop; i += size) {
  6.         auto a = xsimd::batch<double>::load_unaligned(&v1[i]);
  7.         auto b = xsimd::batch<double>::load_unaligned(&v2[i]);
  8.         auto res = a + b;
  9.         res.store_unaligned(&v[i]);
  10.     }
  11.     for (int i = loop; i < n; ++i)
  12.         v[i] = v1[i] + v2[i];
  13. }
复制代码
  上述demo实现了两个向量相加的操作,由于每次都能从vector当中加载size个数据,因此对剩余的不能进行vectorize的数据进行了分别处理。比如说,有一百个数据,每次处理8个数据,到最后剩下4个数不能凑到8,所以用朴素的迭代方式进行求和。这个demo是非对齐内存的处理方式。
  1. using vector_type = std::vector<double, xsimd::default_allocator<double>>;
  2. std::vector<double> v1(1000000), v2(1000000), v(1000000);
  3. vector_type s1(1000000), s2(1000000), s(1000000);
  4. void average_aligned(const vector_type& s1, const vector_type& s2, vector_type& s) {
  5.     int n = s.size();
  6.     int size = xsimd::batch<double>::size;
  7.     int loop = n - n % size;
  8.     for (int i = 0; i < loop; i += size) {
  9.         auto a = xsimd::batch<double>::load_aligned(&s1[i]);
  10.         auto b = xsimd::batch<double>::load_aligned(&s2[i]);
  11.         auto res = a + b;
  12.         res.store_aligned(&s[i]);
  13.     }
  14.     for (int i = loop; i < n; ++i)
  15.         s[i] = s1[i] + s2[i];
  16. }
复制代码
  要实现对齐内存的操作方式,我们必须对vector指定特定的分配器,不然最后运行出来的代码会出现segment fault。
  总之,要记住常用的api,load_aligned, store_aligned, load_unaligned, store_unaligned,它们分别对应了内存对齐与否的处理方式。接下来我们再讲解另外一个demo,并且提供与openmp的性能对比。
  1. auto sum(const std::vector<double>&v) {
  2.     int n = v.size();
  3.     int size = xsimd::batch<int>::size;
  4.     int loop = n - n % size;
  5.     double res{};
  6.     for (int i = 0; i < loop; ++i) {
  7.         auto tmp = xsimd::batch<int>::load_unaligned(&v[i]);
  8.         res += xsimd::hadd(tmp);
  9.     }
  10.     for (int i = loop; i < n; ++i) {
  11.         res += v[i];
  12.     }
  13.     return res;
  14. }
  15. auto aligned_sum(const std::vector<double, xsimd::default_allocator<double>>& v) {
  16.     int n = v.size();
  17.     int size = xsimd::batch<int>::size;
  18.     int loop = n - n % size;
  19.     double res{};
  20.     for (int i = 0; i < loop; ++i) {
  21.         auto tmp = xsimd::batch<int>::load_aligned(&v[i]);
  22.         res += xsimd::hadd(tmp);
  23.     }
  24.     for (int i = loop; i < n; ++i) {
  25.         res += v[i];
  26.     }
  27.    
  28.     return res;
  29. }
复制代码
  这个例子实现了对向量求和的功能。总体与前面基本一样,这里hadd是一个对向量求和的函数。
  对于openmp的向量化实现,则较为简单,只需要在for循环上面加上特定指令即可。不过需要注意的是,openmp支持C语法,有一些C++的新特性可能并不支持,而且需要把花括号放到下一行,我们来看具体操作。
  1. auto parallel_sum(const std::vector<double>& v) {
  2.     double res{};
  3.     int n = v.size();
  4.     #pragma omp simd
  5.     for (int i = 0; i < n; ++i)
  6.         res += v[i];
  7.     return res;
  8. }
复制代码
  不要忘记加上编译选项-fopenmp和-march=native,为了性能测试,我开启了O2优化,以下是简单的测试结果,数据规模是一千万。

  一般情况下进行了内存对齐都会比没有对齐的要快一些,同时可以看到openmp与xsimd也差了一个量级。当然不同平台的结果可能会有差异,需要用更专业的工具进行测量比较。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表