HPC - Lecture 6 - Message Passing
Cluster Model and Distributed Memory
A cluster is a set of computing nodes, each equipped with its own local memory, interconnected via a network or a switch. This architecture represents a distributed-memory MIMD (Multiple Instruction, Multiple Data) system.

- Memory Access: The processing units (CPUs) within a node can access their own local memory but cannot directly access the memories of remote nodes.
- Node Composition: Multi-core CPUs inside each node implement shared-memory MIMD parallelism locally, while the cluster as a whole operates on distributed memory.
- Historical Context: The first clusters emerged in the late 1980s and early 1990s as a cost-effective alternative to monolithic supercomputers.

The Message-Passing Model
In this model, a program running on a single node is defined as a process. A parallel program is composed of a set of processes that cooperate by exchanging data through an explicit communication network.
- Address Space: Each process can only access data in its own private address space.
- Explicit Data Transfer: If a process requires data owned by another, it must perform an explicit transfer.
- Process Identification: Each process is uniquely identified by an integer (rank), typically ranging from
to , where is the total number of processes. - SPMD (Single Program Multiple Data): The standard programming approach where the same executable runs concurrently across all processes, but operates on different data based on the process ID.
Basic Communication Operations
To enable data transfer, programming languages are extended with two fundamental operations:
send(data, dest): Dispatches data to the process with identifierdest.recv(data, source): Receives data from the process with identifiersource.
Global Summation Strategies
A common problem in distributed systems is computing a global sum
Strategy 0: Centralized Collection
Each process sends its partial result to a master process (Process 0), which performs all additions.

- Steps: We need
communication steps to complete. - Observation: Only Process 0 owns the final result.
Centralize Collection Algorithm:
if (myid=0) then
Stot = S
for i =1 to NP-1
recv(remres,i) // proc 0 receives into remres
Stot = Stot + remres
endfor
else
send(S,0) // other processes send
endifStrategy 1: Ring Sum
Processes are arranged in a logical ring. Each process sends a value to the next and receives a value from the previous in every step.

- Steps:
communication steps. - Observation: After completion, all processes own the total result.
Ring Sum Algorithm
Stot = S;
token = S;
for (i = 1; i < NP; i++) {
send(token, (myid + 1) % NP);
recv(token, (myid + NP - 1) % NP);
Stot = Stot + token;
}myidprocess identifierNPnumber of processesSpartial result of the processStottotal resulttokenvalue received and passed by each process
Strategy 2: Cascade Sum
Each process has a partner with which it exchanges data. Processes exchange data with partners at increasing distances (1, 2, 4, …).

- Steps:
communication steps. - Observation: All processes own the total result. Highly efficient for large clusters.
SPMD Algorithm for Cascade Sum:
q = myid
dist = 1
Stot = S
for i=1 to log(NP) //number of steps
r = q%2 //remainder
if (r == 0) then
send(Stot, myid+dist)
recv(token, myid+dist)
else // if odd
send(Stot, myid-dist)
recv(token, myid-dist)
endif
Stot = Stot + token //preparenext iteration
q = q/2
dist = dist * 2qis the quotient of division by 2 andris the remainder of division by 2distis the distance between partner processes.
Performance Considerations
The communication phases in these algorithms do not exploit available parallelism and are often the primary cause of the decay in Speed-up and Efficiency.
Amdahl’s Law
The maximum speed-up
Generalized Amdahl’s Law
For multi-level parallelism:
MPI: Message Passing Interface
MPI is a standard that defines function interfaces to ensure portability across different distributed systems. It is a library, not a new language, primarily used with C, C++, and Fortran.
There are several implementations e.g.:
- Open Source like MPICH and OpenMPI
- Proprietary like Mellanox ScalableMPI and Intel MPI
Developing applications with MPI
The system runs multiple copies of the same executable program (SPMD model). Multiple processes can also run on the same cluster node. In any case, each process has its own address space.

- Context: A group of processes that can communicate with each other.
- Communicator: A data structure (
MPI_Comm) associated with a context. - Default Communicator:
MPI_COMM_WORLDincludes all processes launched at the start.

Within each context, processes are uniquely identified by an integer (rank), which may be different in each context to which they belong.
If

Fundamental MPI Functions
All MPI programs must be structured with the following initialization and finalization calls:
Initialization and Termination:
int MPI_Init(int *argc, char **argv): Initializes the MPI environment.int MPI_Finalize(void): Terminates the MPI environment.
Both return 0 if OK, otherwise an error code. These functions create and destroy the default communication environment, in particular:
- the communicator
- the number of processes and the rank
- the communication data structures
Environment Inquiry:
int MPI_Comm_size(MPI_Comm comm, int *size): Returns the total number of processes in the communicator.int MPI_Comm_rank(MPI_Comm comm, int *rank): Returns the rank of the calling process.
Point-to-Point Communication
In an MPI program, cooperation among processes takes place through explicit communication operations. The most elementary communication operation consists of:
- a sender process (send)
- a receiver process (recv)

MPI_Send
int MPI_Send(void *data, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)- data: Pointer to the buffer.
- count: Number of elements.
- datatype: e.g.,
MPI_INT,MPI_FLOAT,MPI_DOUBLE,MPI_CHAR. - dest: Rank of destination.
- tag: Message identifier to distinguish different types of messages.
MPI_Recv
int MPI_Recv(void *data, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)- source: Rank of sender.
- status: Contains info like the actual number of items received.
Practical Implementation
Compiling and Running
MPI programs are compiled using mpicc (a wrapper for gcc) and executed using mpiexec or mpirun.
- Compiling:
mpicc source.c -o executable - Running:
mpiexec -np <num_proc> ./executable
First example: hello world
#include <stdio.h>
#include <mpi.h>
int main(int argc, char *argv[]) {
int nproc, myid;
MPI_Init(&argc, &argv)
MPI_Comm_size(MPI_COMM_WORLD, &nproc);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
printf("Hello from %d of %d processes\n", myid, nproc);
MPI_Finalize();
}Second Example: Only two processes
#include <stdio.h>
#include <mpi.h>
int main (int argc, char *argv[ ]) {
int nproc, myid; MPI_Status status; /* MPI type defined in mpi.h */
float a[2]; /* array to send */
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &nproc);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
if (myid == 0) {
a[0] = 1;
a[1] = 2;
MPI_Send(a, 2, MPI_FLOAT, 1, 10, MPI_COMM_WORLD);
} else {
MPI_Recv(a, 2, MPI_FLOAT, 0, 10, MPI_COMM_WORLD, &status);
printf("%d: a[0]=%f a[1]=%f\n", myid, a[0], a[1]); }
MPI_Finalize();
}Error codes
All MPI functions are of type int and return an error code to check whether the instruction was executed correctly.
6 basic functions
The MPI standard defines more than 100 functions, but with these 6 basic functions, it is possible to write most applications for distributed-memory MIMD systems:
MPI_INITMPI_FINALIZEMPI_Comm_rankMPI_Comm_sizeMPI_SendMPI_Recv
All MPI type and function declarations are in the file mpi.h.
Slurm Batch Script Example
On high-performance clusters (e.g., PurpleJeans), jobs are submitted via batch scripts:
#!/bin/bash
#SBATCH --job-name=hello
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=4
#SBATCH --partition=xhicpu
ulimit -s 10240
module load ompi-4.1.0-gcc-8.3.1
mpicc -o hello hello.c
mpirun ./helloSee PurpleJeans Intro.
Exercise: Global Sum Implementation
Task: Write an MPI function int sum(int *A, int N) that computes the global sum using the Ring Sum strategy.
Initialization Logic:
If A as: