Nearest Neighbor Send and Receive

One of the most common patterns in MPI programming is nearest-neighbor exchanges. In this case, a process sends to its neighbor and receives from its neighbor. The definition of “neighbor” may depend on the topology of the layout of ranks. For a simple one-dimensional organization the neighbor to the left is rank- and the neighbor to the right is rank+1.

Schematic of nearest-neighbor exchange by rank

Any blocking point-to-point communications can potentially deadlock, but we must be especially careful with nearest-neighbor communications. Each process must be in an appropriate state when a message is sent to it. How do we accomplish this?

As an example, let us consider an exchange in which each process sends its rank to its neighbor and receives the neighbor’s rank. Even-number processes send to their right and odd-numbered processes send to their left.

Deadlock

We might first think all processes should be in the Receive state, then Send the message.

Examples

C++

#include <iostream>
#include <mpi.h>

using namespace std;

int main(int argc, char **argv) {

    int rank, nprocs, message, neighbor;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (nprocs < 2) {
        cout<<"This program works only for at least two processes\n";
	MPI_Finalize();
        return 1;
    }
    else if (nprocs%2 != 0) {
        cout<<"This program works only for an even number of processes\n";
	MPI_Finalize();
        return 2;
    }

    if (rank%2==0) {
        neighbor = rank+1;
    }
    else {
	neighbor = rank-1;
    }

    MPI_Recv(&message, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD, &status);
    MPI_Send(&rank, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD);
    cout<<rank<<" "<<message<<endl;
    MPI_Finalize();

    return 0;
}

Fortran

program exchange
   use mpi
   implicit none
    
   integer :: rank, nprocs, neighbor, ierr
   integer :: status(MPI_STATUS_SIZE)
   integer :: message

   call MPI_Init(ierr)
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr)

   if (nprocs < 2) then
        write(6,*) "This program works only for at least two processes"
        call MPI_Finalize(ierr)
        stop
   else if ( mod(nprocs,2) /= 0 ) then
        write(6,*) "This program works only for an even number of processes"
        call MPI_Finalize(ierr)
        stop
   end if

   if ( mod(rank,2) == 0) then
       neighbor = rank+1
   else
       neighbor = rank-1
   end if

   call MPI_Recv(message, 1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, status, ierr)
   call MPI_Send(rank,1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, ierr)

   write(*,*) rank, message

   call MPI_Finalize(ierr)

end program


Python

import sys
import numpy as np
from mpi4py import MPI

comm=MPI.COMM_WORLD
nprocs=comm.Get_size()
rank=comm.Get_rank()

if nprocs<2:
    print("This program works only for at least two processes.")
    sys.exit()
elif nprocs%2!=0:
    print("This program works only for an even number of processes.")
    sys.exit()

message=np.zeros(1,dtype='int')
rank_val=rank*np.ones(1,dtype='int')

if rank%2==0:
    neighbor=rank+1
else:
    neighbor=rank-1

comm.Recv([message,MPI.INT],source=neighbor)
comm.Send([rank_val,MPI.INT],dest=neighbor)

print(rank,message)

However, this pattern is guaranteed to deadlock because the MPI_Recv is blocking. It will wait indefinitely for a message, so the process never has a chance to send anything.

Unsafe

Perhaps we can swap the MPI_Send and MPI_Recv. The Send will pack up the message into the buffer and return. The process then proceeds to Receive, which accepts the message.

C++

#include <iostream>
#include <mpi.h>

using namespace std;

int main(int argc, char **argv) {

    int rank, nprocs, message, neighbor;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (nprocs < 2) {
        cout<<"This program works only for at least two processes\n";
	MPI_Finalize();
        return 1;
    }
    else if (nprocs%2 != 0) {
        cout<<"This program works only for an even number of processes\n";
	MPI_Finalize();
        return 2;
    }

    if (rank%2==0) {
        neighbor = rank+1;
    }
    else {
	neighbor = rank-1;
    }

    MPI_Send(&rank, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD);
    MPI_Recv(&message, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD, &status);
    cout<<rank<<" "<<message<<endl;
    MPI_Finalize();

    return 0;
}

Fortran

program exchange
   use mpi
   implicit none
    
   integer :: rank, nprocs, neighbor, ierr
   integer :: status(MPI_STATUS_SIZE)
   integer :: message

   call MPI_Init(ierr)
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr)

   if (nprocs < 2) then
        write(6,*) "This program works only for at least two processes"
        call MPI_Finalize(ierr)
        stop
   else if ( mod(nprocs,2) /= 0 ) then
        write(6,*) "This program works only for an even number of processes"
        call MPI_Finalize(ierr)
        stop
   end if

   if ( mod(rank,2) == 0) then
       neighbor = rank+1
   else
       neighbor = rank-1
   end if

   call MPI_Send(rank,1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, ierr)
   call MPI_Recv(message, 1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, status, ierr)

   write(*,*) rank, message

   call MPI_Finalize(ierr)

