我正在尝试实施MPI_Bcast
,我计划通过MPI_Send
和MPI_Recv
来实现这一点,但似乎我无法向自己发送消息?
代码如下
void My_MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm) {
int comm_rank, comm_size, i;
MPI_Comm_rank(comm, &comm_rank);
MPI_Comm_size(comm, &comm_size);
if(comm_rank==root){
for(i = 0; i < comm_size; i++){
MPI_Send(buffer, count, datatype, i, 0, comm);
}
}
MPI_Recv(buffer, count, datatype, root, 0, comm, MPI_STATUS_IGNORE);
}
有什么建议吗?或者我永远不应该向自己发送信息而只是做一个记忆复制?
答案 0 :(得分:4)
您的程序在多个级别上都是错误的。首先,条件中存在错误:
if(comm_rank=root){
这不会将comm_rank
与root
进行比较,而是将root
分配给comm_rank
,然后只有在root
非零时才会执行循环除此之外,所有职级都会执行。
其次,根进程不需要将数据发送给自己,因为数据已经存在。即使你想要发送和接收,你应该注意到MPI_Send
和MPI_Recv
都使用相同的缓冲区空间,这是不正确的。一些MPI实现使用直接内存副本进行自我交互,即库可能使用memcpy()
来传输消息。使用带有重叠缓冲区的memcpy()
(包括使用相同的缓冲区)会导致未定义的行为。
实施线性广播的正确方法是:
void My_MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm)
{
int comm_rank, comm_size, i;
MPI_Comm_rank(comm, &comm_rank);
MPI_Comm_size(comm, &comm_size);
if (comm_rank == root)
{
for (i = 0; i < comm_size; i++)
{
if (i != comm_rank)
MPI_Send(buffer, count, datatype, i, 0, comm);
}
}
else
MPI_Recv(buffer, count, datatype, root, 0, comm, MPI_STATUS_IGNORE);
}
流程在没有死锁的情况下与自己交谈的常用方法是:
MPI_Isend
和MPI_Recv
或MPI_Send
和MPI_Irecv
的组合; MPI_Bsend
; MPI_Sendrecv
或MPI_Sendrecv_replace
。 MPI_Irecv
和MPI_Send
的组合适用于在像您这样的循环中完成多个发送的情况。例如:
MPI_Request req;
// Start a non-blocking receive
MPI_Irecv(buff2, count, datatype, root, 0, comm, &req);
// Send to everyone
for (i = 0; i < comm_size; i++)
MPI_Send(buff1, count, datatype, i, 0, comm);
// Complete the non-blocking receive
MPI_Wait(&req, MPI_STATUS_IGNORE);
请注意使用单独的缓冲区进行发送和接收。可能唯一的点对点MPI通信调用允许相同的缓冲区用于发送和接收MPI_Sendrecv_replace
以及集体MPI调用的就地模式。但这些内部实现的方式是,在任何时候都不会使用相同的内存区域进行发送和接收。
答案 1 :(得分:2)
这是一个不正确的程序。你不能依靠对自己进行阻止MPI_Send ...因为它可能会阻止。在缓冲区再次可用之前,MPI不保证您的MPI_Send返回。在某些情况下,这可能意味着它将阻塞,直到目的地收到消息。在您的程序中,目标可能永远不会调用MPI_Recv,因为它仍在尝试发送。
现在,在My_MPI_Bcast示例中,根进程已经拥有了数据。为什么需要发送或复制它?
答案 2 :(得分:1)
根节点上的MPI_Send / MPI_Recv块可能是死锁。
转换为MPI_Isend可用于解决此问题。但是,可能存在问题,因为发送缓冲区正在被重用,并且root很可能“早期”到达MPI_Recv,然后可能在将其传输到其他级别之前更改该缓冲区。这对大型工作尤其有用。此外,如果从fortran调用此例程,则每个MPI_Send调用上的缓冲区可能会出现问题。
MPI_Sendrecv的使用只能用于根进程。这将允许在根进程进入专用MPI_Sendrecv之前,所有非根等级的MPI_Send“完成”(例如,可以安全地改变发送缓冲区)。 for循环只是以“1”而不是“0”开头,并且MPI_Sendrecv调用添加到该循环的底部。 (为什么这是一个更好的问题,因为数据在“缓冲区”中并且将“缓冲”。)
然而,这一切都引出了一个问题,为什么你要这样做呢?如果这是一个简单的“学术练习”,用点对点电话写一个集体,那就这样吧。但是,你的方法充其量是天真的。在任何合理实施的mpi中,任何MPI_Bcast算法都会击败此总体策略。
答案 3 :(得分:-1)
我认为你应该只为MPI_Recv(buffer, count, datatype, root, 0, comm, MPI_STATUS_IGNORE);
添加rank=root
,否则它可能会挂起