稀疏x密集矩阵乘法性能低效

时间:2016-09-17 12:55:05

标签: c++ eigen

上下文:我使用的是人工神经网络的特征,其中典型的维度是每层大约1000个节点。因此,大多数操作是将大小为〜(1000,1000)的矩阵M与大小为1000的向量或一批B向量相乘,表示为大小为Bx1000的矩阵

在训练神经网络后,我正在使用修剪 - 这是一种常见的压缩技术,最终得到稀疏矩阵(非空参数密度在10%到50%之间)。

目标:我想使用稀疏矩阵进行压缩,其次是性能优化但不是主要目标

问题: 我正在比较不同批量大小的稀疏和密集矩阵乘法(仅计算乘法时间)的性能,我观察以下(使用Eigen 3.2.8,MacBook Pro 64位,没有open_mp,并使用标准g ++):

  • 当B =​​ 1(矩阵x向量)时 - 密度为10%或30%的稀疏矩阵运算比密集矩阵运算更有效 - 这似乎是预期的结果:执行的操作少得多
  • 表示B = 32:
    • 密集矩阵运算所需的时间只是B = 1所需时间的~10倍 - 这很酷 - 它是否显示出一些矢量化效应?
    • 稀疏矩阵运算所需的时间是 67 乘以B = 1所需的时间 - 这意味着它比独立处理32个向量的效率低

MxN multiplication time (ms) for M sparse/dense, and N of size 1000xB

Same numbers but showing the time per vector in a batch of different size for sparse and dense matrix. We see clearly the decrease of time for dense matrix when batch size increase, and the augmentation for sparse matrix showing some wrong. Normalized with time for B=1

代码: 我对稀疏和密集矩阵使用以下类型:

typedef SparseMatrix<float> spMatFloat;
typedef Matrix<float, Dynamic, Dynamic, RowMajor> deMatRowFloat;

我正在进行基准测试的操作如下:

o.noalias()=m*in.transpose();

其中o是密集矩阵(1000xB),m是密集矩阵(1000x1000)或使用m.sparseView()获得的相应稀疏矩阵,in是密集矩阵(Bx1000)

完整代码如下(20个不同随机矩阵的平均时间,并且每次乘法运行50次) - B = 32且B = 1的时间低于。

欢迎任何反馈/直觉!

batch   1   ratio   0.3 dense   0.32    sparse  0.29
batch   32  ratio   0.3 dense   2.75    sparse  15.01
#include <Eigen/Sparse>
#include <Eigen/Dense>
#include <stdlib.h>
#include <boost/timer/timer.hpp>

using namespace Eigen;
using namespace boost::timer;

typedef SparseMatrix<float> spMatFloat;
typedef Matrix<float, Dynamic, Dynamic, RowMajor> deMatRowFloat;

void bench_Sparse(const spMatFloat &m, const deMatRowFloat &in, deMatRowFloat &o) {
  o.noalias()=m*in.transpose();
}

void bench_Dense(const deMatRowFloat &m, const deMatRowFloat &in, deMatRowFloat &o) {
  o.noalias()=m*in.transpose();
}

int main(int argc, const char **argv) {
  float ratio=0.3;
  int iter=20;
  int batch=32;
  float t_dense=0;
  float t_sparse=0;

  deMatRowFloat d_o1(batch,1000);
  deMatRowFloat d_o2(batch,1000);
  for(int k=0; k<iter; k++) {
    deMatRowFloat d_m=deMatRowFloat::Zero(1000,1000);
    deMatRowFloat d_b=deMatRowFloat::Random(batch,1000);
    for(int h=0;h<ratio*1000000;h++) {
      int i=rand()%1000;
      int j=rand()%1000;
      d_m(i,j)=(rand()%1000)/500.-1;
    }
    spMatFloat s_m=d_m.sparseView();
    {
      cpu_timer timer;
      for(int k=0;k<50;k++) bench_Dense(d_m,d_b,d_o1);
      cpu_times const elapsed_times(timer.elapsed());
      nanosecond_type const elapsed(elapsed_times.system+elapsed_times.user);
      t_dense+=elapsed/1000000.;
    }
    {
      cpu_timer timer;
      for(int k=0;k<50;k++) bench_Sparse(s_m,d_b,d_o2);
      cpu_times const elapsed_times(timer.elapsed());
      nanosecond_type const elapsed(elapsed_times.system+elapsed_times.user);
      t_sparse+=elapsed/1000000.;
    }
  }
  std::cout<<"batch\t"<<batch<<"\tratio\t"<<ratio<<"\tdense\t"<<t_dense/50/iter<<"\tsparse\t"<<t_sparse/50/iter<<std::endl;
}

ggael建议后的新结果:我尝试了不同的可能组合,并在更改MB RowMajor / ColMajor时发现了巨大的性能差异。

总结一下,我感兴趣的是M*B M是(1000,1000)和B是(1000,批处理):我有兴趣比较M sparse /的性能密集且批量生长时。

我测试了3种配置:

  • M密,B密集
  • M稀疏,B密集
  • M稀疏,B密集,但M * B的乘法是逐列手动完成的

结果如下 - 其中数字是每列的比率时间,B = 32 /时间,B = 1,矩阵M,密度为0.3:

here

最初报道的问题是更糟糕的情况(M ColMajor,B RowMajor)。对于(M RowMajor,B ColMajor),在B = 32和B = 1之间有5倍的加速,稀疏矩阵的性能几乎等于密集矩阵。

1 个答案:

答案 0 :(得分:2)

在Eigen中,对于密集代数,矩阵矢量和矩阵矩阵产品都经过高度优化,并充分利用了矢量化。正如您所观察到的,基质矩阵产品具有更高的效率。这是因为矩阵矩阵产品可以通过增加算术运算次数和内存访问次数之间的比率,以及利用内存缓存来进一步优化。

然后关于稀疏密集型产品,有两种策略:

  1. 一次处理密集的右侧一列,从而多次扫描稀疏矩阵。对于此策略,最好使用密集矩阵的列主存储(右侧和结果)。在Eigen 3.2中,可以通过手动扫描列来模拟此策略。
  2. 仅扫描稀疏矩阵一次,并处理密集右侧的行并生成最嵌套的循环。这是Eigen 3.2中的默认策略。在这种情况下,最好使用密集矩阵的行主存储(Matrix<float,Dynamic,32,RowMajor>)。
  3. 最后,在任何一种情况下,您都可以尝试使用稀疏矩阵的行主要和列主要存储,并确定稀疏矩阵的策略和存储顺序的哪种组合在您的情况下最有效。