end program


Python

import sys
import numpy as np
from mpi4py import MPI

comm=MPI.COMM_WORLD
nprocs=comm.Get_size()
rank=comm.Get_rank()

if nprocs<2:
    print("This program works only for at least two processes.")
    sys.exit()
elif nprocs%2!=0:
    print("This program works only for an even number of processes.")
    sys.exit()

message=np.zeros(1,dtype='int')
rank_val=rank*np.ones(1,dtype='int')

if rank%2==0:
    neighbor=rank+1
else:
    neighbor=rank-1

comm.Send([rank_val,MPI.INT],dest=neighbor)
comm.Recv([message,MPI.INT],source=neighbor)

print(rank,message)

This pattern is unsafe because the buffer may not be able to contain all the messages to be sent. On most modern systems, however, it will very often work, especially with short messages. For that reason it is a good idea to use built-in MPI tools as much as possible, such as MPI_Sendrecv.

Safe

For a safe exchange, we split the ranks by even and odd. One set sends first, while the other is waiting to receive; the reverse is true for the other set. Note that we have restricted the example to run only on an even number of ranks to avoid the need for special handling of the rightmost process. In general we would have to add conditionals for that case, but we are keeping the example as simple as possible.

C++

#include <iostream>
#include <mpi.h>

using namespace std;

int main(int argc, char **argv) {

    int rank, nprocs, message, neighbor;
    MPI_Status status;

    MPI_Init(&argc, &argv);
    MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    if (nprocs < 2) {
        cout<<"This program works only for at least two processes\n";
	MPI_Finalize();
        return 1;
    }
    else if (nprocs%2 != 0) {
        cout<<"This program works only for an even number of processes\n";
	MPI_Finalize();
        return 2;
    }

    if (rank%2==0) {
        neighbor = rank+1;
    }
    else {
	neighbor = rank-1;
    }

    if (rank%2==0) {
        MPI_Recv(&message, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD, &status);
        MPI_Send(&rank, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD);
    }
    else {
        MPI_Send(&rank, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD);
        MPI_Recv(&message, 1, MPI_INT, neighbor, 0, MPI_COMM_WORLD, &status);
    }

    cout<<rank<<" "<<message<<endl;
    MPI_Finalize();

    return 0;
}

Fortran

program exchange
   use mpi
   implicit none
    
   integer :: rank, nprocs, neighbor, ierr
   integer :: status(MPI_STATUS_SIZE)
   integer :: message

   call MPI_Init(ierr)
   call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr)
   call MPI_Comm_size(MPI_COMM_WORLD, nprocs, ierr)

   if (nprocs < 2) then
        write(6,*) "This program works only for at least two processes"
        call MPI_Finalize(ierr)
        stop
   else if ( mod(nprocs,2) /= 0 ) then
        write(6,*) "This program works only for an even number of processes"
        call MPI_Finalize(ierr)
        stop
   end if

   if ( mod(rank,2)==0 ) then
       neighbor = rank+1
   else
       neighbor = rank-1
   end if

   if ( mod(rank,2)==0 ) then
       call MPI_Recv(message, 1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, status, ierr)
       call MPI_Send(rank,1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, ierr)
   else
       call MPI_Send(rank,1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, ierr)
       call MPI_Recv(message, 1, MPI_INTEGER, neighbor, 0, MPI_COMM_WORLD, status, ierr)
   endif

   write(*,*) rank, message

   call MPI_Finalize(ierr)

end program


Python

import sys
import numpy as np
from mpi4py import MPI

comm=MPI.COMM_WORLD
nprocs=comm.Get_size()
rank=comm.Get_rank()

if nprocs<2:
    print("This program works only for at least two processes.")
    sys.exit()
elif nprocs%2!=0:
    print("This program works only for an even number of processes.")
    sys.exit()

message=np.zeros(1,dtype='int')
rank_val=rank*np.ones(1,dtype='int')

if rank%2==0:
    neighbor=rank+1
else:
    neighbor=rank-1

if rank%2==0:
    comm.Recv([message,MPI.INT],source=neighbor)
    comm.Send([rank_val,MPI.INT],dest=neighbor)
else:
    comm.Send([rank_val,MPI.INT],dest=neighbor)
    comm.Recv([message,MPI.INT],source=neighbor)

print(rank,message)

Exercise

Download the three codes for your language and try them. The first example will have to be canceled manually.

Previous
Next