【问题标题】:Eigen SimplicialLLT gives incorrect results when called from multiple threads从多个线程调用时,Eigen SimplicialLLT 给出不正确的结果
【发布时间】:2021-12-12 04:52:41
【问题描述】:

我正在尝试使用 SimplicialLLT 来计算 SparseMatrix。由于我的程序在循环中运行并且每个稀疏矩阵都不同,我试图并行化对 SimplicialLLT 的调用,如下所示。这不是确切的运行代码。我试图复制进行调用的部分。

#include <iostream>
#include <cstdlib>
#include "Eigen/Core"
#include "Eigen/LU"
#include "Eigen/Sparse"
#include "Eigen/StdVector"
#include <thread>
#include <mutex>

#define NROW 4

void subProg1(int ii, int nodes);
using namespace std;


int main()
{
  int imax = 4;
  int ii, nodes;

  std::thread threadpointer[4];

  nodes = 20000;

  for (ii=0;ii<imax;ii++) {
    threadpointer[ii] = std::thread(subProg1,ii,nodes);
    //threadpointer[ii].join();
  }

  for (ii=0;ii<imax;ii++) {
    threadpointer[ii].join();
  }
    

}

void subProg1(int IROW, int nodes)
{
  static vector<SparseMatrix<double>> Kmat(NROW, SparseMatrix<double> (nodes*3,nodes*3));
  static vector<SimplicialLLT<SparseMatrix<double>>> Kmat_LLT(Kmat.size());
  
  //Asign Kmat values here
  //Kmat[IROW] = ....
  
  //Invert Kmat using SimplicialLLT
  cout<< " Before Kmat_LLT IROW :" <<IROW<<endl;
  Kmat_LLT[IROW].compute(Kmat[IROW]);
  cout<< " After Kmat_LLT IROW:" <<IROW<<endl;

}

在调用 subProg1 后立即加入线程时得到的结果与在对 subProg1 的所有调用之后将线程加入单独的循环中时不同。我试图确定为什么会这样。 cout 语句显示如下

当线程在调用后立即加入时(本质上这是在没有多线程的情况下运行)

before Kmat_LLT IROW: 0
after Kmat_LLT IROW: 0
before Kmat_LLT IROW: 1
after Kmat_LLT IROW: 1
before Kmat_LLT IROW: 2
after Kmat_LLT IROW: 2
before Kmat_LLT IROW: 3
after Kmat_LLT IROW: 3

当 subProg1 作为多线程的一部分被调用并在所有调用完成后加入时

before Kmat_LLT IROW : 3
before Kmat_LLT IROW: 0
before Kmat_LLT IROW: 1
before Kmat_LLT IROW: 2
after Kmat_LLT IROW : 2
after Kmat_LLT IROW: 0
after Kmat_LLT IROW: 1
after Kmat_LLT IROW: 3

我不确定为什么 Kmat_LLT[IROW] 的结果在两种方法之间不同。想知道 subProg1 中 Kmat_LLT 的声明是否有问题。任何帮助表示赞赏。

【问题讨论】:

  • 想知道是否有人对这里可能出现的问题有任何想法。不确定是否应该以不同的方式声明 simplicialLLT
  • 如果有人能帮我解决这个请求,我将不胜感激。
  • 你用的是什么编译器?您对结果使用静态变量。这些在函数第一次运行时被初始化。那是在其中一个线程内。根据编译器的不同,这可能对竞争条件不安全。
  • @Homer512 我正在使用 VS2017。虽然我使用静态数组来存储结果,但它们基于 IROW,对于 subProg1 的每个调用(线程)都是唯一的。我的理解是这不会导致竞争条件。

标签: c++ multithreading eigen


【解决方案1】:

您的问题实际上可能与计算无关。它与 std::cout 一起使用。 C++ 流仅在一次调用中是线程安全的。您的所有线程都尝试同时写入 cout 并开始相互绊倒,可能会完成彼此的行。有两种简单的解决方案:

  1. 将应出现在屏幕上的所有内容(即整行)放在一个 printf 中。
  2. 首先将所有内容放入 std::ostringstream,然后在单个操作中将 ostringstream 写入 cout。

在 C++20 中,您可以改用同步输出流。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0053r0.html

【讨论】:

    【解决方案2】:

    我在这里没有看到任何问题。如果您同时调用 subProg1,则没有理由相信这些打印语句应该按顺序出现。

    您的意思是 Cholesky 分解的内容会因您如何称呼它而有所不同?这可能是一个问题,但也可能是置换矩阵的问题——这个函数将输入分解为可能置换的行和列——你可以specify that the original ordering is required,这将解决这个问题。有很多原因导致它最终会给出不同的置换矩阵,具体取决于算法的具体情况,但只要您考虑到这一点,这完全不重要。

    【讨论】:

    • 是的。根据如何调用 SImplicialLLT,Cholesky 分解的内容是不同的。我尝试按照您的建议添加 NaturalOrdering。然而,它使计算需要非常长的时间,以至于在不指定排序方案的情况下单线程调用要快得多。
    • 另外,如果我对计算 Kmat_LLT[IROW].compute(Kmat[IROW]); 应用互斥锁,那么结果是一致的。
    • 您的输入矩阵是否可能共享内存?简单的检查方法是在调用分解之前制作每个的(深度)副本。由于使用互斥体时结果是一致的,我不认为这会有所帮助,但这是一个很好的健全性检查。
    猜你喜欢
    • 2015-05-09
    • 2013-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 2017-12-14
    • 2019-03-12
    • 1970-01-01
    相关资源
    最近更新 更多