Skip to content

Commit

Permalink
fix missing brackets when naming functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Edward-RSE committed Jul 31, 2024
1 parent b3efa3a commit 7c37257
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 45 deletions.
14 changes: 7 additions & 7 deletions _episodes/02-mpi-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ It's important to note that these functions represent only a subset of the funct

In general, an MPI program follows a basic outline that includes the following steps:

1. ***Initialization:*** The MPI environment is initialized using the `MPI_Init` function. This step sets up the necessary communication infrastructure and prepares the program for message passing.
1. ***Initialization:*** The MPI environment is initialized using the `MPI_Init()` function. This step sets up the necessary communication infrastructure and prepares the program for message passing.
2. ***Communication:*** MPI provides functions for sending and receiving messages between processes. The `MPI_Send` function is used to send messages, while the `MPI_Recv` function is used to receive messages.
3. ***Termination:*** Once the necessary communication has taken place, the MPI environment is finalised using the `MPI_Finalize` function. This ensures the proper termination of the program and releases any allocated resources.
3. ***Termination:*** Once the necessary communication has taken place, the MPI environment is finalised using the `MPI_Finalize()` function. This ensures the proper termination of the program and releases any allocated resources.

## Getting Started with MPI: MPI on HPC

Expand Down Expand Up @@ -186,19 +186,19 @@ mpirun -n 4 ./hello_world

However, in the example above, the program does not know it was started by `mpirun`, and each copy just works as if they were the only one. For the copies to work together, they need to know about their role in the computation, in order to properly take advantage of parallelisation. This usually also requires knowing the total number of tasks running at the same time.

- The program needs to call the `MPI_Init` function.
- `MPI_Init` sets up the environment for MPI, and assigns a number (called the _rank_) to each process.
- At the end, each process should also cleanup by calling `MPI_Finalize`.
- The program needs to call the `MPI_Init()` function.
- `MPI_Init()` sets up the environment for MPI, and assigns a number (called the _rank_) to each process.
- At the end, each process should also cleanup by calling `MPI_Finalize()`.

~~~
int MPI_Init(&argc, &argv);
int MPI_Finalize();
~~~
{: .language-c}

Both `MPI_Init` and `MPI_Finalize` return an integer.
Both `MPI_Init()` and `MPI_Finalize()` return an integer.
This describes errors that may happen in the function.
Usually we will return the value of `MPI_Finalize` from the main function
Usually we will return the value of `MPI_Finalize()` from the main function

> ## I don't use command line arguments
>
Expand Down
46 changes: 23 additions & 23 deletions _episodes/04-point-to-point-communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,33 @@ objectives:
- Describe what is meant by point-to-point communication.
- Learn how to send and receive data between ranks.
keypoints:
- Use `MPI_Send` and `MPI_Recv` to send and receive data between ranks
- Using `MPI_SSend` will always block the sending rank until the message is received
- Using `MPI_Send` may block the sending rank until the message is received, depending on whether the message is buffered and the buffer is available for reuse
- Using `MPI_Recv` will always block the receiving rank until the message is received
- Use `MPI_Send()` and `MPI_Recv()` to send and receive data between ranks
- Using `MPI_Ssend()` will always block the sending rank until the message is received
- Using `MPI_Send()` may block the sending rank until the message is received, depending on whether the message is buffered and the buffer is available for reuse
- Using `MPI_Recv()` will always block the receiving rank until the message is received
---

In the previous episode we introduced the various types of communication in MPI.
In this section we will use the MPI library functions `MPI_Send` and
`MPI_Recv`, which employ point-to-point communication, to send data from one rank to another.
In this section we will use the MPI library functions `MPI_Send()` and
`MPI_Recv()`, which employ point-to-point communication, to send data from one rank to another.

<img src="fig/send-recv.png" alt="Sending data from one rank to another using MPI_SSend and MPI_Recv"/>

Let's look at how `MPI_Send` and `MPI_Recv`are typically used:
Let's look at how `MPI_Send()` and `MPI_Recv()`are typically used:

- Rank A decides to send data to rank B. It first packs the data to send into a buffer, from which it will be taken.
- Rank A then calls `MPI_Send` to create a message for rank B. The underlying MPI communication
- Rank A then calls `MPI_Send()` to create a message for rank B. The underlying MPI communication
is then given the responsibility of routing the message to the correct destination.
- Rank B must know that it is about to receive a message and acknowledge this
by calling `MPI_Recv`. This sets up a buffer for writing the incoming data when it arrives
by calling `MPI_Recv()`. This sets up a buffer for writing the incoming data when it arrives
and instructs the communication device to listen for the message.

