Up until now we looked at:

  • parallel / concurrent
  • fork-join / threads
  • oop on shared memory
  • locking / lock-free / transactional
  • semaphores / memory
    all of these assumed shared memory space

18.1 Distributed Memory

There are two alternatives:

  • functional programming
    • immutable state no synchronization required
  • message passing: isolated mutable state
    • state is mutable but not shared: each thread/task has it’s private state
    • tasks cooperate via message passing

18.1.1 Isolated Mutable State

Each thread has it’s own isolated mutable state. They then exchange messages:

Example the basic counter

With isolated mutability: each thread increments their own counter locally
if we want to get a global count

  • reduce function (map-reduce) gets the count from each and sums them up

Example Bank Account

Example bank account in a distributed memory model

18.1 Types of Messages

Types of messages

18.2 Message Parsing Interface (MPI)

We hide the hardware/software details behind a standardised software library.

  • MPI has C/C++, FORTRAN, etc… libraries

18.2.1 MPI Specifics

We arrange processes into communicators.

There is initially a COMM_WORLD communicator which is global.
We can then have communicators with only certain processes.

Example Different communicators

we can create copies of the communicators and / or create new ones.
Different communicators assign different ranks to processes.

  • So one process has multiple ranks (one for every communicator).

Code Example Access rank in a communicator

18.2.2 Single Program Multiple Data (SPMD)

we write a single program that has branching that differentiates each processes job based on their ranks.

  • they then work on different data

18.2.3 Communication

To communicate we use the Comm.send function. We send a certain memory region (offset = pointer, count (bytes to send, calculated with sizeof(type)).

Datatype: We can only send basic data types. To send a list, we need to “assemble” it ourselves on the other end.

Tag: integer marking a message with a certain type, to have different message types, handled differently.

Message Matching: how messages get to the right place

Receiving messages:

Note: there are special arguments to receive any message, any tag for catch-all.

Specifically Synchronous Message Passing (rarely used)

Asynchronous Send (usually used)

Note When we send very large messages it may be synchronous, when there is not enough memory.
1 GB messages will be stuck, because we need to get that memory somewhere to buffer the messages.

18.2.4 Blocking / Nonblocking

We can have different types of calls, depending on if we want to re-use the memory allocated for keeping the memory.

Blocking

  • when the MPI function returns, we are free to re-use the memory already sent

Non-blocking

  • we reserve the memory until completed and return immediately
  • this frees us up to do other work.
    but we may never modify that memory

We get a handle that we can use to test if message has been sent and buffer can be re-used.

Example Streaming

Using non-blocking sending, we can compute while sending has started streaming.

18.2.5 Summary

18.3 Deadlocks in MPI

When both processes send first, if we have insufficient storage at the destination
blocks
thus this could get stuck.

We need to order our operations more carefully.

or use special functions.

Even more solutions:

We use non-blocking operations on the data.
Since they immediately return, we can’t block.
always solves the deadlock problem.

18.4 MPI Recap