From c5f926830a72c3e9a81213ed35c1ec0422dda5e7 Mon Sep 17 00:00:00 2001 From: Arseniy Obolenskiy Date: Thu, 9 Oct 2025 11:23:57 +0200 Subject: [PATCH] Update 03-mpi-api - Remove mention of Boost MPI - Update AllGather operation slide\ - Update references --- 03-mpi-api/03-mpi-api.tex | 85 +++++++------------------------- 03-mpi-api/03-mpi-api.toc | 7 ++- 03-mpi-api/images/allgather.png | Bin 0 -> 8227 bytes 3 files changed, 21 insertions(+), 71 deletions(-) create mode 100644 03-mpi-api/images/allgather.png diff --git a/03-mpi-api/03-mpi-api.tex b/03-mpi-api/03-mpi-api.tex index 88220e2..1bce01d 100644 --- a/03-mpi-api/03-mpi-api.tex +++ b/03-mpi-api/03-mpi-api.tex @@ -71,61 +71,6 @@ \tableofcontents \end{frame} -\section{Boost.MPI} - -\begin{frame}{Boost.MPI} - Boost.MPI is a part of the Boost C++ libraries that provides C++ bindings for the Message Passing Interface (MPI). - - Boost.MPI makes it easier to write distributed applications in C++ by wrapping the complex MPI API with C++-friendly abstractions, improving safety and reducing the amount of boilerplate code. - - Key Features of Boost.MPI: - \begin{itemize} - \item Simplified use of MPI with C++ bindings. - \item Supports complex data types through Boost.Serialization. - \item Easier management of distributed tasks and communication. - \item Compatible with common MPI implementations like MPICH, OpenMPI, MS MPI, etc. - \end{itemize} - - Note: C API mappting ot Boost.MPI: \href{https://www.boost.org/doc/libs/1_86_0/doc/html/mpi/c_mapping.html}{link} - - {\footnotesize For more details see Boost.MPI docs: \href{https://www.boost.org/doc/libs/1_86_0/doc/html/mpi.html}{link}} -\end{frame} - -\begin{frame}[fragile]{Boost.MPI example} - \lstset{style=CStyle, caption=Hello World example with Boost MPI} - \begin{lstlisting} -#include -#include - -// Namespace alias for convenience -namespace mpi = boost::mpi; - -int main(int argc, char* argv[]) { - // Initialize the MPI environment - mpi::environment env(argc, argv); - mpi::communicator world; - - // Get the rank (ID) of the current process and the total number of processes - int rank = world.rank(); - int size = world.size(); - - if (rank == 0) { - // If this is the root process (rank 0), send a message to another process - std::string message = "Hello from process 0"; - world.send(1, 0, message); // Send to process 1 - std::cout << "Process 0 sent: " << message << std::endl; - } else if (rank == 1) { - // If this is process 1, receive the message - std::string received_message; - world.recv(0, 0, received_message); // Receive from process 0 - std::cout << "Process 1 received: " << received_message << std::endl; - } - - return 0; -} - \end{lstlisting} -\end{frame} - \section{Advanced Send/Receive API} \begin{frame}{Why Using \texttt{MPI\_Send} and \texttt{MPI\_Recv} Is Not Enough?} @@ -144,7 +89,6 @@ \section{Advanced Send/Receive API} { \footnotesize \texttt{int MPI\_Isend(const void *buf, int count, MPI\_Datatype datatype, int dest, int tag, MPI\_Comm comm, MPI\_Request *request);} \\ - \texttt{boost::mpi::request boost::mpi::communicator::isend(int dest, int tag, const T* values, int n);} } Parameters: @@ -167,7 +111,6 @@ \section{Advanced Send/Receive API} { \footnotesize \texttt{int MPI\_Irecv(void *buf, int count, MPI\_Datatype datatype, int source, int tag, MPI\_Comm comm, MPI\_Request *request);} \\ - \texttt{boost::mpi::request boost::mpi::communicator::irecv(int source, int tag, T\& value);} } Parameters: @@ -210,7 +153,6 @@ \section{Synchronization} { \footnotesize \texttt{int MPI\_Barrier(MPI\_Comm comm);} \\ - \texttt{void boost::mpi::communicator::barrier();} } Usage: @@ -252,7 +194,6 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Bcast(void *buffer, int count, MPI\_Datatype datatype, int root, MPI\_Comm comm);} \\ - \texttt{void broadcast(const communicator\& comm, T\& value, int root);} (needs \texttt{\#include }) } \begin{minipage}[t]{0.6\textwidth} @@ -282,7 +223,6 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Reduce(const void *sendbuf, void *recvbuf, int count, MPI\_Datatype datatype, MPI\_Op op, int root, MPI\_Comm comm);} \\ - \texttt{void reduce(const communicator\& comm, const T\& in\_value, T\& out\_value, Op op, int root);} (needs \texttt{\#include }) } \begin{minipage}[t]{0.2\textwidth} @@ -309,7 +249,6 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Gather(const void *sendbuf, int sendcount, MPI\_Datatype sendtype, void *recvbuf, int recvcount, MPI\_Datatype recvtype, int root, MPI\_Comm comm);} \\ - \texttt{void gather(const communicator\& comm, const T\& in\_value, std::vector\& out\_values, int root);} (needs \texttt{\#include }) } \begin{minipage}[t]{0.6\textwidth} @@ -334,7 +273,6 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Scatter(const void *sendbuf, int sendcount, MPI\_Datatype sendtype, void *recvbuf, int recvcount, MPI\_Datatype recvtype, int root, MPI\_Comm comm);} \\ - \texttt{void scatter(const communicator\& comm, const std::vector\& in\_values, T\& out\_value, int root);} (needs \texttt{\#include }) } \begin{minipage}[t]{0.6\textwidth} @@ -359,11 +297,25 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Allgather(const void *sendbuf, int sendcount, MPI\_Datatype sendtype, void *recvbuf, int recvcount, MPI\_Datatype recvtype, MPI\_Comm comm);} \\ - \texttt{void all\_gather(const communicator\& comm, const T\& in\_value, - std::vector\& out\_values);} (needs \texttt{\#include }) } - Usage of this function reduces the need for separate gather and broadcast operations. + \begin{minipage}[t]{0.6\textwidth} + Parameters: + \begin{itemize} + \item sendbuf: Starting address of send buffer + \item sendcount / sendtype: Number and type of elements contributed by each process + \item recvbuf: Starting address of receive buffer + \item recvcount / recvtype: Number and type of elements received from each process + \end{itemize} + \end{minipage} + \hfill + \begin{minipage}[t]{0.35\textwidth} + \begin{figure}[h] + \includegraphics[scale=0.6]{images/allgather.png} + \end{figure} + \end{minipage} + + {\footnotesize Source: \href{https://mpitutorial.com/tutorials/mpi-scatter-gather-and-allgather/}{https://mpitutorial.com/tutorials/mpi-scatter-gather-and-allgather/}} \end{frame} \begin{frame}{All-to-All (\texttt{MPI\_Alltoall})} @@ -372,7 +324,6 @@ \section{Collective operations} { \footnotesize \texttt{int MPI\_Alltoall(const void *sendbuf, int sendcount, MPI\_Datatype sendtype, void *recvbuf, int recvcount, MPI\_Datatype recvtype, MPI\_Comm comm);} \\ - \texttt{void all\_to\_all(const communicator\& comm, const std::vector\& in\_values, std::vector\& out\_values);} (needs \texttt{\#include }) } Note: This operation is communication-intensive. @@ -403,8 +354,8 @@ \section{Collective operations} \begin{frame}{References} \begin{enumerate} \item MPI Standard \href{https://www.mpi-forum.org/docs/}{https://www.mpi-forum.org/docs/} - \item Boost.MPI Chapter in Boost documentation \href{https://www.boost.org/doc/libs/1_86_0/doc/html/mpi.html}{https://www.boost.org/doc/libs/1\_86\_0/doc/html/mpi.html} \item Open MPI v4.0.7 documentation: \href{https://www.open-mpi.org/doc/v4.0/}{https://www.open-mpi.org/doc/v4.0/} + \item MPI Scatter, Gather, and Allgather: \href{https://mpitutorial.com/tutorials/mpi-scatter-gather-and-allgather/}{https://mpitutorial.com/tutorials/mpi-scatter-gather-and-allgather/} \end{enumerate} \end{frame} diff --git a/03-mpi-api/03-mpi-api.toc b/03-mpi-api/03-mpi-api.toc index fd298fb..77c4b37 100644 --- a/03-mpi-api/03-mpi-api.toc +++ b/03-mpi-api/03-mpi-api.toc @@ -1,4 +1,3 @@ -\beamer@sectionintoc {1}{Boost.MPI}{3}{0}{1} -\beamer@sectionintoc {2}{Advanced Send/Receive API}{6}{0}{2} -\beamer@sectionintoc {3}{Synchronization}{10}{0}{3} -\beamer@sectionintoc {4}{Collective operations}{13}{0}{4} +\beamer@sectionintoc {1}{Advanced Send/Receive API}{3}{0}{1} +\beamer@sectionintoc {2}{Synchronization}{7}{0}{2} +\beamer@sectionintoc {3}{Collective operations}{10}{0}{3} diff --git a/03-mpi-api/images/allgather.png b/03-mpi-api/images/allgather.png new file mode 100644 index 0000000000000000000000000000000000000000..f75064bf81b5cfc8f6aabd5d8525ad0e83c785fc GIT binary patch literal 8227 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 D)CiQn literal 0 HcmV?d00001