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