As mentioned in the previous episode, `MPI_Send` and `MPI_Recv` are *synchronous* operations,
As mentioned in the previous episode, `MPI_Send()` and `MPI_Recv()` are *synchronous* operations,
and will not return until the communication on both sides is complete.

## Sending a Message: MPI_Send

The `MPI_Send` function is defined as follows:
The `MPI_Send()` function is defined as follows:

~~~c
int MPI_Send(
Expand Down Expand Up @@ -67,11 +67,11 @@ MPI_Send(message, 14, MPI_CHAR, 1, 0, MPI_COMM_WORLD);
So we are sending 14 elements of `MPI_CHAR` one time, and specified `0` for our message tag since we don't anticipate
having to send more than one type of message. This call is synchronous, and will block until the corresponding
`MPI_Recv` operation receives and acknowledges receipt of the message.
`MPI_Recv()` operation receives and acknowledges receipt of the message.
> ## MPI_Ssend: an Alternative to MPI_Send
>
> `MPI_Send` represents the "standard mode" of sending messages to other ranks, but some aspects of its behaviour
> `MPI_Send()` represents the "standard mode" of sending messages to other ranks, but some aspects of its behaviour
> are dependent on both the implementation of MPI being used, and the circumstances of its use: there are three
> scenarios to consider:
>
Expand All @@ -83,15 +83,15 @@ having to send more than one type of message. This call is synchronous, and will
> receive. It is dependent on the MPI implementation as to what scenario is selected, based on performance, memory,
> and other considerations.
>
> A very similar alternative to `MPI_Send` is to use `MPI_Ssend` - synchronous send - which ensures the communication
> A very similar alternative to `MPI_Send()` is to use `MPI_Ssend()` - synchronous send - which ensures the communication
> is both synchronous and blocking. This function guarantees that when it returns, the destination has categorically
> started receiving the message.
{: .callout}
## Receiving a Message: MPI_Recv
Conversely, the `MPI_Recv` function looks like the following:
Conversely, the `MPI_Recv()` function looks like the following:
~~~c
int MPI_Recv(
Expand Down Expand Up @@ -123,13 +123,13 @@ MPI_Recv(message, 14, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &status);
{: .language-c}

Here, we create our buffer to receive the data, as well as a variable to hold the exit status of the receive operation.
We then call `MPI_Recv`, specifying our returned data buffer, the number of elements we will receive (14) which will be
We then call `MPI_Recv()`, specifying our returned data buffer, the number of elements we will receive (14) which will be
of type `MPI_CHAR` and sent by rank 0, with a message tag of 0.
As with `MPI_Send`, this call will block - in this case until the message is received and acknowledgement is sent
As with `MPI_Send()`, this call will block - in this case until the message is received and acknowledgement is sent
to rank 0, at which case both ranks will proceed.

Let's put this together with what we've learned so far.
Here's an example program that uses `MPI_Send` and `MPI_Recv` to send the string `"Hello World!"`
Here's an example program that uses `MPI_Send()` and `MPI_Recv()` to send the string `"Hello World!"`
from rank 0 to rank 1:

~~~
Expand Down Expand Up @@ -212,7 +212,7 @@ int main(int argc, char **argv) {
> > ## Solution
> >
> > 1. The program will hang since it's waiting for a message with a tag that will never be sent (press `Ctrl-C` to kill
> > the hanging process). To resolve this, make the tag in `MPI_Recv` match the tag you specified in `MPI_Send`.
> > the hanging process). To resolve this, make the tag in `MPI_Recv()` match the tag you specified in `MPI_Send()`.
> > 2. You will likely see a message like the following:
> > ~~~
> > [...:220695] *** An error occurred in MPI_Recv
Expand Down Expand Up @@ -391,13 +391,13 @@ int main(int argc, char **argv) {
>
>> ## Solution
>>
>> `MPI_Send` will block execution until the receiving process has called
>> `MPI_Recv`. This prevents the sender from unintentionally modifying the message
>> `MPI_Send()` will block execution until the receiving process has called
>> `MPI_Recv()`. This prevents the sender from unintentionally modifying the message
>> buffer before the message is actually sent.
>> Above, both ranks call `MPI_Send` and just wait for the other to respond.
>> Above, both ranks call `MPI_Send()` and just wait for the other to respond.
>> The solution is to have one of the ranks receive its message before sending.
>>
>> Sometimes `MPI_Send` will actually make a copy of the buffer and return immediately.
>> Sometimes `MPI_Send()` will actually make a copy of the buffer and return immediately.
>> This generally happens only for short messages.
>> Even when this happens, the actual transfer will not start before the receive is posted.
>>
Expand Down
22 changes: 11 additions & 11 deletions _episodes/09-optimising-mpi.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ communication overhead typically increases with the number of
processes used.

> ## Testing Code Performance on SLURM
>
>
> We also need a way to test our code on our HPC infrastructure of choice.
> This will likely vary from system to system depending on your infrastructure configuration,
> but for the COSMA site on DiRAC this may look something like (replacing the `<account>`, `<queue>`,
> `<directory_containing_poisson_executable>` accordingly):
>
>
> ~~~
> #!/usr/bin/env bash
> #SBATCH --account=<account>
Expand All @@ -123,15 +123,15 @@ processes used.
> module unload gnu_comp
> module load gnu_comp/11.1.0
> module load openmpi/4.1.4
>
>
> time mpirun -n 1 poisson_mpi
> ~~~
> {: .language-bash}
>
>
> So here, after loading the required compiler and OpenMPI modules,
> we use the `time` command to output how long the process took to run for a given number of processors.
> and ensure we specify `ntasks` correctly as the required number of cores we wish to use.
>
>
> We can then submit this using `sbatch`, e.g. `sbatch poisson-mpi.sh`,
> with the output captured by default in a `slurm-....out` file
> which will include the time taken to run the program.
Expand Down Expand Up @@ -246,9 +246,9 @@ to exhibit *linear weak scaling*.
The other significant factors in the speed of a parallel program are
communication speed and latency.
Communication speed is determined by the amount of data one needs to
Communication speed is determined by the amount of data one needs to
send/receive, and the bandwidth of the underlying hardware for the communication.
Latency consists of the software latency (how long the
Latency consists of the software latency (how long the
operating system needs in order to prepare for a communication),
and the hardware latency (how long the hardware takes to
send/receive even a small bit of data).
Expand Down Expand Up @@ -410,19 +410,19 @@ spent in the actual compute sections of the code.
> ## Profile Your Poisson Code
>
> Compile, run and analyse your own MPI version of the poisson code.
>
>
> How closely does it match the performance above? What are the main differences?
> Try reducing the number of processes used, rerun and investigate the profile.
> Is it still MPI-bound?
>
> Is it still MPI-bound?
>
> Increase the problem size, recompile, rerun and investigate the profile.
> What has changed now?
{: .challenge}
> ## Iterative Improvement
>
> In the Poisson code, try changing the location of the calls
> to `MPI_Send`. How does this affect performance?
> to `MPI_Send()`. How does this affect performance?
>
{: .challenge}
Expand Down
8 changes: 4 additions & 4 deletions _episodes/11-advanced-communication.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ questions:
- How do I communicate non-contiguous data which isn't easy to express as a derived data type?
objectives:
- Know how to define and use derived datatypes to communicate structures
- Know how to use `MPI_Pack` and `MPI_Unpack` to communicate complex data structures
- Know how to use `MPI_Pack()` and `MPI_Unpack()` to communicate complex data structures
keypoints:
- Structures can be communicated easier by using `MPI_Type_create_struct` to create a derived type describing the structure
- The functions `MPI_Pack` and `MPI_Unpack` can be used to manually create a contiguous memory block of data, to communicate complex and/or heterogeneous data structures
- The functions `MPI_Pack()` and `MPI_Unpack()` can be used to manually create a contiguous memory block of data, to communicate complex and/or heterogeneous data structures
---

In an earlier episode, we introduced the concept of derived data types to send vectors or a sub-array of a larger array,
which may or may not be contiguous in memory. Other than vectors, there are multiple other types of derived data types
that allow us to handle other complex data structures efficiently. In this episode, we will see how to create structure
derived types. Additionally, we will also learn how to use `MPI_Pack` and `MPI_Unpack` to manually pack complex data
derived types. Additionally, we will also learn how to use `MPI_Pack()` and `MPI_Unpack()` to manually pack complex data
structures and heterogeneous into a single contiguous buffer, when other methods of communication are too complicated or
inefficient.

Expand All @@ -43,7 +43,7 @@ int MPI_Type_create_struct(
```

| `count`: | The number of fields in the struct |
| `*array_of_blocklengths`: | The length of each field, as you would use to send that field using `MPI_Send` |
| `*array_of_blocklengths`: | The length of each field, as you would use to send that field using `MPI_Send()` |
| `*array_of_displacements`: | The relative positions of each field in bytes |
| `*array_of_types`: | The MPI type of each field |
| `*newtype`: | The newly created data type for the struct |
Expand Down

0 comments on commit 7c37257

Please sign in to comment.