并行计算debug

一个简单的并行计算程序出错引发的思考

问题背景

给定N × N矩阵A,N维向量B,设计并行算法计算矩阵与向量的乘积C。

基本思路

给定并行的程序个数numprocs,将矩阵按行分为numprocs块,然后由主进程将分块后的矩阵分配给其他各个进程,与此同时,将存储在主进程当中的向量B广播给其他所有进程。之后,由各个子进程将分块矩阵与向量相乘的结果发送回主进程,由主进程重新组装,然后输出。

基本环境

Linux下的Open MPI

Bug1 没有初始化的变量十分危险

程序的运行命令是

mpirun -np 100 MATRIX_VECTOR

np后面的参数代表设定的并行进程数。

一开始,我键入

mpirun -np 1 MATRIX_VECTOR

只设定了一个并行进程,实际上相当于按普通的串行算法计算矩阵与向量的乘积。
运行无错,结果正确。

之后,我将并行进程数设定为10出现错误。报错Segment Fault(11), 外加Invalid Permission(2)。

我的第一反应是数组越界,于是进行了长时间的检查,最终成功地修复了另一个Bug,不过被修复的这个Bug与数组越界无关。随后,我猜想可能是设定的数组过大,用来计算的并行机允许分配的内存不足。于是,我又将N设定为100,依然报错。最终,我将N设定为10,报错依旧,基本排除是内存分配不足的原因。

随后,根据经验会不会只是10这个数字比较特殊,使得程序中某些由变量运算生成的指标越界了呢?最终,在反复实验下,我发现对于一段连续的整数np都报错。即便np = 2,也会报错。况且,自己观察报错信息,发现对于所有的子进程,全部报Segment Fault错误。因此,很有可能Segment Fault并不出现在计算环节。

由于Open MPI提供的debug的工具学习成本高,接下来,我只好采用“探针方法”,在程序的不同位置插入printf,观察程序能否运行至此处,假如有输出,则出错在此处之后,如果无,则出错在此处之前。最终大约可以确定,程序出错在主进程发送分块矩阵阶段。

在Open MPI当中,发送数据和接收数据依赖于一对库函数MPI_Send与MPI_Recv。
它们的定义分别如下

#include <mpi.h>
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest,
    int tag, MPI_Comm comm)

https://www.open-mpi.org/doc/v1.6/man3/MPI_Send.3.php

以及

#include <mpi.h>
int MPI_Recv(void *buf, int count, MPI_Datatype datatype,
    int source, int tag, MPI_Comm comm, MPI_Status *status)

https://www.open-mpi.org/doc/v1.6/man3/MPI_Recv.3.php

其中,有许多和发送接受无关的参数,如comm,tag等,可以随意指定。但是,看似无关紧要的参数status却是问题的所在。

一开始我只是在程序的开头写了

  MPI_Status *status;

随后在子进程中调用MPI_Recv。

MPI_Recv(&(A[0][0]), N * N / numprocs, MPI_INT, 0, 1, MPI_COMM_WORLD, status);

而正是这一行代码引发了错误。
首先,在C语言中声明一个变量和定义一个变量是不同的。所谓声明变量,指的是存在这样一个符号,但并不分配内存。而是否给变量分配内存,实际上是决定一个变量在内存中存在的关键。

如果一个变量只是被声明而没有被分配内存,尤其是对于指针变量,它不存在合法的值,所以,任何与它的值有关的操作都会导致Segment Fault。在这个程序中,出错的地方正是引用了status。

Bug2 指针与地址的不同

在二维数组A[m][n]当中,A是一个指针,相当于DataType**,是一个指向指向DataType的指针的指针,此外A[m]的值虽然与第m行第0列元素的地址相同,但这个变量本身的意义是一个指针,而不是内存地址。因此,只有&(A[m][0])才是第m行第0列元素的地址。

而MPI_Send与MPI_Recv的第一个参数需要的是一个元素地址,而不是指针。所以,写A, A[m],都是数据类型不匹配的实参。

所谓指针,某种意义上说,就是被赋予了意义的地址,但它不等同于地址

Bug3 可以同名收发

在主进程中send(A),子进程中写recv(A),由于主进程子进程地址空间的分离,这两个符号并不会引发冲突。

Bug4 MPI_Bcast

MPI_Bcast(&(B[0]), N, MPI_INT, 0, MPI_COMM_WORLD);

MPI_Bcast是对所有进程的同一符号的变量的广播,它代表的意义是,一旦运行完毕,所有子进程内这一符号的变量的值全部将与指定的第k个进程的这一符号的值相同,它的作用是“统一化”。这样理解,比较合适。

把它只在主进程中调用是不合适的,并不会改变其他子进程中这一变量的值。它必须独立于

if(myid ==k) {...}

,要在所有的进程中被同时执行。

参看

https://stackoverflow.com/questions/5104847/mpi-bcast-a-dynamic-2d-array

总结

MPI并行环境类似于同一台机器多线程的情况,内存模型是相同的,程序语句执行模型也是相同的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值