Собственный, параллельный ConjugateGradient не удалось: больше потоков, больше затрат

Я хочу использовать параллельный ConjugateGradient в Eigen 3.3.7 (gitlab) для решения Ax=b, но он показал, что чем больше потоков, тем больше вычислительных затрат. Я тестирую код в этом вопрос и измените размер матрицы с 90000 на 9000000. Вот код (я называю файл test-cg-parallel.cpp):

    // Use RowMajor to make use of multi-threading
typedef SparseMatrix<double, RowMajor> SpMat;
typedef Triplet<double> T;

// Assemble sparse matrix from
// https://eigen.tuxfamily.org/dox/TutorialSparse_example_details.html
void insertCoefficient(int id, int i, int j, double w, vector<T>& coeffs,
                       VectorXd& b, const VectorXd& boundary)
{
  int n = int(boundary.size());
  int id1 = i+j*n;
  if(i==-1 || i==n) b(id) -= w * boundary(j); // constrained coefficient
  else  if(j==-1 || j==n) b(id) -= w * boundary(i); // constrained coefficient
  else  coeffs.push_back(T(id,id1,w));              // unknown coefficient
}

void buildProblem(vector<T>& coefficients, VectorXd& b, int n)
{
  b.setZero();
  ArrayXd boundary = ArrayXd::LinSpaced(n, 0,M_PI).sin().pow(2);
  for(int j=0; j<n; ++j)
    {
      for(int i=0; i<n; ++i)
        {
          int id = i+j*n;
          insertCoefficient(id, i-1,j, -1, coefficients, b, boundary);
          insertCoefficient(id, i+1,j, -1, coefficients, b, boundary);
          insertCoefficient(id, i,j-1, -1, coefficients, b, boundary);
          insertCoefficient(id, i,j+1, -1, coefficients, b, boundary);
          insertCoefficient(id, i,j,    4, coefficients, b, boundary);
        }
    }
}

int main()
{
  int n = 3000;  // size of the image
  int m = n*n;  // number of unknowns (=number of pixels)
  // Assembly:
  vector<T> coefficients;          // list of non-zeros coefficients
  VectorXd b(m);                   // the right hand side-vector resulting from the constraints
  buildProblem(coefficients, b, n);
  SpMat A(m,m);
  A.setFromTriplets(coefficients.begin(), coefficients.end());
  // Solving:
  // Use ConjugateGradient with Lower|Upper as the UpLo template parameter to make use of multi-threading
  clock_t time_start, time_end;
  time_start=clock();
  ConjugateGradient<SpMat, Lower|Upper> solver(A);
  VectorXd x = solver.solve(b);         // use the factorization to solve for the given right hand side

  time_end=clock();
   cout<<"time use:"<<1000*(time_end-time_start)/(double)CLOCKS_PER_SEC<<"ms"<<endl;
   return 0;
}

Я компилирую код с помощью gcc 7.4.0, процессора Intel Xeon E2186G с 6 ядрами (12 потоков), подробности компиляции и запуска следующие:

liu@liu-Precision-3630-Tower:~/test$ g++ test-cg-parallel.cpp -O3 -fopenmp -o cg
liu@liu-Precision-3630-Tower:~/test$ OMP_NUM_THREADS=1 ./cg

time use:747563ms

liu@liu-Precision-3630-Tower:~/test$ OMP_NUM_THREADS=4 ./cg

time use: 1.49821e+06ms

liu@liu-Precision-3630-Tower:~/test$ OMP_NUM_THREADS=8 ./cg

time use: 2.60207e+06ms

Кто-нибудь может дать мне несколько советов? Большое спасибо.


person Richard LIU    schedule 02.03.2020    source источник
comment
Ваш способ измерения времени неверен. clock здесь измеряется параллельное время, а не время настенных часов. Лучше использовать метод OpenMP omp_get_wtime или даже метод C++ std::chrono::steady_clock. Фактическое время настенных часов немного уменьшается с увеличением количества потоков. Однако обратите внимание, что в этом случае Eigen плохо масштабируется. Вместо этого вы можете использовать PLASMA. Это альтернатива LAPACK, специально разработанная для масштабирования (насколько это возможно) одной многоядерной машины.   -  person Jérôme Richard    schedule 02.03.2020
comment
@ Жером Ричард Спасибо за ответ. Я сделал по вашему предложению и обнаружил, что эффективность немного увеличивается с количеством потоков (от 1 до 6 потоков). И мне интересно, есть ли способ повысить эффективность многопоточности для Eigen. Я хочу решить линейные уравнения с разреженной матрицей, используя итерационные методы, применима ли для меня PLASMA?   -  person Richard LIU    schedule 03.03.2020
comment
Судя по всему, PLASMA этого не поддерживает. Вы можете проверить список библиотек, поддерживающих этот здесь. Я слышал, что PETSc может это сделать, но никогда этим не пользовался. Кроме того, насколько я знаю, PETSc использует MPI для внутреннего использования, поэтому его может быть труднее использовать (хотя, возможно, и быстрее). Вы можете начать с здесь и узнать, реализует ли PETSc именно то, что вы хочу.   -  person Jérôme Richard    schedule 06.03.2020