diff --git a/report/img/task2-cuda-run.png b/report/img/task2-cuda-run.png new file mode 100644 index 0000000..9c03cb4 Binary files /dev/null and b/report/img/task2-cuda-run.png differ diff --git a/report/img/task2-mpi-run.png b/report/img/task2-mpi-run.png new file mode 100644 index 0000000..266180a Binary files /dev/null and b/report/img/task2-mpi-run.png differ diff --git a/report/img/task2-sacct.png b/report/img/task2-sacct.png new file mode 100644 index 0000000..a70d795 Binary files /dev/null and b/report/img/task2-sacct.png differ diff --git a/report/img/task2-time-comparison.png b/report/img/task2-time-comparison.png new file mode 100644 index 0000000..6f7909f Binary files /dev/null and b/report/img/task2-time-comparison.png differ diff --git a/report/img/task2-wave-first-step.png b/report/img/task2-wave-first-step.png new file mode 100644 index 0000000..0d253da Binary files /dev/null and b/report/img/task2-wave-first-step.png differ diff --git a/report/img/task2-wave-fon.png b/report/img/task2-wave-fon.png new file mode 100644 index 0000000..3cb0786 Binary files /dev/null and b/report/img/task2-wave-fon.png differ diff --git a/report/img/task2-wave-last-step.png b/report/img/task2-wave-last-step.png new file mode 100644 index 0000000..fa502d6 Binary files /dev/null and b/report/img/task2-wave-last-step.png differ diff --git a/report/img/task2-wave-reverse.png b/report/img/task2-wave-reverse.png new file mode 100644 index 0000000..78812ac Binary files /dev/null and b/report/img/task2-wave-reverse.png differ diff --git a/report/img/task2-wave-third-step.png b/report/img/task2-wave-third-step.png new file mode 100644 index 0000000..d0c180d Binary files /dev/null and b/report/img/task2-wave-third-step.png differ diff --git a/report/refs.bib b/report/refs.bib index c13904e..62bb9f9 100755 --- a/report/refs.bib +++ b/report/refs.bib @@ -74,4 +74,14 @@ author={Chalmers, Noel and Kurzak, Jakub and McDougall, Damon and Bauman, Paul}, journal={arXiv preprint arXiv:2304.10397}, year={2023} +} + +@article{lee1961algorithm, + title={An Algorithm for Path Connections and Its Applications}, + author={Lee, C. Y.}, + journal={IRE Transactions on Electronic Computers}, + volume={EC-10}, + number={3}, + pages={346--365}, + year={1961} } \ No newline at end of file diff --git a/report/report.tex b/report/report.tex index d2c01cc..f1e03b0 100755 --- a/report/report.tex +++ b/report/report.tex @@ -172,6 +172,8 @@ \begin{center} \small{Санкт-Петербург, 2026} \end{center} \thispagestyle{empty} % выключаем отображение номера для этой страницы +\newpage +\tableofcontents \newpage \section*{Введение} @@ -368,10 +370,205 @@ Host polytech \subsection{Постановка задачи} +В рамках второго задания требовалось решить следующие задачи: +\begin{enumerate} + \item изучить технологию межпроцессного взаимодействия MPI; + \item разработать параллельный масштабируемый алгоритм для решения вычислительной задачи; + \item реализовать разработанный алгоритм с использованием технологии MPI; + \item исследовать производительность реализации на 1, 2 и 4-х узлах СКЦ <<Политехнический>>; + \item сравнить время решения вычислительной задачи с использованием MPI и CUDA. +\end{enumerate} + +В качестве вычислительной задачи использовалась задача построения пути движения робота по полигону, решавшаяся в предыдущем семестре с помощью CUDA. Для текущего задания тот же алгоритм был реализован на MPI и исследована масштабируемость при увеличении числа узлов. + +\subsection{Математическое описание} + + +Дано: +\begin{itemize} + \item массив $P$ --- полигон размера $n \times n$ целых чисел; + \item точки $P_1(x_1, y_1)$ и $P_2(x_2, y_2)$ на полигоне; + \item контуры $V$, запрещённые для движения (ячейки со значением $-1$). +\end{itemize} +Требуется построить $L$ --- кратчайшую траекторию, соединяющую $P_1$ и $P_2$, либо сообщить, что такой траектории не существует. Траектория не может проходить через ячейки со значением $-1$. + + +Для решения задачи используется волновой алгоритм \cite{lee1961algorithm}. Алгоритм состоит из двух фаз. + +\textbf{Фаза 1 --- распространение волны.} От начальной точки к конечной распространяется волна. На каждом шаге волна пополняется свободными ячейками, которые ещё не принадлежат волне и являются соседями ячеек, попавших в волну на предыдущем шаге. Для определения соседей используется окрестность фон Неймана (рис.~\ref{fig:task2-fon}): четыре ячейки сверху, снизу, слева и справа. Каждая новая ячейка заполняется значением +\begin{equation} + d_{i,j} = \min\bigl(d_{i,j},\; d_{i-1,j}+1,\; d_{i+1,j}+1,\; d_{i,j-1}+1,\; d_{i,j+1}+1\bigr), +\end{equation} +где $d_{i,j}$ --- текущее расстояние от стартовой точки до ячейки $(i,j)$. Ячейки с препятствиями ($P_{i,j} = -1$) игнорируются. + +\begin{figure}[H] + \centering + \includegraphics[width=0.15\textwidth]{img/task2-wave-fon.png} + \caption{Окрестность фон Неймана} + \label{fig:task2-fon} +\end{figure} + +Начальная ячейка $P_1$ заполняется нулём. Остальные свободные ячейки инициализируются значением $\infty$. Итерации продолжаются до тех пор, пока хотя бы одна ячейка обновляется. + +Иллюстрации первого, третьего и последнего шагов распространения волны приведены на рис.~\ref{fig:task2-first}--\ref{fig:task2-last}. + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{img/task2-wave-first-step.png} + \caption{Первый шаг распространения волны} + \label{fig:task2-first} +\end{figure} + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{img/task2-wave-third-step.png} + \caption{Третий шаг распространения волны} + \label{fig:task2-third} +\end{figure} + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{img/task2-wave-last-step.png} + \caption{Последний шаг распространения волны} + \label{fig:task2-last} +\end{figure} + +\textbf{Фаза 2 --- восстановление пути.} Из конечной ячейки $P_2$ к начальной $P_1$ выполняется обратный ход: на каждом шаге выбирается соседняя ячейка со значением на единицу меньше текущего (рис.~\ref{fig:task2-reverse}). + +\begin{figure}[H] + \centering + \includegraphics[width=0.4\textwidth]{img/task2-wave-reverse.png} + \caption{Восстановление пути} + \label{fig:task2-reverse} +\end{figure} + +\subsection{Особенности реализации MPI} + +Для распараллеливания волнового алгоритма с использованием MPI \cite{gropp2014mpi} применена декомпозиция полигона по строкам. Полигон размера $n \times n$ разбивается на горизонтальные полосы, каждая из которых обрабатывается отдельным MPI-процессом. При использовании $P$ процессов каждый из них получает примерно $n / P$ строк. + +Для корректной обработки граничных ячеек каждый процесс дополнительно хранит по одной теневой строке (ghost row) сверху и снизу, содержащей актуальные значения расстояний из соседних процессов. + +Алгоритм работы программы: +\begin{enumerate} + \item Процесс с рангом 0 генерирует полигон $P$. + \item Полигон рассылается всем процессам через \texttt{MPI\_Bcast}. + \item Массив расстояний $dist$ распределяется по процессам через \texttt{MPI\_Scatterv}. + \item Итеративно выполняется: + \begin{enumerate} + \item обмен теневыми строками с соседними процессами через \texttt{MPI\_Sendrecv}; + \item волновой шаг на локальных строках; + \item проверка глобальной сходимости через \texttt{MPI\_Allreduce} с операцией \texttt{MPI\_LOR}. + \end{enumerate} + \item Результат собирается на процессе 0 через \texttt{MPI\_Gatherv}. + \item Процесс 0 выводит длину пути и время выполнения (замер через \texttt{MPI\_Wtime}). +\end{enumerate} + +Полный текст MPI-реализации приведён в приложении Е. + +\subsection{CUDA-реализация для сравнения} + +Для сопоставления с MPI-реализацией подготовлена CUDA-версия того же волнового алгоритма, основанная на программе прошлого семестра. В отличие от исходной версии, размер полигона задаётся через аргумент командной строки (вместо \texttt{\#define}). Используется ядро с глобальной памятью: каждый поток обрабатывает несколько ячеек с шагом, равным общему числу потоков. Полный текст CUDA-реализации приведён в приложении Ж. + +\subsection{Сборка и запуск} + +Файлы для второго задания были размещены в каталоге \texttt{/home/ipmmstudy1/tm3u21/supercomputers/task2}. + +MPI-версия запускалась пакетным файлом \texttt{task2/scripts/run\_mpi.slurm} в разделе \texttt{tornado} на 1, 2 и 4 узлах. Для каждого запуска число узлов задавалось при отправке задачи: +\begin{verbatim} +sbatch --nodes=1 scripts/run_mpi.slurm +sbatch --nodes=2 scripts/run_mpi.slurm +sbatch --nodes=4 scripts/run_mpi.slurm +\end{verbatim} +Полный текст данного файла приведён в приложении З. + +CUDA-версия запускалась пакетным файлом \texttt{task2/scripts/run\_cuda.slurm} в разделе \texttt{tornado-k40} на одном узле. Полный текст приведён в приложении И. + +Обе программы последовательно запускались на полигонах размером $500$, $1000$, $2000$, $3000$, $5000$. + +\subsection{Результаты эксперимента} + +\begin{figure}[H] + \centering + \includegraphics[width=0.8\textwidth]{img/task2-mpi-run.png} + \caption{Терминальный вывод MPI-реализации волнового алгоритма} + \label{fig:task2-mpi-run} +\end{figure} + +\begin{figure}[H] + \centering + \includegraphics[width=0.8\textwidth]{img/task2-cuda-run.png} + \caption{Терминальный вывод CUDA-реализации волнового алгоритма} + \label{fig:task2-cuda-run} +\end{figure} + +\begin{figure}[H] + \centering + \includegraphics[width=0.8\textwidth]{img/task2-sacct.png} + \caption{Сведения Slurm о выполнении задач второго задания} + \label{fig:task2-sacct} +\end{figure} + +Для сравнения были использованы четыре фактических запуска на СКЦ <<Политехнический>>: CUDA-реализация (задание \texttt{6619329}, узел \texttt{n02p001}, раздел \texttt{tornado-k40}) и три запуска MPI-реализации на 1, 2 и 4 узлах (задания \texttt{6619332}, \texttt{6619333}, \texttt{6619334}, раздел \texttt{tornado}). + +Терминальный вывод MPI- и CUDA-реализаций приведён на рис.~\ref{fig:task2-mpi-run} и рис.~\ref{fig:task2-cuda-run}. Сведения Slurm о выполнении задач показаны на рис.~\ref{fig:task2-sacct}. + +Численные результаты сведены в таблицу~\ref{tab:task2-results}. + +\begin{table}[H] + \centering + \small + \caption{Сравнение времени выполнения MPI (1, 2, 4 узла) и CUDA} + \label{tab:task2-results} + \begin{tabular}{ccccccc} + \toprule + $n$ & $t_{\text{MPI-1}}$, мс & $t_{\text{MPI-2}}$, мс & $t_{\text{MPI-4}}$, мс & $t_{\text{CUDA}}$, мс & $S_2$ & $S_4$ \\ + \midrule + 500 & 30.60 & 14.94 & 17.85 & 48.89 & 2.05 & 1.71 \\ + 1000 & 191.56 & 97.56 & 55.69 & 283.94 & 1.96 & 3.44 \\ + 2000 & 1304.08 & 654.28 & 343.95 & 1949.41 & 1.99 & 3.79 \\ + 3000 & 4616.74 & 2363.96 & 1200.42 & 6372.31 & 1.95 & 3.85 \\ + 5000 & 19897.95 & 9960.07 & 5349.19 & 27251.48 & 2.00 & 3.72 \\ + \bottomrule + \end{tabular} +\end{table} + +В таблице $S_2 = t_{\text{MPI-1}} / t_{\text{MPI-2}}$ и $S_4 = t_{\text{MPI-1}} / t_{\text{MPI-4}}$ --- ускорение при увеличении числа узлов. + +\begin{figure}[H] + \centering + \imgplaceholder{img/task2-time-comparison.png} + \caption{Зависимость времени вычисления от размера полигона} + \label{fig:task2-time-comparison} +\end{figure} + +Графическое сравнение времени решения приведено на рис.~\ref{fig:task2-time-comparison}. Скрипт, использованный для построения данного графика, приведён в приложении К. + +По полученным результатам можно сделать следующие наблюдения: +\begin{itemize} + \item при увеличении числа узлов с 1 до 2 наблюдается практически линейное ускорение: $S_2$ составляет от $1{,}95$ до $2{,}05$ на всех размерах задач; + \item при увеличении числа узлов до 4 ускорение $S_4$ зависит от размера задачи: для $n = 500$ оно составляет лишь $1{,}71$ из-за доминирования накладных расходов на обмен, а для $n \ge 1000$ достигает $3{,}44$--$3{,}85$; + \item MPI-реализация на одном узле оказывается быстрее CUDA-реализации на всех размерах задач: например, при $n = 5000$ MPI выполняется за $19{,}9$ с, а CUDA --- за $27{,}3$ с; + \item различие в производительности объясняется тем, что при последовательном обходе строк в MPI-версии волна может распространяться на несколько ячеек за одну итерацию, тогда как в CUDA все ячейки обрабатываются параллельно и за одну итерацию волна продвигается не более чем на одну ячейку (для $n = 5000$: 166 итераций MPI против 7541 итерации CUDA); + \item на 4 узлах MPI-реализация работает в $3{,}7$--$5{,}1$ раза быстрее CUDA. +\end{itemize} + +\subsection{Выводы} + +В рамках второго задания была реализована MPI-версия волнового алгоритма для поиска кратчайшего пути на полигоне. Программа использует декомпозицию по строкам с обменом теневых строк между соседними процессами. Для сравнения подготовлена CUDA-реализация того же алгоритма, основанная на программе прошлого семестра. + +Экспериментальные запуски показали, что MPI-реализация хорошо масштабируется: при переходе с 1 на 2 узла достигается практически линейное ускорение ($S_2 \approx 2{,}0$), а на 4 узлах --- ускорение до $3{,}85$ раза при достаточном размере задачи ($n \ge 1000$). MPI-реализация даже на одном узле оказалась быстрее CUDA благодаря меньшему числу итераций алгоритма. + + \newpage \section*{Заключение} \addcontentsline{toc}{section}{Заключение} +В рамках данной работы были выполнены два практических задания, направленных на изучение технологий высокопроизводительных вычислений. + +В первом задании была разработана собственная CUDA-реализация LINPACK-подобного теста и проведено сравнение с эталонным Intel LINPACK. Собственная реализация показала меньшее время решения на всех исследованных размерах задач с ускорением от $2{,}00$ до $6{,}72$ раза. + +Во втором задании волновой алгоритм поиска пути был реализован с использованием MPI и исследована его масштабируемость при запуске на 1, 2 и 4 узлах СКЦ <<Политехнический>>. MPI-реализация продемонстрировала хорошую масштабируемость: ускорение при переходе с 1 на 2 узла составило около $2{,}0$, а на 4 узлах --- до $3{,}85$ раза. При сравнении с CUDA MPI-версия оказалась быстрее на всех размерах задач благодаря меньшему числу итераций при последовательном обходе строк. + \newpage \printbibliography[heading=bibintoc] @@ -405,5 +602,35 @@ Host polytech \label{app:task1-plot-script} \lstinputlisting[caption={Файл \texttt{task1/scripts/plot\_task1\_results.py}}, label={lst:task1-plot-script}]{../task1/scripts/plot_task1_results.py} +\newpage +\section*{Приложение Е} +\addcontentsline{toc}{section}{Приложение Е} +\label{app:task2-mpi-source} +\lstinputlisting[language=C, caption={Файл \texttt{task2/src/wave\_mpi.c}}, label={lst:task2-mpi}]{../task2/src/wave_mpi.c} + +\newpage +\section*{Приложение Ж} +\addcontentsline{toc}{section}{Приложение Ж} +\label{app:task2-cuda-source} +\lstinputlisting[language=C, caption={Файл \texttt{task2/src/wave\_cuda.cu}}, label={lst:task2-cuda}]{../task2/src/wave_cuda.cu} + +\newpage +\section*{Приложение З} +\addcontentsline{toc}{section}{Приложение З} +\label{app:task2-mpi-slurm} +\lstinputlisting[language=bash, caption={Файл \texttt{task2/scripts/run\_mpi.slurm}}, label={lst:task2-mpi-slurm}]{../task2/scripts/run_mpi.slurm} + +\newpage +\section*{Приложение И} +\addcontentsline{toc}{section}{Приложение И} +\label{app:task2-cuda-slurm} +\lstinputlisting[language=bash, caption={Файл \texttt{task2/scripts/run\_cuda.slurm}}, label={lst:task2-cuda-slurm}]{../task2/scripts/run_cuda.slurm} + +\newpage +\section*{Приложение К} +\addcontentsline{toc}{section}{Приложение К} +\label{app:task2-plot-script} +\lstinputlisting[language=python, caption={Файл \texttt{task2/scripts/plot\_task2\_results.py}}, label={lst:task2-plot}]{../task2/scripts/plot_task2_results.py} + \end{document} diff --git a/task2/.gitignore b/task2/.gitignore index 33517b1..f4067aa 100644 --- a/task2/.gitignore +++ b/task2/.gitignore @@ -1,4 +1,5 @@ bin/ +.venv/ results/*.out results/*.err results/*.csv