C语言中的MPI数据广播与否

计算科学 并行计算 C mpi
2021-12-07 21:02:13

我有两个略有不同但得到相同结果的 MPI 代码。

第一个来自一个开源包,其中包含几个数据交换步骤:

int main ( int argc, char **argv )
{
    int i,j,nx=600,nz=300,NP, MYID;
    int idum[2];
    float v[420][720];

    for (i=0;i<420;i++){
      for (j=0;j<720;j++){
        if(i<161) { v[i][j] = 2800.0; }
        else { v[i][j] = 5200.0; }
      }
    }

    MPI_Init ( &argc, &argv );
    MPI_Comm_size ( MPI_COMM_WORLD, &NP );
    MPI_Comm_rank ( MPI_COMM_WORLD, &MYID );

    if(MYID==0){
        idum[0] = nx;
        idum[1] = nz;
    }
    MPI_Barrier(MPI_COMM_WORLD);
    MPI_Bcast(&idum,5,MPI_INT,0,MPI_COMM_WORLD);
    MPI_Bcast(&v,420*720,MPI_FLOAT,0,MPI_COMM_WORLD);
    MPI_Barrier(MPI_COMM_WORLD);

    nx = idum[0];
    nz = idum[1];

    for (i=0;i<5;i++){
      printf("id=%d,v[%d][350]=%f,\n",MYID,i*100+19,v[i*100+19][350]);
    }
    printf("nx=%d,nz=%d\n",nx,nz);
    MPI_Finalize();
    exit(0);
}

我使用内核运行mpirun代码4结果是:

id=0,v[19][350]=2800.000000,
id=0,v[119][350]=2800.000000,
id=0,v[219][350]=5200.000000,
id=0,v[319][350]=5200.000000,
id=0,v[419][350]=5200.000000,
nx=600,nz=300
id=0,v[19][350]=2800.000000,
id=0,v[119][350]=2800.000000,
id=0,v[219][350]=5200.000000,
id=0,v[319][350]=5200.000000,
id=0,v[419][350]=5200.000000,
nx=600,nz=300
id=0,v[19][350]=2800.000000,
id=0,v[119][350]=2800.000000,
id=0,v[219][350]=5200.000000,
id=0,v[319][350]=5200.000000,
id=0,v[419][350]=5200.000000,
nx=600,nz=300
id=0,v[19][350]=2800.000000,
id=0,v[119][350]=2800.000000,
id=0,v[219][350]=5200.000000,
id=0,v[319][350]=5200.000000,
id=0,v[419][350]=5200.000000,
nx=600,nz=300

但是我觉得数据交换部分有点“冗余?”,所以我把上面的代码简化为:

int main ( int argc, char **argv )
{
    int i,j,nx=600, nz=300, NP=0, MYID;
    float v[420][720];

    for (i=0;i<420;i++){
      for (j=0;j<720;j++){
        if(i<161) { v[i][j] = 2800.0; }
        else { v[i][j] = 5200.0; }
      }
    }

    MPI_Init ( &argc, &argv );
    MPI_Comm_size ( MPI_COMM_WORLD, &NP );
    MPI_Comm_rank ( MPI_COMM_WORLD, &MYID );

    for (i=0;i<5;i++){
      printf("id=%d,v[%d][350]=%f,\n",MYID,i*100+19,v[i*100+19][350]);
    }

    printf("nx=%d,nz=%d\n",nx,nz);
    MPI_Finalize();
    exit(0);
}

我得到与第一个代码相同的结果。

这两个代码哪一个是正确的?如果两者都正确,哪个更好?为什么我们需要在第一个代码中包含数据交换行,或者不必?

2个回答

根据您的代码,我认为您要问的问题是:

A. 最好让每个节点做多余的工作

B. 最好让一个节点完成工作并分配结果。

答案通常是 A

要了解原因,请考虑每个程序员都应该知道的这些延迟数字:

每个程序员都应该知道的延迟数字]

请注意,CPU 操作基本上不需要时间(~1ns),并且在 1KB 的本地内存上执行相当复杂的操作需要 3 微秒。另一方面,通过网络发送 1KB 的数据需要 10 微秒。因此,通过在本地执行操作,您的移动速度(至少)快了三倍。

但它甚至比这更好。在并行计算中,我们将“关键路径”称为我们必须执行的最长的一系列串行操作。在我们计算然后广播的程序中,总时间是这两个操作的总和。在此示例中,为 13 微秒。

通信还引入了不可预测性:消息传输时间可以非线性方式取决于消息长度:

MPI 数据包大小与带宽

通信带宽也是通信进程数量的函数:

MPI 带宽与任务数量

所以问题是:你想把你的计算速度委托给一个时序属性未知的慢速网络吗?或者你想在本地进行冗余计算,你知道它会很快吗?

如果你比较网络和内存带宽的趋势,避免通信的推动力只会增加:

网络与内存带宽

这就是为什么避免通信的算法是一个热门话题的原因。这意味着不仅要避免网络节点之间的通信,还要避免 RAM 和 CPU 缓存之间的通信,甚至是不同级别的 CPU 缓存之间的通信。计算机各个部分的速度彼此呈指数级差异,因此尽可能避免系统的慢速部分变得越来越重要。

有几件事:

  • 之前开始计算肯定有点奇怪MPI_INITv现在,在初始化 MPI 执行环境之前执行数组的填充。至少根据 MPICH,应该尽量少做 beforeMPI_INIT和 afterMPI_FINILIZE,因为 MPI 标准并没有具体说明什么是实际允许的。
  • 我会假设,代码应该:
    1. 初始化 MPI
    2. v填充主节点上的数组( ID=0)
    3. 向通信器 ( )中的其他节点广播有关 theidumvfrom的信息。ID=0MPI_COMM_WORLD
    4. 在所有节点上打印出数组的一些元素。

现在很多事情都出错了(如果至少我做出的一些假设是正确的)

  • 我怀疑这段代码实际上是使用 MPI 启动(正确)的,因为根据您的输出,每个处理器都会输出他的ID=0.
  • 如果数组vidum应该在所有处理器上填充(不仅是主处理器),那么这些广播当然是无用的。但是 MPI 并行化是没有用的。
  • 您的代码的“固定版本”实际上根本没有使用 MPI。

我的建议:

  1. 修复 MPI 的初始化并将其移动到尽可能靠近main函数顶部的位置
  2. 确定,您要在哪里填充数组v:在所有节点上或仅在主节点上
  3. 在此基础上,必要时决定广播。
  4. 确保您使用正确的 MPI 命令运行它id,并且输出中的 s 实际上并非全为零。