使用犰狳,稀疏的x密集矩阵意外地变慢

时间:2018-04-11 09:13:38

标签: c++ r sparse-matrix rcpp armadillo

这是我刚遇到的事情。出于某种原因,在犰狳中乘以密集的稀疏矩阵要比稀疏和密集矩阵的乘法慢得多(即,颠倒顺序)。

RcppArmadillo.h

我正在使用RcppArmadillo软件包将Arma与R接口; armadillo包括set.seed(98765) # 10000 x 10000 sparse matrices, 99% sparse a <- rsparsematrix(1e4, 1e4, 0.01, rand.x=function(n) rpois(n, 1) + 1) b <- rsparsematrix(1e4, 1e4, 0.01, rand.x=function(n) rpois(n, 1) + 1) # dense copies a_den <- as.matrix(a) b_den <- as.matrix(b) system.time(mult_sp_den_to_sp(a, b_den)) # user system elapsed # 508.66 0.79 509.95 system.time(mult_den_sp_to_sp(a_den, b)) # user system elapsed # 13.52 0.74 14.29 。这是R中的一些时间,在几个相当大的垫子上:

// [[Rcpp::export]]
arma::sp_mat mult_sp_den_to_sp2(arma::sp_mat& a, arma::mat& b)
{
    // sparse x dense -> sparse
    // copy dense to sparse, then multiply
    arma::sp_mat temp(b);
    arma::sp_mat result(a * temp);
    return result;
}

所以第一次乘法比第二次乘以大约35倍(所有时间都以秒为单位)。

有趣的是,如果我只是制作密集矩阵的临时稀疏副本,性能会大大提高:

system.time(mult_sp_den_to_sp2(a, b_den))
#   user  system elapsed 
#   5.45    0.41    5.86 
this.apiSerivce.userlist({'offset':'0','limit':'10'})
    .subscribe( resultObj => {
    console.log(resultObj)
    this.userlist = resultObj.response
    //or
    //this.userlist = resultObj['response']
    this.status = resultObj.status
})

这是预期的行为吗?我知道,对于稀疏矩阵,您执行操作的确切方式会对代码的效率产生很大影响,远远超过密集。然而,35倍的速度差异似乎相当大。

2 个答案:

答案 0 :(得分:2)

稀疏和密集矩阵以非常不同的方式存储。 Armadillo使用CMS(列主存储)用于密集矩阵,而CSC(压缩稀疏列)用于稀疏矩阵。来自Armadillo的文档:

  

<强>垫
  的
  的 cx_mat
  密集矩阵的类,其中元素以列主要顺序存储(即逐列)

     

<强> SpMat
  sp_mat
  sp_cx_mat
  稀疏矩阵的类,其中元素以压缩稀疏列(CSC)格式存储

我们必须首先了解每种格式需要多少存储空间:

给定数量element_size(单精度为4个字节,双精度为8个字节),index_size(如果使用32位整数则为4个字节,如果使用64位整数则为8个字节) ,num_rows(矩阵的行数),num_cols(矩阵的列数)和num_nnz(矩阵的非零元素数),以下公式给我们每种格式的存储空间:

storage_cms = num_rows * num_cols * element_size
storage_csc = num_nnz * element_size + num_nnz * index_size + num_cols * index_size

有关存储格式的详细信息,请参阅wikipedianetlib

假设双精度和32位indeces,在你的情况下意味着:

storage_cms = 800MB
storage_csc = 12.04MB

因此,当您将稀疏x密集(或密集x稀疏)矩阵相乘时,您正在访问~812MB的内存,而在乘以稀疏x稀疏矩阵时,您只能访问~24MB的内存。

请注意,这不包括你编写结果的内存,这可能是一个很重要的部分(在这两种情况下都高达~800MB),但我对Armadillo不是很熟悉,以及它用于矩阵的算法乘法,所以不能确切地说它如何存储中间结果。

无论算法是什么,它肯定需要多次访问两个输入矩阵,这解释了为什么将密集矩阵转换为稀疏(只需要访问800MB密集矩阵),然后执行稀疏x稀疏产品(这需要多次访问24MB内存)比密集x稀疏和稀疏x密集产品更有效。

这里还有各种各样的缓存效果,这需要知道算法的精确实现和硬件(以及大量时间)才能正确解释,但上面是一般的想法。

至于为什么dense x sparsesparse x dense更快,这是因为稀疏矩阵的CSC存储格式。如scipy's documentation中所述,CSC格式对列切片有效,对行切片有效。 dense x sparse乘法算法需要对稀疏矩阵进行列切片,sparse x dense需要对稀疏矩阵进行行切片。请注意,如果armadillo使用CSR而不是CSC,则sparse x dense会有效,而dense x sparse则不会。

我知道这并不是你所看到的所有性能影响的完整答案,但应该让你大致了解正在发生的事情。正确的分析需要花费更多的时间和精力,并且必须包括算法的具体实现,以及有关运行它的硬件的信息。

答案 1 :(得分:2)

这应该在即将到来的Armadillo 8.500中修复,这将包含在RcppArmadillo 0.8.5 Real Soon Now中。具体做法是:

  • 稀疏矩阵转置更快
  • (sparse x dense)重新实现为((dense^T) x (sparse^T))^T,利用相对快速的(dense x sparse)代码

当我测试它时,所花费的时间从约500秒减少到约18秒,这与其他时间相当。