Compare commits
10 Commits
adbad08ae2
...
6207d591cf
| Author | SHA1 | Date | |
|---|---|---|---|
| 6207d591cf | |||
| bbb1b91e95 | |||
| 6f718668c8 | |||
| 32f8e92bcb | |||
| a9369a1801 | |||
| 0ea5f54194 | |||
| 0286361343 | |||
| 05f05a8a96 | |||
| 23ac524a4f | |||
| 9f6793616a |
BIN
report/img/task1-cuda-node.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
report/img/task1-cuda-run.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
report/img/task1-cuda-sacct.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
report/img/task1-gflops-comparison.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
report/img/task1-intel-run.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
report/img/task1-intel-sacct.png
Normal file
|
After Width: | Height: | Size: 141 KiB |
BIN
report/img/task1-login.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
report/img/task1-time-comparison.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
report/img/task2-cuda-run.png
Normal file
|
After Width: | Height: | Size: 358 KiB |
BIN
report/img/task2-mpi-run.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
report/img/task2-sacct.png
Normal file
|
After Width: | Height: | Size: 412 KiB |
BIN
report/img/task2-time-comparison.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
report/img/task2-wave-first-step.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
report/img/task2-wave-fon.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
report/img/task2-wave-last-step.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
report/img/task2-wave-reverse.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
report/img/task2-wave-third-step.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
@@ -75,3 +75,13 @@
|
|||||||
journal={arXiv preprint arXiv:2304.10397},
|
journal={arXiv preprint arXiv:2304.10397},
|
||||||
year={2023}
|
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}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
\usepackage[utf8]{inputenc}
|
\usepackage[utf8]{inputenc}
|
||||||
\usepackage[russian]{babel}
|
\usepackage[russian]{babel}
|
||||||
\usepackage{amsmath}
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amssymb}
|
||||||
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
||||||
\usepackage{ragged2e} %для растягивания по ширине
|
\usepackage{ragged2e} %для растягивания по ширине
|
||||||
\usepackage{setspace} %для межстрочного интервала
|
\usepackage{setspace} %для межстрочного интервала
|
||||||
@@ -53,6 +54,9 @@
|
|||||||
\usepackage{enumitem} %для перечислений
|
\usepackage{enumitem} %для перечислений
|
||||||
|
|
||||||
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
|
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
|
||||||
|
\newcommand{\imgplaceholder}[2][0.9\textwidth]{%
|
||||||
|
\includegraphics[width=#1]{#2}%
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
|
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
|
||||||
@@ -168,6 +172,8 @@
|
|||||||
\begin{center} \small{Санкт-Петербург, 2026} \end{center}
|
\begin{center} \small{Санкт-Петербург, 2026} \end{center}
|
||||||
\thispagestyle{empty} % выключаем отображение номера для этой страницы
|
\thispagestyle{empty} % выключаем отображение номера для этой страницы
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\tableofcontents
|
||||||
|
|
||||||
\newpage
|
\newpage
|
||||||
\section*{Введение}
|
\section*{Введение}
|
||||||
@@ -183,27 +189,448 @@
|
|||||||
|
|
||||||
Все вычислительные эксперименты проводились на вычислительном кластере Суперкомпьютерного центра Политехнического университета (СКЦ Политехнический). Доступ к вычислительным ресурсам осуществлялся через удалённое подключение по протоколу SSH. Для выполнения экспериментов использовалась учётная запись \texttt{tm3u21}, вход на кластер производился через узел доступа \texttt{login1.hpc.spbstu.ru}.
|
Все вычислительные эксперименты проводились на вычислительном кластере Суперкомпьютерного центра Политехнического университета (СКЦ Политехнический). Доступ к вычислительным ресурсам осуществлялся через удалённое подключение по протоколу SSH. Для выполнения экспериментов использовалась учётная запись \texttt{tm3u21}, вход на кластер производился через узел доступа \texttt{login1.hpc.spbstu.ru}.
|
||||||
|
|
||||||
Полученные в ходе работы результаты позволяют оценить влияние различных технологий параллельных вычислений на производительность алгоритмов решения вычислительных задач и сравнить эффективность использования различных вычислительных архитектур.
|
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
\section{Задача 1}
|
\section{Задача 1}
|
||||||
|
|
||||||
\subsection{Постановка задачи}
|
\subsection{Постановка задачи}
|
||||||
|
|
||||||
|
В рамках первого задания требовалось исследовать производительность вычислительной системы при решении плотной системы линейных алгебраических уравнений и сопоставить результаты собственной реализации с эталонным тестом LINPACK. Для достижения этой цели были поставлены следующие задачи:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item изучить подходы к оценке производительности вычислительных систем;
|
||||||
|
\item разработать собственную CUDA-реализацию LINPACK-подобного теста;
|
||||||
|
\item выполнить экспериментальные запуски на вычислительном узле СКЦ Политехнический;
|
||||||
|
\item сравнить результаты собственной программы и стандартного Intel LINPACK.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Фрагмент файла \texttt{\~{}/.ssh/config}}, label={lst:ssh-config}]
|
||||||
|
Host polytech
|
||||||
|
HostName login1.hpc.spbstu.ru
|
||||||
|
User tm3u21
|
||||||
|
IdentityFile ~/.ssh/09
|
||||||
|
ForwardAgent yes
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
Для подключения к СКЦ Политехнический в файл \texttt{\~{}/.ssh/config} была добавлена запись, приведённая в листинге~\ref{lst:ssh-config}. После этого вход на кластер выполнялся командой \texttt{ssh polytech}. Подтверждение входа под учётной записью \texttt{tm3u21} приведено на рис.~\ref{fig:task1-login}.
|
||||||
|
|
||||||
|
\subsection{Математическое описание}
|
||||||
|
|
||||||
|
Тест LINPACK применяется для оценки производительности вычислительных систем при решении плотной системы линейных уравнений \cite{dongarra2003linpack}. Рассматривается задача
|
||||||
|
\begin{equation}
|
||||||
|
A x = b,
|
||||||
|
\end{equation}
|
||||||
|
где $A \in \mathbb{R}^{n \times n}$ --- плотная квадратная матрица, $x \in \mathbb{R}^{n}$ --- искомый вектор решения, $b \in \mathbb{R}^{n}$ --- вектор правой части.
|
||||||
|
|
||||||
|
В классическом варианте LINPACK обычно применяются прямые методы, основанные на LU-разложении \cite{golub2013matrix}. В собственной реализации для первого задания использован итерационный метод Якоби, так как он хорошо распараллеливается на GPU: каждая строка матрицы может обрабатываться независимо \cite{saad2003iterative}.
|
||||||
|
|
||||||
|
Для метода Якоби очередное приближение вычисляется по формуле
|
||||||
|
\begin{equation}
|
||||||
|
x_i^{(k+1)} = \frac{1}{a_{ii}} \left( b_i - \sum\limits_{j \ne i} a_{ij} x_j^{(k)} \right), \quad i = 1, 2, \dots, n.
|
||||||
|
\end{equation}
|
||||||
|
|
||||||
|
Критерий остановки выбран в виде
|
||||||
|
\begin{equation}
|
||||||
|
\max_i \left| x_i^{(k+1)} - x_i^{(k)} \right| \le \varepsilon.
|
||||||
|
\end{equation}
|
||||||
|
|
||||||
|
Чтобы обеспечить сходимость метода, в программе формируется строго диагонально доминирующая матрица. Для контроля качества решения дополнительно вычисляются:
|
||||||
|
\begin{equation}
|
||||||
|
\| A x - b \|_{\infty},
|
||||||
|
\end{equation}
|
||||||
|
\begin{equation}
|
||||||
|
\| x - x_{true} \|_{\infty},
|
||||||
|
\end{equation}
|
||||||
|
где $x_{true}$ --- заранее известное опорное решение, использованное при построении тестовой системы.
|
||||||
|
|
||||||
|
Для приближённой оценки производительности используется LINPACK-подобная метрика
|
||||||
|
\begin{equation}
|
||||||
|
R = \frac{\frac{2}{3} n^3}{t},
|
||||||
|
\end{equation}
|
||||||
|
где $t$ --- время решения, измеренное в секундах. В отчёте далее используется величина $R$ в GFLOPS.
|
||||||
|
|
||||||
|
\subsection{Особенности реализации}
|
||||||
|
|
||||||
|
Собственная программа реализована на CUDA C++ и находится в файле \texttt{task1/src/main.cu}. В реализации приняты следующие решения:
|
||||||
|
\begin{itemize}
|
||||||
|
\item для каждой строки матрицы запускается отдельный поток CUDA, который вычисляет новое значение одной компоненты вектора решения;
|
||||||
|
\item матрица $A$ и вектор $b$ один раз копируются на устройство, а далее итерационный процесс выполняется на GPU;
|
||||||
|
\item завершение итераций контролируется через флаг \texttt{converged}, который может быть сброшен любым потоком, если изменение соответствующей компоненты превысило $\varepsilon$;
|
||||||
|
\item после завершения расчёта на стороне CPU вычисляются невязка и ошибка относительно заранее известного решения;
|
||||||
|
\item для удобства дальнейшего анализа программа может сохранять результаты в CSV-файл.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
Такой вариант не является буквальной реализацией классического LU-LINPACK, однако решает ту же прикладную задачу --- измерение производительности при решении плотной СЛАУ --- и позволяет исследовать эффективность распараллеливания на GPU.
|
||||||
|
Полный текст исходного кода CUDA-реализации приведён в приложении А.
|
||||||
|
|
||||||
|
\subsection{Сборка и запуск}
|
||||||
|
|
||||||
|
Подготовленные файлы для выполнения задания были размещены в каталоге \texttt{/home/ipmmstudy1/tm3u21/supercomputers/task1}. Запуск собственной CUDA-реализации выполнялся пакетным файлом \texttt{task1/scripts/run\_cuda.slurm}, который загружал модули \texttt{compiler/gcc/11} и \texttt{nvidia/cuda/11.6u2}, собирал программу через \texttt{nvcc} и запускал серию вычислительных экспериментов. Полный текст данного файла приведён в приложении Б.
|
||||||
|
|
||||||
|
Эталонный CPU-вариант Intel LINPACK запускался из архива Intel oneMKL Benchmarks Suite for Linux, предварительно распакованного в каталог \\ \texttt{/home/ipmmstudy1/tm3u21/LINPACK}. В результате файл \texttt{xlinpack\_xeon64} был получен по пути \texttt{LINPACK/benchmarks\_2025.3/linux/share/mkl/benchmarks/linpack}. Для запуска использовался пакетный файл \texttt{task1/scripts/run\_intel\_linpack.slurm}, полный текст которого приведён в приложении В.
|
||||||
|
|
||||||
|
Для Intel LINPACK был подготовлен отдельный входной файл \texttt{lininput\_report\_xeon64}, в котором зафиксированы те же размеры задач, что и для CUDA-реализации: $1000$, $1500$, $2000$, $2500$, $3000$, $3500$. Полный текст этого файла приведён в приложении Г.
|
||||||
|
|
||||||
|
\subsection{Результаты эксперимента}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-login.png}
|
||||||
|
\caption{Подключение к СКЦ Политехнический под учётной записью \texttt{tm3u21}}
|
||||||
|
\label{fig:task1-login}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-cuda-node.png}
|
||||||
|
\caption{Конфигурация узла и графического ускорителя, использованных для CUDA-эксперимента}
|
||||||
|
\label{fig:task1-cuda-node}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-cuda-run.png}
|
||||||
|
\caption{Терминальный вывод собственной CUDA-реализации теста}
|
||||||
|
\label{fig:task1-cuda-run}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-cuda-sacct.png}
|
||||||
|
\caption{Сведения Slurm о выполнении собственной CUDA-реализации}
|
||||||
|
\label{fig:task1-cuda-sacct}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-intel-run.png}
|
||||||
|
\caption{Терминальный вывод стандартного Intel LINPACK}
|
||||||
|
\label{fig:task1-intel-run}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-intel-sacct.png}
|
||||||
|
\caption{Сведения Slurm о выполнении Intel LINPACK}
|
||||||
|
\label{fig:task1-intel-sacct}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Для сравнения были использованы два фактических запуска на СКЦ Политехнический: собственная CUDA-реализация (задание \texttt{6616336}, узел \texttt{n02p009}, раздел \texttt{tornado-k40}) и стандартный Intel LINPACK (задание \texttt{6616818}, узел \texttt{n01p090}, раздел \texttt{tornado}). Для Intel LINPACK использовался отдельный входной файл, содержащий те же размеры задач, что и в CUDA-реализации.
|
||||||
|
|
||||||
|
Подключение к кластеру под учётной записью \texttt{tm3u21} показано на рис.~\ref{fig:task1-login}. Конфигурация вычислительного узла и графических ускорителей, задействованных при CUDA-эксперименте, приведена на рис.~\ref{fig:task1-cuda-node}. Терминальный вывод собственной CUDA-реализации и сведения Slurm о её выполнении приведены на рис.~\ref{fig:task1-cuda-run} и рис.~\ref{fig:task1-cuda-sacct}. Аналогичные материалы для эталонного CPU-запуска Intel LINPACK приведены на рис.~\ref{fig:task1-intel-run} и рис.~\ref{fig:task1-intel-sacct}.
|
||||||
|
|
||||||
|
Численные результаты сведены в таблицу \ref{tab:task1-results}. Для столбцов CUDA использованы значения из файла \texttt{results/task1-cuda-6616336.csv}. Для Intel LINPACK в таблицу внесены минимальное время из серии прогонов и максимальная производительность из секции \texttt{Performance Summary}.
|
||||||
|
|
||||||
|
\begin{table}[H]
|
||||||
|
\centering
|
||||||
|
\small
|
||||||
|
\caption{Сравнение собственной CUDA-реализации и Intel LINPACK}
|
||||||
|
\label{tab:task1-results}
|
||||||
|
\begin{tabular}{cccccccc}
|
||||||
|
\toprule
|
||||||
|
$N$ & $t_{CUDA}$, мс & Iter & $\|Ax-b\|_{\infty}$ & $R_{CUDA}$, GFLOPS & $t_{Intel}$, c & $R_{Intel}$, GFLOPS & $S_t$ \\
|
||||||
|
\midrule
|
||||||
|
1000 & 5.0083 & 6 & $2.242 \cdot 10^{-6}$ & 133.114 & 0.010 & 67.331 & 2.00 \\
|
||||||
|
1500 & 7.4931 & 6 & $1.107 \cdot 10^{-6}$ & 300.277 & 0.020 & 114.360 & 2.67 \\
|
||||||
|
2000 & 8.3563 & 5 & $2.443 \cdot 10^{-5}$ & 638.244 & 0.028 & 193.276 & 3.35 \\
|
||||||
|
2500 & 10.4837 & 5 & $1.593 \cdot 10^{-5}$ & 993.608 & 0.042 & 250.731 & 4.01 \\
|
||||||
|
3000 & 12.6709 & 5 & $1.288 \cdot 10^{-5}$ & 1420.573 & 0.069 & 260.451 & 5.45 \\
|
||||||
|
3500 & 14.8861 & 5 & $9.516 \cdot 10^{-6}$ & 1920.138 & 0.100 & 286.264 & 6.72 \\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
|
\begin{figure}[H]
|
||||||
|
\centering
|
||||||
|
\imgplaceholder{img/task1-time-comparison.png}
|
||||||
|
\caption{Сравнение времени решения эталонной и собственной реализаций}
|
||||||
|
\label{fig:task1-time-comparison}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Графическое сравнение времени решения приведено на рис.~\ref{fig:task1-time-comparison}. Скрипт, использованный для построения данного графика, приведён в приложении Д.
|
||||||
|
|
||||||
|
По полученным результатам можно сделать следующие наблюдения:
|
||||||
|
\begin{itemize}
|
||||||
|
\item все тестовые случаи для размеров от $1000$ до $3500$ успешно сошлись за $5$--$6$ итераций;
|
||||||
|
\item время решения возрастает плавно: от $5.0083$ мс при $N = 1000$ до $14.8861$ мс при $N = 3500$;
|
||||||
|
\item ошибка по известному решению остаётся на уровне порядка $10^{-9}$--$10^{-8}$, а невязка --- на уровне $10^{-6}$--$10^{-5}$, что подтверждает корректность полученного решения;
|
||||||
|
\item на всех рассмотренных размерах задач собственная реализация работает быстрее Intel LINPACK, а выигрыш по времени возрастает от $2.00$ до $6.72$ раза.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
\subsection{Выводы}
|
\subsection{Выводы}
|
||||||
|
|
||||||
|
В рамках первого задания была подготовлена собственная CUDA-реализация LINPACK-подобного теста для решения плотной СЛАУ методом Якоби. Программа поддерживает запуск серии экспериментов, измерение времени выполнения, а также вычисление невязки и ошибки относительно известного точного решения.
|
||||||
|
|
||||||
|
Дополнительно были подготовлены пакетные файлы запуска для собственной CUDA-реализации и для стандартного Intel LINPACK. Полные тексты этих файлов приведены в приложениях Б и В.
|
||||||
|
|
||||||
|
На текущем этапе можно зафиксировать, что собственная CUDA-реализация корректно работает на узле \texttt{tornado-k40} и обеспечивает сходимость на всём исследованном диапазоне размеров. В сопоставлении со стандартным Intel LINPACK на CPU она показывает меньшее время решения на всех исследованных размерах: собственная реализация оказывается более производительной, а выигрыш по времени возрастает от $2.00$ раза при $N = 1000$ до $6.72$ раза при $N = 3500$.
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
\section{Задача 2}
|
\section{Задача 2}
|
||||||
|
|
||||||
\subsection{Постановка задачи}
|
\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
|
\newpage
|
||||||
\section*{Заключение}
|
\section*{Заключение}
|
||||||
\addcontentsline{toc}{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
|
\newpage
|
||||||
\printbibliography[heading=bibintoc]
|
\printbibliography[heading=bibintoc]
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Приложение А}
|
||||||
|
\addcontentsline{toc}{section}{Приложение А}
|
||||||
|
\label{app:task1-cuda-source}
|
||||||
|
\lstinputlisting[caption={Файл \texttt{task1/src/main.cu}}, label={lst:task1-main}]{../task1/src/main.cu}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Приложение Б}
|
||||||
|
\addcontentsline{toc}{section}{Приложение Б}
|
||||||
|
\label{app:task1-cuda-slurm}
|
||||||
|
\lstinputlisting[caption={Файл \texttt{task1/scripts/run\_cuda.slurm}}, label={lst:task1-cuda-slurm}]{../task1/scripts/run_cuda.slurm}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Приложение В}
|
||||||
|
\addcontentsline{toc}{section}{Приложение В}
|
||||||
|
\label{app:task1-intel-slurm}
|
||||||
|
\lstinputlisting[caption={Файл \texttt{task1/scripts/run\_intel\_linpack.slurm}}, label={lst:task1-intel-slurm}]{../task1/scripts/run_intel_linpack.slurm}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Приложение Г}
|
||||||
|
\addcontentsline{toc}{section}{Приложение Г}
|
||||||
|
\label{app:task1-intel-input}
|
||||||
|
\lstinputlisting[caption={Файл \texttt{task1/intel/lininput\_report\_xeon64}}, label={lst:task1-intel-input}]{../task1/intel/lininput_report_xeon64}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Приложение Д}
|
||||||
|
\addcontentsline{toc}{section}{Приложение Д}
|
||||||
|
\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}
|
\end{document}
|
||||||
60
task1.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
# Задание 1
|
||||||
|
## Постановка задачи
|
||||||
|
В рамках данной работы необходимо:
|
||||||
|
### 1. Изучить методы определения производительности вычислительных систем.
|
||||||
|
### 2. Разработать собственную реализацию теста LINPACK, применив уникальный метод распараллеливания вычислений.
|
||||||
|
### 3. С помощью собственной реализации и стандартного теста LINPACK исследовать производительность узла СКЦ .Политехнический.
|
||||||
|
|
||||||
|
Математическое описание
|
||||||
|
### 2.1 Задача решения СЛАУ
|
||||||
|
Дано:
|
||||||
|
a11x1 + a12x2 + ・ ・ ・ + a1nxN = b1
|
||||||
|
a21x1 + a22x2 + ・ ・ ・ + a2nxN = b2
|
||||||
|
. . .
|
||||||
|
aN1x1 + am2x2 + ・ ・ ・ + amnxN = bN
|
||||||
|
Ax = b — система N линейных уравнений с N неизвестными, где:
|
||||||
|
– A — матрица N × N коэффициентов
|
||||||
|
– x — вектор N неизвестных
|
||||||
|
– b — вектор N свободных членов уравнений
|
||||||
|
Найти:
|
||||||
|
Элементы вектора x, при которых |Ax − b| ≤ ϵ, где ϵ — точность найденного
|
||||||
|
решения.
|
||||||
|
Численные методы решения СЛАУ
|
||||||
|
Для решения СЛАУ применяют в основном два класса методов: прямые (выполняемые за заранее известное количество действий) и итерационные (обеспечивающие постепенную сходимость к корню уравнения, зависящую от многих
|
||||||
|
факторов).
|
||||||
|
### 2.2.1 Прямые методы
|
||||||
|
Прямые методы используют конечные соотношения (формулы) для вычисления неизвестных. К ним относятся: метод Гаусса, Крамера, метод прогонки и др.
|
||||||
|
Достоинства:
|
||||||
|
– дают решение после выполнения заранее известного числа операций
|
||||||
|
– сравнительно просты и наиболее универсальны (пригодны для решения широкого класса линейных систем).
|
||||||
|
Недостатки:
|
||||||
|
– необходимость хранения в оперативной памяти компьютера сразу всей матрицы,
|
||||||
|
– накапливание погрешностей в процессе решения.
|
||||||
|
В связи с этим прямые методы используют обычно для не слишком больших
|
||||||
|
(N ≤ 200) систем с плотно заполненной матрицей и не близким к нулю определителем. Для больших N используются итерационные методы.
|
||||||
|
### 2.2.2 Итерационные методы
|
||||||
|
Итерационные методы – это методы последовательных приближений. Среди
|
||||||
|
них: метод Якоби, метод Гаусса-Зейделя и др.
|
||||||
|
В итерационных методах необходимо задать некоторое приближённое решение – начальное приближение. После этого с помощью некоторого алгоритма
|
||||||
|
проводится один цикл вычислений, называемый итерацией. В результате итерации находят новое приближение. Итерации проводятся до получения решения с
|
||||||
|
требуемой точностью.
|
||||||
|
### 2.3 Метод Якоби
|
||||||
|
Пусть требуется численно решить систему линейных уравнений:
|
||||||
|
a11x1 + . . . + a1nxn = b1
|
||||||
|
. . .
|
||||||
|
an1x1 + . . . + annxn = bn
|
||||||
|
Предполагается, что aii ̸= 0, i ∈ {1, . . . , n} (иначе метод Якоби неприменим).
|
||||||
|
Выразим x1 через первое уравнение, x2 — через второе и т.д.:
|
||||||
|
В методе Якоби последовательность приближений x(k) строится следующим
|
||||||
|
образом. Выбирается первое приближение x(0), формула для остальных приближений имеет вид:
|
||||||
|
|
||||||
|
|
||||||
|
Постановка эксперимента
|
||||||
|
|
||||||
|
(Произведено исследование производительности с помощью реализации теста
|
||||||
|
от Intel - Intel High Performance Linpack, - и собственной реализации на CUDA.
|
||||||
|
Тестирование производилось для набора значений N от 1000 до 15000.)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5
task1/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
bin/
|
||||||
|
results/*.out
|
||||||
|
results/*.err
|
||||||
|
results/*.csv
|
||||||
|
*.pyc
|
||||||
245
task1/README.md
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
# Задание 1: CUDA-реализация LINPACK-подобного теста
|
||||||
|
|
||||||
|
В папке лежит готовый каркас под первое задание:
|
||||||
|
|
||||||
|
- `src/main.cu` — собственная CUDA-реализация решения плотной СЛАУ методом Якоби.
|
||||||
|
- `scripts/build.sh` — сборка программы через `nvcc`.
|
||||||
|
- `scripts/run_cuda.slurm` — пакетный запуск собственной CUDA-версии.
|
||||||
|
- `scripts/run_intel_linpack.slurm` — пакетный запуск стандартного Intel LINPACK на CPU.
|
||||||
|
|
||||||
|
Программа генерирует строго диагонально доминирующую матрицу `A`, заранее известный вектор решения `x_true`, правую часть `b = A * x_true`, после чего решает систему методом Якоби на GPU. В выводе печатаются:
|
||||||
|
|
||||||
|
- размер матрицы `N`;
|
||||||
|
- лучшее время решения в миллисекундах;
|
||||||
|
- число итераций;
|
||||||
|
- норма невязки `||Ax - b||_inf`;
|
||||||
|
- ошибка `||x - x_true||_inf`;
|
||||||
|
- LINPACK-like производительность в GFLOPS.
|
||||||
|
|
||||||
|
## Что сделать на СКЦ
|
||||||
|
|
||||||
|
### 1. Передать папку на кластер
|
||||||
|
|
||||||
|
Если алиас `polytech` уже прописан в `~/.ssh/config`, достаточно:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r task1 polytech:~/supercomputers/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Подключиться
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh polytech
|
||||||
|
cd ~/supercomputers/task1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Запустить собственную CUDA-реализацию
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sbatch scripts/run_cuda.slurm
|
||||||
|
```
|
||||||
|
|
||||||
|
Сразу после отправки Slurm вернёт `job id`. Дальше:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
squeue -u tm3u21
|
||||||
|
sacct -j <JOBID_CUDA> --format=JobID,JobName,Partition,State,Start,End,Elapsed,NNodes,AllocTRES%40,NodeList,ExitCode
|
||||||
|
```
|
||||||
|
|
||||||
|
В текущей конфигурации СКЦ в `tornado-k40` GPU выбирается самим разделом, поэтому в `slurm`-скрипте не используется `--gres=gpu:1`. Сам скрипт запускается из `SLURM_SUBMIT_DIR`, как в рабочем примере из методички, чтобы сборка и логи всегда шли именно в `~/supercomputers/task1`, а не во временную директорию Slurm.
|
||||||
|
|
||||||
|
Модули `compiler/gcc/11` и `nvidia/cuda/11.6u2` загружаются прямо внутри `run_cuda.slurm` до запуска программы. Это важно: если загрузить их только в отдельном `build.sh`, бинарник может собраться с новой `libstdc++`, а запускаться уже с системной, что даёт ошибки вида `GLIBCXX_* not found`.
|
||||||
|
|
||||||
|
После завершения посмотри:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less results/task1-cuda-<JOBID_CUDA>.out
|
||||||
|
cat results/task1-cuda-<JOBID_CUDA>.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Если на кластере нужна другая GPU-архитектура, можно пересобрать так:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CUDA_ARCH=sm_70 ./scripts/build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
По умолчанию в `build.sh` стоит `sm_35`, потому что пример ориентирован на `tornado-k40`.
|
||||||
|
|
||||||
|
### 4. Запустить стандартный Intel LINPACK
|
||||||
|
|
||||||
|
В примере эталонный LINPACK сначала подготавливается отдельно. Для вашей учётной записи не нужно писать в `/linux/share/...`; правильнее развернуть архив в домашнем каталоге и запускать оттуда.
|
||||||
|
|
||||||
|
Пошагово:
|
||||||
|
|
||||||
|
1. Скачай официальный архив Intel oneMKL Benchmarks Suite for Linux с сайта Intel:
|
||||||
|
|
||||||
|
- страница загрузки: `https://www.intel.com/content/www/us/en/download/780783/intel-oneapi-math-kernel-library-onemkl-benchmarks-suite-for-linux.html`
|
||||||
|
- на 2026-03-16 там доступен файл `l_onemklbench_p_2025.3.0_422.tgz`
|
||||||
|
|
||||||
|
Сохрани его локально и при желании переименуй в `linpack.tgz` для удобства.
|
||||||
|
|
||||||
|
2. На локальной машине скопируй архив на кластер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp linpack.tgz polytech:~/linpack.tgz
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Подключись к кластеру:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh polytech
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Распакуй архив в домашнюю папку:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/LINPACK
|
||||||
|
tar -xzf ~/linpack.tgz -C ~/LINPACK
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Найди каталог, в котором реально лежит `xlinpack_xeon64`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
find ~/LINPACK -name xlinpack_xeon64 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
В вашем случае по логам нужный каталог уже известен:
|
||||||
|
`/home/ipmmstudy1/tm3u21/LINPACK/benchmarks_2025.3/linux/share/mkl/benchmarks/linpack`.
|
||||||
|
|
||||||
|
6. Подготовь этот каталог, как в примере:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/ipmmstudy1/tm3u21/LINPACK/benchmarks_2025.3/linux/share/mkl/benchmarks/linpack
|
||||||
|
mkdir -p stdio
|
||||||
|
chmod +x *
|
||||||
|
chmod -x *.*
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Отправь batch-задачу с явным указанием `LINPACK_DIR`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/supercomputers/task1
|
||||||
|
sbatch --export=ALL,LINPACK_DIR=/home/ipmmstudy1/tm3u21/LINPACK/benchmarks_2025.3/linux/share/mkl/benchmarks/linpack scripts/run_intel_linpack.slurm
|
||||||
|
```
|
||||||
|
|
||||||
|
По умолчанию скрипт использует файл `task1/intel/lininput_report_xeon64`, где уже зафиксированы размеры
|
||||||
|
`1000 1500 2000 2500 3000 3500`, чтобы Intel LINPACK можно было напрямую сравнить с вашей CUDA-реализацией в отчёте.
|
||||||
|
|
||||||
|
8. Проверь статус:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sacct -j <JOBID_INTEL> --format=JobID,JobName,Partition,State,Start,End,Elapsed,NNodes,AllocTRES%40,NodeList,ExitCode
|
||||||
|
```
|
||||||
|
|
||||||
|
9. Посмотри вывод:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less ~/supercomputers/task1/stdio/task1-intel-linpack-<JOBID_INTEL>.out
|
||||||
|
```
|
||||||
|
|
||||||
|
В этом файле ищи строки с размерами `1000`, `1500`, `2000`, `2500`, `3000`, `3500`, а внизу --- секцию `Performance Summary`.
|
||||||
|
|
||||||
|
## Что нужно собрать для отчёта
|
||||||
|
|
||||||
|
Ниже последовательность, которая даст все обязательные материалы для отчёта и скриншотов.
|
||||||
|
|
||||||
|
### Шаг 1. Скрин входа с логином
|
||||||
|
|
||||||
|
На login-узле выполни:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
whoami
|
||||||
|
hostname
|
||||||
|
date
|
||||||
|
```
|
||||||
|
|
||||||
|
Сделай скрин терминала. На нём должен быть виден логин `tm3u21`.
|
||||||
|
|
||||||
|
### Шаг 2. Скрин конфигурации узла и GPU
|
||||||
|
|
||||||
|
После завершения CUDA-задачи открой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less results/task1-cuda-<JOBID_CUDA>.out
|
||||||
|
```
|
||||||
|
|
||||||
|
В начале файла уже будут:
|
||||||
|
|
||||||
|
- `whoami`, `hostname`, `date`;
|
||||||
|
- `scontrol show job ...`;
|
||||||
|
- `scontrol show node ...`;
|
||||||
|
- `lscpu`;
|
||||||
|
- `nvidia-smi`.
|
||||||
|
|
||||||
|
Сделай отдельные скрины с этой информацией.
|
||||||
|
|
||||||
|
### Шаг 3. Скрин времени выполнения и числа узлов
|
||||||
|
|
||||||
|
Выполни:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sacct -j <JOBID_CUDA>,<JOBID_INTEL> --format=JobID,JobName,Partition,State,Elapsed,NNodes,AllocTRES%40,NodeList,ExitCode
|
||||||
|
```
|
||||||
|
|
||||||
|
На этом скрине будут:
|
||||||
|
|
||||||
|
- время выполнения;
|
||||||
|
- количество узлов;
|
||||||
|
- список узлов;
|
||||||
|
- тип выделенных ресурсов.
|
||||||
|
|
||||||
|
### Шаг 4. Вынести численные результаты
|
||||||
|
|
||||||
|
Для собственной программы значения бери из файла:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat results/task1-cuda-<JOBID_CUDA>.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Для Intel LINPACK значения времени и GFLOPS бери из:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less ~/supercomputers/task1/stdio/task1-intel-linpack-<JOBID_INTEL>.out
|
||||||
|
```
|
||||||
|
|
||||||
|
Ищи секцию `Performance Summary`.
|
||||||
|
|
||||||
|
## Какие картинки ожидает `report/report.tex`
|
||||||
|
|
||||||
|
В отчёте уже подготовлены следующие пути:
|
||||||
|
|
||||||
|
- `report/img/task1-login.png`
|
||||||
|
- `report/img/task1-cuda-node.png`
|
||||||
|
- `report/img/task1-cuda-run.png`
|
||||||
|
- `report/img/task1-cuda-sacct.png`
|
||||||
|
- `report/img/task1-intel-run.png`
|
||||||
|
- `report/img/task1-intel-sacct.png`
|
||||||
|
|
||||||
|
Просто положи туда свои скриншоты с этими именами.
|
||||||
|
|
||||||
|
## Если нужно поменять размеры задач
|
||||||
|
|
||||||
|
Собственная программа сейчас запускается на:
|
||||||
|
|
||||||
|
- `1000`
|
||||||
|
- `1500`
|
||||||
|
- `2000`
|
||||||
|
- `2500`
|
||||||
|
- `3000`
|
||||||
|
- `3500`
|
||||||
|
|
||||||
|
Это задаётся параметрами в `scripts/run_cuda.slurm`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--start 1000 --step 500 --count 6
|
||||||
|
```
|
||||||
|
|
||||||
|
Если удобнее задать точный набор размеров, используй:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/linpack_cuda --sizes 1000,2000,3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
Этот код я здесь локально не компилировал, потому что в окружении нет гарантированно настроенного CUDA toolchain и GPU. Поэтому первый реальный прогон лучше делать сразу на СКЦ; если что-то упадёт по модулю, архитектуре GPU или пути к Intel LINPACK, пришли ошибку, и я быстро подправлю.
|
||||||
7
task1/intel/lininput_report_xeon64
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Shared-memory version of Intel(R) Distribution for LINPACK* Benchmark. *Other names and brands may be claimed as the property of others.
|
||||||
|
Custom data file for task1 report.
|
||||||
|
6 # number of tests
|
||||||
|
1000 1500 2000 2500 3000 3500 # problem sizes
|
||||||
|
1000 1504 2000 2504 3000 3504 # leading dimensions
|
||||||
|
8 6 6 5 5 5 # times to run a test
|
||||||
|
4 4 4 4 4 4 # alignment values (in KBytes)
|
||||||
1
task1/results/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
25
task1/scripts/build.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
mkdir -p bin results
|
||||||
|
|
||||||
|
CUDA_ARCH="${CUDA_ARCH:-sm_35}"
|
||||||
|
|
||||||
|
module purge
|
||||||
|
module load compiler/gcc/11
|
||||||
|
module load nvidia/cuda/11.6u2
|
||||||
|
|
||||||
|
nvcc \
|
||||||
|
-ccbin g++ \
|
||||||
|
-O3 \
|
||||||
|
-std=c++14 \
|
||||||
|
-lineinfo \
|
||||||
|
-Wno-deprecated-gpu-targets \
|
||||||
|
-arch="${CUDA_ARCH}" \
|
||||||
|
-o bin/linpack_cuda \
|
||||||
|
src/main.cu
|
||||||
|
|
||||||
|
echo "Built: $ROOT_DIR/bin/linpack_cuda"
|
||||||
92
task1/scripts/plot_task1_results.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
N_VALUES = [1000, 1500, 2000, 2500, 3000, 3500]
|
||||||
|
|
||||||
|
CUDA_TIME_MS = [5.0083, 7.4931, 8.3563, 10.4837, 12.6709, 14.8861]
|
||||||
|
INTEL_TIME_S = [0.010, 0.020, 0.028, 0.042, 0.069, 0.100]
|
||||||
|
INTEL_TIME_MS = [value * 1000.0 for value in INTEL_TIME_S]
|
||||||
|
|
||||||
|
CUDA_GFLOPS = [133.114, 300.277, 638.244, 993.608, 1420.573, 1920.138]
|
||||||
|
INTEL_GFLOPS = [67.331, 114.360, 193.276, 250.731, 260.451, 286.264]
|
||||||
|
|
||||||
|
|
||||||
|
def configure_plot() -> None:
|
||||||
|
plt.style.use("seaborn-v0_8-whitegrid")
|
||||||
|
plt.rcParams["figure.figsize"] = (8.4, 5.2)
|
||||||
|
plt.rcParams["figure.dpi"] = 140
|
||||||
|
plt.rcParams["savefig.dpi"] = 220
|
||||||
|
plt.rcParams["font.size"] = 11
|
||||||
|
plt.rcParams["axes.labelsize"] = 11
|
||||||
|
plt.rcParams["axes.titlesize"] = 12
|
||||||
|
plt.rcParams["legend.fontsize"] = 10
|
||||||
|
|
||||||
|
|
||||||
|
def plot_time(output_path: Path) -> None:
|
||||||
|
plt.figure()
|
||||||
|
plt.plot(
|
||||||
|
N_VALUES,
|
||||||
|
INTEL_TIME_MS,
|
||||||
|
marker="o",
|
||||||
|
linewidth=2.2,
|
||||||
|
color="#1f77b4",
|
||||||
|
label="Intel LINPACK",
|
||||||
|
)
|
||||||
|
plt.plot(
|
||||||
|
N_VALUES,
|
||||||
|
CUDA_TIME_MS,
|
||||||
|
marker="s",
|
||||||
|
linewidth=2.2,
|
||||||
|
color="#d62728",
|
||||||
|
label="Собственная реализация",
|
||||||
|
)
|
||||||
|
plt.xlabel("Размер матрицы N")
|
||||||
|
plt.ylabel("Время решения, мс")
|
||||||
|
plt.legend(loc="upper left")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, bbox_inches="tight")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def plot_gflops(output_path: Path) -> None:
|
||||||
|
plt.figure()
|
||||||
|
plt.plot(
|
||||||
|
N_VALUES,
|
||||||
|
INTEL_GFLOPS,
|
||||||
|
marker="o",
|
||||||
|
linewidth=2.2,
|
||||||
|
color="#1f77b4",
|
||||||
|
label="Intel LINPACK",
|
||||||
|
)
|
||||||
|
plt.plot(
|
||||||
|
N_VALUES,
|
||||||
|
CUDA_GFLOPS,
|
||||||
|
marker="s",
|
||||||
|
linewidth=2.2,
|
||||||
|
color="#d62728",
|
||||||
|
label="Собственная реализация",
|
||||||
|
)
|
||||||
|
plt.xlabel("Размер матрицы N")
|
||||||
|
plt.ylabel("Производительность, GFLOPS")
|
||||||
|
plt.legend(loc="upper left")
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, bbox_inches="tight")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
configure_plot()
|
||||||
|
|
||||||
|
output_dir = Path(__file__).resolve().parents[2] / "report" / "img"
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
plot_time(output_dir / "task1-time-comparison.png")
|
||||||
|
plot_gflops(output_dir / "task1-gflops-comparison.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
58
task1/scripts/run_cuda.slurm
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#SBATCH --job-name=task1-cuda
|
||||||
|
#SBATCH --partition=tornado-k40
|
||||||
|
#SBATCH --nodes=1
|
||||||
|
#SBATCH --ntasks=1
|
||||||
|
#SBATCH --time=00:20:00
|
||||||
|
#SBATCH --output=results/%x-%j.out
|
||||||
|
#SBATCH --error=results/%x-%j.err
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "${SLURM_SUBMIT_DIR}"
|
||||||
|
ROOT_DIR="${SLURM_SUBMIT_DIR}"
|
||||||
|
|
||||||
|
module purge
|
||||||
|
module load compiler/gcc/11
|
||||||
|
module load nvidia/cuda/11.6u2
|
||||||
|
|
||||||
|
mkdir -p results bin
|
||||||
|
|
||||||
|
./scripts/build.sh
|
||||||
|
|
||||||
|
echo "===== account info ====="
|
||||||
|
whoami
|
||||||
|
hostname
|
||||||
|
date
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== slurm info ====="
|
||||||
|
echo "SLURM_JOB_ID=${SLURM_JOB_ID:-unknown}"
|
||||||
|
echo "SLURM_JOB_NAME=${SLURM_JOB_NAME:-unknown}"
|
||||||
|
echo "SLURM_JOB_PARTITION=${SLURM_JOB_PARTITION:-unknown}"
|
||||||
|
echo "SLURM_JOB_NUM_NODES=${SLURM_JOB_NUM_NODES:-unknown}"
|
||||||
|
echo "SLURM_NODELIST=${SLURM_NODELIST:-unknown}"
|
||||||
|
echo "CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES:-unset}"
|
||||||
|
scontrol show job "${SLURM_JOB_ID}" || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== node config ====="
|
||||||
|
lscpu | sed -n '1,20p'
|
||||||
|
if [ -n "${SLURMD_NODENAME:-}" ]; then
|
||||||
|
scontrol show node "${SLURMD_NODENAME}" || true
|
||||||
|
fi
|
||||||
|
nvidia-smi -L || true
|
||||||
|
nvidia-smi || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== benchmark ====="
|
||||||
|
./bin/linpack_cuda \
|
||||||
|
--start 1000 \
|
||||||
|
--step 500 \
|
||||||
|
--count 6 \
|
||||||
|
--eps 1e-6 \
|
||||||
|
--max-iters 15000 \
|
||||||
|
--threads 256 \
|
||||||
|
--repeat 3 \
|
||||||
|
--warmup 1 \
|
||||||
|
--csv "results/task1-cuda-${SLURM_JOB_ID}.csv"
|
||||||
86
task1/scripts/run_intel_linpack.slurm
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#SBATCH --job-name=task1-intel-linpack
|
||||||
|
#SBATCH --partition=tornado
|
||||||
|
#SBATCH --nodes=1
|
||||||
|
#SBATCH --ntasks=1
|
||||||
|
#SBATCH --cpus-per-task=56
|
||||||
|
#SBATCH --time=00:20:00
|
||||||
|
#SBATCH --output=stdio/%x-%j.out
|
||||||
|
#SBATCH --error=stdio/%x-%j.err
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TASK1_DIR="${SLURM_SUBMIT_DIR:-$PWD}"
|
||||||
|
module purge
|
||||||
|
|
||||||
|
LINPACK_DIR="${LINPACK_DIR:-$HOME/LINPACK}"
|
||||||
|
LINPACK_INPUT="${LINPACK_INPUT:-$TASK1_DIR/intel/lininput_report_xeon64}"
|
||||||
|
|
||||||
|
if [ ! -d "${LINPACK_DIR}" ]; then
|
||||||
|
echo "LINPACK directory not found: ${LINPACK_DIR}"
|
||||||
|
echo "Prepare Intel LINPACK in your home directory first."
|
||||||
|
echo "Example:"
|
||||||
|
echo " mkdir -p \$HOME/LINPACK"
|
||||||
|
echo " tar -xzf \$HOME/linpack.tgz -C \$HOME/LINPACK"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolve_linpack_dir() {
|
||||||
|
if [ -x "${LINPACK_DIR}/xlinpack_xeon64" ]; then
|
||||||
|
printf '%s\n' "${LINPACK_DIR}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local found
|
||||||
|
found="$(find "${LINPACK_DIR}" -name xlinpack_xeon64 2>/dev/null | head -n 1 || true)"
|
||||||
|
if [ -n "${found}" ]; then
|
||||||
|
dirname "${found}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! LINPACK_DIR="$(resolve_linpack_dir)"; then
|
||||||
|
echo "Intel LINPACK binary not found under: ${LINPACK_DIR}"
|
||||||
|
echo "Check archive contents with:"
|
||||||
|
echo " find ${LINPACK_DIR} -name xlinpack_xeon64 2>/dev/null"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "${LINPACK_DIR}"
|
||||||
|
|
||||||
|
chmod +x ./* || true
|
||||||
|
chmod -x ./*.* || true
|
||||||
|
mkdir -p stdio
|
||||||
|
|
||||||
|
echo "===== account info ====="
|
||||||
|
whoami
|
||||||
|
hostname
|
||||||
|
date
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== slurm info ====="
|
||||||
|
echo "SLURM_JOB_ID=${SLURM_JOB_ID:-unknown}"
|
||||||
|
echo "SLURM_JOB_NAME=${SLURM_JOB_NAME:-unknown}"
|
||||||
|
echo "SLURM_JOB_PARTITION=${SLURM_JOB_PARTITION:-unknown}"
|
||||||
|
echo "SLURM_JOB_NUM_NODES=${SLURM_JOB_NUM_NODES:-unknown}"
|
||||||
|
echo "SLURM_NODELIST=${SLURM_NODELIST:-unknown}"
|
||||||
|
echo "OMP_NUM_THREADS=${SLURM_CPUS_PER_TASK:-56}"
|
||||||
|
scontrol show job "${SLURM_JOB_ID}" || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== node config ====="
|
||||||
|
lscpu | sed -n '1,20p'
|
||||||
|
if [ -n "${SLURMD_NODENAME:-}" ]; then
|
||||||
|
scontrol show node "${SLURMD_NODENAME}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== intel linpack ====="
|
||||||
|
echo "LINPACK_DIR=${LINPACK_DIR}"
|
||||||
|
echo "LINPACK_INPUT=${LINPACK_INPUT}"
|
||||||
|
export OMP_NUM_THREADS="${SLURM_CPUS_PER_TASK:-56}"
|
||||||
|
export MKL_NUM_THREADS="${SLURM_CPUS_PER_TASK:-56}"
|
||||||
|
|
||||||
|
srun ./xlinpack_xeon64 "${LINPACK_INPUT}"
|
||||||
559
task1/src/main.cu
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
#include <cuda_runtime.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define CUDA_CHECK(call) \
|
||||||
|
do { \
|
||||||
|
cudaError_t err__ = (call); \
|
||||||
|
if (err__ != cudaSuccess) { \
|
||||||
|
std::cerr << "CUDA error at " << __FILE__ << ":" << __LINE__ \
|
||||||
|
<< " -> " << cudaGetErrorString(err__) << std::endl; \
|
||||||
|
std::exit(EXIT_FAILURE); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
int start = 1000;
|
||||||
|
int step = 500;
|
||||||
|
int count = 6;
|
||||||
|
std::vector<int> sizes;
|
||||||
|
int threads = 256;
|
||||||
|
int max_iters = 10000;
|
||||||
|
int repeat = 3;
|
||||||
|
int warmup = 1;
|
||||||
|
unsigned int seed = 42U;
|
||||||
|
double eps = 1e-6;
|
||||||
|
std::string csv_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Metrics {
|
||||||
|
double elapsed_ms = std::numeric_limits<double>::max();
|
||||||
|
int iterations = 0;
|
||||||
|
double residual_inf = std::numeric_limits<double>::infinity();
|
||||||
|
double x_error_inf = std::numeric_limits<double>::infinity();
|
||||||
|
double gflops = 0.0;
|
||||||
|
bool converged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
__global__ void jacobi_iteration(const double *a,
|
||||||
|
const double *b,
|
||||||
|
const double *x_in,
|
||||||
|
double *x_out,
|
||||||
|
int n,
|
||||||
|
double eps,
|
||||||
|
int *converged) {
|
||||||
|
const int row = blockIdx.x * blockDim.x + threadIdx.x;
|
||||||
|
if (row >= n) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int row_offset = row * n;
|
||||||
|
double sum = 0.0;
|
||||||
|
for (int col = 0; col < n; ++col) {
|
||||||
|
if (col != row) {
|
||||||
|
sum += a[row_offset + col] * x_in[col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const double next = (b[row] - sum) / a[row_offset + row];
|
||||||
|
x_out[row] = next;
|
||||||
|
if (fabs(next - x_in[row]) > eps) {
|
||||||
|
atomicAnd(converged, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_usage(const char *program) {
|
||||||
|
std::cout
|
||||||
|
<< "Usage: " << program << " [options]\n"
|
||||||
|
<< "Options:\n"
|
||||||
|
<< " --start N First matrix size (default: 1000)\n"
|
||||||
|
<< " --step N Size increment (default: 500)\n"
|
||||||
|
<< " --count N Number of tests (default: 6)\n"
|
||||||
|
<< " --sizes a,b,c Comma-separated matrix sizes\n"
|
||||||
|
<< " --threads N Threads per block (default: 256)\n"
|
||||||
|
<< " --max-iters N Max Jacobi iterations (default: 10000)\n"
|
||||||
|
<< " --eps X Convergence epsilon (default: 1e-6)\n"
|
||||||
|
<< " --repeat N Timed repetitions, best is kept (default: 3)\n"
|
||||||
|
<< " --warmup N Warmup repetitions (default: 1)\n"
|
||||||
|
<< " --seed N RNG seed (default: 42)\n"
|
||||||
|
<< " --csv PATH Write CSV summary to PATH\n"
|
||||||
|
<< " --help Print this help\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_int_arg(const std::string &text, int &out) {
|
||||||
|
try {
|
||||||
|
size_t pos = 0;
|
||||||
|
const int parsed = std::stoi(text, &pos);
|
||||||
|
if (pos != text.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = parsed;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_uint_arg(const std::string &text, unsigned int &out) {
|
||||||
|
try {
|
||||||
|
size_t pos = 0;
|
||||||
|
const unsigned long parsed = std::stoul(text, &pos);
|
||||||
|
if (pos != text.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = static_cast<unsigned int>(parsed);
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_double_arg(const std::string &text, double &out) {
|
||||||
|
try {
|
||||||
|
size_t pos = 0;
|
||||||
|
const double parsed = std::stod(text, &pos);
|
||||||
|
if (pos != text.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = parsed;
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_sizes_arg(const std::string &text, std::vector<int> &sizes) {
|
||||||
|
std::stringstream ss(text);
|
||||||
|
std::string token;
|
||||||
|
std::vector<int> parsed;
|
||||||
|
|
||||||
|
while (std::getline(ss, token, ',')) {
|
||||||
|
token.erase(
|
||||||
|
std::remove_if(token.begin(),
|
||||||
|
token.end(),
|
||||||
|
[](unsigned char c) { return std::isspace(c) != 0; }),
|
||||||
|
token.end());
|
||||||
|
if (token.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
if (!parse_int_arg(token, value) || value <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parsed.push_back(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes = parsed;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parse_options(int argc, char **argv, Options &options) {
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
auto require_value = [&](const char *name) -> const char * {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
std::cerr << "Missing value for " << name << '\n';
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return argv[++i];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (arg == "--help") {
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (arg == "--start") {
|
||||||
|
const char *v = require_value("--start");
|
||||||
|
if (!v || !parse_int_arg(v, options.start) || options.start <= 0) {
|
||||||
|
std::cerr << "Invalid --start value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--step") {
|
||||||
|
const char *v = require_value("--step");
|
||||||
|
if (!v || !parse_int_arg(v, options.step) || options.step <= 0) {
|
||||||
|
std::cerr << "Invalid --step value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--count") {
|
||||||
|
const char *v = require_value("--count");
|
||||||
|
if (!v || !parse_int_arg(v, options.count) || options.count <= 0) {
|
||||||
|
std::cerr << "Invalid --count value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--threads") {
|
||||||
|
const char *v = require_value("--threads");
|
||||||
|
if (!v || !parse_int_arg(v, options.threads) || options.threads <= 0 ||
|
||||||
|
options.threads > 1024) {
|
||||||
|
std::cerr << "Invalid --threads value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--max-iters") {
|
||||||
|
const char *v = require_value("--max-iters");
|
||||||
|
if (!v || !parse_int_arg(v, options.max_iters) ||
|
||||||
|
options.max_iters <= 0) {
|
||||||
|
std::cerr << "Invalid --max-iters value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--eps") {
|
||||||
|
const char *v = require_value("--eps");
|
||||||
|
if (!v || !parse_double_arg(v, options.eps) || options.eps <= 0.0) {
|
||||||
|
std::cerr << "Invalid --eps value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--repeat") {
|
||||||
|
const char *v = require_value("--repeat");
|
||||||
|
if (!v || !parse_int_arg(v, options.repeat) || options.repeat <= 0) {
|
||||||
|
std::cerr << "Invalid --repeat value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--warmup") {
|
||||||
|
const char *v = require_value("--warmup");
|
||||||
|
if (!v || !parse_int_arg(v, options.warmup) || options.warmup < 0) {
|
||||||
|
std::cerr << "Invalid --warmup value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--seed") {
|
||||||
|
const char *v = require_value("--seed");
|
||||||
|
if (!v || !parse_uint_arg(v, options.seed)) {
|
||||||
|
std::cerr << "Invalid --seed value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--sizes") {
|
||||||
|
const char *v = require_value("--sizes");
|
||||||
|
if (!v || !parse_sizes_arg(v, options.sizes)) {
|
||||||
|
std::cerr << "Invalid --sizes value\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arg == "--csv") {
|
||||||
|
const char *v = require_value("--csv");
|
||||||
|
if (!v) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
options.csv_path = v;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "Unknown option: " << arg << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.sizes.empty()) {
|
||||||
|
options.sizes.reserve(static_cast<size_t>(options.count));
|
||||||
|
for (int i = 0; i < options.count; ++i) {
|
||||||
|
options.sizes.push_back(options.start + i * options.step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double next_random_value(uint32_t &state) {
|
||||||
|
state = state * 1664525U + 1013904223U;
|
||||||
|
const double normalized =
|
||||||
|
static_cast<double>(state & 0x00FFFFFFU) /
|
||||||
|
static_cast<double>(0x00FFFFFFU);
|
||||||
|
return normalized * 2.0 - 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void build_system(int n,
|
||||||
|
unsigned int seed,
|
||||||
|
std::vector<double> &a,
|
||||||
|
std::vector<double> &x_true,
|
||||||
|
std::vector<double> &b) {
|
||||||
|
const size_t nn = static_cast<size_t>(n) * static_cast<size_t>(n);
|
||||||
|
a.assign(nn, 0.0);
|
||||||
|
x_true.assign(static_cast<size_t>(n), 0.0);
|
||||||
|
b.assign(static_cast<size_t>(n), 0.0);
|
||||||
|
|
||||||
|
uint32_t state = seed;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
x_true[static_cast<size_t>(i)] = next_random_value(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = 0; row < n; ++row) {
|
||||||
|
double off_diag_sum = 0.0;
|
||||||
|
const size_t row_offset = static_cast<size_t>(row) * static_cast<size_t>(n);
|
||||||
|
for (int col = 0; col < n; ++col) {
|
||||||
|
if (col == row) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const double value = next_random_value(state);
|
||||||
|
a[row_offset + static_cast<size_t>(col)] = value;
|
||||||
|
off_diag_sum += std::fabs(value);
|
||||||
|
}
|
||||||
|
a[row_offset + static_cast<size_t>(row)] =
|
||||||
|
off_diag_sum + 2.0 + std::fabs(next_random_value(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int row = 0; row < n; ++row) {
|
||||||
|
const size_t row_offset = static_cast<size_t>(row) * static_cast<size_t>(n);
|
||||||
|
double sum = 0.0;
|
||||||
|
for (int col = 0; col < n; ++col) {
|
||||||
|
sum += a[row_offset + static_cast<size_t>(col)] *
|
||||||
|
x_true[static_cast<size_t>(col)];
|
||||||
|
}
|
||||||
|
b[static_cast<size_t>(row)] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double compute_residual_inf(const std::vector<double> &a,
|
||||||
|
const std::vector<double> &x,
|
||||||
|
const std::vector<double> &b,
|
||||||
|
int n) {
|
||||||
|
double max_residual = 0.0;
|
||||||
|
for (int row = 0; row < n; ++row) {
|
||||||
|
const size_t row_offset = static_cast<size_t>(row) * static_cast<size_t>(n);
|
||||||
|
double sum = 0.0;
|
||||||
|
for (int col = 0; col < n; ++col) {
|
||||||
|
sum += a[row_offset + static_cast<size_t>(col)] *
|
||||||
|
x[static_cast<size_t>(col)];
|
||||||
|
}
|
||||||
|
max_residual =
|
||||||
|
std::max(max_residual, std::fabs(sum - b[static_cast<size_t>(row)]));
|
||||||
|
}
|
||||||
|
return max_residual;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double compute_x_error_inf(const std::vector<double> &x,
|
||||||
|
const std::vector<double> &x_true) {
|
||||||
|
double max_error = 0.0;
|
||||||
|
for (size_t i = 0; i < x.size(); ++i) {
|
||||||
|
max_error = std::max(max_error, std::fabs(x[i] - x_true[i]));
|
||||||
|
}
|
||||||
|
return max_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Metrics solve_once(const Options &options,
|
||||||
|
int n,
|
||||||
|
const std::vector<double> &a,
|
||||||
|
const std::vector<double> &b,
|
||||||
|
const std::vector<double> &x_true,
|
||||||
|
double *d_a,
|
||||||
|
double *d_b,
|
||||||
|
double *d_x_old,
|
||||||
|
double *d_x_new,
|
||||||
|
int *d_converged) {
|
||||||
|
Metrics metrics;
|
||||||
|
std::vector<double> x(static_cast<size_t>(n), 0.0);
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaMemset(d_x_old, 0, static_cast<size_t>(n) * sizeof(double)));
|
||||||
|
CUDA_CHECK(cudaMemset(d_x_new, 0, static_cast<size_t>(n) * sizeof(double)));
|
||||||
|
|
||||||
|
cudaEvent_t start = nullptr;
|
||||||
|
cudaEvent_t stop = nullptr;
|
||||||
|
CUDA_CHECK(cudaEventCreate(&start));
|
||||||
|
CUDA_CHECK(cudaEventCreate(&stop));
|
||||||
|
CUDA_CHECK(cudaEventRecord(start, 0));
|
||||||
|
|
||||||
|
const int block = options.threads;
|
||||||
|
const int grid = (n + block - 1) / block;
|
||||||
|
|
||||||
|
int h_converged = 0;
|
||||||
|
int iterations = 0;
|
||||||
|
while (iterations < options.max_iters) {
|
||||||
|
h_converged = 1;
|
||||||
|
CUDA_CHECK(cudaMemcpy(
|
||||||
|
d_converged, &h_converged, sizeof(int), cudaMemcpyHostToDevice));
|
||||||
|
|
||||||
|
jacobi_iteration<<<grid, block>>>(
|
||||||
|
d_a, d_b, d_x_old, d_x_new, n, options.eps, d_converged);
|
||||||
|
CUDA_CHECK(cudaGetLastError());
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaMemcpy(
|
||||||
|
&h_converged, d_converged, sizeof(int), cudaMemcpyDeviceToHost));
|
||||||
|
|
||||||
|
std::swap(d_x_old, d_x_new);
|
||||||
|
++iterations;
|
||||||
|
|
||||||
|
if (h_converged == 1) {
|
||||||
|
metrics.converged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaEventRecord(stop, 0));
|
||||||
|
CUDA_CHECK(cudaEventSynchronize(stop));
|
||||||
|
|
||||||
|
float elapsed_ms = 0.0f;
|
||||||
|
CUDA_CHECK(cudaEventElapsedTime(&elapsed_ms, start, stop));
|
||||||
|
CUDA_CHECK(cudaEventDestroy(start));
|
||||||
|
CUDA_CHECK(cudaEventDestroy(stop));
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaMemcpy(x.data(),
|
||||||
|
d_x_old,
|
||||||
|
static_cast<size_t>(n) * sizeof(double),
|
||||||
|
cudaMemcpyDeviceToHost));
|
||||||
|
|
||||||
|
metrics.elapsed_ms = static_cast<double>(elapsed_ms);
|
||||||
|
metrics.iterations = iterations;
|
||||||
|
metrics.residual_inf = compute_residual_inf(a, x, b, n);
|
||||||
|
metrics.x_error_inf = compute_x_error_inf(x, x_true);
|
||||||
|
|
||||||
|
const double seconds = metrics.elapsed_ms / 1000.0;
|
||||||
|
const double flops =
|
||||||
|
(2.0 / 3.0) * static_cast<double>(n) * static_cast<double>(n) *
|
||||||
|
static_cast<double>(n);
|
||||||
|
metrics.gflops = (seconds > 0.0) ? (flops / seconds) / 1e9 : 0.0;
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Metrics benchmark_size(const Options &options,
|
||||||
|
int n,
|
||||||
|
const std::vector<double> &a,
|
||||||
|
const std::vector<double> &b,
|
||||||
|
const std::vector<double> &x_true) {
|
||||||
|
const size_t matrix_bytes =
|
||||||
|
static_cast<size_t>(n) * static_cast<size_t>(n) * sizeof(double);
|
||||||
|
const size_t vector_bytes = static_cast<size_t>(n) * sizeof(double);
|
||||||
|
|
||||||
|
double *d_a = nullptr;
|
||||||
|
double *d_b = nullptr;
|
||||||
|
double *d_x_old = nullptr;
|
||||||
|
double *d_x_new = nullptr;
|
||||||
|
int *d_converged = nullptr;
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaMalloc(reinterpret_cast<void **>(&d_a), matrix_bytes));
|
||||||
|
CUDA_CHECK(cudaMalloc(reinterpret_cast<void **>(&d_b), vector_bytes));
|
||||||
|
CUDA_CHECK(cudaMalloc(reinterpret_cast<void **>(&d_x_old), vector_bytes));
|
||||||
|
CUDA_CHECK(cudaMalloc(reinterpret_cast<void **>(&d_x_new), vector_bytes));
|
||||||
|
CUDA_CHECK(cudaMalloc(reinterpret_cast<void **>(&d_converged), sizeof(int)));
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaMemcpy(
|
||||||
|
d_a, a.data(), matrix_bytes, cudaMemcpyHostToDevice));
|
||||||
|
CUDA_CHECK(cudaMemcpy(
|
||||||
|
d_b, b.data(), vector_bytes, cudaMemcpyHostToDevice));
|
||||||
|
|
||||||
|
for (int w = 0; w < options.warmup; ++w) {
|
||||||
|
(void)solve_once(options, n, a, b, x_true, d_a, d_b, d_x_old, d_x_new,
|
||||||
|
d_converged);
|
||||||
|
}
|
||||||
|
|
||||||
|
Metrics best;
|
||||||
|
for (int run = 0; run < options.repeat; ++run) {
|
||||||
|
Metrics current = solve_once(
|
||||||
|
options, n, a, b, x_true, d_a, d_b, d_x_old, d_x_new, d_converged);
|
||||||
|
if (current.elapsed_ms < best.elapsed_ms) {
|
||||||
|
best = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CUDA_CHECK(cudaFree(d_a));
|
||||||
|
CUDA_CHECK(cudaFree(d_b));
|
||||||
|
CUDA_CHECK(cudaFree(d_x_old));
|
||||||
|
CUDA_CHECK(cudaFree(d_x_new));
|
||||||
|
CUDA_CHECK(cudaFree(d_converged));
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
Options options;
|
||||||
|
if (!parse_options(argc, argv, options)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int device = 0;
|
||||||
|
CUDA_CHECK(cudaGetDevice(&device));
|
||||||
|
cudaDeviceProp prop{};
|
||||||
|
CUDA_CHECK(cudaGetDeviceProperties(&prop, device));
|
||||||
|
|
||||||
|
std::ofstream csv_file;
|
||||||
|
if (!options.csv_path.empty()) {
|
||||||
|
csv_file.open(options.csv_path.c_str(), std::ios::out | std::ios::trunc);
|
||||||
|
if (!csv_file) {
|
||||||
|
std::cerr << "Failed to open CSV file: " << options.csv_path << '\n';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
csv_file
|
||||||
|
<< "n,elapsed_ms,iterations,residual_inf,x_error_inf,gflops,converged\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "CUDA Jacobi LINPACK-like benchmark\n";
|
||||||
|
std::cout << "device = " << prop.name << ", compute capability = "
|
||||||
|
<< prop.major << '.' << prop.minor << ", threads = "
|
||||||
|
<< options.threads << ", repeat = " << options.repeat
|
||||||
|
<< ", warmup = " << options.warmup
|
||||||
|
<< ", eps = " << std::scientific << options.eps << std::defaultfloat
|
||||||
|
<< "\n\n";
|
||||||
|
|
||||||
|
std::cout << std::left << std::setw(8) << "N" << std::setw(14) << "Time(ms)"
|
||||||
|
<< std::setw(12) << "Iter" << std::setw(18) << "ResidualInf"
|
||||||
|
<< std::setw(18) << "XerrInf" << std::setw(14) << "GFLOPS"
|
||||||
|
<< "Status\n";
|
||||||
|
|
||||||
|
for (size_t i = 0; i < options.sizes.size(); ++i) {
|
||||||
|
const int n = options.sizes[i];
|
||||||
|
if (n <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> a;
|
||||||
|
std::vector<double> x_true;
|
||||||
|
std::vector<double> b;
|
||||||
|
build_system(
|
||||||
|
n, options.seed + static_cast<unsigned int>(n), a, x_true, b);
|
||||||
|
|
||||||
|
Metrics metrics = benchmark_size(options, n, a, b, x_true);
|
||||||
|
|
||||||
|
std::cout << std::left << std::setw(8) << n << std::setw(14)
|
||||||
|
<< std::fixed << std::setprecision(4) << metrics.elapsed_ms
|
||||||
|
<< std::setw(12) << metrics.iterations << std::setw(18)
|
||||||
|
<< std::scientific << std::setprecision(3)
|
||||||
|
<< metrics.residual_inf << std::setw(18) << metrics.x_error_inf
|
||||||
|
<< std::setw(14) << std::fixed << std::setprecision(3)
|
||||||
|
<< metrics.gflops
|
||||||
|
<< (metrics.converged ? "converged" : "max_iters") << '\n';
|
||||||
|
|
||||||
|
if (csv_file) {
|
||||||
|
csv_file << n << ',' << std::fixed << std::setprecision(6)
|
||||||
|
<< metrics.elapsed_ms << ',' << metrics.iterations << ','
|
||||||
|
<< std::scientific << std::setprecision(8)
|
||||||
|
<< metrics.residual_inf << ',' << metrics.x_error_inf << ','
|
||||||
|
<< std::fixed << std::setprecision(6) << metrics.gflops
|
||||||
|
<< ',' << (metrics.converged ? 1 : 0) << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csv_file) {
|
||||||
|
csv_file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1
task1/stdio/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
15
task2.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# задание 2
|
||||||
|
|
||||||
|
## Постановка задачи
|
||||||
|
В рамках данной лабораторной работы необходимо решить следующие задачи:
|
||||||
|
1. Изучить технологию межпроцессного взаимодействия MPI.
|
||||||
|
2. Разработать параллельный масштабируемый алгоритм для решения вычислительной задачи.
|
||||||
|
3. Реализовать разработанный алгоритм с использованием технологии MPI.
|
||||||
|
4. Исследовать производительность реализации на 1, 2 и 4-х узлах СКЦ .Политехнический..
|
||||||
|
5. Сравнить время решения вычислительной задачи с использованием MPI и
|
||||||
|
CUDA.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Рис. 5: Зависимость времени вычисления от размера полигона P
|
||||||
6
task2/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
bin/
|
||||||
|
.venv/
|
||||||
|
results/*.out
|
||||||
|
results/*.err
|
||||||
|
results/*.csv
|
||||||
|
*.pyc
|
||||||
122
task2/README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# Задание 2: MPI-реализация волнового алгоритма
|
||||||
|
|
||||||
|
MPI-версия волнового алгоритма (алгоритм Ли) для поиска кратчайшего пути робота на полигоне. Для сравнения также подготовлена CUDA-версия того же алгоритма.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- `src/wave_mpi.c` — MPI-реализация (декомпозиция по строкам, ghost rows).
|
||||||
|
- `src/wave_cuda.cu` — CUDA-реализация (глобальная память, по мотивам программы прошлого семестра).
|
||||||
|
- `scripts/build_mpi.sh` — сборка MPI-версии.
|
||||||
|
- `scripts/build_cuda.sh` — сборка CUDA-версии.
|
||||||
|
- `scripts/run_mpi.slurm` — пакетный запуск MPI на кластере.
|
||||||
|
- `scripts/run_cuda.slurm` — пакетный запуск CUDA на кластере.
|
||||||
|
- `scripts/plot_task2_results.py` — построение графика для отчёта.
|
||||||
|
|
||||||
|
## Что сделать на СКЦ
|
||||||
|
|
||||||
|
### 1. Передать папку на кластер
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r task2 polytech:~/supercomputers/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Подключиться
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh polytech
|
||||||
|
cd ~/supercomputers/task2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Проверить доступные MPI-модули
|
||||||
|
|
||||||
|
```bash
|
||||||
|
module avail mpi
|
||||||
|
```
|
||||||
|
|
||||||
|
Если модуль `mpi/openmpi` не найден, посмотри список и подставь нужное имя в `scripts/run_mpi.slurm` (строка `module load mpi/openmpi`).
|
||||||
|
|
||||||
|
### 4. Запустить CUDA-версию (для сравнения)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sbatch scripts/run_cuda.slurm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Запустить MPI на 1, 2, 4 узлах
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sbatch --nodes=1 scripts/run_mpi.slurm
|
||||||
|
sbatch --nodes=2 scripts/run_mpi.slurm
|
||||||
|
sbatch --nodes=4 scripts/run_mpi.slurm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Проверить статус
|
||||||
|
|
||||||
|
```bash
|
||||||
|
squeue -u tm3u21
|
||||||
|
sacct -j <JOBID> --format=JobID,JobName,Partition,State,Elapsed,NNodes,AllocTRES%40,NodeList,ExitCode
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Посмотреть результаты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less results/task2-mpi-<JOBID>.out
|
||||||
|
cat results/task2-mpi-1n-<JOBID>.csv
|
||||||
|
cat results/task2-mpi-2n-<JOBID>.csv
|
||||||
|
cat results/task2-mpi-4n-<JOBID>.csv
|
||||||
|
cat results/task2-cuda-<JOBID>.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Построить график
|
||||||
|
|
||||||
|
На локальной машине (нужен `matplotlib`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/plot_task2_results.py \
|
||||||
|
--mpi1 results/task2-mpi-1n-XXXXX.csv \
|
||||||
|
--mpi2 results/task2-mpi-2n-XXXXX.csv \
|
||||||
|
--mpi4 results/task2-mpi-4n-XXXXX.csv \
|
||||||
|
--cuda results/task2-cuda-XXXXX.csv \
|
||||||
|
-o ../report/img/task2-time-comparison.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## Что нужно собрать для отчёта
|
||||||
|
|
||||||
|
### Скриншот 1: `task2-mpi-run.png` — вывод MPI-программы
|
||||||
|
|
||||||
|
После завершения MPI-задач открой вывод одной из них:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less results/task2-mpi-<JOBID>.out
|
||||||
|
```
|
||||||
|
|
||||||
|
Сделай скрин блока `===== benchmark =====` — там будут все размеры с временем.
|
||||||
|
|
||||||
|
### Скриншот 2: `task2-cuda-run.png` — вывод CUDA-программы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
less results/task2-cuda-<JOBID>.out
|
||||||
|
```
|
||||||
|
|
||||||
|
Сделай скрин блока `===== benchmark =====`.
|
||||||
|
|
||||||
|
### Скриншот 3: `task2-sacct.png` — сведения Slurm
|
||||||
|
|
||||||
|
Собери все JOBID (3 MPI + 1 CUDA) и выполни:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sacct -j <JOB_MPI1>,<JOB_MPI2>,<JOB_MPI4>,<JOB_CUDA> \
|
||||||
|
--format=JobID,JobName,Partition,State,Elapsed,NNodes,AllocTRES%40,NodeList,ExitCode
|
||||||
|
```
|
||||||
|
|
||||||
|
### Скриншот 4: `task2-time-comparison.png` — график
|
||||||
|
|
||||||
|
Генерируется скриптом `plot_task2_results.py` (см. шаг 8 выше).
|
||||||
|
|
||||||
|
### Куда положить скриншоты
|
||||||
|
|
||||||
|
Все картинки кладутся в `report/img/`:
|
||||||
|
|
||||||
|
- `report/img/task2-mpi-run.png`
|
||||||
|
- `report/img/task2-cuda-run.png`
|
||||||
|
- `report/img/task2-sacct.png`
|
||||||
|
- `report/img/task2-time-comparison.png`
|
||||||
0
task2/results/.gitkeep
Normal file
7
task2/scripts/build_cuda.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
CUDA_ARCH="${CUDA_ARCH:-sm_35}"
|
||||||
|
mkdir -p bin
|
||||||
|
nvcc -ccbin g++ -O3 -arch="$CUDA_ARCH" -o bin/wave_cuda src/wave_cuda.cu
|
||||||
|
echo "Built bin/wave_cuda (arch=$CUDA_ARCH)"
|
||||||
6
task2/scripts/build_mpi.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
mkdir -p bin
|
||||||
|
mpicc -O3 -std=c99 -o bin/wave_mpi src/wave_mpi.c
|
||||||
|
echo "Built bin/wave_mpi"
|
||||||
73
task2/scripts/plot_task2_results.py
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Строит график зависимости времени вычисления от размера полигона
|
||||||
|
для MPI (1, 2, 4 узла) и CUDA.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
python3 plot_task2_results.py \
|
||||||
|
--mpi1 results/task2-mpi-1n-XXXXX.csv \
|
||||||
|
--mpi2 results/task2-mpi-2n-XXXXX.csv \
|
||||||
|
--mpi4 results/task2-mpi-4n-XXXXX.csv \
|
||||||
|
--cuda results/task2-cuda-XXXXX.csv \
|
||||||
|
-o report/img/task2-time-comparison.png
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
|
||||||
|
def read_mpi_csv(path: str) -> tuple[list[int], list[float]]:
|
||||||
|
sizes, times = [], []
|
||||||
|
with open(path) as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
sizes.append(int(row["n"]))
|
||||||
|
times.append(float(row["time_ms"]))
|
||||||
|
return sizes, times
|
||||||
|
|
||||||
|
|
||||||
|
def read_cuda_csv(path: str) -> tuple[list[int], list[float]]:
|
||||||
|
sizes, times = [], []
|
||||||
|
with open(path) as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
sizes.append(int(row["n"]))
|
||||||
|
times.append(float(row["time_ms"]))
|
||||||
|
return sizes, times
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--mpi1", required=True, help="CSV for MPI 1 node")
|
||||||
|
parser.add_argument("--mpi2", required=True, help="CSV for MPI 2 nodes")
|
||||||
|
parser.add_argument("--mpi4", required=True, help="CSV for MPI 4 nodes")
|
||||||
|
parser.add_argument("--cuda", required=True, help="CSV for CUDA")
|
||||||
|
parser.add_argument("-o", "--output", default="task2-time-comparison.png")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(10, 6))
|
||||||
|
|
||||||
|
for label, path in [
|
||||||
|
("MPI 1 node", args.mpi1),
|
||||||
|
("MPI 2 nodes", args.mpi2),
|
||||||
|
("MPI 4 nodes", args.mpi4),
|
||||||
|
("CUDA", args.cuda),
|
||||||
|
]:
|
||||||
|
sizes, times = read_mpi_csv(path) if "mpi" in label.lower() else read_cuda_csv(path)
|
||||||
|
ax.plot(sizes, times, marker="o", label=label)
|
||||||
|
|
||||||
|
ax.set_xlabel("Размер полигона n")
|
||||||
|
ax.set_ylabel("Время, мс")
|
||||||
|
ax.set_title("Зависимость времени вычисления от размера полигона")
|
||||||
|
ax.legend()
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
Path(args.output).parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
fig.savefig(args.output, dpi=150, bbox_inches="tight")
|
||||||
|
print(f"Saved: {args.output}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
49
task2/scripts/run_cuda.slurm
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#SBATCH --job-name=task2-cuda
|
||||||
|
#SBATCH --partition=tornado-k40
|
||||||
|
#SBATCH --nodes=1
|
||||||
|
#SBATCH --ntasks=1
|
||||||
|
#SBATCH --time=00:20:00
|
||||||
|
#SBATCH --output=results/%x-%j.out
|
||||||
|
#SBATCH --error=results/%x-%j.err
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "${SLURM_SUBMIT_DIR}"
|
||||||
|
|
||||||
|
module purge
|
||||||
|
module load compiler/gcc/11
|
||||||
|
module load nvidia/cuda/11.6u2
|
||||||
|
|
||||||
|
mkdir -p results bin
|
||||||
|
|
||||||
|
./scripts/build_cuda.sh
|
||||||
|
|
||||||
|
echo "===== account info ====="
|
||||||
|
whoami; hostname; date
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== slurm info ====="
|
||||||
|
echo "SLURM_JOB_ID=${SLURM_JOB_ID:-unknown}"
|
||||||
|
echo "SLURM_JOB_PARTITION=${SLURM_JOB_PARTITION:-unknown}"
|
||||||
|
echo "SLURM_NODELIST=${SLURM_NODELIST:-unknown}"
|
||||||
|
scontrol show job "${SLURM_JOB_ID}" || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== node config ====="
|
||||||
|
lscpu | head -20
|
||||||
|
nvidia-smi -L || true
|
||||||
|
nvidia-smi || true
|
||||||
|
|
||||||
|
CSV="results/task2-cuda-${SLURM_JOB_ID}.csv"
|
||||||
|
echo "n,impl,time_ms,path_len,iterations" > "$CSV"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== benchmark ====="
|
||||||
|
for N in 500 1000 2000 3000 5000; do
|
||||||
|
echo "--- n=$N ---"
|
||||||
|
./bin/wave_cuda "$N" 256 256 "$CSV"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== done ====="
|
||||||
54
task2/scripts/run_mpi.slurm
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#SBATCH --job-name=task2-mpi
|
||||||
|
#SBATCH --partition=tornado
|
||||||
|
#SBATCH --ntasks-per-node=1
|
||||||
|
#SBATCH --cpus-per-task=56
|
||||||
|
#SBATCH --time=00:20:00
|
||||||
|
#SBATCH --output=results/%x-%j.out
|
||||||
|
#SBATCH --error=results/%x-%j.err
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "${SLURM_SUBMIT_DIR}"
|
||||||
|
|
||||||
|
module purge
|
||||||
|
module load compiler/gcc/11
|
||||||
|
module load mpi/openmpi
|
||||||
|
|
||||||
|
mkdir -p results bin
|
||||||
|
|
||||||
|
./scripts/build_mpi.sh
|
||||||
|
|
||||||
|
RANKS=${SLURM_JOB_NUM_NODES}
|
||||||
|
|
||||||
|
echo "===== account info ====="
|
||||||
|
whoami; hostname; date
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== slurm info ====="
|
||||||
|
echo "SLURM_JOB_ID=${SLURM_JOB_ID:-unknown}"
|
||||||
|
echo "SLURM_JOB_PARTITION=${SLURM_JOB_PARTITION:-unknown}"
|
||||||
|
echo "SLURM_JOB_NUM_NODES=${SLURM_JOB_NUM_NODES:-unknown}"
|
||||||
|
echo "SLURM_NODELIST=${SLURM_NODELIST:-unknown}"
|
||||||
|
echo "RANKS=${RANKS}"
|
||||||
|
scontrol show job "${SLURM_JOB_ID}" || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== node config ====="
|
||||||
|
lscpu | head -20
|
||||||
|
if [ -n "${SLURMD_NODENAME:-}" ]; then
|
||||||
|
scontrol show node "${SLURMD_NODENAME}" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
CSV="results/task2-mpi-${RANKS}n-${SLURM_JOB_ID}.csv"
|
||||||
|
echo "n,procs,time_ms,path_len,iterations" > "$CSV"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== benchmark (${RANKS} nodes / ${RANKS} ranks) ====="
|
||||||
|
for N in 500 1000 2000 3000 5000; do
|
||||||
|
echo "--- n=$N ---"
|
||||||
|
mpirun -np "${RANKS}" --map-by ppr:1:node --bind-to none ./bin/wave_mpi "$N" "$CSV"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===== done ====="
|
||||||
126
task2/src/wave_cuda.cu
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <cuda_runtime.h>
|
||||||
|
#include <device_launch_parameters.h>
|
||||||
|
|
||||||
|
#define INF UINT_MAX
|
||||||
|
#define OBSTACLE_PROB 10
|
||||||
|
#define DEFAULT_BLOCKS 256
|
||||||
|
#define DEFAULT_THREADS 256
|
||||||
|
|
||||||
|
static void generate_polygon(int *P, int n) {
|
||||||
|
srand(42);
|
||||||
|
for (int i = 0; i < n * n; i++)
|
||||||
|
P[i] = (rand() % 100 < OBSTACLE_PROB) ? -1 : 0;
|
||||||
|
int sx = 2, sy = 2;
|
||||||
|
int fx = n - 3, fy = n - 3;
|
||||||
|
P[sx * n + sy] = 0;
|
||||||
|
P[fx * n + fy] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__global__ void wave_step(int *P, unsigned int *dist, int n, bool *changed) {
|
||||||
|
int tid = threadIdx.x + blockIdx.x * blockDim.x;
|
||||||
|
|
||||||
|
while (tid < n * n) {
|
||||||
|
int i = tid / n;
|
||||||
|
int j = tid % n;
|
||||||
|
|
||||||
|
if (P[tid] != -1) {
|
||||||
|
unsigned int cur = dist[tid];
|
||||||
|
unsigned int mn = cur;
|
||||||
|
|
||||||
|
if (i > 0 && dist[(i-1)*n + j] != INF) mn = min(mn, dist[(i-1)*n + j] + 1);
|
||||||
|
if (i < n - 1 && dist[(i+1)*n + j] != INF) mn = min(mn, dist[(i+1)*n + j] + 1);
|
||||||
|
if (j > 0 && dist[i*n + j - 1] != INF) mn = min(mn, dist[i*n + j - 1] + 1);
|
||||||
|
if (j < n - 1 && dist[i*n + j + 1] != INF) mn = min(mn, dist[i*n + j + 1] + 1);
|
||||||
|
|
||||||
|
if (mn < cur) {
|
||||||
|
dist[tid] = mn;
|
||||||
|
*changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tid += blockDim.x * gridDim.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s <matrix_size> [blocks] [threads] [csv_file]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = atoi(argv[1]);
|
||||||
|
int blocks = (argc >= 3) ? atoi(argv[2]) : DEFAULT_BLOCKS;
|
||||||
|
int threads = (argc >= 4) ? atoi(argv[3]) : DEFAULT_THREADS;
|
||||||
|
const char *csv_path = (argc >= 5) ? argv[4] : NULL;
|
||||||
|
|
||||||
|
int sx = 2, sy = 2;
|
||||||
|
int fx = n - 3, fy = n - 3;
|
||||||
|
|
||||||
|
int *P = (int *)malloc(n * n * sizeof(int));
|
||||||
|
generate_polygon(P, n);
|
||||||
|
|
||||||
|
unsigned int *dist_h = (unsigned int *)malloc(n * n * sizeof(unsigned int));
|
||||||
|
for (int i = 0; i < n * n; i++) dist_h[i] = INF;
|
||||||
|
dist_h[sx * n + sy] = 0;
|
||||||
|
|
||||||
|
int *d_P;
|
||||||
|
unsigned int *d_dist;
|
||||||
|
bool *d_changed;
|
||||||
|
cudaMalloc(&d_P, n * n * sizeof(int));
|
||||||
|
cudaMalloc(&d_dist, n * n * sizeof(unsigned int));
|
||||||
|
cudaMalloc(&d_changed, sizeof(bool));
|
||||||
|
|
||||||
|
cudaMemcpy(d_P, P, n * n * sizeof(int), cudaMemcpyHostToDevice);
|
||||||
|
cudaMemcpy(d_dist, dist_h, n * n * sizeof(unsigned int), cudaMemcpyHostToDevice);
|
||||||
|
|
||||||
|
cudaEvent_t t0, t1;
|
||||||
|
cudaEventCreate(&t0);
|
||||||
|
cudaEventCreate(&t1);
|
||||||
|
cudaEventRecord(t0);
|
||||||
|
|
||||||
|
int iterations = 0;
|
||||||
|
bool changed;
|
||||||
|
do {
|
||||||
|
changed = false;
|
||||||
|
cudaMemcpy(d_changed, &changed, sizeof(bool), cudaMemcpyHostToDevice);
|
||||||
|
wave_step<<<blocks, threads>>>(d_P, d_dist, n, d_changed);
|
||||||
|
cudaDeviceSynchronize();
|
||||||
|
cudaMemcpy(&changed, d_changed, sizeof(bool), cudaMemcpyDeviceToHost);
|
||||||
|
iterations++;
|
||||||
|
} while (changed && iterations < 2 * n);
|
||||||
|
|
||||||
|
cudaEventRecord(t1);
|
||||||
|
cudaEventSynchronize(t1);
|
||||||
|
float elapsed_ms = 0;
|
||||||
|
cudaEventElapsedTime(&elapsed_ms, t0, t1);
|
||||||
|
|
||||||
|
cudaMemcpy(dist_h, d_dist, n * n * sizeof(unsigned int), cudaMemcpyDeviceToHost);
|
||||||
|
|
||||||
|
unsigned int path_len = dist_h[fx * n + fy];
|
||||||
|
if (path_len == INF)
|
||||||
|
printf("n=%d Path not found! time=%.2f ms iters=%d blocks=%d threads=%d\n",
|
||||||
|
n, elapsed_ms, iterations, blocks, threads);
|
||||||
|
else
|
||||||
|
printf("n=%d path_len=%u time=%.2f ms iters=%d blocks=%d threads=%d\n",
|
||||||
|
n, path_len, elapsed_ms, iterations, blocks, threads);
|
||||||
|
|
||||||
|
if (csv_path) {
|
||||||
|
FILE *fp = fopen(csv_path, "a");
|
||||||
|
if (fp) {
|
||||||
|
fprintf(fp, "%d,cuda,%.4f,%u,%d\n", n, elapsed_ms, path_len, iterations);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(P);
|
||||||
|
free(dist_h);
|
||||||
|
cudaFree(d_P);
|
||||||
|
cudaFree(d_dist);
|
||||||
|
cudaFree(d_changed);
|
||||||
|
cudaEventDestroy(t0);
|
||||||
|
cudaEventDestroy(t1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
185
task2/src/wave_mpi.c
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <mpi.h>
|
||||||
|
|
||||||
|
#define INF UINT_MAX
|
||||||
|
#define OBSTACLE_PROB 10
|
||||||
|
|
||||||
|
static void generate_polygon(int *P, int n) {
|
||||||
|
srand(42);
|
||||||
|
for (int i = 0; i < n * n; i++)
|
||||||
|
P[i] = (rand() % 100 < OBSTACLE_PROB) ? -1 : 0;
|
||||||
|
int sx = 2, sy = 2;
|
||||||
|
int fx = n - 3, fy = n - 3;
|
||||||
|
P[sx * n + sy] = 0;
|
||||||
|
P[fx * n + fy] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
MPI_Init(&argc, &argv);
|
||||||
|
|
||||||
|
int rank, size;
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &size);
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
if (rank == 0)
|
||||||
|
fprintf(stderr, "Usage: mpirun -np <P> %s <matrix_size> [csv_file]\n", argv[0]);
|
||||||
|
MPI_Finalize();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = atoi(argv[1]);
|
||||||
|
const char *csv_path = (argc >= 3) ? argv[2] : NULL;
|
||||||
|
int sx = 2, sy = 2;
|
||||||
|
int fx = n - 3, fy = n - 3;
|
||||||
|
|
||||||
|
int *P = (int *)malloc(n * n * sizeof(int));
|
||||||
|
unsigned int *dist = NULL;
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
generate_polygon(P, n);
|
||||||
|
dist = (unsigned int *)malloc(n * n * sizeof(unsigned int));
|
||||||
|
for (int i = 0; i < n * n; i++)
|
||||||
|
dist[i] = INF;
|
||||||
|
dist[sx * n + sy] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Bcast(P, n * n, MPI_INT, 0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
int base_rows = n / size;
|
||||||
|
int remainder = n % size;
|
||||||
|
int local_rows = base_rows + (rank < remainder ? 1 : 0);
|
||||||
|
int start_row = rank * base_rows + (rank < remainder ? rank : remainder);
|
||||||
|
|
||||||
|
int ghost_top = (rank > 0) ? 1 : 0;
|
||||||
|
int ghost_bot = (rank < size - 1) ? 1 : 0;
|
||||||
|
int total_local = (ghost_top + local_rows + ghost_bot) * n;
|
||||||
|
|
||||||
|
unsigned int *local_dist = (unsigned int *)malloc(total_local * sizeof(unsigned int));
|
||||||
|
int *local_P = (int *)malloc(total_local * sizeof(int));
|
||||||
|
|
||||||
|
for (int i = 0; i < total_local; i++) {
|
||||||
|
local_dist[i] = INF;
|
||||||
|
local_P[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int *sendcounts = NULL, *displs = NULL;
|
||||||
|
if (rank == 0) {
|
||||||
|
sendcounts = (int *)malloc(size * sizeof(int));
|
||||||
|
displs = (int *)malloc(size * sizeof(int));
|
||||||
|
int off = 0;
|
||||||
|
for (int r = 0; r < size; r++) {
|
||||||
|
int rr = base_rows + (r < remainder ? 1 : 0);
|
||||||
|
sendcounts[r] = rr * n;
|
||||||
|
displs[r] = off;
|
||||||
|
off += rr * n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Scatterv(
|
||||||
|
(rank == 0) ? dist : NULL, sendcounts, displs, MPI_UNSIGNED,
|
||||||
|
local_dist + ghost_top * n, local_rows * n, MPI_UNSIGNED,
|
||||||
|
0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
for (int i = 0; i < local_rows; i++)
|
||||||
|
memcpy(local_P + (ghost_top + i) * n, P + (start_row + i) * n, n * sizeof(int));
|
||||||
|
|
||||||
|
if (ghost_top) {
|
||||||
|
memcpy(local_P, P + (start_row - 1) * n, n * sizeof(int));
|
||||||
|
}
|
||||||
|
if (ghost_bot) {
|
||||||
|
memcpy(local_P + (ghost_top + local_rows) * n,
|
||||||
|
P + (start_row + local_rows) * n, n * sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Barrier(MPI_COMM_WORLD);
|
||||||
|
double t_start = MPI_Wtime();
|
||||||
|
|
||||||
|
int prev_rank = (rank > 0) ? rank - 1 : MPI_PROC_NULL;
|
||||||
|
int next_rank = (rank < size - 1) ? rank + 1 : MPI_PROC_NULL;
|
||||||
|
|
||||||
|
int iteration = 0;
|
||||||
|
int global_changed;
|
||||||
|
do {
|
||||||
|
/* exchange ghost rows */
|
||||||
|
MPI_Sendrecv(
|
||||||
|
local_dist + ghost_top * n, n, MPI_UNSIGNED, prev_rank, 0,
|
||||||
|
local_dist + (ghost_top + local_rows) * n, n, MPI_UNSIGNED, next_rank, 0,
|
||||||
|
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
MPI_Sendrecv(
|
||||||
|
local_dist + (ghost_top + local_rows - 1) * n, n, MPI_UNSIGNED, next_rank, 1,
|
||||||
|
local_dist, n, MPI_UNSIGNED, prev_rank, 1,
|
||||||
|
MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||||||
|
|
||||||
|
int local_changed = 0;
|
||||||
|
|
||||||
|
for (int li = ghost_top; li < ghost_top + local_rows; li++) {
|
||||||
|
for (int j = 0; j < n; j++) {
|
||||||
|
int idx = li * n + j;
|
||||||
|
if (local_P[idx] == -1) continue;
|
||||||
|
|
||||||
|
unsigned int cur = local_dist[idx];
|
||||||
|
unsigned int mn = cur;
|
||||||
|
|
||||||
|
if (li > 0 && local_dist[(li - 1) * n + j] != INF)
|
||||||
|
mn = (local_dist[(li - 1) * n + j] + 1 < mn) ? local_dist[(li - 1) * n + j] + 1 : mn;
|
||||||
|
if (li < ghost_top + local_rows + ghost_bot - 1 && local_dist[(li + 1) * n + j] != INF)
|
||||||
|
mn = (local_dist[(li + 1) * n + j] + 1 < mn) ? local_dist[(li + 1) * n + j] + 1 : mn;
|
||||||
|
if (j > 0 && local_dist[li * n + j - 1] != INF)
|
||||||
|
mn = (local_dist[li * n + j - 1] + 1 < mn) ? local_dist[li * n + j - 1] + 1 : mn;
|
||||||
|
if (j < n - 1 && local_dist[li * n + j + 1] != INF)
|
||||||
|
mn = (local_dist[li * n + j + 1] + 1 < mn) ? local_dist[li * n + j + 1] + 1 : mn;
|
||||||
|
|
||||||
|
if (mn < cur) {
|
||||||
|
local_dist[idx] = mn;
|
||||||
|
local_changed = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Allreduce(&local_changed, &global_changed, 1, MPI_INT, MPI_LOR, MPI_COMM_WORLD);
|
||||||
|
iteration++;
|
||||||
|
} while (global_changed && iteration < 2 * n);
|
||||||
|
|
||||||
|
double t_end = MPI_Wtime();
|
||||||
|
double elapsed_ms = (t_end - t_start) * 1000.0;
|
||||||
|
|
||||||
|
MPI_Gatherv(
|
||||||
|
local_dist + ghost_top * n, local_rows * n, MPI_UNSIGNED,
|
||||||
|
(rank == 0) ? dist : NULL, sendcounts, displs, MPI_UNSIGNED,
|
||||||
|
0, MPI_COMM_WORLD);
|
||||||
|
|
||||||
|
if (rank == 0) {
|
||||||
|
unsigned int path_len = dist[fx * n + fy];
|
||||||
|
if (path_len == INF)
|
||||||
|
printf("n=%d Path not found! time=%.2f ms iters=%d procs=%d\n",
|
||||||
|
n, elapsed_ms, iteration, size);
|
||||||
|
else
|
||||||
|
printf("n=%d path_len=%u time=%.2f ms iters=%d procs=%d\n",
|
||||||
|
n, path_len, elapsed_ms, iteration, size);
|
||||||
|
|
||||||
|
if (csv_path) {
|
||||||
|
FILE *fp = fopen(csv_path, "a");
|
||||||
|
if (fp) {
|
||||||
|
fprintf(fp, "%d,%d,%.4f,%u,%d\n",
|
||||||
|
n, size, elapsed_ms, path_len, iteration);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(sendcounts);
|
||||||
|
free(displs);
|
||||||
|
free(dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(P);
|
||||||
|
free(local_dist);
|
||||||
|
free(local_P);
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||