使用 mpirun 执行我的程序会大大降低性能

Posted

技术标签:

【中文标题】使用 mpirun 执行我的程序会大大降低性能【英文标题】:Performance degrades a lot by using mpirun to execute my program 【发布时间】:2020-06-25 11:38:36 【问题描述】:

我是 MPI 领域的新手。我使用 Intel Math Kernel Library 编写程序,并且我想按块计算矩阵-矩阵乘法,这意味着我将大矩阵 X 沿列拆分为许多小矩阵,如下所示。我的矩阵很大,所以每次我只计算 (N, M) x (M, N) 我可以手动设置 M 的地方。

XX^T = X_1X_1^T + X_2X_2^T + ... + X_nX_n^T

我首先将总线程数设置为16,M等于1024。然后我直接运行我的程序如下。我检查了我的cpu状态,发现cpu使用率为1600%,这是正常的。

./MMNET_MPI --block 1024 --numThreads 16

但是,我尝试使用 MPI 运行我的程序,如下所示。然后我发现cpu使用率只有200-300%。奇怪的是,我把block number改成64,cpu使用率可以得到一点性能提升1200%。

mpirun -n 1 --bind-to none ./MMNET_MPI --block 1024 --numThreads 16

我不知道问题是什么。似乎mpirun 做了一些对我的程序有影响的默认设置。以下是我的矩阵乘法代码的一部分。命令#pragma omp parallel for 旨在从并行压缩格式中提取小 N x M 矩阵。之后我使用clubs_dgemv 来计算矩阵-矩阵乘法。

#include "MemoryUtils.h"
#include "Timer.h"
#include "omp.h"
#include <mpi.h>
#include <mkl.h>

#include <iostream>

using namespace std;

int main(int argc, char** argv) 
  omp_set_num_threads(16);
  Timer timer;
  double start_time = timer.get_time();

  MPI_Init(&argc, &argv);

  int total_process;
  int id;
  MPI_Comm_size(MPI_COMM_WORLD, &total_process);
  MPI_Comm_rank(MPI_COMM_WORLD, &id);

  if (id == 0) 
    cout << "========== Testing MPI properties for MMNET ==========" << endl;
  

  cout << "Initialize the random matrix ..." << endl;

  unsigned long N = 30000;
  unsigned long M = 500000;
  unsigned long snpsPerBlock = 1024;

  auto* matrix = ALIGN_ALLOCATE_DOUBLES(N*M);
  auto* vector = ALIGN_ALLOCATE_DOUBLES(N);
  auto* result = ALIGN_ALLOCATE_DOUBLES(M);
  auto *temp1 = ALIGN_ALLOCATE_DOUBLES(snpsPerBlock);
  memset(result, 0, sizeof(double) * M);

  cout << "Time for allocating is " << timer.update_time() << " sec" << endl;

  memset(matrix, 1.1234, sizeof(double) * N * M);
  memset(vector, 1.5678, sizeof(double) * N);
  // #pragma omp parallel for
  // for (unsigned long row = 0; row < N * M; row++) 
  //     matrix[row] = (double)rand() / RAND_MAX;
  // 

  // #pragma omp parallel for
  // for (unsigned long row = 0; row < N; row++) 
  //     vector[row] = (double)rand() / RAND_MAX;
  // 

  cout << "Time for generating data is " << timer.update_time() << " sec" << endl;

  cout << "Starting calculating..." << endl;

  for (unsigned long m0 = 0; m0 < M; m0 += snpsPerBlock) 
    uint64 snpsPerBLockCrop = std::min(M, m0 + snpsPerBlock) - m0;
    auto* snpBlock = matrix + m0 * N;

    MKL_INT row = N;
    MKL_INT col = snpsPerBLockCrop;
    double alpha = 1.0;
    MKL_INT lda = N;
    MKL_INT incx = 1;
    double beta = 0.0;
    MKL_INT incy = 1;
    cblas_dgemv(CblasColMajor, CblasTrans, row, col, alpha, snpBlock, lda, vector, incx, beta, temp1, incy);

    // compute XA
    double beta1 = 1.0;
    cblas_dgemv(CblasColMajor, CblasNoTrans, row, col, alpha, snpBlock, lda, temp1, incx, beta1, result, incy);
  

  cout << "Time for computation is " << timer.update_time() << " sec" << endl;
  ALIGN_FREE(matrix);
  ALIGN_FREE(vector);
  ALIGN_FREE(result);
  ALIGN_FREE(temp1);
  return 0;

我的cpu信息如下。

Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              44
On-line CPU(s) list: 0-43
Thread(s) per core:  1
Core(s) per socket:  22
Socket(s):           2
NUMA node(s):        2
Vendor ID:           GenuineIntel
CPU family:          6
Model:               85
Model name:          Intel(R) Xeon(R) Gold 6152 CPU @ 2.10GHz
Stepping:            4
CPU MHz:             1252.786
CPU max MHz:         2101.0000
CPU min MHz:         1000.0000
BogoMIPS:            4200.00
Virtualization:      VT-x
L1d cache:           32K
L1i cache:           32K
L2 cache:            1024K
L3 cache:            30976K
NUMA node0 CPU(s):   0-21
NUMA node1 CPU(s):   22-43

