Merge pull request #37 from fverdugo/francesc

Minor changes in MPI point to point notebook
This commit is contained in:
Francesc Verdugo 2024-08-22 10:33:25 +02:00 committed by GitHub
commit 4e6b7696f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -528,7 +528,7 @@
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>Note:</b> Function `mpiexec` provided by `MPI.jl` is a convenient way of accessing the `mpiexec` program that matches the MPI installation used my Julia.\n",
"<b>Note:</b> Function `mpiexec` provided by `MPI.jl` is a convenient way of accessing the `mpiexec` program that matches the MPI installation used by Julia.\n",
"</div>"
]
},
@ -974,7 +974,7 @@
"\n",
"\n",
"\n",
"`MPI_Send` is also often called a blocking send, but this is very misleading. `MPI_Send` might or not wait for a matching `MPI_Recv`. Look into the following example (which in fact is an incorrect MPI program)."
"`MPI_Send` is also often called a blocking send, but this is very misleading. `MPI_Send` might or not wait for a matching `MPI_Recv`. Assuming that `MPI_Send` will block waiting for a matching receive is erroneous. I.e., we cannot assume that `MPI_Send` has synchronization side effects with the receiver process. However, assuming that `MPI_Send` will not block is also erroneous. Look into the following example (which in fact is an incorrect MPI program). In contrast, `MPI_Send` guarantees that the send buffer can be reused when function returns (complete operation)."
]
},
{
@ -1047,7 +1047,7 @@
"source": [
"## Fixing cyclic dependencies\n",
"\n",
"Returning to the incorrect program seen before. Remember that on my laptop it worked for `n=1`, but it failed for `n=10000`. For `n=1` the MPI implementation provably decided that the best trade-off was to buffer the small message and return from the `MPI_Send` as soon as possible. For the large message (`n=10000`) it provably decided to wait for a matching receive in ordeer to avoid copying data. In this case, both ranks were blocked on the call to `MPI_Send` and the receives were never posted leading to a dead lock.\n",
"Returning to the incorrect program seen before. Remember that on my laptop it worked for `n=1`, but it failed for `n=10000`. For `n=1` the MPI implementation provably decided that the best trade-off was to buffer the small message and return from the `MPI_Send` as soon as possible. For the large message (`n=10000`) it provably decided to wait for a matching receive in order to avoid copying data. In this case, both ranks were blocked on the call to `MPI_Send` and the receives were never posted leading to a dead lock.\n",
"\n",
"We can fix these cyclic dependences by smartly ordering the sends and the receives:"
]
@ -1149,10 +1149,11 @@
"source": [
"### Standard mode\n",
"\n",
"* Function `MPI_Send` \n",
"* Function `MPI_Send`.\n",
"* Programmer cannot make any assumptions whether the message is buffered or not. This is up to the system.\n",
"* Assuming buffering might lead to incorrect programs (as the example above).\n",
"* Assuming that a matching receive started is also incorrect."
"* Assuming that `MPI_Send` does not block because it copies the message into a buffer is erroneous (as the example above).\n",
"* Assuming that `MPI_Send` will block waiting for a matching receive is also incorrect. \n",
"* Assuming that a matching receive started when `MPI_Send`returns is also incorrect (no synchronization guarantee)."
]
},
{
@ -1160,9 +1161,9 @@
"id": "014ef71f",
"metadata": {},
"source": [
"### Buffered\n",
"### Buffered mode\n",
"\n",
"* Function `MPI_Bsend`\n",
"* Function `MPI_Bsend`.\n",
"* Programmer provides additional internal buffer space with function `MPI_Buffer_attach`.\n",
"* `MPI_Bsend` completes when message is copied into a local buffer.\n",
"* Erroneous if buffer space is insufficient.\n",
@ -1174,12 +1175,12 @@
"id": "bf456a3e",
"metadata": {},
"source": [
"### Synchronous\n",
"### Synchronous mode\n",
"\n",
"* Function `MPI_Ssend`\n",
"* It waits for a matching receive and it is guaranteed that the receive started.\n",
"* No extra data copy\n",
"* Easy to get deadlocks\n",
"* Function `MPI_Ssend`.\n",
"* It waits for a matching receive and it is guaranteed that the receive started (synchronization guarantee).\n",
"* No extra data copy.\n",
"* Easy to get deadlocks.\n",
"* It can be started whether or not a matching receive was posted."
]
},
@ -1188,9 +1189,9 @@
"id": "a1bd72c3",
"metadata": {},
"source": [
"### Ready\n",
"### Ready mode\n",
"\n",
"* Function `MPI_Rsend`\n",
"* Function `MPI_Rsend`.\n",
"* It may be started only if the matching receive is already posted. This allows the underlying implementation to skip some operations, such as a [handshake](https://en.wikipedia.org/wiki/Handshake_(computing)).\n",
"* Erroneous if there is no matching receive yet.\n",
"* Otherwise, the same as the synchronous `MPI_Ssend`."
@ -1242,7 +1243,7 @@
"source": [
"## Incomplete operations\n",
"\n",
"Functions `MPI_Isend` and `MPI_Irecv` are *incomplete* operations, hence the `I` in `MPI_Isend` and `MPI_Irecv`. This means that, when these functions return, there is no guarantee that the underlying operation has finished. Function `MPI_Wait` should be used to wait for completion of the send and/or receive.\n",
"Functions `MPI_Isend` and `MPI_Irecv` are *incomplete* operations, hence the `I` in `MPI_Isend` and `MPI_Irecv`. This means that, when these functions return, there is no guarantee that the underlying operation has finished. Function `MPI_Wait` should be used to wait for completion of the send and/or receive.\n",
"\n",
"In particular:\n",
"\n",
@ -1354,7 +1355,7 @@
"\n",
"Remember that we used `MPI_Probe` to query for the size of an incoming message. However, if we use `MPI_Probe` we miss the opportunity to do local work before a matching send started, i.e. we cannot do latency hiding.\n",
"\n",
"This can be fixed using an `MPI_Iprobe`, i.e., an incomplete probe. It allows us to check for incoming messages without blocking.\n",
"This can be fixed using an `MPI_Iprobe`, i.e., an incomplete probe (aka a non-blocking probe). It allows us to check for incoming messages without blocking.\n",
"\n",
"In Julia:\n",
"```julia\n",
@ -1363,8 +1364,11 @@
"\n",
"In C:\n",
"```C\n",
"int MPI_Iprobe(int source, int tag, MPI_Comm comm, int *flag,\n",
" MPI_Status *status)\n"
"int MPI_Iprobe(int source, int tag, MPI_Comm comm, int *ismsg,\n",
" MPI_Status *status)\n",
"```\n",
"\n",
"Function `MPI_Iprobe` returns immediately without waiting for a matching send. The value of `ismsg` tells if a send was posted or not. If there was a send, the `status` object can be used to get information about the incoming message as we saw before."
]
},
{
@ -1464,7 +1468,7 @@
"source": [
"## Summary\n",
"\n",
"We have seen different ways of sending and receiving messages with MPI, each one with its pros and cons. You as user should decide which is the best option for a given problem. We also learned how to get information about an incoming messages with `MPI_Status`, `MPI_Probe`, and `MPI_Iprobe`. In addition, we saw how easy is to write incorrect programs if you do not follow the semantics of the MPI operations properly.\n"
"We have seen different ways of sending and receiving messages with MPI, each one with its pros and cons. You as user should decide which is the best option for a given problem. We also learned how to get information about an incoming messages with `MPI_Status`, `MPI_Probe`, and `MPI_Iprobe`. In addition, we saw how easy is to write incorrect programs with if you do not follow the semantics of the MPI operations properly.\n"
]
},
{