Compare commits
8 Commits
850ae49a14
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 33f1e2b053 | |||
| a62ed3b44f | |||
| 950085feaa | |||
| d34d885762 | |||
| 5b0f9f1904 | |||
| 447ad9c0b8 | |||
| d0b593fa92 | |||
| 4ee74c099f |
3
report/.gitignore
vendored
3
report/.gitignore
vendored
@@ -2,4 +2,5 @@
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!img/**
|
||||
!plot.py
|
||||
BIN
report/img/plot.png
Normal file
BIN
report/img/plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
27
report/plot.py
Normal file
27
report/plot.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Код для рисования графика для отчёта."""
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.xlabel("Количество потоков в блоке", fontsize=18)
|
||||
plt.ylabel("Время выполения, мс", fontsize=18)
|
||||
plt.grid(True, which="both", linestyle="--", linewidth=0.5)
|
||||
threads_per_block = [1, 10, 100, 1000]
|
||||
|
||||
# Тут указываем своё время
|
||||
plt.plot(threads_per_block, [44648, 23257, 3643, 596], label="1 блок")
|
||||
plt.plot(threads_per_block, [15096, 4075, 536, 88], label="10 блоков")
|
||||
plt.plot(threads_per_block, [1694, 554, 93, 49], label="100 блоков")
|
||||
plt.plot(threads_per_block, [1027, 291, 55, 64], label="1000 блоков")
|
||||
plt.plot(threads_per_block, [1140, 223, 77, 196], label="10000 блоков")
|
||||
|
||||
# Тут указываем параметры оси Y (0, макс время, шаг)
|
||||
# plt.yticks([i for i in range(0, 44648, 2500)])
|
||||
# plt.xticks([i for i in range(0, 1000, 100)])
|
||||
|
||||
# Либо так, чтобы был логарифмический масштаб
|
||||
plt.yscale("log")
|
||||
plt.xscale("log")
|
||||
|
||||
|
||||
plt.legend()
|
||||
plt.show()
|
||||
@@ -481,7 +481,7 @@ K40. Ниже приведены вычислительные возможнос
|
||||
|
||||
Надо:
|
||||
\begin{itemize}
|
||||
\item построить L - траекторию, соединяющую P1 и P2.
|
||||
\item построить L - траекторию, соединяющую P1 и P2, либо вывести на экран сообщение о том, что такой траектории не существует.
|
||||
\end{itemize}
|
||||
|
||||
Ограничения:
|
||||
@@ -539,11 +539,246 @@ K40. Ниже приведены вычислительные возможнос
|
||||
|
||||
Волновой алгоритм либо находит кратчайший путь от начальной к конечной точке, либо информирует о неудаче, если путь к конечной точке блокируется препятствиями.
|
||||
|
||||
\subsection{Метод распараллеливания алгоритма}
|
||||
Рассмотрим два подхода к распараллеливанию этой задачи на CUDA: с использованием глобальной памяти и с использованием разделяемой (shared) памяти.
|
||||
|
||||
В обоих подходах восстановление пути происходит на CPU и не подвергается распараллеливанию.
|
||||
|
||||
\subsubsection{Глобальная память}
|
||||
Используется одномерная сетка блоков и одномерная сетка потоков внутри блоков. Таким образом уникальный идентификатор потока вычисляется по формуле:
|
||||
|
||||
\texttt{tid = threadIdx.x + blockIdx.x * blockDim.x}
|
||||
|
||||
Каждый поток обрабатывает несколько ячеек исходной матрицы с шагом \texttt{blockDim.x * gridDim.x} (общее количество потоков во всех блоках). Каждая клетка исходной матрицы обрабатывается только одним потоком.
|
||||
|
||||
На каждом шаге алгоритма каждый поток пытается уменьшить значения своих клеток. Поток обращается к глобальной памяти, чтобы получить значения соседних клеток. Значение текущей клетки обновляется, если значение одной из соседних клеток, увеличенное на единицу, меньше текущего значения клетки. То есть по следующей формуле:
|
||||
|
||||
$$
|
||||
cell = \min(cell, left + 1, right + 1, top + 1, bottom + 1),
|
||||
$$
|
||||
где $cell$ -- значение в текущей клетке, $left$ -- значение в соседней слева, $right$ -- значение в соседней справа, $top$ -- значение в соседней сверху, $bottom$ -- значение в соседней снизу. Клетки с препятствиями игнорируются и не участвуют в формуле.
|
||||
|
||||
Следующий шаг алгоритма запускается, если хотя бы одному потоку удалось обновить значение в какой-нибудь своей клетке. Алгоритм завершается, если больше не осталось незаполненных клеток и дальнейшее обновление расстояний невозможно. Для отслеживания изменений используется флаг, который также хранится в глобальной памяти.
|
||||
|
||||
\subsubsection{Разделяемая память}
|
||||
Используется одномерная сетка блоков. Потоки внутри блоков организованы в двумерную сетку.
|
||||
|
||||
Каждый блок потоков обрабатывает несколько соразмерных ему подматриц исходной матрицы с шагом, равным количеству используемых блоков. Каждая подматрица обрабатывается только одним блоком. Посколько блок и поматрица имеют равные размеры, каждому потоку блока в соответствие ставится одна ячейка поматрицы. Таким образом каждая ячейка исходной матрицы обрабатывается только одним потоком.
|
||||
|
||||
На каждом шаге алгоритма каждый блок потоков копирует соответствующую подматрицу исходной матриц из глобальной памяти в разделяемую. Затем на подматрице запускается паралелльная версия волнового алгоритма, описанная в предыдущем разделе, с двумя отличиями:
|
||||
\begin{itemize}
|
||||
\item К глобальной памяти обращаются только те потоки, которые обрабатывают клетки на границах подматрицы. Потому что некоторые соседи граничных клеток находятся вне скопированной в разделяемую память подматрицы. Такой подход позволяет значительно сократить обращения к глобальной памяти при использовании больших размеров блоков.
|
||||
\item Локальный флаг для отслеживания изменений хранится в разделяемой памяти каждого потока.
|
||||
\end{itemize}
|
||||
После выполнения волнового алгоритма на подматрице в разделяемой памяти, её значения копируются в соответствующую подматрицу исходной матрицы в глобальной памяти.
|
||||
|
||||
Таким образом, на каждом шаге алгоритма к волне прибавляются не просто отдельные ячейки, а целые подматрицы, соразмерные используемым блокам потоков.
|
||||
|
||||
Следующий шаг алгоритма запускается, если хотя бы одному блоку потоков удалось обновить значения в своей подматрице. Алгоритм завершается, если больше не осталось незаполненных подматриц и дальнейшее обновление расстояний невозможно. Для отслеживания изменений в подматрицах используется дополнительный флаг, хранящийся в глобальной памяти.
|
||||
|
||||
|
||||
\newpage
|
||||
\section{Описание эксперимента}
|
||||
В этом разделе выполняется исследование времени решения задачи при изменении следующих параметров:
|
||||
|
||||
\begin{itemize}
|
||||
\item Размеры матрицы: 500 × 500, 1000 × 1000, 1500 × 1500;
|
||||
\item Количество блоков: 1, 10, 100, 1000, 10000;
|
||||
\item Количество потоков: 1, 9, 100, 1024;
|
||||
\item Используемая память: глобальная, глобальная и разделяемая.
|
||||
\end{itemize}
|
||||
|
||||
Для каждой комбинации параметров было проведено 100 измерений с использованием событий CUDA, после чего вычислялось среднее значение.
|
||||
|
||||
\newpage
|
||||
\section{Анализ результатов}
|
||||
В таблицах 1, 2, 3 приведены результаты измерения времени в миллисекундах для глобальной памяти для размеров матрицы 100 × 100, 500 × 500, 1000 × 1000 соответственно. В таблицах 4, 5, 6 приведены результаты измерения времени в миллисекундах для разделяемой памяти для размеров матрицы 100 × 100, 500 × 500, 1000 × 1000 соответственно.
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 100 × 100 и глобальной памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 171.85 & 120.98 & 19.41 & 12.84 & 23.68 \\
|
||||
\hline
|
||||
9 & 233.35 & 29.13 & 8.14 & 5.68 & 10.55 \\
|
||||
\hline
|
||||
100 & 27.57 & 7.79 & 3.90 & \textbf{3.79} & 8.97 \\
|
||||
\hline
|
||||
1024 & 7.96 & 4.04 & 4.16 & 6.47 & 32.24 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 500 × 500 и глобальной памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 44648 & 15096 & 1694 & 1027 & 1140 \\
|
||||
\hline
|
||||
9 & 23257 & 4075 & 554 & 291 & 223 \\
|
||||
\hline
|
||||
100 & 3643 & 536 & 93 & 55 & 77 \\
|
||||
\hline
|
||||
1024 & 596 & 88 & \textbf{49} & 64 & 196 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 1000 × 1000 и глобальной памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 189324 & 121341 & 12760 & 7010 & 6472 \\
|
||||
\hline
|
||||
9 & 164810 & 29369 & 4435 & 2208 & 1356 \\
|
||||
\hline
|
||||
100 & 28946 & 2985 & 628 & 329 & 360 \\
|
||||
\hline
|
||||
1024 & 3701 & 609 & \textbf{277} & 322 & 579 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 100 × 100 и разделяемой памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 260.60 & 230.96 & 47.04 & 18.13 & 32.70 \\
|
||||
\hline
|
||||
9 & 28.65 & 19.92 & 7.27 & 3.10 & 4.38 \\
|
||||
\hline
|
||||
100 & 5.46 & 4.71 & \textbf{1.35} & 1.36 & 1.45 \\
|
||||
\hline
|
||||
1024 & 4.39 & 2.68 & 2.65 & 2.67 & 3.00 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 500 × 500 и разделяемой памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 48837 & 31345 & 3566 & 1786 & 1724 \\
|
||||
\hline
|
||||
9 & 1120 & 2109 & 347 & 163 & 148 \\
|
||||
\hline
|
||||
100 & 166 & 166 & 65 & 37 & \textbf{17} \\
|
||||
\hline
|
||||
1024 & 99 & 64 & 34 & 112 & 114 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Результаты измерения времени исполнения программы для матрицы 1000 × 1000 и разделяемой памяти. Время указано в миллисекундах.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|X|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Число потоков в блоков} & \multicolumn{5}{c|}{\textbf{Число блоков}} \\
|
||||
\hline
|
||||
& 1 & 10 & 100 & 1000 & 10000 \\
|
||||
\hline
|
||||
1 & 127512 & 48471 & 24677 & 14065 & 12445 \\
|
||||
\hline
|
||||
9 & 5775 & 16595 & 2062 & 1138 & 727 \\
|
||||
\hline
|
||||
100 & 656 & 961 & 491 & 124 & \textbf{103} \\
|
||||
\hline
|
||||
1024 & 398 & 190 & 222 & 384 & 618 \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\end{table}
|
||||
|
||||
\newpage
|
||||
В целом, увеличение количества потоков приводит к уменьшению времени выполнения вплоть до некоторого предела, после которого время начинает возрастать. Это связано с тем, что слишком большое число потоков приводит к неэффективному использованию ресурсов GPU, вызывая задержки из-за управления бездействующими потоками. В среднем лучше всего себя показывают конфигурации, где количестов потоков близко к количеству элементов в матрице.
|
||||
|
||||
Были выделены лучшие конфигурации для матриц с разными размерами:
|
||||
\begin{itemize}
|
||||
\item Для матрицы 100 × 100:
|
||||
\begin{itemize}
|
||||
\item Глобальная память: 100 блоков, 100 потоков, 3.79 мс.
|
||||
\item Разделяемая память: 100 блоков, 100 потоков, 1.35 мс.
|
||||
\end{itemize}
|
||||
\item Для матрицы 500 × 500:
|
||||
\begin{itemize}
|
||||
\item Глобальная память: 100 блоков, 1024 потока, 49 мс.
|
||||
\item Разделяемая память: 10 000 блоков, 100 потоков, 17 мс.
|
||||
\end{itemize}
|
||||
\item Для матрицы 1000 × 1000:
|
||||
\begin{itemize}
|
||||
\item Глобальная память: 100 блоков, 1024 потока, 277 мс.
|
||||
\item Разделяемая память: 10 000 блоков, 100 потоков, 103 мс.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
На Рис.~\ref{fig:plot} отображена зависимость времения от разного числа потоков в определенной выборке блоков.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/plot.png}
|
||||
\caption{Зависимость времени выполнения программы от разного числа потоков в блоке для матрицы 1000 x 1000 и глобальной памяти. Используется логарифмический масштаб по обеим осям.}
|
||||
\label{fig:plot}
|
||||
\end{figure}
|
||||
|
||||
Использование разделяемой памяти в большинстве случаев позволяет значительно ускорить выполнение алгоритма по сравнению с использованием только глобальной памяти (в среднем на 60\%). Особенно заметно это на больших размерах матриц, где доступ к глобальной памяти становится узким местом.
|
||||
|
||||
Алгоритму с разделяемой памятью важно, чтобы в каждом блоке было как можно больше потоков. Это связано с тем, что чем больше потоков в блоке, тем больше ячеек матрицы можно обработать одновременно, используя быструю разделяемую память вместо глобальной. При небольшом количестве потоков в блоке преимуществ разделяемой памяти становится меньше, так как потоки вынуждены чаще обращаться к глобальной памяти, что снижает общую производительность.
|
||||
|
||||
|
||||
\newpage
|
||||
\phantom{text}
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
\addcontentsline{toc}{section}{Заключение}
|
||||
|
||||
В рамках курсовой работы было изучена технология параллельного программирования на основе архитектуры Nvidia CUDA.
|
||||
|
||||
Для задачи построения пути движения робота по полигону был разработан параллельный асинхронный алгоритм, алгоритм был реализован на языке CUDA C. Программа была
|
||||
запущена на ресурсах суперкомпьютерного центра «Политехнический». Для запуска использовался узел типа «Торнадо» с видеокартой NVIDIA Tesla K40X. Запуск
|
||||
программы проводился на одном узле с использованием одной видеокарты.
|
||||
|
||||
Было измерено время работы программы при различной степени распараллеливания, разных размерах матриц и разной памяти. Использование оптимальной конфигурации позволило
|
||||
уменьшить время выполнения в 100 раз относительно наихудшей конфигурации для
|
||||
матрциы 100 × 100, в 1000 раз для 500 × 500 и в 1500 раз для 1000 × 1000.
|
||||
|
||||
Реализация алгоритма с использованием разделяемой памяти показала значительно более высокую эффективность. Время выполнения алгоритма снизилось в среднем на 60\%.
|
||||
|
||||
В рамках курсовой работы была написана программа размером 280 строк. Работа на СКЦ «Политехнический» шла две недели, за это время было сделано примерно 100 запусков задач на исполнение.
|
||||
Для сборки использовался компилятор NVCC версии 11.6u2.
|
||||
|
||||
|
||||
\newpage
|
||||
\section*{Список литературы}
|
||||
@@ -552,7 +787,7 @@ K40. Ниже приведены вычислительные возможнос
|
||||
\vspace{-1.5cm}
|
||||
\begin{thebibliography}{0}
|
||||
\bibitem{mayers}
|
||||
Майерс, Г. Искусство тестирования программ. -- Санкт-Петербург: Диалектика, 2012 г.
|
||||
Сандерс, Д. Технология CUDA в примерах: введение в программирование графических процессоров -- Москва: изд. ДМК Пресс, 2013 г -- 232 с.
|
||||
\end{thebibliography}
|
||||
|
||||
\end{document}
|
||||
Reference in New Issue
Block a user