C++ 基于Eigen和OpenMP的线程安全并行稀疏矩阵乘法

C++ 基于Eigen和OpenMP的线程安全并行稀疏矩阵乘法,c++,openmp,sparse-matrix,eigen,rcpp,C++,Openmp,Sparse Matrix,Eigen,Rcpp,根据,Eigen支持多线程密集v。密集,或行主稀疏v。密集的,但不是稀疏的。稀疏矩阵乘法。在我的应用程序中,我有两个稀疏矩阵,我想将它们并行相乘,即获得C=A*B,其中C和B是主要列 [编辑:针对我的应用程序,我还预先知道所有矩阵的稀疏模式,并且我需要多次执行此类矩阵乘法(每次使用更新的A和B更新C的非零值)。这让我想到,在这种情况下,应该有一种线程安全的方法来执行此操作,因为C的内存分配是固定的[end edit] 假设Eigen对列主要稀疏矩阵的列块进行快速读/写,我可以通过将B子集为列块来

根据,Eigen支持多线程密集v。密集,或行主稀疏v。密集的,但不是稀疏的。稀疏矩阵乘法。在我的应用程序中,我有两个稀疏矩阵,我想将它们并行相乘,即获得
C=A*B
,其中
C
B
是主要列

[编辑:针对我的应用程序,我还预先知道所有矩阵的稀疏模式,并且我需要多次执行此类矩阵乘法(每次使用更新的
A
B
更新
C
的非零值)。这让我想到,在这种情况下,应该有一种线程安全的方法来执行此操作,因为
C
的内存分配是固定的[end edit]

假设Eigen对列主要稀疏矩阵的列块进行快速读/写,我可以通过将
B
子集为列块来执行操作(假设
A
B
C
都已初始化且大小正确):

在R

  library(Matrix)

  set.seed(1)

  n <- 5000 # choose size
  A <- B <- matrix(0, ncol=n, nrow=n)

  nnz <- 250
  A_rows <- sample(1:n, nnz, replace=F)
  A_cols <- sample(1:n, nnz, replace=F)
  B_rows <- sample(1:n, nnz, replace=F)
  B_cols <- sample(1:n, nnz, replace=F)

  A[A_rows, A_cols] <- apply(A[A_rows, A_cols], 1:2, function(x) runif(1))
  B[B_rows, B_cols] <- apply(B[B_rows, B_cols], 1:2, function(x) runif(1))

  A <- as(A, "dgRMatrix")
  B <- as(B, "dgCMatrix")

  C <- spmat_mult(A, B)

库(矩阵)
种子(1)

作为一个调试原则,对于这样的代码,我更倾向于首先将它与R隔离开来。因此,我将编写一个简单的
main()
来做您想做的事情。如果这是可行的,那么可能是R妨碍了,可能是因为RcppEigen构造函数重新使用R内存,这是您通常需要避免的——请参阅RcppParallel vignettes了解更多关于这一点的信息。嗨,Dirk,这个问题出现在C中,我编写这个函数(似乎有效)只是为了发布这个问题。不幸的是,我无法在一个简单的例子中重现这个问题。在我的应用程序中,我只使用上面的代码编写矩阵,删除
#pragma omp parallel
完全解决了这个问题。您是否验证了Eigen允许并行更新相同的稀疏矩阵(即使列不同)?简言之,问题是,写入
SparseMatrix
不是线程安全的,因为它可能涉及全局内存分配和副本。@mkln好的,如果您的稀疏模式是预先设置的,并且保持不变,那么期望对不同列/元素的并发访问是安全的(仍然,请仔细检查)。这是问题中值得一提的一点:)
//[[Rcpp::export]]
Rcpp::List spmat_mult(const Eigen::SparseMatrix<double, Eigen::RowMajor>& A,
                      const Eigen::SparseMatrix<double>& B){

  int nchunks = 7;
  int chunksize = B.cols() / nchunks;

  std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
  Eigen::SparseMatrix<double> Cs(A.rows(), B.cols());
  Cs = A * B;

  std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
  clog << "Standard "
       << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
       << "us.\n";

  Eigen::SparseMatrix<double> C(A.rows(), B.cols());
  C = Cs;

  start = std::chrono::steady_clock::now();
#pragma omp parallel for
  for(int i=0; i<nchunks; i++){
    if(i == nchunks-1){
      C.rightCols(B.cols() - (nchunks-1)*chunksize) = A * B.rightCols(B.cols() - (nchunks-1)*chunksize);
    } else {
      C.middleCols(i*chunksize, chunksize) = A * B.middleCols(i*chunksize, chunksize);
    }
  }

  end = std::chrono::steady_clock::now();
  clog << "OMP "
       << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
       << "us.\n";

  return Rcpp::List::create(
    Rcpp::Named("Cs") = Cs,
    Rcpp::Named("C") = C
  );
}
  library(Matrix)

  set.seed(1)

  n <- 5000 # choose size
  A <- B <- matrix(0, ncol=n, nrow=n)

  nnz <- 250
  A_rows <- sample(1:n, nnz, replace=F)
  A_cols <- sample(1:n, nnz, replace=F)
  B_rows <- sample(1:n, nnz, replace=F)
  B_cols <- sample(1:n, nnz, replace=F)

  A[A_rows, A_cols] <- apply(A[A_rows, A_cols], 1:2, function(x) runif(1))
  B[B_rows, B_cols] <- apply(B[B_rows, B_cols], 1:2, function(x) runif(1))

  A <- as(A, "dgRMatrix")
  B <- as(B, "dgCMatrix")

  C <- spmat_mult(A, B)