diff --git a/.gitignore b/.gitignore index c6127b3..530595e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Directories to ignore +lib/ + # Prerequisites *.d diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..de53c96 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +CC = mpicc +CFLAGS = -Wall -Wextra -g +OFLAFS = -march=native -mtune=native -O1 + +build: sync async + +sync: sync/p2p sync/ping_pong sync/ring + +async: async/p2p async/ping_pong async/ring + +hello_world: src/mpi_hello_world.c + @mkdir -p target/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +sync/p2p: src/sync/mpi_p2p.c + @mkdir -p target/sync/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +sync/ping_pong: src/sync/mpi_ping_pong.c + @mkdir -p target/sync/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +sync/ring: src/sync/mpi_ring.c + @mkdir -p target/sync/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +async/p2p: src/async/mpi_p2p.c + @mkdir -p target/async/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +async/ping_pong: src/async/mpi_ping_pong.c + @mkdir -p target/async/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +async/ring: src/async/mpi_ring.c + @mkdir -p target/async/ + $(CC) $(CFLAGS) $(OFLAFS) $< -o target/$@ + +clean: + rm -rf target/ diff --git a/README.md b/README.md index c5c177e..835db82 100644 --- a/README.md +++ b/README.md @@ -1 +1,29 @@ -# async-mpi-benchmarks \ No newline at end of file +# Discovering MPI primitives + +This repository regroups small, basic MPI programs. These are aimed at learning the basics of the MPI standard (using the OpenMPI implementation) and more specifically, the asynchronous primitives in view of benchmarking them later on. + +## Dependencies +This project equires you to have an MPI-able compiler (such as the `mpicc` wrapper), and a shell script that allows you to run MPI programs (such as `mpirun`. You can install both of these using the [spack](https://github.com/spack/spack) package manager. + +## Building +The provided Makefile gives you the following commands: +``` +# Build the sync programs +make sync + +# Build the async programs +make async + +# Build both +make +``` +You can then remove the generated executables using the provided `make clean` command. + +## Running +Run the programs with the `mpirun` script and specify the number of processes to run with using the `-n` option. +An example: +``` +make async +mpirun -n 2 target/async/ping_pong +mpirun -n 4 target/async/ring +``` diff --git a/src/async/mpi_p2p.c b/src/async/mpi_p2p.c new file mode 100644 index 0000000..808eef0 --- /dev/null +++ b/src/async/mpi_p2p.c @@ -0,0 +1,49 @@ +#include +#include +#include + +int main() { + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are only two processes running + if (world_size != 2) { + fprintf(stderr, "Error: world size is %d but must be 2\n", world_size); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int token = 0; + + // Send if you are process 0, receive if you are process 1 + if (world_rank == 0) { + token = 42; + printf("Process %d: sending token `%d` to process 1\n", world_rank, token); + + MPI_Request req; + MPI_Isend(&token, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &req); + + // Simulate doing an expensive computation + printf("Process %d: performing expensive computation...\n", world_rank); + sleep(3); + printf("Process %d: finished expensive computation\n", world_rank); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: sent token\n", world_rank); + } else if (world_rank == 1) { + MPI_Request req; + MPI_Irecv(&token, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &req); + + // Simulate doing an expensive computation + printf("Process %d: performing expensive computation...\n", world_rank); + sleep(1); + printf("Process %d: finished expensive computation\n", world_rank); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: received token `%d` from process 0\n", world_rank, token); + } + + MPI_Finalize(); +} diff --git a/src/async/mpi_ping_pong.c b/src/async/mpi_ping_pong.c new file mode 100644 index 0000000..89b413f --- /dev/null +++ b/src/async/mpi_ping_pong.c @@ -0,0 +1,52 @@ +#include +#include +#include + +const int PING_PONG_LIMIT = 16; + +int main() { + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are only two processes running + if (world_size != 2) { + fprintf(stderr, "Error: world size must be 2\n"); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int ping_pong_count = 0; + int partner_rank = (world_rank + 1) % 2; + + // Continue sending and receiving while we have not reached the limit + while (ping_pong_count < PING_PONG_LIMIT) { + MPI_Request req; + if (world_rank == ping_pong_count % 2) { + ping_pong_count += 1; + + MPI_Isend(&ping_pong_count, 1, MPI_INT, partner_rank, 0, + MPI_COMM_WORLD, &req); + + // Simulate doing an expensive computation + sleep(1); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: ping `%d` process %d\n", + world_rank, ping_pong_count, partner_rank); + } else { + MPI_Irecv(&ping_pong_count, 1, MPI_INT, partner_rank, 0, + MPI_COMM_WORLD, &req); + + // Simulate doing an expensive computation + sleep(1); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: pong `%d` process %d\n", + world_rank, ping_pong_count, partner_rank); + } + } + + MPI_Finalize(); +} diff --git a/src/async/mpi_ring.c b/src/async/mpi_ring.c new file mode 100644 index 0000000..cf83765 --- /dev/null +++ b/src/async/mpi_ring.c @@ -0,0 +1,67 @@ +#include +#include +#include + +const int LOOPING_LIMIT = 16; + +int main() +{ + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are at least two processes running + if (world_size < 2) { + fprintf(stderr, "Error: world size must be at least 2\n"); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int looping_count = 0; + + // Continue looping while we have not reached the limit + while (looping_count < LOOPING_LIMIT) { + MPI_Request req; + + // If process is NOT 0, receive the loop counter from your predecessor + if (world_rank != 0) { + MPI_Irecv(&looping_count, 1, MPI_INT, world_rank - 1, 0, + MPI_COMM_WORLD, &req); + + // Do some heavy computation here + sleep(1); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: received token from process %d\n", + world_rank, world_rank - 1); + } else { // Process 0 increments the loop counter + looping_count += 1; + } + + // Whichever process you are, send the loop counter + MPI_Isend(&looping_count, 1, MPI_INT, (world_rank + 1) % world_size, 0, + MPI_COMM_WORLD, &req); + + // Do some heavy computation here + sleep(2); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + + // If you are process 0, receive the loop counter from the last + // process in the world + if (world_rank == 0) { + MPI_Irecv(&looping_count, 1, MPI_INT, world_size - 1, 0, + MPI_COMM_WORLD, &req); + + // Do some heavy computation here + sleep(1); + + MPI_Wait(&req, MPI_STATUS_IGNORE); + printf("Process %d: received token from process %d, loop #%d\n", + world_rank, world_size - 1, looping_count); + } + } + + MPI_Finalize(); +} diff --git a/src/mpi_hello_world.c b/src/mpi_hello_world.c new file mode 100644 index 0000000..31e2b05 --- /dev/null +++ b/src/mpi_hello_world.c @@ -0,0 +1,21 @@ +#include +#include + +int main() { + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + char proc_name[MPI_MAX_PROCESSOR_NAME]; + int name_len; + MPI_Get_processor_name(proc_name, &name_len); + + printf("Hello from processor %s, rank %d out %d processors\n", + proc_name, + world_rank, + world_size); + + MPI_Finalize(); +} diff --git a/src/sync/mpi_p2p.c b/src/sync/mpi_p2p.c new file mode 100644 index 0000000..9d38be0 --- /dev/null +++ b/src/sync/mpi_p2p.c @@ -0,0 +1,30 @@ +#include +#include + +int main() { + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are only two processes running + if (world_size != 2) { + fprintf(stderr, "Error: world size is %d but must be 2\n", world_size); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int token = 0; + + // Send if you are process 0, receive if you are process 1 + if (world_rank == 0) { + token = 42; + printf("Process %d: sending tokenber `%d` to process 1\n", world_rank, token); + MPI_Send(&token, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); + } else if (world_rank == 1) { + MPI_Recv(&token, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + printf("Process %d: received tokenber `%d` from process 0\n", world_rank, token); + } + + MPI_Finalize(); +} diff --git a/src/sync/mpi_ping_pong.c b/src/sync/mpi_ping_pong.c new file mode 100644 index 0000000..0f35f5e --- /dev/null +++ b/src/sync/mpi_ping_pong.c @@ -0,0 +1,43 @@ +#include +#include + +const int PING_PONG_LIMIT = 16; + +int main() { + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are only two processes running + if (world_size != 2) { + fprintf(stderr, "Error: world size must be 2\n"); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int ping_pong_count = 0; + int partner_rank = (world_rank + 1) % 2; + + // Continue sending and receiving while we have not reached the limit + while (ping_pong_count < PING_PONG_LIMIT) { + if (world_rank == ping_pong_count % 2) { + ping_pong_count += 1; + MPI_Send(&ping_pong_count, 1, MPI_INT, partner_rank, 0, + MPI_COMM_WORLD); + printf("Process %d: ping `%d` process %d\n", + world_rank, + ping_pong_count, + partner_rank); + } else { + MPI_Recv(&ping_pong_count, 1, MPI_INT, partner_rank, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + printf("Process %d: pong `%d` process %d\n", + world_rank, + ping_pong_count, + partner_rank); + } + } + + MPI_Finalize(); +} diff --git a/src/sync/mpi_ring.c b/src/sync/mpi_ring.c new file mode 100644 index 0000000..afd8627 --- /dev/null +++ b/src/sync/mpi_ring.c @@ -0,0 +1,49 @@ +#include +#include + +const int LOOPING_LIMIT = 16; + +int main() +{ + MPI_Init(NULL, NULL); + + int world_size, world_rank; + MPI_Comm_size(MPI_COMM_WORLD, &world_size); + MPI_Comm_rank(MPI_COMM_WORLD, &world_rank); + + // Assert that there are at least two processes running + if (world_size < 2) { + fprintf(stderr, "Error: world size must be at least 2\n"); + MPI_Abort(MPI_COMM_WORLD, 1); + } + + int looping_count = 0; + + // Continue looping while we have not reached the limit + while (looping_count < LOOPING_LIMIT) { + // If process is NOT 0, receive the loop counter from your predecessor + if (world_rank != 0) { + MPI_Recv(&looping_count, 1, MPI_INT, world_rank - 1, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + printf("Process %d: received token from process %d\n", + world_rank, world_rank - 1); + } else { // Process 0 increments the loop counter + looping_count += 1; + } + + // Whichever process you are, send the loop counter + MPI_Send(&looping_count, 1, MPI_INT, (world_rank + 1) % world_size, + 0, MPI_COMM_WORLD); + + // If you are process 0, receive the loop counter from the last + // process in the world + if (world_rank == 0) { + MPI_Recv(&looping_count, 1, MPI_INT, world_size - 1, 0, + MPI_COMM_WORLD, MPI_STATUS_IGNORE); + printf("Process %d: received token from process %d, loop #%d\n", + world_rank, world_size - 1, looping_count); + } + } + + MPI_Finalize(); +}