Module 2 With Programs
Module 2 With Programs
Programming
6 Hours
• MPI is a message-passing parallel programming model, in which data is moved from the address
space of one process to that of another process through cooperative operations on each process
• MPI is not a programming language, and all MPI operations are expressed as functions, subroutines,
or methods used by C, C++, Fortran-77, and Fortran-95 etc which are part of MPI standard
• The underlying hardware is assumed to be a collection of processors, each with its own local memory
• A processor has direct access only to the instructions and data stored in its local memory
• Processor A may send a message containing some of its local data values to processor B, giving processor
B indirect access to these values
• The existence of the interconnection network provides an implicit communication channel between every
pair of processes
• The user specifies the number of concurrent processes when the program begins, and the number of
active processes remains constant throughout the execution of the program
• Every process executes the same program, but because each one has a unique ID number, different
processes may perform different operations on the same program
• In a message-passing model, processes pass messages both to communicate and to synchronize with
each other
• They are a natural fit for multicomputers, which do not support a global address space
• However, it is also possible to execute message-passing programs on multiprocessors by using shared variables as
message buffers
• The MPI programs tend to exhibit high cache hit rates when executing on multiprocessors, leading to good
performance
• Debugging MPI programs is simpler than debugging shared-variable programs. Since each process controls its own
memory, it is not possible for one process to accidentally overwrite a variable controlled by another process, a common
bug in shared-variable programs.
• Processors communicate using message passing via calls to message passing library routines
• Programmers “parallelize” programs by adding message calls between manager process and worker process
• The MPI programs tend to exhibit high cache hit rates when executing on multiprocessors, leading to good performance
• The names of all MPI entities (routines, constants, types, etc.) begin with MPI_ to
avoid conflicts
Example: MPI_Init(&argc, &argv)
• All MPI constants are strings of capital letters and underscores beginning with MPI_
Example: MPI_COMM_WORLD
• MPI routines are implemented as functions which return the exit status of the function call
int ierr;
...
ierr = MPI_Init(&argc, &argv);
• The error code returned is MPI_SUCCESS if the routine ran successfully or else the integer returned
has an implementation-dependent value indicating the specific error
MPI_Finalize()
• It terminates MPI environment after releasing all the held up resources
• It should be the last MPI function call
Communicators & Rank
1 3 4
dest 1
4 0 0Comm1 2 5
0
2 1 3 Comm2
6 1
2 3
source 5 2 0
Communicator MPI_COMM_WORLD
MPI_COMM_WORLD
• When MPI has been initialized, every active process becomes a member of a
communicator called MPI_COMM_WORLD
• A communicator is an object that provides the environment for message passing among
processes
• However, you can create your own communicators if you need to partition the processes
into independent communication groups
Communicators & Rank
• In a communicator with p processes, each process has a unique rank (ID number)
between 0 and p – 1
• A process may use its rank to determine which portion of a computation and/or a dataset
it is responsible for
Communicators & Rank
int my_rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank)
• A process calls this function to determine its rank within a communicator
MPI_Comm_size(MPI_COMM_WORLD, &size)
• A process calls this function to determine the total number of processes in a communicator
return 0;
}
Hello World for MPI
• Each processor executes the same code, including probing for its rank and size and printing the
string.
• In the following example, process h is not involved in a communication. It continues executing statement,
manipulating its local variables. Process i performs local computations, then sends a message to process j.
After the message is sent, it continues on with its computation. Process j performs local computations, then
blocks until it receives a message from process i.
compute
compute
Time
compute Send to j
wait
Receive from i
compute
compute
MPI Errors
• This routine sends a message and block until the application buffer in the sending task is free for
reuse
• The MPI implementation may buffer your send allowing it to return almost immediately
• If the implementation does not buffer the send, the send will not complete until the matching receive
occurs
Blocking Message Passing Routines
MPI_ANY_SOURCE MPI_ANY_TAG
MPI_Recv
• This routine returns only after the requested data is available in the application buffer
• The status record contains information about the just-completed receive function. In particular:
struct MPI_Struct {
int MPI_SOURCE;
int MPI_TAG;
int MPI_ERROR;
int _cancelled;
size_t _ucount;
};
Blocking send-recv (2-processes)
#include <mpi.h> #include<stdio.h>
if(rank == 0){
send_number = 777;
MPI_Send(&send_number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
}
else if (rank == 1) {
MPI_Recv(&send_number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0 \n", send_number);
}
MPI_Finalize();
return 0;
}
Blocking send-recv (n-processes)
#include <mpi.h> #include<stdio.h>
if(rank == 0){
send_number = 777;
MPI_Send(&send_number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
}
else if (rank == 1) {
MPI_Recv(&send_number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0 \n", send_number);
}
else
printf("Hello from process %d \n", rank);
MPI_Finalize();
return 0;
}
Blocking send-recv (using MPI_Status -1 )
#include <mpi.h> #include<stdio.h>
MPI_Status status;
if(rank == 0){
send_number = 999;
MPI_Send(&send_number, 1, MPI_INT, 1, 3, MPI_COMM_WORLD);
}
if (rank == 1) {
MPI_Recv(&send_number, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
printf("Process %d received number %d from process %d in message %d \n", rank, send_number,
status.MPI_SOURCE, status.MPI_TAG);
}
else
printf("Hello from process %d \n", rank);
MPI_Finalize();
return 0;
}
Blocking send-recv (using MPI_Status -2 )
#include <mpi.h> #include<stdio.h>
if(rank == 0){
send_number0 = 555;
MPI_Send(&send_number0, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
}
if(rank == 1){
send_number1 = 777;
MPI_Send(&send_number1, 1, MPI_INT, 1, 1, MPI_COMM_WORLD);
}
while(1) {
MPI_Recv(&recv_buffer, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
printf("Process %d received number %d from process %d in message %d \n", rank, send_number,
status.MPI_SOURCE, status.MPI_TAG);
}
MPI_Finalize();
return 0;
}
Synchronous Message Passing Routines
MPI_Ssend
• This routing sends a message and block until the application buffer in the sending task is free for
reuse and the destination process has started to receive the message
Synchronous Ssend-recv (n-processes)
#include <mpi.h> #include<stdio.h>
if(rank == 0){
send_number = 555;
MPI_Ssend(&send_number, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
}
else if (rank == 1) {
MPI_Recv(&send_number, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process 1 received number %d from process 0 \n", send_number);
}
else
printf("Hello from process %d \n", rank);
MPI_Finalize();
return 0;
}
Buffered Message Passing Routines
MPI_Bsend
• This routine permits the programmer to allocate the required amount of buffer space into which data
can be copied until it is delivered
• Insulates against the problems associated with insufficient system buffer space
• Routine returns after the data has been copied from application buffer space to the allocated buffer
MPI_Buffer_detach
• The size argument is specified in actual data bytes - not a count of data elements
if(rank == 0){
MPI_Buffer_attach(buffer_attached, buffer_attached_size);
printf("Sender %d sent value %d \n", rank, data_sent);
MPI_Bsend(&data_sent, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
MPI_Buffer_detach(&buffer_attached, &buffer_attached_size);
free(buffer_attached);
}
else if (rank == 1) {
MPI_Recv(&received, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf(" Receiver %d received value: %d \n", rank, received);
}
MPI_Finalize();
return 0;
}
Broadcasting using send-recv
#include <mpi.h>
#include<stdio.h>
int main (int argc, char *argv[]) {
int i, root_rank = 0, my_rank, size, recv_buffer;
if(my_rank == 0){
int send_buffer=777;
for(i=1; i<size; i++)
MPI_Send(&send_buffer, 1, MPI_INT, i, 2, MPI_COMM_WORLD);
}
else{
MPI_Recv(&recv_buffer, 1, MPI_INT, 0, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
printf("Process %d received number %d from process 0 \n", my_rank, recv_buffer);
}
MPI_Finalize();
return 0;
}
Deadlock
"A process is in a deadlock stale if it is blocked waiting for a condition that will never
become true"
Deadlock (Recv-Recv)
int a,b,c;
int rank;
MPI_Status status;
………….
if(rank==0)
{
MPI_Recv(&b,1,MPI_INT,1,0,MPI_COMM_WORLD,&status);
MPI_Send(&a,1,MPI_INT,1,0,MPI_COMM_WORLD);
c=a+b/2;
}
else if(rank==1)
{
MPI_Recv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&status);
MPI_Send(&b,1,MPI_INT,0,0,MPI_COMM_WORLD);
c=a+b/2;
}
Deadlock (Tag mismatch)
int a,b,c;
int rank;
MPI_Status status;
………….
if(rank==0)
{
MPI_Send(&a,1,MPI_INT,1,1,MPI_COMM_WORLD);
MPI_Recv(&b,1,MPI_INT,1,1,MPI_COMM_WORLD,&status);
c=a+b/2;
}
else if(rank==1)
{
MPI_Send(&b,1,MPI_INT,0,0,MPI_COMM_WORLD);
MPI_Recv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&status);
c=a+b/2;
}
Deadlock (Rank mismatch)
int a,b,c;
int rank;
MPI_Status status;
………….
if(rank==0)
{
MPI_Send(&a,1,MPI_INT,2,1,MPI_COMM_WORLD);
MPI_Recv(&b,1,MPI_INT,2,1,MPI_COMM_WORLD,&status);
c=a+b/2;
}
else if(rank==1)
{
MPI_Send(&b,1,MPI_INT,0,0,MPI_COMM_WORLD);
MPI_Recv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&status);
c=a+b/2;
}
Deadlock (Communicator mismatch)
int a,b,c;
int rank;
MPI_Status status;
………….
if(rank==0)
{
MPI_Send(&a,1,MPI_INT,1,1,My_Communicator);
MPI_Recv(&b,1,MPI_INT,1,1,MPI_COMM_WORLD,&status);
c=a+b/2;
}
else if(rank==1)
{
MPI_Send(&b,1,MPI_INT,0,0,MPI_COMM_WORLD);
MPI_Recv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&status);
c=a+b/2;
}
Deadlock (self blocking Send)
int a,b,c;
int rank;
MPI_Status status;
………….
if(rank==0)
{
MPI_Send(&a,1,MPI_INT,0,1,MPI_COMM_WORLD);
MPI_Recv(&b,1,MPI_INT,1,1,MPI_COMM_WORLD,&status);
c=a+b/2;
}
else if(rank==1)
{
MPI_Send(&b,1,MPI_INT,0,0,MPI_COMM_WORLD);
MPI_Recv(&a,1,MPI_INT,0,0,MPI_COMM_WORLD,&status);
c=a+b/2;
}
Collective Communication in MPI
• A collective communication is a communication operation in which a group of processes works together to
distribute or gather together a set of one or more values
• Scope:
o Collective communication routines must involve all processes within the scope of a communicator
o All processes are by default, members in the communicator MPI_COMM_WORLD
o Unexpected behavior, including program failure, can occur if even one process in the communicator
doesn't participate
o It is the programmer's responsibility to ensure that all processes within a communicator participate in any
collective operations.
1. Synchronization:
processes wait until all members of the group have reached the synchronization point
2. Data Movement:
processes send/receive data among themselves
3. Collective Computation:
one or more member of the group collects data from the other members and performs an operation
(min, max, add, multiply, etc.) on that data
Operator Meaning
MPI_BAND Bitwise and
MPI_BOR Bitwise or
MPI_BXOR Bitwise exclusive or
MPI_LAND Logical and
MPI_LOR Logical or
MPI_LXOR Logical exclusive or
MPI_ MAX Maximum
MPl_MAXLOC Maximum and location of maximum
MPl_MIN Minimum
MPI_MlNLOC Minimum and location of minimum
MPl_PROD Product
MPl SUM Sum
• Must be called by all the processes with the same arguments Data
Root
Data
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == root){
printf("I am Root: enter a number to broadcast \n");
scanf("%d",&buf);
}
/* everyone calls bcast, data is taken from root and ends up in everyone's buf */
MPI_Bcast(&buf, 1, MPI_INT, root, MPI_COMM_WORLD);
printf("Process[%d]: After Bcast, buf is %d\n", rank, buf);
MPI_Finalize();
return 0;
}
MPI_Reduce
MPI_Reduce(void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype,
MPI_Op op, int root, MPI_Comm comm)
• One process (root) collects data from all the other processes in the same communicator, and performs
an operation on the data (i.e combines elements provided by input buffer of each process in the group
using operation op.)
• Returns combined value in the output buffer of process with rank root
Root Operator
MPI_SUM
MPI_MAX
operator MPI_MIN
MPI_PROD
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(size != 4)
{
printf("This application is meant to be run with 4 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Each MPI process sends its rank to reduction, root MPI process collects the result
int reduction_result = 0;
printf("Sending my rank from process %d \n", my_rank);
MPI_Reduce(&my_rank, &reduction_result, 1, MPI_INT, MPI_SUM, root_rank, MPI_COMM_WORLD);
if(my_rank == root_rank)
{
printf("The sum of all ranks is %d.\n", reduction_result);
}
MPI_Finalize();
return 0;
}
MPI_Reduce – computing partial result
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[]) {
int root_rank = 0, size, my_rank, sum=0, reduction_result=0;
int arr[4]={1,2,3,4};
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(size != 3)
{
printf("This application is meant to be run with 3 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
// Each MPI process sends its rank to reduction, root MPI process collects the result
MPI_Reduce(&sum, &reduction_result, 1, MPI_INT, MPI_SUM, root_rank, MPI_COMM_WORLD);
if(my_rank == root_rank)
{
printf("The sum of all ranks is %d.\n", reduction_result);
}
MPI_Finalize();
return 0;
}
MPI_Scatter
MPI_Scatter (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf,
int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
• Sends individual messages from the root process to all other processes
• Inverse to MPI_Gather
• sendbuf is ignored by all non-root processes
Root
Data Data
Data Data
// Root process will scatter the data to other processes including itself
MPI_Scatter(send_buf, 1, MPI_INT, &recv_buf, 1, MPI_INT, root_rank, MPI_COMM_WORLD);
printf("data in process[%d] = %d", my_rank, recv_buf);
MPI_Finalize();
return 0;
}
MPI_Scatter – sending multiple data
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(size != 2)
{
printf("This application is meant to be run with 2 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Root process will scatter the data to other processes including itself
MPI_Scatter(send_buf, 2, MPI_INT, &recv_buf, 2, MPI_INT, root_rank, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
MPI_Scatter – sending string
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char *argv[]) {
int root_rank = 0, size, my_rank;
char *send_buf="HELLO WORLD";
int len = strlen(send_buf);
char *recv_buf = (char *)malloc(len*sizeof(char));
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(size != 6)
{
printf("This application is meant to be run with 6 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Root process will scatter the data to other processes including itself
MPI_Scatter(send_buf, 2, MPI_CHAR, recv_buf, 2, MPI_CHAR, root_rank, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
MPI_Gather
MPI_Gather (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf,
int recvcnt, MPI_Datatype recvtype, int root, MPI_Comm comm)
• One process (root) collects data from all the other processes in the same communicator (i.e each process in
comm (including root itself) sends its sendbuf to root.)
• The root process receives the messages in recvbuf in rank order
• Must be called by all the processes with the same arguments
Process
Data Data
Data Data
Inverse to MPI_Scatter
MPI_Gather – sending numbers
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[]) {
int root_rank = 0, size, my_rank;
int send_buf = 111;
int recv_buf[4];
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
if(size != 4)
{
printf("This application is meant to be run with 4 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
send_buf = send_buf + my_rank;
// Each MPI process sends its send_buf to Root proess which collects the data in rank order
printf("Sending data %d from from process %d \n", send_buf, my_rank);
MPI_Gather(&send_buf, 1, MPI_INT, &recv_buf, 1, MPI_INT, root_rank, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
MPI_Gather – sending Strings
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char *argv[]) {
int root_rank = 0, size, my_rank;
char send_buf[10] = "AA ";
char *recv_buf;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Each MPI process sends its send_buf to Root proess which collects the data in rank order
printf("Sending data %s from from process %d \n", send_buf, my_rank);
MPI_Gather(send_buf, len, MPI_CHAR, recv_buf, len, MPI_CHAR, root_rank, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
MPI_Allgather
MPI_Allgather (void *sendbuf, int sendcnt, MPI_Datatype sendtype, void
*recvbuf, int recvcnt, MPI_Datatype recvtype, MPI_Comm comm)
• All the processes collects data from all the other processes in the same communicator (i.e similar to
MPI_Gather except now all processes receive the result.)
• recvbuf is NOT ignored
• Must be called by all the processes with the same arguments
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
// Each MPI process sends its send_buf to Root proess which collects the data in rank order
printf("Sending data %s from from process %d \n", send_buf, my_rank);
MPI_Allgather(send_buf, len, MPI_CHAR, recv_buf, len, MPI_CHAR, MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
MPI_Alltoall
MPI_Alltoall (void *sendbuf, int sendcount, MPI_Datatype sendtype, void
*recvbuf, int recvcount, MPI_Datatype recvtyp, MPI_Comm comm )
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
if(size != 4)
{
printf("This application is meant to be run with 4 MPI processes.\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
for(i=0; i<4;i++)
send_buf[i] += my_rank;
MPI_Finalize();
return 0;
}
MPI_Scan
MPI_Scan (void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype,
MPI_Op op, MPI_Comm comm )
Process
Data Data
Data Data
• For example:
o if you had 10 processes and you were taking the sum of their rank, MPI_Reduce would give you the scalar
45 (0+1+2+3+4+5+6+7+8+9) on the root process,
o while MPI_scan would give you the scalar result of the reduction up to the rank of that process on each
process. So processor 0 would get 0, processor 1 would get 1, processor 2 would get 3, and so on.
Processor 9 would get 45
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
MPI_Finalize();
return 0;
}
Benchmarking Parallel Performance
• Benchmarking parallel program performance measure how well parallel programs perform against
their sequential counterparts in the "middle area" between reading the dataset and writing the results
Reading data
Execution
Benchmarking Performance
time
Display
results
Typically, we are going to ignore the time spent initiating MPI processes, establishing communications
sockets between them, and performing I/O on sequential devices
• We can benchmark a section of code by putting a pair of calls to function MPI_Wtime before and after
the section. The difference between the two values returned by the function is the number of seconds
elapsed
double start_time, end_time;
………….
MPI_Init(&argc, &argv);
start_time = MPI_Wtime();
end_time = MPI_Wtime();
printf("Process took %f seconds\n",endtime-starttime);
01/03/2021 Bhargav Bhatkalkar 68
Setting barrier for process synchronization
• From a logical point of view, every MPI process begins execution at the same time, but this is
not true in practice
• MPI processes executing on different processors may begin executing seconds apart. This can
throw off timings significantly
• We address this problem by introducing a barrier synchronization before the first call to
MPI_Wtime.
• No process can proceed beyond a barrier until all processes have reached it
• Hence a barrier ensures that all processes are going into the measured section of code at
more or less the same time
MPI_Barrier(MPI_COMM_WORLD)
start_time = MPI_Wtime();
end_time = MPI_Wtime();
printf("Process took %f seconds\n",endtime-starttime);
MPI Error Handling Functions
• When an error is occurred while executing a MPI program typically, the program aborts
• MPI calls a default error handler MPI_ERRORS_ARE_FATAL every time an MPI error is detected within the
communicator
• MPI_ERRORS_ARE_FATAL abort the whole parallel program as soon as any MPI error is detected
• There is another predefined error handler MPI_ERRORS_RETURN which is used to return the generated error for
custom handling
• The default error handler MPI_ERRORS_ARE_FATAL can be replaced with MPI_ERRORS_RETURN by calling
function MPI_Errhandler_set ()
MPI_Errhandler_set(MPI_COMM_WORLD, MPI_ERRORS_RETURN)
• MPI standard defines error classes. Every error code, must belong to some error class, and the error
class for a given error code can be obtained by calling function MPI_Error_class ()