【问题讨论】:

您的代码中没有 MPI 运行mpirun ... perf stat ./MMNET_MPI 是否能说明任何问题? --bind-to none 应该和正常运行一样,但也许它实际上是在以某种方式设置 CPU 亲和力?也许在您的作业运行时运行taskset -p $(pidof MMNET_MPI) 以查询亲和掩码,看看mpirun 是否设置了除全1 以外的其他内容(ffff 或其他)。或者如果它启动了您的程序的多个副本?或者它可能通过 OpenMP 环境变量? (您使用的是什么操作系统?Linux?) 如果您提供有关哪个 MPI 实现(Open MPI?)、哪个版本、如何将 --numThreads 参数转换为 MKL 线程数、如何链接可执行文件的信息,将会有所帮助与 MKL,等等。 @ptb 这只是我程序的一小部分。我只用一个进程运行这个程序。所以我相信没关系。 @PeterCordes 我发现了一件有趣的事情。当我调用mkl_set_num_threads()时,程序可以正常充分利用cpu资源。 【参考方案1】:

默认情况下,MKL 实现了一些智能动态选择要使用的线程数。这由变量MKL_DYNAMIC 控制,默认设置为TRUE。 MKL 的文档说明:

如果您 [sic] 能够检测到 MPI 的存在,但无法确定它是否已在线程安全模式下被调用(使用 MPICH 1.2.x 无法检测到这一点,例如),并且 MKL_DYNAMIC 没有从其默认值 TRUE 更改,英特尔 MKL 将运行一个线程。

由于您调用 MPI_Init() 而不是 MPI_Init_thread() 来初始化 MPI,因此您实际上是在请求单线程 MPI 级别 (MPI_THREAD_SINGLE)。该库免费为您提供任何线程级别,并且保守地坚持MPI_THREAD_SINGLE。您可以在初始化后通过调用MPI_Query_thread(&amp;provided) 来检查,看看输出值是否大于MPI_THREAD_SINGLE

由于您将 OpenMP 和线程 MKL 与 MPI 混合使用,因此您确实应该通过调用 MPI_Init_thread() 来告诉 MPI 以更高的线程支持级别进行初始化:

int provided;

MPI_Init_thread(NULL, NULL, MPI_THREAD_MULTIPLE, &provided);
// This ensures that MPI actually provides MPI_THREAD_MULTIPLE
if (provided < MPI_THREAD_MULTIPLE) 
  // Complain

(从技术上讲,你需要MPI_THREAD_FUNNNELED,如果你不从主线程外部进行 MPI 调用,但这不是 MKL 理解的线程安全模式)

即使您向 MPI 请求特定的线程支持级别,也不能保证您会得到它,这就是您必须检查提供的级别的原因。此外,较旧的 Open MPI 版本必须显式构建并支持此类支持 - 默认情况下不支持 MPI_THREAD_MULTIPLE 构建,因为某些网络模块不是线程安全的。您可以通过运行 ompi_info 并查找与此类似的行来检查是否是这种情况:

Thread support: posix (MPI_THREAD_MULTIPLE: yes, OPAL support: yes, OMPI progress: no, ORTE progress: yes, Event lib: yes)

现在,现实情况是,即使 MPI 没有提供比MPI_THREAD_SINGLE 更高级别的线程支持,大多数不在主线程之外进行 MPI 调用的线程软件运行得非常好,即大多数 MPI 实现 MPI_THREAD_SINGLE相当于MPI_THREAD_FUNNELED。在这种情况下,将 MKL_DYNAMIC 设置为 FALSE 应该会使 MKL 的行为与在没有 mpirun 的情况下运行时一样:

mpirun -x MKL_DYNAMIC=FALSE ...

在任何情况下,由于您的程序接受线程数作为参数,因此只需同时调用mkl_set_num_threads()omp_set_num_threads(),不要依赖神奇的默认机制。

编辑:启用全线程支持会产生后果 - 延迟增加并且某些网络模块可能会拒绝工作,例如旧 Open MPI 版本中的 InfiniBand 模块,导致库悄悄地切换到较慢的传输,例如 TCP/IP。更好地请求 MPI_THREAD_FUNNELED 并明确设置 MKL 和 OpenMP 线程的数量。

【讨论】:

以上是关于使用 mpirun 执行我的程序会大大降低性能的主要内容,如果未能解决你的问题,请参考以下文章

Swift SpriteKit 从图像的纹理中制作物理体会大大降低我的应用程序的速度

添加隐藏层会大大降低神经网络的性能

结束 mpirun 进程会终止 bash 循环

添加缩略图如何大大降低我的网站速度?

多线程会降低 GPU 性能

未使用的 use 语句会降低性能吗?