3620 lines
192 KiB
TeX
3620 lines
192 KiB
TeX
\documentclass[a4paper, final]{article}
|
||
\usepackage[14pt]{extsizes}
|
||
\usepackage[T2A]{fontenc}
|
||
\usepackage[utf8]{inputenc}
|
||
\usepackage[russian]{babel}
|
||
\usepackage{amsmath}
|
||
\usepackage{subfig}
|
||
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
||
\usepackage{ragged2e}
|
||
\usepackage{setspace}
|
||
\usepackage{float}
|
||
\usepackage{stackengine}
|
||
\usepackage{indentfirst}
|
||
\usepackage{moreverb}
|
||
\renewcommand\verbatimtabsize{4\relax}
|
||
\renewcommand\listingoffset{0.2em}
|
||
\renewcommand{\arraystretch}{1.4}
|
||
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption}
|
||
\usepackage{listingsutf8}
|
||
\usepackage{xcolor}
|
||
\usepackage{listings}
|
||
\usepackage{hyperref}
|
||
\usepackage{enumitem}
|
||
\usepackage{verbatim}
|
||
\usepackage{tikz}
|
||
\usepackage{amssymb}
|
||
\usepackage{pdflscape} %для pdf
|
||
\usepackage{pdfpages} %для pdf
|
||
\usepackage{graphicx}
|
||
\usepackage{tabularx}
|
||
\usepackage{multirow}
|
||
\usepackage{cmap}
|
||
\usepackage{longtable}
|
||
\usepackage{booktabs}
|
||
|
||
\definecolor{apricot}{HTML}{FFF0DA}
|
||
\definecolor{mygreen}{rgb}{0,0.6,0}
|
||
\definecolor{string}{HTML}{B40000} % цвет строк в коде
|
||
\definecolor{comment}{HTML}{008000} % цвет комментариев в коде
|
||
\definecolor{keyword}{HTML}{1A00FF} % цвет ключевых слов в коде
|
||
\definecolor{morecomment}{HTML}{8000FF} % цвет include и других элементов в коде
|
||
\definecolor{captiontext}{HTML}{FFFFFF} % цвет текста заголовка в коде
|
||
\definecolor{captionbk}{HTML}{999999} % цвет фона заголовка в коде
|
||
\definecolor{bk}{HTML}{FFFFFF} % цвет фона в коде
|
||
\definecolor{frame}{HTML}{999999} % цвет рамки в коде
|
||
\definecolor{brackets}{HTML}{B40000} % цвет скобок в коде
|
||
|
||
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
|
||
|
||
\hypersetup{colorlinks,
|
||
allcolors=[RGB]{061 087 255}}
|
||
|
||
|
||
|
||
\textheight=24cm
|
||
\textwidth=16cm
|
||
\oddsidemargin=0pt
|
||
\topmargin=-1.5cm
|
||
\parindent=24pt
|
||
\parskip=0pt
|
||
\tolerance=2000
|
||
\flushbottom
|
||
|
||
|
||
\lstset{
|
||
language=Java, % Язык кода по умолчанию
|
||
morekeywords={*,...}, % если хотите добавить ключевые слова, то добавляйте
|
||
% Цвета
|
||
keywordstyle=\color{keyword}\ttfamily\bfseries,
|
||
%stringstyle=\color{string}\ttfamily,
|
||
stringstyle=\ttfamily\color{red!50!brown},
|
||
commentstyle=\color{comment}\ttfamily,
|
||
morecomment=[l][\color{morecomment}]{\#},
|
||
% Настройки отображения
|
||
breaklines=true, % Перенос длинных строк
|
||
basicstyle=\ttfamily\footnotesize, % Шрифт для отображения кода
|
||
backgroundcolor=\color{bk}, % Цвет фона кода
|
||
frame=single,xleftmargin=\fboxsep,xrightmargin=-\fboxsep, % Рамка, подогнанная к заголовку
|
||
rulecolor=\color{frame}, % Цвет рамки
|
||
tabsize=3, % Размер табуляции в пробелах
|
||
% Настройка отображения номеров строк. Если не нужно, то удалите весь блок
|
||
numbers=left, % Слева отображаются номера строк
|
||
stepnumber=1, % Каждую строку нумеровать
|
||
numbersep=5pt, % Отступ от кода
|
||
numberstyle=\small\color{black}, % Стиль написания номеров строк
|
||
% Для отображения русского языка
|
||
extendedchars=true,
|
||
showstringspaces=false, % не показывать пробелы в виде подчёркиваний
|
||
keepspaces=true, % сохранять исходное количество пробелов
|
||
columns=fullflexible,
|
||
literate={\\}{{\textbackslash}}1
|
||
{Ö}{{\"O}}1
|
||
{+}{{\texttt{+}}}1
|
||
{-}{{\texttt{-}}}1
|
||
{*}{{\texttt{*}}}1
|
||
{/}{{\texttt{/}}}1
|
||
{|}{{\texttt{|}}}1
|
||
{(}{{\texttt{(}}}1
|
||
{)}{{\texttt{)}}}1
|
||
{[}{{[}}1
|
||
{]}{{]}}1
|
||
{^}{{\texttt{\^}}}1
|
||
{\$}{{\texttt{\$}}}1
|
||
{~}{{\textasciitilde}}1
|
||
{а}{{\selectfont\char224}}1
|
||
{б}{{\selectfont\char225}}1
|
||
{в}{{\selectfont\char226}}1
|
||
{г}{{\selectfont\char227}}1
|
||
{д}{{\selectfont\char228}}1
|
||
{е}{{\selectfont\char229}}1
|
||
{ё}{{\"e}}1
|
||
{ж}{{\selectfont\char230}}1
|
||
{з}{{\selectfont\char231}}1
|
||
{и}{{\selectfont\char232}}1
|
||
{й}{{\selectfont\char233}}1
|
||
{к}{{\selectfont\char234}}1
|
||
{л}{{\selectfont\char235}}1
|
||
{м}{{\selectfont\char236}}1
|
||
{н}{{\selectfont\char237}}1
|
||
{о}{{\selectfont\char238}}1
|
||
{п}{{\selectfont\char239}}1
|
||
{р}{{\selectfont\char240}}1
|
||
{с}{{\selectfont\char241}}1
|
||
{т}{{\selectfont\char242}}1
|
||
{у}{{\selectfont\char243}}1
|
||
{ф}{{\selectfont\char244}}1
|
||
{х}{{\selectfont\char245}}1
|
||
{ц}{{\selectfont\char246}}1
|
||
{ч}{{\selectfont\char247}}1
|
||
{ш}{{\selectfont\char248}}1
|
||
{щ}{{\selectfont\char249}}1
|
||
{ъ}{{\selectfont\char250}}1
|
||
{ы}{{\selectfont\char251}}1
|
||
{ь}{{\selectfont\char252}}1
|
||
{э}{{\selectfont\char253}}1
|
||
{ю}{{\selectfont\char254}}1
|
||
{я}{{\selectfont\char255}}1
|
||
{А}{{\selectfont\char192}}1
|
||
{Б}{{\selectfont\char193}}1
|
||
{В}{{\selectfont\char194}}1
|
||
{Г}{{\selectfont\char195}}1
|
||
{Д}{{\selectfont\char196}}1
|
||
{Е}{{\selectfont\char197}}1
|
||
{Ё}{{\"E}}1
|
||
{Ж}{{\selectfont\char198}}1
|
||
{З}{{\selectfont\char199}}1
|
||
{И}{{\selectfont\char200}}1
|
||
{Й}{{\selectfont\char201}}1
|
||
{К}{{\selectfont\char202}}1
|
||
{Л}{{\selectfont\char203}}1
|
||
{М}{{\selectfont\char204}}1
|
||
{Н}{{\selectfont\char205}}1
|
||
{О}{{\selectfont\char206}}1
|
||
{П}{{\selectfont\char207}}1
|
||
{Р}{{\selectfont\char208}}1
|
||
{С}{{\selectfont\char209}}1
|
||
{Т}{{\selectfont\char210}}1
|
||
{У}{{\selectfont\char211}}1
|
||
{Ф}{{\selectfont\char212}}1
|
||
{Х}{{\selectfont\char213}}1
|
||
{Ц}{{\selectfont\char214}}1
|
||
{Ч}{{\selectfont\char215}}1
|
||
{Ш}{{\selectfont\char216}}1
|
||
{Щ}{{\selectfont\char217}}1
|
||
{Ъ}{{\selectfont\char218}}1
|
||
{Ы}{{\selectfont\char219}}1
|
||
{Ь}{{\selectfont\char220}}1
|
||
{Э}{{\selectfont\char221}}1
|
||
{Ю}{{\selectfont\char222}}1
|
||
{Я}{{\selectfont\char223}}1
|
||
{\#}{{\texttt{\#}}}1
|
||
{\{}{{{\color{brackets}\{}}}1 % Цвет скобок {
|
||
{\}}{{{\color{brackets}\}}}}1 % Цвет скобок }
|
||
}
|
||
|
||
|
||
\begin{document}
|
||
|
||
\newcommand{\smalltexttt}[1]{\texttt{\small #1}}
|
||
|
||
\begin{center}
|
||
\hfill \break
|
||
\hfill \break
|
||
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
|
||
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
|
||
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
|
||
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
|
||
\normalsize{Направление: 02.03.01 Математика и компьютерные науки}\\
|
||
|
||
\hfill \break
|
||
\hfill \break
|
||
\hfill \break
|
||
\large{<<Архитектура суперкомпьютерных систем>>}\\
|
||
\large{Отчет по выполению лабораторной работы}\\
|
||
\large{Вариант 18}\\
|
||
\hfill \break
|
||
\vspace{2cm}
|
||
|
||
|
||
\end{center}
|
||
|
||
\small{
|
||
\begin{tabular}{lrrl}
|
||
\!\!\!Студент, & \hspace{2cm} & & \\
|
||
\!\!\!группы 5130201/20101 & \hspace{2cm} & \underline{\hspace{3cm}} & Тищенко А. А. \\\\
|
||
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Чуватов М. В.\\\\
|
||
&&\hspace{5cm}
|
||
\end{tabular}
|
||
\begin{flushright}
|
||
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2026г.
|
||
\end{flushright}
|
||
}
|
||
|
||
|
||
\hfill \break
|
||
\begin{center} \small{Санкт-Петербург, 2026} \end{center}
|
||
\thispagestyle{empty}
|
||
|
||
\begin{center}
|
||
\section*{РЕФЕРАТ}
|
||
\end{center}
|
||
% 79 с., 1 кн., 2 рис., 5 табл., 15 источн., 3 прил.
|
||
|
||
|
||
ГЕТЕРОГЕННЫЕ ВЫЧИСЛИТЕЛЬНЫЕ СИСТЕМЫ, ПАРАЛЛЕЛЬНОЕ ВЫЧИСЛЕНИЕ, MPI, GPU-ВЫЧИСЛЕНИЯ, OPENMPI, HYPER-V, CUDA, ВРЕМЕННЫЕ РЯДЫ, АГРЕГАЦИЯ ДАННЫХ.
|
||
|
||
|
||
Объектом исследования в текущей работе является гетерогенный вычислительный кластер, использующий
|
||
ресурсы GPU и CPU вычислителей. Так как ресурсы физических суперкомпьютерных систем зачастую не доступны, кластер такого рода самостоятельно создается с использованием доступных вычислительных ресурсов.
|
||
Целью работы является создание, настройка и тестирование высокопроизводительного вычислительного кластера, способного эффективно выполнять задачи параллельных вычислений с использованием разнородных аппаратных ресурсов.
|
||
|
||
|
||
В разработанной системе воссоздается окружение суперкомпьютерного вычислителя, использующего разные узлы (виртуальные машины) и конфигурацию slurm.
|
||
|
||
|
||
На базе реализованного кластера разработано параллельное приложение, использующее технологии CUDA и OpenMPI, выполняющее анализ временных рядов исторических данных о стоимости Bitcoin с целью выявления интервалов значительного изменения цены на основе агрегированных дневных статистик.
|
||
|
||
\newpage
|
||
|
||
\begin{center}
|
||
\tableofcontents
|
||
\end{center}
|
||
|
||
\newpage
|
||
|
||
\begin{center}
|
||
\section*{ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ}
|
||
\begin{enumerate}
|
||
\item \textbf{Гетерогенные вычислительные системы} — электронные системы, использующие различные типы вычислительных блоков. Они позволяют эффективно решать задачи за счёт использования компонентов с различными архитектурами.
|
||
\item \textbf{GPU (Graphics Processing Unit)} — графический процессор, предназначенный для параллельной обработки данных, особенно эффективен для вычислений с высокой степенью параллелизма.
|
||
\item \textbf{CPU (Central Processing Unit)} — центральный процессор общего назначения, оптимизированный для последовательных вычислений.
|
||
\item \textbf{Hyper-V} — технология виртуализации от Microsoft, позволяющая создавать и управлять виртуальными машинами на Windows.
|
||
\item \textbf{NFS (Network File System)} — протокол сетевой файловой системы, позволяющий разделять данные между узлами кластера.
|
||
\item \textbf{MPI (Message Passing Interface)} — стандарт взаимодействия между процессами в параллельных вычислительных системах.
|
||
\item \textbf{Slurm} — менеджер ресурсов и планировщик задач для кластерных систем.
|
||
\item \textbf{Контейнеризация} — технология виртуализации, которая позволяет изолировать программное обеспечение в контейнерах для повышения переносимости и устранения конфликтов версий.
|
||
\item \textbf{CUDA (Compute Unified Device Architecture)} — программная платформа от NVIDIA для разработки параллельных приложений на графических процессорах.
|
||
\item \textbf{OpenMPI} — высокопроизводительная реализация стандарта MPI, обеспечивающая взаимодействие между процессами в распределённых системах.
|
||
\item \textbf{MUNGE} — инструмент аутентификации, используемый для обеспечения безопасности в вычислительных кластерах.
|
||
\item \textbf{Rank} — идентификатор процесса в системе MPI, используемый для определения роли процесса.
|
||
\item \textbf{MPI\_Send, MPI\_Recv} — функции MPI для отправки и получения данных между процессами.
|
||
\end{enumerate}
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПЕРЕЧЕНЬ СОКРАЩЕНИЙ И ОБОЗНАЧЕНИЙ}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПЕРЕЧЕНЬ СОКРАЩЕНИЙ И ОБОЗНАЧЕНИЙ}
|
||
|
||
\begin{enumerate}
|
||
\item \textbf{GPU} — Graphics Processing Unit (графический процессор).
|
||
\item \textbf{CPU} — Central Processing Unit (центральный процессор).
|
||
\item \textbf{NFS} — Network File System (сетевая файловая система).
|
||
\item \textbf{MPI} — Message Passing Interface (интерфейс передачи сообщений).
|
||
\item \textbf{Slurm} — Simple Linux Utility for Resource Management (утилита управления ресурсами для Linux).
|
||
\item \textbf{CUDA} — Compute Unified Device Architecture (единая архитектура вычислений от NVIDIA).
|
||
\item \textbf{OpenMPI} — Open Message Passing Interface (реализация интерфейса передачи сообщений).
|
||
\item \textbf{MUNGE} — MUNGE Uid 'N' Gid Emporium (инструмент аутентификации для кластеров).
|
||
\item \textbf{HPC} — High-Performance Computing (высокопроизводительные вычисления).
|
||
\item \textbf{SSH} — Secure Shell (безопасная оболочка).
|
||
\item \textbf{RAM} — Random Access Memory (оперативная память).
|
||
\item \textbf{TCP/IP} — Transmission Control Protocol/Internet Protocol (протокол управления передачей/интернет-протокол).
|
||
\item \textbf{OS} — Operating System (операционная система).
|
||
\end{enumerate}
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ВВЕДЕНИЕ}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ВВЕДЕНИЕ}
|
||
|
||
Использование графических ускорителей (GPU) наряду с центральными процессорами (CPU) является распространенной практикой в области параллельных вычислений на гетерогенных платформах. Гетерогенные вычислительные системы объединяют различные типы вычислительных блоков, что позволяет эффективно решать задачи, выбирая оптимальный вычислительный ресурс для каждого этапа обработки данных.
|
||
|
||
GPU и CPU имеют различную архитектуру и изначально проектировались для решения разных классов задач. GPU обладает большим количеством простых вычислительных ядер, оптимизированных для массово-параллельных операций, в то время как CPU имеет меньше ядер, но с более сложной логикой и лучшей производительностью на одно ядро. Совместное использование GPU и CPU осложняется рядом особенностей: они имеют раздельную память, различные адресные пространства, и для передачи данных требуется явное копирование через системные вызовы. Однако при правильном распределении задач и росте объёма данных использование GPU может значительно ускорить вычисления.
|
||
|
||
Обеспечение взаимодействия между узлами вычислительного кластера осуществляется с помощью Message Passing Interface (MPI) — стандарта передачи сообщений между процессами. Основными реализациями являются Open MPI и MPICH, которые позволяют процессам синхронизироваться и обмениваться данными.
|
||
|
||
Для GPU-вычислений используются специализированные технологии, такие как CUDA (для устройств NVIDIA), ROCm (для устройств AMD) и OpenCL (кроссплатформенный стандарт). В данной работе используется CUDA Toolkit и библиотека CUB для эффективной параллельной обработки данных на GPU.
|
||
|
||
В качестве операционной системы в вычислительных кластерах традиционно используются Linux-дистрибутивы благодаря их производительности, открытости исходного кода и широкой поддержке необходимых технологий. В рамках текущей работы используется Ubuntu Server на виртуальных машинах.
|
||
|
||
Доступ к физическим суперкомпьютерным системам не всегда возможен, поэтому для разработки, тестирования и отладки параллельных приложений целесообразно создание собственного виртуального кластера. В данной работе используется технология виртуализации Hyper-V, разработанная Microsoft, которая позволяет создавать и управлять виртуальными машинами с возможностью проброса GPU-ресурсов. \cite{hyperv}
|
||
|
||
Задача анализа временных рядов больших объёмов данных является хорошим примером для демонстрации эффективности параллельных вычислений на гетерогенных системах. Обработка исторических данных о стоимости криптовалюты Bitcoin включает операции чтения больших файлов, агрегации данных по временным интервалам и поиска паттернов изменения цены — задачи, которые естественным образом поддаются распараллеливанию между узлами кластера и ускорению на GPU.
|
||
|
||
Целью данной работы является создание виртуального гетерогенного вычислительного кластера и разработка параллельного приложения для анализа временных рядов с эффективным использованием ресурсов как CPU, так и GPU узлов.
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПОСТАНОВКА ЗАДАЧИ}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПОСТАНОВКА ЗАДАЧИ}
|
||
|
||
В рамках лабораторных работ необходимо выполнить следующие задачи:
|
||
\begin{enumerate}
|
||
\item Создание виртуальных машин с разнородными типами вычислительных ресурсов:
|
||
\begin{itemize}
|
||
\item CPU-узлы;
|
||
\item GPU-узлы.
|
||
\end{itemize}
|
||
\item Настройка сети для связи хост-системы и виртуальных узлов;
|
||
\item Решение задачи по выбранному варианту:
|
||
\begin{itemize}
|
||
\item Необходимо разработать параллельное приложение, задействующее вычислительные
|
||
ресурсы CPU-узлов и CUDA-узлов, используя механизм OpenMPI, выполняющее на
|
||
предоставленном наборе данных следующие действия. Задача разбита на 2 этапа, которые необходимо выполнить, используя разнородный тип вычислительных ресурсов;
|
||
\item Этап 1. Агрегация данных: для временного ряда исторических данных о стоимости Bitcoin (исходные данные содержат информацию по каждым 10 секундам) необходимо выполнить группировку по дням и для каждого дня вычислить среднюю цену как математическое ожидание значений Low и High, а также минимальные и максимальные значения Open и Close.
|
||
\item Этап 2. Поиск интервалов изменения цены: на основе дневных агрегированных данных необходимо выявить интервалы дат (начиная с начальной даты в наборе данных), в которых средняя дневная цена изменилась не менее чем на 10\% относительно начала интервала. Для каждого интервала необходимо вывести начальную и конечную даты, а также минимальные и максимальные значения Open и Close за все дни внутри интервала.
|
||
\end{itemize}
|
||
\end{enumerate}
|
||
|
||
Файл с исходными данными доступен в свободном доступе в сети интернет\cite{kaggle}.
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section{ОСНОВНАЯ ЧАСТЬ РАБОТЫ}
|
||
\end{center}
|
||
|
||
\subsection{Создание виртуального кластера}
|
||
В рамках лабораторной работы необходимо создать виртуальный вычислительный кластер, использующий разнородный вид вычислителей: GPU и CPU. В рамках лабораторной работы необходимо создать виртуальные машины используя нативный механизм аппаратной виртуализации Windows: Hyper-V.
|
||
|
||
В качестве общей конфигурации виртуальной машины выбраны следующие параметры:
|
||
\begin{itemize}
|
||
\item Ubuntu Server 22.04.05 LTS;
|
||
\item выделенное ОЗУ: 4096 MB;
|
||
\item число виртуальных процессоров: 2;
|
||
\item имя виртуальной машины: "tishcpuX", "tishgpuX" (X - номер узла).
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Создание виртуальной машины}
|
||
Для создания виртуальной машины (узла) для будущего кластера, необходимо выполнить следующие шаги:
|
||
\begin{enumerate}
|
||
\item Открыть "Диспетчер Hyper-V".
|
||
\item Открыть меню для сервера (В текущей работе сервер: XSPMAIN).
|
||
\item Выбрать "Создать" $\rightarrow$ "Виртуальная машина...". Для мастера создания виртуальной машины выбрать следующие параметры:
|
||
\begin{enumerate}
|
||
\item "Укажите имя и местанохождение"
|
||
\begin{itemize}
|
||
\item Имя: \smalltexttt{"tishcpu1"};
|
||
\item Сохранить виртуальную машину: \smalltexttt{V:$\backslash$ Virtual machines}.
|
||
\end{itemize}
|
||
\item "Укажите поколение"
|
||
\begin{itemize}
|
||
\item Выбрать \smalltexttt{"Поколение 2"} (Далее этот параметр поменять невозможно).
|
||
\end{itemize}
|
||
\item "Выделить память":
|
||
\begin{itemize}
|
||
\item "Память, выделяемое при запуске": \smalltexttt{4096 МБ};
|
||
\item "Использовать для этой виртуальной машины динамическую память": \smalltexttt{Ставим галочку}.
|
||
\end{itemize}
|
||
\item "Настройка сети": пока данный раздел просто пропускается.
|
||
\item "Подключить виртуальный жесткий диск":
|
||
\begin{itemize}
|
||
\item "Имя": tishcpu1.vhdx;
|
||
\item "Расположение": \smalltexttt{V:$\backslash$Virtual machines$\backslash$Virtual Hard Disks$\backslash$};
|
||
\item "Размер": \smalltexttt{100 ГБ}.
|
||
\end{itemize}
|
||
\item "Параметры установки":
|
||
\begin{itemize}
|
||
\item "Установить операционную систему из загрузочного образа": выбираем путь к \smalltexttt{*.iso} файлу образа ОС.
|
||
\end{itemize}
|
||
\end{enumerate}
|
||
\end{enumerate}
|
||
|
||
После создания виртуальной машины необходимо настроить параметры:
|
||
\begin{enumerate}
|
||
\item В списке виртуальных машин в контекстном меню выбрать "Параметры...".
|
||
\item Далее необходимо настроить следующие параметры:
|
||
\begin{itemize}
|
||
\item "Процессор"$\rightarrow$"Число виртуальных процессоров": \smalltexttt{2};
|
||
\item "Безопасность"$\rightarrow$"Включить безопасную загрузку": \smalltexttt{Не ставим галочку};
|
||
\item "SCSI-контроллер": DVD-дисковод;
|
||
\item "Сетевой адаптер"$\rightarrow$"Виртуальный коммутатор": \smalltexttt{Default Switch};
|
||
\item "Автоматическое действие при запуске": \smalltexttt{Ничего};
|
||
\item "Память"$\rightarrow$"Включить динамическую память": \smalltexttt{Не ставим галочку}.
|
||
\end{itemize}
|
||
\item Нажать "Применить"$\rightarrow$"Ок".
|
||
\end{enumerate}
|
||
|
||
|
||
Для установки ОС необходимо запустить созданную ранее виртуальную машину. Для этого необходимо через консоль диспетчера Hyper-V и запустить ее:
|
||
\subsubsection*{Установка ОС}
|
||
При установке ОС выполняются следующие шаги:
|
||
\begin{enumerate}
|
||
\item Выбор языка: \smalltexttt{"English"}.
|
||
\item Для раскладки выбираем предлагаемую английскую раскладку.
|
||
\item Тип установки: \smalltexttt{"Ubuntu Server"}.
|
||
\item Настройки сети пока не трогаем.
|
||
\item Настройки пространства памяти:
|
||
\begin{enumerate}
|
||
\item Выбираем опцию: \smalltexttt{"Custom Storage Layout"}.
|
||
\item Для нашего свободного пространства: заводим swap и основное пространство памяти. (Размеры 50 и 4 ГБ)
|
||
\end{enumerate}
|
||
\item Настраиваем параметры профиля системы:
|
||
\begin{itemize}
|
||
\item "Your name": имя (например: \smalltexttt{Arity});
|
||
\item "Your server's name": \smalltexttt{tishcpu1} (как у виртуальной машины);
|
||
\item "Pick a username": \smalltexttt{arity};
|
||
\item Пароль выбирается на свое усмотрение.
|
||
\end{itemize}
|
||
\end{enumerate}
|
||
|
||
\subsubsection*{Создание пользователя root}
|
||
При создании виртуальной машины, не был сконфигурирован пользователь root. Для его создания необходимо выполнить следующие команды:
|
||
|
||
|
||
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishcpu1:~$ sudo su -
|
||
root@tishcpu1:~# passwd
|
||
# Конфигурация пароля для пользователя root
|
||
\end{lstlisting}
|
||
|
||
\subsection{Конфигурация пакетов}
|
||
В рамках лабораторной работы необходимо установить следующие пакеты:
|
||
\begin{itemize}
|
||
\item libopenmpi3 - пакет для библиотеки Open MPI;
|
||
\item slurmd - пакет для управляющего задачами демона;
|
||
\item openssh-client - клиент OpenSSH;
|
||
\item openssh-server - сервер OpenSSH.
|
||
\end{itemize}
|
||
Для этого необходимо выполнить следующие команды:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishcpu1:~$ sudo apt update
|
||
aritytishcpu1:~$ sudo apt upgrade
|
||
aritytishcpu1:~$ sudo apt install libopenmpi3
|
||
aritytishcpu1:~$ sudo apt install slurmd
|
||
aritytishcpu1:~$ sudo apt install openssh-client
|
||
aritytishcpu1:~$ sudo apt install openssh-server
|
||
aritytishcpu1:~$ sudo apt clean
|
||
\end{lstlisting}
|
||
|
||
\cite{ubuntuDocs}
|
||
\begin{enumerate}
|
||
\item \smalltexttt{apt update} - обновляет локальный индекс пакетов в системе, скачивая актуальную информацию о доступных пакетах из репозиториев, указанных в файлах \smalltexttt{/etc/apt/sources.list} и \smalltexttt{/etc/apt/sources.list.d/}. Это нужно для того, чтобы ОС знала о новых версиях пакетов и их зависимостях.
|
||
|
||
\item \smalltexttt{apt upgrade} - обновляет установленные пакеты в системе. Предварительно рекомендуется обновить локальные индексы пакетов в системе.
|
||
|
||
\item \smalltexttt{apt install <name>} - устанавливает в системе пакет с именем \smalltexttt{<name>}.
|
||
|
||
\item \smalltexttt{apt clean} - удаляет все загруженные архивы пакетов из кеша APT, освобождая место на диске.
|
||
\end{enumerate}
|
||
|
||
\subsubsection*{Настройка сети на виртуальной машине}
|
||
Для настройки сети на виртуальных машинах необходимо настроить следующие файлы:
|
||
\begin{enumerate}
|
||
\item \smalltexttt{/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg} - отключение управления сетевыми настройками через cloud-init, чтобы использовать другие способы конфигурации сети на системе.
|
||
\item \smalltexttt{/etc/netplan/50-cloud-init.yaml} - используется для конфигурации сетевых настроек в системе через cloud-init, и обычно генерируется автоматически в облачных окружениях для настройки сети при запуске. Однако при настройке файла \smalltexttt{.../99-disable-network-config.cfg} можно самостоятельно настроить сетевые параметры так, чтобы система не генерировала файл автоматически.
|
||
\end{enumerate}
|
||
Конфигурация netplan:
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
root@tishgpu1:/home/arity# cat /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
|
||
network: {config: disabled}
|
||
\end{lstlisting}
|
||
|
||
|
||
Содержимое файла {\small \smalltexttt{/etc/netplan/50-cloud-init.yaml}}:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishcpu1:~$ sudo vim /etc/netplan/50-cloud-init.yaml
|
||
# This file is generated from information provided by the datasource. Changes
|
||
# to it will not persist across an instance reboot. To disable cloud-init's
|
||
# network configuration capabilities, write a file
|
||
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
|
||
# network: {config: disabled}
|
||
network:
|
||
ethernets:
|
||
eth0:
|
||
addresses:
|
||
- 10.200.166.125/24
|
||
nameservers:
|
||
addresses:
|
||
- 8.8.8.8
|
||
search:
|
||
- tish
|
||
routes:
|
||
- to: default
|
||
via: 10.200.166.254
|
||
version: 2
|
||
\end{lstlisting}
|
||
\begin{enumerate}
|
||
\item Интерфейс \smalltexttt{eth0}:
|
||
\begin{itemize}
|
||
\item \smalltexttt{addresses: [10.200.166.125/24]}: Интерфейсу \smalltexttt{eth0} присваивается IP-адрес \smalltexttt{10.200.166.125} с маской подсети \smalltexttt{255.255.255.0} (CIDR \smalltexttt{/24}).
|
||
\item \smalltexttt{routes:}
|
||
\begin{itemize}
|
||
\item Указывает маршрут по умолчанию (default route), который направляет весь остальной трафик через шлюз \smalltexttt{10.200.166.254}.
|
||
\end{itemize}
|
||
\item \smalltexttt{nameservers:}
|
||
\begin{itemize}
|
||
\item Настраиваются DNS-серверы: \smalltexttt{8.8.8.8} (это публичные DNS от Google);
|
||
\item Указываем \smalltexttt{tish} для параметра search.
|
||
\end{itemize}
|
||
\end{itemize}
|
||
\item Версия netplan:
|
||
\begin{itemize}
|
||
\item \smalltexttt{version: 2}: Указывает, что используется вторая версия формата Netplan.
|
||
\end{itemize}
|
||
\end{enumerate}
|
||
|
||
|
||
\subsection{Конфигурация сети}
|
||
Для взаимодействия с виртуальными машинами с помощью хост-узла, необходимо пробросить порты на настроенные ранее адреса. Для этого необходимо воспользоваться следующими PowerShell Cmdlet'ами:
|
||
\begin{enumerate}
|
||
\item New-VMSwitch.
|
||
\item New-NetIPAddress.
|
||
\item New-NetNat.
|
||
\item Add-NetNatStaticMapping.
|
||
\end{enumerate}
|
||
\subsubsection*{New-VMSwitch}
|
||
\smalltexttt{New-VMSwitch} - Создает новый виртуальный сетевой адаптер для виртуальных машин.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{SwitchName} - алиас для \smalltexttt{Name}. Уточняет имя виртуального сетевого адаптера. Обязательный параметр;
|
||
\smalltexttt{SwitchType} - Уточняет тип создаваемого адаптера. Доступные значения для типа коммутатора — \smalltexttt{Internal} (внутренний) и \smalltexttt{Private} (частный). Чтобы создать \smalltexttt{External} (внешний) виртуальный коммутатор, нужно указать либо параметр \smalltexttt{NetAdapterInterfaceDescription}, либо \smalltexttt{NetAdapterName}, что автоматически установит тип коммутатора как \smalltexttt{External}.
|
||
\smalltexttt{Internal} и \smalltexttt{Private} — это типы сетевых адаптеров, которые могут быть использованы для настройки виртуальных машин в Hyper-V:
|
||
\begin{itemize}
|
||
\item \smalltexttt{Internal} - позволяет виртуальной машине обмениваться данными с хостовой машиной и другими виртуальными машинами на том же хосте;
|
||
\item \smalltexttt{External} - позволяет виртуальной машине общаться только с другими виртуальными машинами, но не имеет доступа к хосту или внешней сети;
|
||
\end{itemize}
|
||
\cite{newVmSwitch}
|
||
\end{itemize}
|
||
|
||
Создание виртуального сетевого адаптера в PowerShell:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> New-VMSwitch -SwitchName "TishNet" -SwitchType Internal
|
||
\end{lstlisting}
|
||
|
||
\subsubsection*{New-NetIPAddress}
|
||
\smalltexttt{New-NetIPAddress} - Создает новый IP-адрес и привязывает его к указанному сетевому интерфейсу.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{InterfaceAlias} - Указывает имя сетевого интерфейса, к которому будет привязан новый IP-адрес. Обязательный параметр;
|
||
\item \smalltexttt{IPAddress} - Указывает IP-адрес, который нужно настроить. Обязательный параметр;
|
||
\item \smalltexttt{PrefixLength} - Указывает длину префикса подсети для IP-адреса. Например, для маски подсети \smalltexttt{255.255.255.0} длина префикса равна \smalltexttt{24}. Обязательный параметр;
|
||
\item \smalltexttt{DefaultGateway} - Указывает адрес шлюза по умолчанию, который будет использоваться для указанного IP-адреса. Необязательный параметр.
|
||
\end{itemize}
|
||
|
||
Пример настройки нового IP-адреса в PowerShell:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> New-NetIPAddress -InterfaceAlias "vEthernet (TishNet)" -IPAddress 10.200.166.254 -PrefixLength 24
|
||
\end{lstlisting}
|
||
|
||
Параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{InterfaceAlias} - Имя интерфейса, например, \smalltexttt{"vEthernet (TishNet)"}, к которому привязывается IP-адрес;
|
||
\item \smalltexttt{IPAddress} - Указанный IP-адрес (например, \smalltexttt{10.200.166.254});
|
||
\item \smalltexttt{PrefixLength} - Длина префикса подсети, например, \smalltexttt{24} для \smalltexttt{255.255.255.0};
|
||
\item \smalltexttt{DefaultGateway} - Адрес шлюза по умолчанию (опционально).
|
||
\end{itemize}
|
||
|
||
\cite{newNetIpAddress}
|
||
|
||
Для проверки что IP-адресс корректно сконфигурирован можно воспользоваться команд-летом: {\small \smalltexttt{Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "vEthernet (t1)"}}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias "vEthernet (TishNet)"
|
||
IPAddress : 10.200.166.254
|
||
InterfaceIndex : 10
|
||
InterfaceAlias : vEthernet (TishNet)
|
||
AddressFamily : IPv4
|
||
Type : Unicast
|
||
PrefixLength : 24
|
||
PrefixOrigin : Manual
|
||
SuffixOrigin : Manual
|
||
AddressState : Preferred
|
||
ValidLifetime :
|
||
PreferredLifetime :
|
||
SkipAsSource : False
|
||
PolicyStore : ActiveStore
|
||
\end{lstlisting}
|
||
|
||
% \iffalse
|
||
% \begin{comment}
|
||
|
||
|
||
% IPAddress : 172.27.144.1
|
||
% InterfaceIndex : 18
|
||
% InterfaceAlias : vEthernet (Default Switch)
|
||
% AddressFamily : IPv4
|
||
% Type : Unicast
|
||
% PrefixLength : 20
|
||
% PrefixOrigin : Manual
|
||
% SuffixOrigin : Manual
|
||
% AddressState : Preferred
|
||
% ValidLifetime :
|
||
% PreferredLifetime :
|
||
% SkipAsSource : False
|
||
% PolicyStore : ActiveStore
|
||
|
||
% IPAddress : 192.168.0.166
|
||
% InterfaceIndex : 16
|
||
% InterfaceAlias : Ethernet
|
||
% AddressFamily : IPv4
|
||
% Type : Unicast
|
||
% PrefixLength : 24
|
||
% PrefixOrigin : Dhcp
|
||
% SuffixOrigin : Dhcp
|
||
% AddressState : Preferred
|
||
% ValidLifetime : 21:30:01
|
||
% PreferredLifetime : 21:30:01
|
||
% SkipAsSource : False
|
||
% PolicyStore : ActiveStore
|
||
|
||
% IPAddress : 127.0.0.1
|
||
% InterfaceIndex : 1
|
||
% InterfaceAlias : Loopback Pseudo-Interface 1
|
||
% AddressFamily : IPv4
|
||
% Type : Unicast
|
||
% PrefixLength : 8
|
||
% PrefixOrigin : WellKnown
|
||
% SuffixOrigin : WellKnown
|
||
% AddressState : Preferred
|
||
% ValidLifetime :
|
||
% PreferredLifetime :
|
||
% SkipAsSource : False
|
||
% PolicyStore : ActiveStore
|
||
|
||
|
||
% PS C:\Windows\system32> New-NetIPAddress -InterfaceAlias "vEthernet (t1)" -IPAddress "10.200.166.254" -PrefixLength 24
|
||
|
||
% IPAddress : 10.200.166.254
|
||
% InterfaceIndex : 32
|
||
% InterfaceAlias : vEthernet (t1)
|
||
% AddressFamily : IPv4
|
||
% Type : Unicast
|
||
% PrefixLength : 24
|
||
% PrefixOrigin : Manual
|
||
% SuffixOrigin : Manual
|
||
% AddressState : Tentative
|
||
% ValidLifetime :
|
||
% PreferredLifetime :
|
||
% SkipAsSource : False
|
||
% PolicyStore : ActiveStore
|
||
|
||
% IPAddress : 10.200.166.254
|
||
% InterfaceIndex : 32
|
||
% InterfaceAlias : vEthernet (t1)
|
||
% AddressFamily : IPv4
|
||
% Type : Unicast
|
||
% PrefixLength : 24
|
||
% PrefixOrigin : Manual
|
||
% SuffixOrigin : Manual
|
||
% AddressState : Invalid
|
||
% ValidLifetime :
|
||
% PreferredLifetime :
|
||
% SkipAsSource : False
|
||
% PolicyStore : PersistentStore
|
||
% \end{comment}
|
||
% \fi
|
||
|
||
\subsubsection*{New-NetNat}
|
||
|
||
\smalltexttt{New-NetNat} - Создает новый NAT (Network Address Translation) для указанного интерфейса внутренней сети.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{Name} - Указывает имя NAT-объекта. Обязательный параметр;
|
||
\item \smalltexttt{InternalIPInterfaceAddressPrefix} - Указывает адресный префикс внутренней сети, который будет использоваться для NAT. Обязательный параметр;
|
||
\item \smalltexttt{ExternalIPInterfaceAddressPrefix} - Указывает адресный префикс внешней сети. Необязательный параметр;
|
||
\item \smalltexttt{Description} - Добавляет описание к NAT-объекту. Необязательный параметр.
|
||
\end{itemize}
|
||
|
||
Пример создания NAT в PowerShell:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> New-NetNat -Name TishNat -InternalIPInterfaceAddressPrefix 10.200.166.0/24
|
||
\end{lstlisting}
|
||
|
||
Параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{Name} - Имя NAT-объекта, например, \smalltexttt{TishNat};
|
||
\item \smalltexttt{InternalIPInterfaceAddressPrefix} - Префикс внутренней сети, например, \smalltexttt{10.200.166.0/24}.
|
||
\end{itemize}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> New-NetNat -Name TishNat -InternalIPInterfaceAddressPrefix 10.200.166.0/24
|
||
Name : TishNat
|
||
ExternalIPInterfaceAddressPrefix :
|
||
InternalIPInterfaceAddressPrefix : 10.200.166.0/24
|
||
IcmpQueryTimeout : 30
|
||
TcpEstablishedConnectionTimeout : 1800
|
||
TcpTransientConnectionTimeout : 120
|
||
TcpFilteringBehavior : AddressDependentFiltering
|
||
UdpFilteringBehavior : AddressDependentFiltering
|
||
UdpIdleSessionTimeout : 120
|
||
UdpInboundRefresh : False
|
||
Store : Local
|
||
Active : True
|
||
\end{lstlisting}
|
||
\cite{newNetNat}
|
||
|
||
\subsubsection*{Add-NetNatStaticMapping}
|
||
\smalltexttt{Add-NetNatStaticMapping} - Добавляет статическое сопоставление портов между внешними и внутренними адресами для NAT (Network Address Translation). Это позволяет направлять трафик, поступающий на внешний адрес и порт, к определенному внутреннему адресу и порту.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{NatName} - Указывает имя существующего объекта NAT, к которому применяется статическое сопоставление. Обязательный параметр;
|
||
\item \smalltexttt{ExternalIPAddress} - Указывает внешний IP-адрес, на который поступает входящий трафик. Для разрешения любых IP-адресов можно использовать \smalltexttt{0.0.0.0/0}. Обязательный параметр;
|
||
\item \smalltexttt{ExternalPort} - Указывает порт внешнего IP-адреса, на который будет перенаправляться трафик. Обязательный параметр;
|
||
\item \smalltexttt{InternalIPAddress} - Указывает IP-адрес устройства внутри сети, к которому будет перенаправляться трафик. Обязательный параметр;
|
||
\item \smalltexttt{InternalPort} - Указывает порт устройства внутри сети, который будет использоваться для трафика. Обязательный параметр;
|
||
\item \smalltexttt{Protocol} - Указывает протокол (например, \smalltexttt{TCP} или \smalltexttt{UDP}), который будет использоваться для сопоставления. Обязательный параметр.
|
||
\end{itemize}
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22801 -InternalIPAddress 10.200.166.125 -InternalPort 22 -Protocol TCP
|
||
\end{lstlisting}
|
||
\cite{addNetNatStaticMapping}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22801 -InternalIPAddress 10.200.166.125 -InternalPort 22 -Protocol TCP
|
||
|
||
|
||
StaticMappingID : 0
|
||
NatName : TishNat
|
||
Protocol : TCP
|
||
RemoteExternalIPAddressPrefix : 0.0.0.0/0
|
||
ExternalIPAddress : 0.0.0.0
|
||
ExternalPort : 22801
|
||
InternalIPAddress : 10.200.166.125
|
||
InternalPort : 22
|
||
InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000}
|
||
Active : True
|
||
|
||
|
||
|
||
PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22802 -InternalIPAddress 10.200.166.126 -InternalPort 22 -Protocol TCP
|
||
|
||
|
||
StaticMappingID : 1
|
||
NatName : TishNat
|
||
Protocol : TCP
|
||
RemoteExternalIPAddressPrefix : 0.0.0.0/0
|
||
ExternalIPAddress : 0.0.0.0
|
||
ExternalPort : 22802
|
||
InternalIPAddress : 10.200.166.126
|
||
InternalPort : 22
|
||
InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000}
|
||
Active : True
|
||
|
||
|
||
|
||
PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22803 -InternalIPAddress 10.200.166.127 -InternalPort 22 -Protocol TCP
|
||
|
||
|
||
StaticMappingID : 4
|
||
NatName : TishNat
|
||
Protocol : TCP
|
||
RemoteExternalIPAddressPrefix : 0.0.0.0/0
|
||
ExternalIPAddress : 0.0.0.0
|
||
ExternalPort : 22803
|
||
InternalIPAddress : 10.200.166.127
|
||
InternalPort : 22
|
||
InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000}
|
||
Active : True
|
||
|
||
|
||
PS C:\Windows\system32> Add-NetNatStaticMapping -NatName TishNat -ExternalIPAddress 0.0.0.0/0 -ExternalPort 22804 -InternalIPAddress 10.200.166.128 -InternalPort 22 -Protocol TCP
|
||
|
||
|
||
StaticMappingID : 5
|
||
NatName : TishNat
|
||
Protocol : TCP
|
||
RemoteExternalIPAddressPrefix : 0.0.0.0/0
|
||
ExternalIPAddress : 0.0.0.0
|
||
ExternalPort : 22804
|
||
InternalIPAddress : 10.200.166.128
|
||
InternalPort : 22
|
||
InternalRoutingDomainId : {00000000-0000-0000-0000-000000000000}
|
||
Active : True
|
||
\end{lstlisting}
|
||
|
||
|
||
Для более быстрой настройки других виртуальных машин, можно скопировать tishcpu1, а при настройке первой tishgpu1 выполнить копирование в tishgpu2.
|
||
|
||
В результате создания виртуального кластеры были получены следующие виртуальные машины:
|
||
\begin{itemize}
|
||
\item tishcpu1 - "главный" вычислительный узел;
|
||
\item tishcpu2;
|
||
\item tishgpu1;
|
||
\item tishgpu2.
|
||
\end{itemize}
|
||
|
||
\subsection{Конфигурация ресурсов GPU}
|
||
Для конфигурации ресурсов GPU для узлов, необходимо воспользоваться следующими PowerShell Cmdlet'ами:
|
||
\begin{enumerate}
|
||
\item Get-VMHostPartitionableGpu.
|
||
\item Add-VMGpuPartitionAdapter.
|
||
\item Set-VM.
|
||
%\item Get-CimInstance.
|
||
\end{enumerate}
|
||
Также необходимо установить драйвера GPU, CUDA-toolkit.
|
||
|
||
\subsubsection*{Get-VMHostPartitionableGpu}
|
||
\smalltexttt{Get-VMHostPartitionableGpu} - Возвращает список GPU, установленных на хосте Hyper-V, которые поддерживают разделение ресурсов (Partitioning). Эти GPU могут быть разделены и назначены виртуальным машинам для эффективного использования.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{-CimSession} (необязательный) - Позволяет указать удаленную сессию CIM (Common Information Model) для выполнения команды на другом компьютере. Если параметр не указан, команда выполняется локально.
|
||
\item \smalltexttt{-ThrottleLimit} (необязательный) - Ограничивает количество одновременных операций. Если параметр не указан, используется системное значение по умолчанию.
|
||
\end{itemize}
|
||
|
||
Выходные данные команды включают информацию о GPU, например:
|
||
\begin{itemize}
|
||
\item \smalltexttt{Name} - Имя GPU;
|
||
\item \smalltexttt{TotalMemory} - Общий объем памяти GPU;
|
||
\item \smalltexttt{AvailableMemory} - Доступный объем памяти GPU;
|
||
\item \smalltexttt{Status} - Текущий статус GPU (например, \smalltexttt{OK}).
|
||
\end{itemize}
|
||
|
||
Пример использования команды:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Get-VMHostPartitionableGpu
|
||
\end{lstlisting}
|
||
|
||
Результат выполнения:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Get-VMHostPartitionableGpu
|
||
|
||
|
||
Name : \\?\PCI#VEN_1002&DEV_164E&SUBSYS_D0001458&REV_C5#4&16012499&0&0041#{064092b3-625e-43bf-
|
||
9eb5-dc845897dd59}\GPUPARAV
|
||
ValidPartitionCounts : {32}
|
||
PartitionCount : 32
|
||
TotalVRAM : 1000000000
|
||
AvailableVRAM : 1000000000
|
||
SupportsIncomingLiveMigration : False
|
||
MinPartitionVRAM : 0
|
||
MaxPartitionVRAM : 1000000000
|
||
OptimalPartitionVRAM : 1000000000
|
||
TotalEncode : 18446744073709551615
|
||
AvailableEncode : 18446744073709551615
|
||
MinPartitionEncode : 0
|
||
MaxPartitionEncode : 18446744073709551615
|
||
OptimalPartitionEncode : 18446744073709551615
|
||
TotalDecode : 1000000000
|
||
AvailableDecode : 1000000000
|
||
MinPartitionDecode : 0
|
||
MaxPartitionDecode : 1000000000
|
||
OptimalPartitionDecode : 1000000000
|
||
TotalCompute : 1000000000
|
||
AvailableCompute : 1000000000
|
||
MinPartitionCompute : 0
|
||
MaxPartitionCompute : 1000000000
|
||
OptimalPartitionCompute : 1000000000
|
||
CimSession : CimSession: .
|
||
ComputerName : XSPMAIN
|
||
IsDeleted : False
|
||
|
||
Name : \\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf
|
||
-9eb5-dc845897dd59}\GPUPARAV
|
||
ValidPartitionCounts : {32}
|
||
PartitionCount : 32
|
||
TotalVRAM : 1000000000
|
||
AvailableVRAM : 1000000000
|
||
SupportsIncomingLiveMigration : False
|
||
MinPartitionVRAM : 0
|
||
MaxPartitionVRAM : 1000000000
|
||
OptimalPartitionVRAM : 1000000000
|
||
TotalEncode : 18446744073709551615
|
||
AvailableEncode : 18446744073709551615
|
||
MinPartitionEncode : 0
|
||
MaxPartitionEncode : 18446744073709551615
|
||
OptimalPartitionEncode : 18446744073709551615
|
||
TotalDecode : 1000000000
|
||
AvailableDecode : 1000000000
|
||
MinPartitionDecode : 0
|
||
MaxPartitionDecode : 1000000000
|
||
OptimalPartitionDecode : 1000000000
|
||
TotalCompute : 1000000000
|
||
AvailableCompute : 1000000000
|
||
MinPartitionCompute : 0
|
||
MaxPartitionCompute : 1000000000
|
||
OptimalPartitionCompute : 1000000000
|
||
CimSession : CimSession: .
|
||
ComputerName : XSPMAIN
|
||
IsDeleted : False
|
||
\end{lstlisting}
|
||
|
||
\cite{getVmHostPartitionableGpu}
|
||
Эта команда является первым шагом в настройке GPU для виртуальных машин. Она позволяет проверить, какие GPU на хосте поддерживают разделение и сколько ресурсов доступно для выделения.
|
||
|
||
CUDA-ядра есть в \smalltexttt{VEN\_10DE\&DEV}. Это графическое устройство с ядром Nvidia 5070.
|
||
\subsubsection*{Add-VMGpuPartitionAdapter}
|
||
\smalltexttt{Add-VMGpuPartitionAdapter} - Добавляет адаптер для разделения ресурсов GPU к указанной виртуальной машине (VM). Этот адаптер позволяет виртуальной машине использовать определенную часть вычислительных, графических и других ресурсов физического GPU.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{-VMName} - Указывает имя виртуальной машины, к которой будет добавлен GPU-адаптер. Обязательный параметр;
|
||
\item \smalltexttt{-InstancePath} - Указывает путь к конкретному GPU, который будет разделен для использования виртуальной машиной. Обязательный параметр;
|
||
\item \smalltexttt{-MinPartitionVRAM}, \smalltexttt{-MaxPartitionVRAM}, \smalltexttt{-OptimalPartitionVRAM} - Устанавливают минимальный, максимальный и оптимальный объем видеопамяти (VRAM), который будет доступен виртуальной машине;
|
||
\item \smalltexttt{-MinPartitionEncode}, \smalltexttt{-MaxPartitionEncode}, \smalltexttt{-OptimalPartitionEncode} - Устанавливают минимальное, максимальное и оптимальное количество ресурсов для кодирования видео;
|
||
\item \smalltexttt{-MinPartitionDecode}, \smalltexttt{-MaxPartitionDecode}, \smalltexttt{-OptimalPartitionDecode} - Устанавливают минимальное, максимальное и оптимальное количество ресурсов для декодирования видео;
|
||
\item \smalltexttt{-MinPartitionCompute}, \smalltexttt{-MaxPartitionCompute},
|
||
|
||
\smalltexttt{-OptimalPartitionCompute} - Устанавливают минимальное, максимальное и оптимальное количество вычислительных ресурсов (Compute), доступных виртуальной машине.
|
||
\end{itemize}
|
||
Пример использования:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
PS C:\Windows\system32> Add-VMGpuPartitionAdapter -VMName tishgpu1 -InstancePath "\\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf-9eb5-dc845897dd59}\GPUPARAV" -MinPartitionVRAM 100000000 -MaxPartitionVRAM 1000000000 -OptimalPartitionVRAM 1000000000 -MinPartitionCompute 100000000 -MaxPartitionCompute 1000000000 -OptimalPartitionCompute 1000000000
|
||
|
||
PS C:\Windows\system32> Get-VMGpuPartitionAdapter -VMName tishgpu1
|
||
|
||
|
||
InstancePath : \\?\PCI#VEN_10DE&DEV_2F04&SUBSYS_F3261569&REV_A1#740E0A73882DB04800#{064092b3-625e-43bf
|
||
-9eb5-dc845897dd59}\GPUPARAV
|
||
SupportsOutgoingLiveMigration : False
|
||
CurrentPartitionVRAM : 1000000000
|
||
MinPartitionVRAM : 100000000
|
||
MaxPartitionVRAM : 1000000000
|
||
OptimalPartitionVRAM : 1000000000
|
||
CurrentPartitionEncode : 1000000000
|
||
MinPartitionEncode :
|
||
MaxPartitionEncode :
|
||
OptimalPartitionEncode :
|
||
CurrentPartitionDecode : 1000000000
|
||
MinPartitionDecode :
|
||
MaxPartitionDecode :
|
||
OptimalPartitionDecode :
|
||
CurrentPartitionCompute : 0
|
||
MinPartitionCompute : 100000000
|
||
MaxPartitionCompute : 1000000000
|
||
OptimalPartitionCompute : 1000000000
|
||
PartitionId : 0
|
||
PartitionVfLuid : 050760292
|
||
Name : Параметры раздела GPU
|
||
Id : Microsoft:AE124752-47A6-4788-9C91-8DAE6D45A744\5B2FD022-36CF-4FD9-83D7-D5B60274737E
|
||
VMId : ae124752-47a6-4788-9c91-8dae6d45a744
|
||
VMName : tishgpu1
|
||
VMSnapshotId : 00000000-0000-0000-0000-000000000000
|
||
VMSnapshotName :
|
||
CimSession : CimSession: .
|
||
ComputerName : XSPMAIN
|
||
IsDeleted : False
|
||
VMCheckpointId : 00000000-0000-0000-0000-000000000000
|
||
VMCheckpointName :
|
||
\end{lstlisting}
|
||
|
||
\subsubsection*{Set-VM}
|
||
\smalltexttt{Set-VM} - Изменяет параметры виртуальной машины (VM), включая настройки памяти, процессора и других ресурсов.
|
||
|
||
Принимаемые параметры:
|
||
\begin{itemize}
|
||
\item \smalltexttt{-VMName} - Указывает имя виртуальной машины, для которой изменяются настройки. Обязательный параметр;
|
||
\item \smalltexttt{-GuestControlledCacheTypes} - Указывает, разрешено ли гостевой операционной системе управлять типами кэширования. Значение \smalltexttt{\$true} включает эту возможность;
|
||
\item \smalltexttt{-LowMemoryMappedIoSpace} - Устанавливает объем выделенного адресного пространства для низкоуровневого памяти, используемой устройствами, например, 3GB;
|
||
\item \smalltexttt{-HighMemoryMappedIoSpace} - Устанавливает объем выделенного адресного пространства для высокоуровневого памяти, например, 32GB;
|
||
\item \smalltexttt{-ProcessorCount} (необязательный) - Позволяет задать количество процессоров, доступных виртуальной машине;
|
||
\item \smalltexttt{-DynamicMemory} (необязательный) - Разрешает использование динамической памяти для виртуальной машины.
|
||
\end{itemize}
|
||
|
||
Пример использования команды:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
Set-VM -VMName tishgpu1 -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 3GB -HighMemoryMappedIoSpace 32GB
|
||
\end{lstlisting}
|
||
|
||
Команда изменяет настройки виртуальной машины \smalltexttt{tishgpu1}, разрешая гостевой ОС управлять типами кэширования и выделяя 3GB для низкоуровневого адресного пространства и 32GB для высокоуровневого адресного пространства.
|
||
|
||
\cite{setVm}
|
||
|
||
|
||
% \iffalse
|
||
% \begin{comment}
|
||
|
||
% \begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
% PS C:\Windows\system32> Get-CimInstance -ClassName Win32_VideoController -Property *
|
||
|
||
% Caption : Intel(R) UHD Graphics 750
|
||
% Description : Intel(R) UHD Graphics 750
|
||
% InstallDate :
|
||
% Name : Intel(R) UHD Graphics 750
|
||
% Status : OK
|
||
% Availability : 8
|
||
% ConfigManagerErrorCode : 0
|
||
% ConfigManagerUserConfig : False
|
||
% CreationClassName : Win32_VideoController
|
||
% DeviceID : VideoController1
|
||
% ErrorCleared :
|
||
% ErrorDescription :
|
||
% LastErrorCode :
|
||
% PNPDeviceID : PCI\VEN_8086&DEV_4C8A&SUBSYS_7D171462&REV_04\3&11583659&0&10
|
||
% PowerManagementCapabilities :
|
||
% PowerManagementSupported :
|
||
% StatusInfo :
|
||
% SystemCreationClassName : Win32_ComputerSystem
|
||
% SystemName : 4E305-10
|
||
% MaxNumberControlled :
|
||
% ProtocolSupported :
|
||
% TimeOfLastReset :
|
||
% AcceleratorCapabilities :
|
||
% CapabilityDescriptions :
|
||
% CurrentBitsPerPixel :
|
||
% CurrentHorizontalResolution :
|
||
% CurrentNumberOfColors :
|
||
% CurrentNumberOfColumns :
|
||
% CurrentNumberOfRows :
|
||
% CurrentRefreshRate :
|
||
% CurrentScanMode :
|
||
% CurrentVerticalResolution :
|
||
% MaxMemorySupported :
|
||
% MaxRefreshRate :
|
||
% MinRefreshRate :
|
||
% NumberOfVideoPages :
|
||
% VideoMemoryType : 2
|
||
% VideoProcessor : Intel(R) UHD Graphics Family
|
||
% NumberOfColorPlanes :
|
||
% VideoArchitecture : 5
|
||
% VideoMode :
|
||
% AdapterCompatibility : Intel Corporation
|
||
% AdapterDACType : Internal
|
||
% AdapterRAM : 2147479552
|
||
% ColorTableEntries :
|
||
% DeviceSpecificPens :
|
||
% DitherType :
|
||
% DriverDate : 19.08.2024 3:00:00
|
||
% DriverVersion : 32.0.101.5972
|
||
% ICMIntent :
|
||
% ICMMethod :
|
||
% InfFilename : oem47.inf
|
||
% InfSection : iRKLD_w10_DS
|
||
% InstalledDisplayDrivers : C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4ec1a03daa49235f\igdum
|
||
% dim64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4ec1a03daa49
|
||
% 235f\igd10iumd64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch.inf_amd64_4
|
||
% ec1a03daa49235f\igd10iumd64.dll,C:\Windows\System32\DriverStore\FileRepository\iigd_dch.
|
||
% inf_amd64_4ec1a03daa49235f\igd12umd64.dll
|
||
% Monochrome : False
|
||
% ReservedSystemPaletteEntries :
|
||
% SpecificationVersion :
|
||
% SystemPaletteEntries :
|
||
% VideoModeDescription :
|
||
% PSComputerName :
|
||
% CimClass : root/cimv2:Win32_VideoController
|
||
% CimInstanceProperties : {Caption, Description, InstallDate, Name...}
|
||
% CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
|
||
|
||
|
||
|
||
|
||
|
||
|
||
% Caption : NVIDIA GeForce RTX 3060
|
||
% Description : NVIDIA GeForce RTX 3060
|
||
% InstallDate :
|
||
% Name : NVIDIA GeForce RTX 3060
|
||
% Status : OK
|
||
% Availability : 3
|
||
% ConfigManagerErrorCode : 0
|
||
% ConfigManagerUserConfig : False
|
||
% CreationClassName : Win32_VideoController
|
||
% DeviceID : VideoController2
|
||
% ErrorCleared :
|
||
% ErrorDescription :
|
||
% LastErrorCode :
|
||
% PNPDeviceID : PCI\VEN_10DE&DEV_2487&SUBSYS_40741458&REV_A1\4&D0467E6&0&0008
|
||
% PowerManagementCapabilities :
|
||
% PowerManagementSupported :
|
||
% StatusInfo :
|
||
% SystemCreationClassName : Win32_ComputerSystem
|
||
% SystemName : 4E305-10
|
||
% MaxNumberControlled :
|
||
% ProtocolSupported :
|
||
% TimeOfLastReset :
|
||
% AcceleratorCapabilities :
|
||
% CapabilityDescriptions :
|
||
% CurrentBitsPerPixel : 32
|
||
% CurrentHorizontalResolution : 1920
|
||
% CurrentNumberOfColors : 4294967296
|
||
% CurrentNumberOfColumns : 0
|
||
% CurrentNumberOfRows : 0
|
||
% CurrentRefreshRate : 60
|
||
% CurrentScanMode : 4
|
||
% CurrentVerticalResolution : 1080
|
||
% MaxMemorySupported :
|
||
% MaxRefreshRate : 165
|
||
% MinRefreshRate : 50
|
||
% NumberOfVideoPages :
|
||
% VideoMemoryType : 2
|
||
% VideoProcessor : NVIDIA GeForce RTX 3060
|
||
% NumberOfColorPlanes :
|
||
% VideoArchitecture : 5
|
||
% VideoMode :
|
||
% AdapterCompatibility : NVIDIA
|
||
% AdapterDACType : Integrated RAMDAC
|
||
% AdapterRAM : 4293918720
|
||
% ColorTableEntries :
|
||
% DeviceSpecificPens :
|
||
% DitherType : 0
|
||
% DriverDate : 14.08.2024 3:00:00
|
||
% DriverVersion : 32.0.15.6094
|
||
% ICMIntent :
|
||
% ICMMethod :
|
||
% InfFilename : oem43.inf
|
||
% InfSection : Section070
|
||
% InstalledDisplayDrivers : C:\Windows\System32\DriverStore\FileRepository
|
||
% \nv_dispi.inf_amd64_20ae8f14a487d5db\nvld
|
||
% umdx.dll,C:\Windows\System32\DriverSt
|
||
% ore\FileRepository\nv_dispig.inf_amd64_0afec3f20500
|
||
% 14a0\nvldumdx.dll,C:\Windows\System32
|
||
% \DriverStore\FileRepository\nv_dispig.inf_amd64_0af
|
||
% ec3f2050014a0\nvldumdx.dll,C:\Windows\System32\DriverStore\FileRepository\nv_dispig.inf_
|
||
% amd64_0afec3f2050014a0\nvldumdx.dll
|
||
% Monochrome : False
|
||
% ReservedSystemPaletteEntries :
|
||
% SpecificationVersion :
|
||
% SystemPaletteEntries :
|
||
% VideoModeDescription : Цвета: 1920 x 1080 x 4294967296
|
||
% PSComputerName :
|
||
% CimClass : root/cimv2:Win32_VideoController
|
||
% CimInstanceProperties : {Caption, Description, InstallDate, Name...}
|
||
% CimSystemProperties : Microsoft.Management.Infrastructure.CimSystemProperties
|
||
% \end{lstlisting}
|
||
|
||
% \end{comment}
|
||
% \fi
|
||
\subsubsection*{Установка ПО}
|
||
Для установки драйверов графического устройства необходимо скопировать их с хост-устройства с помощью {\small \smalltexttt{scp}}.
|
||
|
||
Выполнение копирования драйверов GPU:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Копируем содержимое папки с драйверами с хоста на виртуальную машину через SCP
|
||
# -r: копирование рекурсивно (включая подкаталоги)
|
||
# -P 22803: указание порта для подключения (22803)
|
||
scp -r -P 22803 C:\WINDOWS\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db arity@127.0.0.1:/tmp/
|
||
|
||
# Создаем директорию для драйверов в WSL (если еще не существует)
|
||
root@tishgpu1:/tmp# mkdir -p /usr/lib/wsl/drivers
|
||
|
||
# Переходим в созданную директорию
|
||
root@tishgpu1:/tmp# cd /usr/lib/wsl/drivers
|
||
|
||
# Проверяем содержимое директории (на данный момент она пуста)
|
||
root@tishgpu1:/usr/lib/wsl/drivers# ls
|
||
|
||
# Переходим на уровень выше, в директорию WSL
|
||
root@tishgpu1:/usr/lib/wsl/drivers# cd ..
|
||
|
||
# Проверяем содержимое директории /usr/lib/wsl (содержит только папку drivers)
|
||
root@tishgpu1:/usr/lib/wsl# ls drivers
|
||
|
||
# Создаем новую директорию lib в WSL (может быть нужна для других целей)
|
||
root@tishgpu1:/usr/lib/wsl# mkdir lib
|
||
|
||
# Проверяем, что теперь в /usr/lib/wsl есть две папки: drivers и lib
|
||
root@tishgpu1:/usr/lib/wsl# ls drivers lib
|
||
|
||
# Перемещаем скачанную папку драйвера из /tmp в директорию drivers
|
||
root@tishgpu1:/usr/lib/wsl# mv /tmp/nv_dispi.inf_amd64_20ae8f14a487d5db/ /usr/lib/wsl/drivers/
|
||
|
||
# Проверяем содержимое директории /usr/lib/wsl (папка drivers содержит перемещенные данные)
|
||
root@tishgpu1:/usr/lib/wsl# ls
|
||
drivers lib
|
||
|
||
# Убеждаемся, что в папке drivers теперь находится папка с драйверами
|
||
root@tishgpu1:/usr/lib/wsl# ls drivers/
|
||
nv_dispi.inf_amd64_20ae8f14a487d5db
|
||
|
||
# Операция завершена, драйверы находятся в правильной директории
|
||
root@tishgpu1:/usr/lib/wsl#
|
||
\end{lstlisting}
|
||
|
||
Конфигурация драйверов на виртуальной машине:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Копируем библиотеку lib из Windows на виртуальную машину через SCP
|
||
# -r: копирование рекурсивно
|
||
# -P 22803: указание порта для подключения
|
||
PS C:\Windows\system32> scp -r -P 22803 C:\Windows\System32\lxss\lib arity@127.0.0.1:/tmp/
|
||
|
||
# Перемещаем скопированную библиотеку в директорию WSL
|
||
root@tishgpu1:/usr/lib/wsl# mv /tmp/lib/ /usr/lib/wsl/
|
||
|
||
# Проверяем содержимое директории /usr/lib/wsl, чтобы убедиться, что библиотека перемещена
|
||
root@tishgpu1:/usr/lib/wsl# ls
|
||
drivers lib
|
||
|
||
# Проверяем содержимое директории lib в WSL
|
||
root@tishgpu1:/usr/lib/wsl# ls lib
|
||
libcudadebugger.so.1 libd3d12core.so libnvcuvid.so.1 libnvidia-ml.so.1 libnvwgf2umx.so
|
||
libcuda.so libd3d12.so libnvdxdlkernels.so libnvidia-opticalflow.so nvidia-smi
|
||
libcuda.so.1 libdxcore.so libnvidia-encode.so libnvidia-opticalflow.so.1
|
||
libcuda.so.1.1 libnvcuvid.so libnvidia-encode.so.1 libnvoptix.so.1
|
||
|
||
# Устанавливаем права доступа на директорию WSL: только чтение и выполнение (555)
|
||
root@tishgpu1:/usr/lib# chmod -R 555 wsl/
|
||
|
||
# Устанавливаем владельцем директории WSL пользователя root и группу root
|
||
root@tishgpu1:/usr/lib# chown -R root:root wsl/
|
||
|
||
# Редактируем файл ld.so.conf.d для добавления пути к библиотекам WSL
|
||
root@tishgpu1:/usr/lib/wsl/lib# vim /etc/ld.so.conf.d/ld.wsl.conf
|
||
|
||
# Добавляем путь к библиотекам в файл ld.wsl.conf
|
||
/usr/lib/wsl/lib
|
||
|
||
# Применяем изменения с помощью ldconfig, чтобы обновить кэш динамических библиотек
|
||
root@tishgpu1:/usr/lib/wsl/lib# ldconfig
|
||
/sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link
|
||
|
||
# Редактируем или создаем файл /etc/profile.d/wsl.sh для добавления пути в системный PATH
|
||
root@tishgpu1:/usr/lib# vim /etc/profile.d/wsl.sh
|
||
|
||
# Проверяем содержимое файла wsl.sh, чтобы убедиться в правильности пути
|
||
root@tishgpu1:/usr/lib/wsl/lib# cat /etc/profile.d/wsl.sh
|
||
export PATH=$PATH:/usr/lib/wsl/lib
|
||
|
||
# Делаем файл wsl.sh исполняемым
|
||
root@tishgpu1:/usr/lib# chmod +x /etc/profile.d/wsl.sh
|
||
\end{lstlisting}
|
||
|
||
Сборка ядра с использованием готового shell скрипта:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Загружаем и выполняем скрипт установки DXGKRNL (DirectX Kernel) через DKMS (Dynamic Kernel Module Support):
|
||
# - curl: утилита для загрузки данных из интернета.
|
||
# -fsSL:
|
||
# -f: завершить с ошибкой, если произошел сбой HTTP-запроса.
|
||
# -s: тихий режим, скрывающий прогресс загрузки.
|
||
# -S: отображение ошибок даже в тихом режиме.
|
||
# -L: автоматическое следование за перенаправлениями.
|
||
# https://content.staralt.dev/dxgkrnl-dkms/main/install.sh: URL скрипта установки.
|
||
# |: передача загруженного содержимого скрипта как ввода следующей команде.
|
||
# sudo bash -es:
|
||
# - bash: запускает загруженный скрипт в интерпретаторе команд bash.
|
||
# - -e: завершает выполнение скрипта при любой ошибке.
|
||
# - -s: интерпретирует данные, подаваемые через стандартный ввод, как сценарий bash.
|
||
aritytishgpu1:~$ curl -fsSL https://content.staralt.dev/dxgkrnl-dkms/main/install.sh | sudo bash -es
|
||
|
||
Target Kernel Version: 5.15.0-124-generic
|
||
|
||
Installing dependencies...
|
||
\end{lstlisting}
|
||
|
||
Для проверки корректности работы GPU на виртуальном узле можно воспользоваться утилитами:
|
||
\begin{itemize}
|
||
\item lspci;
|
||
\begin{itemize}
|
||
\item Используется для отображения списка PCI-устройств, подключенных к системе. Ключ -v выводит подробную информацию о каждом устройстве. В данном случае, команда подтверждает наличие GPU, который использует драйвер dxgkrnl;
|
||
\end{itemize}
|
||
\item nvidia-smi;
|
||
\begin{itemize}
|
||
\item Утилита для управления и мониторинга графических карт NVIDIA. Показывает информацию о версии драйвера, состоянии GPU, использовании памяти, температуре и запущенных процессах. В данном примере отображается статус GPU NVIDIA GeForce RTX 5070, использование 1543 MiB памяти и базовая загрузка GPU;
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishgpu1:~$ lspci -v
|
||
c556:00:00.0 3D controller: Microsoft Corporation Device 008e
|
||
Physical Slot: 4111917767
|
||
Flags: bus master, fast devsel, latency 0, NUMA node 0
|
||
Capabilities: <access denied>
|
||
Kernel driver in use: dxgkrnl
|
||
Kernel modules: dxgkrnl
|
||
|
||
|
||
aritytishgpu1:~$ nvidia-smi
|
||
Sat Jan 3 17:25:15 2026
|
||
+-----------------------------------------------------------------------------------------+
|
||
| NVIDIA-SMI 580.102.01 Driver Version: 581.80 CUDA Version: 13.0 |
|
||
+-----------------------------------------+------------------------+----------------------+
|
||
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
|
||
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
|
||
| | | MIG M. |
|
||
|=========================================+========================+======================|
|
||
| 0 NVIDIA GeForce RTX 5070 On | 00000000:01:00.0 On | N/A |
|
||
| 0% 57C P0 36W / 250W | 1543MiB / 12227MiB | 6% Default |
|
||
| | | N/A |
|
||
+-----------------------------------------+------------------------+----------------------+
|
||
|
||
+-----------------------------------------------------------------------------------------+
|
||
| Processes: |
|
||
| GPU GI CI PID Type Process name GPU Memory |
|
||
| ID ID Usage |
|
||
|=========================================================================================|
|
||
| No running processes found |
|
||
+-----------------------------------------------------------------------------------------+
|
||
\end{lstlisting}
|
||
|
||
% \iffalse
|
||
% \begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
% C:\Windows\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db\
|
||
|
||
% PS C:\Windows\system32> scp -P 41122 -r C:\Windows\System32\DriverStore\FileRepository\nv_dispi.inf_amd64_20ae8f14a487d5db arity@127.0.0.1:/tmp/drv
|
||
|
||
% PS C:\Windows\system32> scp -P 41122 -r C:\Windows\System32\lxss\lib arity@127.0.0.1:/tmp/drv-lib
|
||
|
||
% root@tishgpu1:/tmp# ls -l
|
||
% total 36
|
||
% drwx---r-x 5 arity arity 12288 Nov 19 10:27 drv
|
||
% drwx------ 2 arity arity 4096 Nov 19 10:32 drv-lib
|
||
|
||
% root@tishgpu1:/tmp# mkdir -p /usr/lib/wsl/lib
|
||
|
||
% root@tishgpu1:/usr/lib/wsl# cp -r /tmp/drv-lib/ /usr/lib/wsl/
|
||
% root@tishgpu1:/usr/lib/wsl# ls
|
||
% drv-lib
|
||
% root@tishgpu1:/usr/lib/wsl# mv drv-lib/ lib
|
||
% root@tishgpu1:/usr/lib/wsl# ls
|
||
% lib
|
||
|
||
% root@tishgpu1:/usr/lib/wsl# cp -r /tmp/drv /usr/lib/wsl/lib/
|
||
% root@tishgpu1:/usr/lib/wsl# ls
|
||
% lib
|
||
% root@tishgpu1:/usr/lib/wsl# ls lib/
|
||
% drv libdxcore.so libnvidia-opticalflow.so
|
||
% libcudadebugger.so.1 libnvcuvid.so libnvidia-opticalflow.so.1
|
||
% libcuda.so libnvcuvid.so.1 libnvoptix.so.1
|
||
% libcuda.so.1 libnvdxdlkernels.so libnvwgf2umx.so
|
||
% libcuda.so.1.1 libnvidia-encode.so nvidia-smi
|
||
% libd3d12core.so libnvidia-encode.so.1
|
||
% libd3d12.so libnvidia-ml.so.1
|
||
% root@tishgpu1:/usr/lib/wsl/lib# mv drv/ drivers
|
||
% root@tishgpu1:/usr/lib/wsl/lib# ls -l
|
||
% total 242276
|
||
% drwx---r-x 5 root root 12288 Nov 19 10:47 drivers
|
||
% -rw-r--r-- 1 root root 10182600 Nov 19 10:46 libcudadebugger.so.1
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1.1
|
||
% -rw-r--r-- 1 root root 5913128 Nov 19 10:46 libd3d12core.so
|
||
% -rw-r--r-- 1 root root 789192 Nov 19 10:46 libd3d12.so
|
||
% -rw-r--r-- 1 root root 832144 Nov 19 10:46 libdxcore.so
|
||
% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so
|
||
% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so.1
|
||
% -rw-r--r-- 1 root root 128602944 Nov 19 10:46 libnvdxdlkernels.so
|
||
% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so
|
||
% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so.1
|
||
% -rw-r--r-- 1 root root 245192 Nov 19 10:46 libnvidia-ml.so.1
|
||
% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so
|
||
% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so.1
|
||
% -rw-r--r-- 1 root root 72664 Nov 19 10:46 libnvoptix.so.1
|
||
% -rw-r--r-- 1 root root 72175656 Nov 19 10:46 libnvwgf2umx.so
|
||
% -rw-r--r-- 1 root root 729600 Nov 19 10:46 nvidia-smi
|
||
% root@tishgpu1:/usr/lib/wsl/lib#root@tishgpu1:/usr/lib/wsl/lib# mv drv/ drivers
|
||
% root@tishgpu1:/usr/lib/wsl/lib# ls -l
|
||
% total 242276
|
||
% drwx---r-x 5 root root 12288 Nov 19 10:47 drivers
|
||
% -rw-r--r-- 1 root root 10182600 Nov 19 10:46 libcudadebugger.so.1
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1
|
||
% -rw-r--r-- 1 root root 162608 Nov 19 10:46 libcuda.so.1.1
|
||
% -rw-r--r-- 1 root root 5913128 Nov 19 10:46 libd3d12core.so
|
||
% -rw-r--r-- 1 root root 789192 Nov 19 10:46 libd3d12.so
|
||
% -rw-r--r-- 1 root root 832144 Nov 19 10:46 libdxcore.so
|
||
% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so
|
||
% -rw-r--r-- 1 root root 13061496 Nov 19 10:46 libnvcuvid.so.1
|
||
% -rw-r--r-- 1 root root 128602944 Nov 19 10:46 libnvdxdlkernels.so
|
||
% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so
|
||
% -rw-r--r-- 1 root root 584296 Nov 19 10:46 libnvidia-encode.so.1
|
||
% -rw-r--r-- 1 root root 245192 Nov 19 10:46 libnvidia-ml.so.1
|
||
% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so
|
||
% -rw-r--r-- 1 root root 362960 Nov 19 10:46 libnvidia-opticalflow.so.1
|
||
% -rw-r--r-- 1 root root 72664 Nov 19 10:46 libnvoptix.so.1
|
||
% -rw-r--r-- 1 root root 72175656 Nov 19 10:46 libnvwgf2umx.so
|
||
% -rw-r--r-- 1 root root 729600 Nov 19 10:46 nvidia-smi
|
||
|
||
% root@tishgpu1:/usr/lib/wsl/lib# chmod 555 /usr/lib/wsl/lib/*
|
||
% root@tishgpu1:/usr/lib/wsl/lib# chown -R root:root /usr/lib/wsl
|
||
|
||
% root@tishgpu1:/usr/lib/wsl/lib# vim /etc/ld.so.conf.d/ld.wsl.conf
|
||
|
||
% /usr/lib/wsl/lib
|
||
|
||
% root@tishgpu1:/usr/lib/wsl/lib# root@tishgpu1:/usr/lib/wsl/lib# ldconfig
|
||
% /sbin/ldconfig.real: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link
|
||
|
||
% root@tishgpu1:/usr/lib/wsl/lib# vim /etc/profile.d/wsl.sh
|
||
% root@tishgpu1:/usr/lib/wsl/lib# root@tishgpu1:/usr/lib/wsl/lib# cat /etc/profile.d/wsl.sh
|
||
% export PATH=$PATH:/usr/lib/wsl/lib
|
||
% chmod +x /etc/profile.d/wsl.sh
|
||
|
||
% \end{lstlisting}
|
||
% \fi
|
||
|
||
|
||
Для установки библиотеки для работы с CUDA необходимо выполнить следующие команды:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Загружаем публичный ключ репозитория NVIDIA для подписи пакетов.
|
||
# Важно для обеспечения безопасности и проверки пакетов.
|
||
aritytishgpu1:~$ sudo apt-key adv --fetch-keys https://developer.download.
|
||
nvidia.com
|
||
/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub
|
||
|
||
# Внимание: apt-key устарел. Рекомендуется использовать новый метод управления ключами через директорию trusted.gpg.d.
|
||
# Ключ успешно импортирован:
|
||
# "cudatools <cudatools@nvidia.com>"
|
||
|
||
# Добавляем репозиторий CUDA в список источников пакетов.
|
||
# Это позволяет получать доступ к последним версиям инструментов CUDA.
|
||
aritytishgpu1:~$ sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/
|
||
cuda/repos/ubuntu2204/x86_64/ /"
|
||
|
||
# Указанный репозиторий добавлен в файл /etc/apt/sources.list.d.
|
||
# Обновляем список пакетов для загрузки метаданных из нового репозитория.
|
||
# Получаем пакеты из репозитория NVIDIA и стандартных репозиториев Ubuntu.
|
||
# Замечание: ключ репозитория сохранён в устаревшем формате, как указано в предупреждении.
|
||
|
||
# Устанавливаем пакет CUDA Toolkit версии 12.
|
||
# Этот пакет включает библиотеки, компиляторы и утилиты для разработки с использованием GPU.
|
||
aritytishgpu1:~$ sudo apt install cuda-toolkit-12
|
||
|
||
# Создаём скрипт окружения для автоматической настройки переменных PATH, CUDA_HOME и LD_LIBRARY_PATH.
|
||
aritytishgpu1:/usr/local/cuda/bin$ sudo touch /etc/profile.d/cuda.sh
|
||
|
||
# Редактируем файл cuda.sh для добавления переменных окружения.
|
||
# Эти переменные позволяют системе находить бинарные файлы и библиотеки CUDA.
|
||
aritytishgpu1:/usr/local/cuda$ sudo vim /etc/profile.d/cuda.sh
|
||
|
||
# Проверяем содержимое файла, чтобы убедиться в правильной настройке переменных.
|
||
aritytishgpu1:/usr/local/cuda$ cat /etc/profile.d/cuda.sh
|
||
export PATH=$PATH:/usr/local/cuda/bin
|
||
export CUDA_HOME=$CUDA_HOME:/usr/local/cuda
|
||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64
|
||
|
||
# Делаем файл cuda.sh исполняемым, чтобы переменные окружения применялись при входе в систему.
|
||
aritytishgpu1:~$ sudo chmod +x /etc/profile.d/cuda.sh
|
||
\end{lstlisting}
|
||
|
||
\subsection{Конфигурация NFS}
|
||
NFS (Network File System) — это сетевой протокол, разработанный для предоставления общего доступа к файловым системам через сеть. С помощью NFS пользователи или приложения могут работать с файлами, расположенными на удалённом сервере, так, как будто они находятся на локальной машине. \cite{rfc1094}
|
||
|
||
Для корректной работы исполняемых и других файлов на виртуальных машинах, вместо постоянного копирования можно воспользоваться протоколом сетевого доступа к файловой системе одной из машин. Для этого можно настроить NFS на одном из узлов (например tishgpu1) и далее выполнить монтирование в каталоги других виртуальных машин.
|
||
|
||
\subsubsection*{Настройка etc/hosts}
|
||
Для удобной работы в среде виртуальных машин можно задать доменные имена в файле \smalltexttt{etc/hosts}. Для этого его необходимо отредактировать на каждой виртуальной машине.
|
||
|
||
Формат создания доменного имени:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# IP-адрес, Основное имя хоста, Полное доменное имя
|
||
10.200.166.126 tishcpu2 tishcpu2.tish
|
||
\end{lstlisting}
|
||
Пример файла \smalltexttt{etc/hosts}:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishcpu1:~$ cat /etc/hosts
|
||
127.0.0.1 localhost
|
||
10.200.166.125 tishcpu1 tishcpu1.tish
|
||
10.200.166.126 tishcpu2 tishcpu2.tish
|
||
10.200.166.127 tishgpu1 tishgpu1.tish
|
||
10.200.166.128 tishgpu2 tishgpu2.tish
|
||
|
||
# The following lines are desirable for IPv6 capable hosts
|
||
::1 ip6-localhost ip6-loopback
|
||
fe00::0 ip6-localnet
|
||
ff00::0 ip6-mcastprefix
|
||
ff02::1 ip6-allnodes
|
||
\end{lstlisting}
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Содержимое файла /etc/exports на tishcpu1
|
||
# Экспортируем директорию /home/arity для общего доступа через NFS
|
||
# *: доступ разрешён для всех хостов
|
||
# rw: разрешение на чтение и запись
|
||
# nohide: дочерние файловые системы видны
|
||
# no_subtree_check: отключает проверку вложенных поддеревьев для повышения производительности
|
||
aritytishcpu1:~$ cat /etc/exports
|
||
/home/arity *(rw,nohide,no_subtree_check)
|
||
|
||
# Применяем изменения в настройках NFS (перезапускаем экспорт)
|
||
# -a: экспорт всех записей
|
||
# -r: перезапускает экспорт
|
||
# -v: подробный вывод
|
||
aritytishcpu1:~$ sudo exportfs -arv
|
||
exporting *:/home/arity
|
||
|
||
# Устанавливаем клиент NFS на tishcpu2
|
||
sudo apt install nfs-common
|
||
|
||
# Удаляем старую папку (если существует) для монтирования
|
||
aritytishcpu2:/mnt$ sudo rmdir share
|
||
|
||
# Проверяем содержимое директории /mnt
|
||
aritytishcpu2:/mnt$ ls
|
||
|
||
# Создаём новую папку share для монтирования NFS
|
||
aritytishcpu2:/mnt$ sudo mkdir share
|
||
|
||
# Монтируем экспортированную директорию /home/arity с tishgpu1 в локальную папку /mnt/share
|
||
# -t nfs: указывает тип файловой системы (NFS)
|
||
aritytishcpu2:/mnt$ sudo mount -t nfs tishgpu1:/home/arity /mnt/share
|
||
|
||
# Проверяем содержимое смонтированной директории
|
||
aritytishgpu2:/mnt$ ls share/
|
||
hello.txt install.sh
|
||
|
||
# Читаем файл hello.txt из смонтированной директории
|
||
aritytishgpu2:/mnt$ cat share/hello.txt
|
||
happy hacking
|
||
aritytishgpu2:/mnt$
|
||
\end{lstlisting}
|
||
|
||
\subsection{Конфигурация slurm}
|
||
SLURM (Simple Linux Utility for Resource Management) — это открытая система управления задачами и ресурсами в кластерах. Она используется для распределения вычислительных задач между узлами и управления их выполнением.
|
||
|
||
С помощью \smalltexttt{conf.html} файла конфигурации необходимо сформировать конфигурацию в \smalltexttt{/etc/slurm/slurm.conf}.
|
||
|
||
\begin{itemize}
|
||
\item \textbf{ClusterName} - Имя кластера SLURM (например, "tish"), используется для идентификации кластера.
|
||
\item \textbf{SlurmctldHost} - Имя хоста, на котором работает демон управления SLURM (slurmctld), в данном случае это \smalltexttt{tishcpu1}.
|
||
\item \textbf{MpiDefault} - Указывает стандартную реализацию MPI (по умолчанию \smalltexttt{none}, т.е. MPI не используется).
|
||
\item \textbf{ProctrackType} - Метод отслеживания процессов; \smalltexttt{proctrack/cgroup} означает использование cgroup для изоляции процессов.
|
||
\item \textbf{ReturnToService} - Указывает, должен ли узел автоматически возвращаться в работу после восстановления (\smalltexttt{1} - включено).
|
||
\item \textbf{SlurmctldPidFile} - Путь к файлу PID для демона slurmctld.
|
||
\item \textbf{SlurmdPidFile} - Путь к файлу PID для демона slurmd.
|
||
\item \textbf{SlurmdSpoolDir} - Директория, где slurmd хранит временные файлы и информацию о заданиях.
|
||
\item \textbf{SlurmUser} - Имя пользователя, под которым запускаются процессы SLURM (обычно \smalltexttt{slurm}).
|
||
\item \textbf{StateSaveLocation} - Директория для сохранения состояния кластера, необходима для восстановления после перезапуска.
|
||
\item \textbf{SwitchType} - Тип сетевого коммутатора; \smalltexttt{switch/none} указывает, что коммутатор не используется.
|
||
\item \textbf{TaskPlugin} - Плагин для управления задачами; \smalltexttt{task/affinity} позволяет задавать привязку задач к CPU.
|
||
\item \textbf{SchedulerType} - Тип планировщика задач; \smalltexttt{sched/backfill} разрешает задачи меньшего размера выполняться параллельно с крупными.
|
||
\item \textbf{SelectType} - Механизм выбора ресурсов;
|
||
\item \textbf{SelectTypeParameters} - Параметры выбора ресурсов; \smalltexttt{CR\_Core} указывает распределение по ядрам.
|
||
\item \textbf{JobAcctGatherType} - Метод сбора данных о выполнении задач;
|
||
\item \textbf{SlurmctldLogFile} - Путь к файлу журнала для демона slurmctld.
|
||
\item \textbf{SlurmdLogFile} - Путь к файлу журнала для демона slurmd.
|
||
\item \textbf{NodeName} - Описание вычислительных узлов; описывает 4 узла с 2 CPU на каждом.
|
||
\item \textbf{PartitionName} - Имя раздела (partition), включающего все узлы (\smalltexttt{Nodes=ALL}); используется для распределения задач.
|
||
\item \textbf{Default} - Указывает, что данный раздел является разделом по умолчанию (\smalltexttt{YES}).
|
||
\item \textbf{MaxTime} - Максимальное время выполнения задачи; \smalltexttt{INFINITE} означает, что ограничений нет.
|
||
\item \textbf{State} - Статус узлов или раздела (\smalltexttt{UP} - узлы в рабочем состоянии).
|
||
\end{itemize}
|
||
|
||
Пример установки конфигурации:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
aritytishcpu1:~$ sudo apt install slurm-wlm
|
||
|
||
aritytishcpu1:~$ nano /etc/slurm/slurm.conf
|
||
#Указываем:
|
||
|
||
# slurm.conf file generated by configurator easy.html.
|
||
# Put this file on all nodes of your cluster.
|
||
# See the slurm.conf man page for more information.
|
||
#
|
||
ClusterName=tish
|
||
SlurmctldHost=tishcpu1
|
||
#
|
||
#MailProg=/bin/mail
|
||
#MpiDefault=
|
||
#MpiParams=ports=#-#
|
||
ProctrackType=proctrack/cgroup
|
||
ReturnToService=2
|
||
SlurmctldPidFile=/var/run/slurmctld.pid
|
||
#SlurmctldPort=6817
|
||
SlurmdPidFile=/var/run/slurmd.pid
|
||
#SlurmdPort=6818
|
||
SlurmdSpoolDir=/var/spool/slurmd
|
||
SlurmUser=slurm
|
||
#SlurmdUser=root
|
||
StateSaveLocation=/var/spool/slurmctld
|
||
#SwitchType=
|
||
TaskPlugin=task/affinity,task/cgroup
|
||
#
|
||
#
|
||
# TIMERS
|
||
#KillWait=30
|
||
#MinJobAge=300
|
||
#SlurmctldTimeout=120
|
||
#SlurmdTimeout=300
|
||
#
|
||
#
|
||
# SCHEDULING
|
||
SchedulerType=sched/backfill
|
||
SelectType=select/cons_tres
|
||
#
|
||
#
|
||
# LOGGING AND ACCOUNTING
|
||
#AccountingStorageType=
|
||
#JobAcctGatherFrequency=30
|
||
#JobAcctGatherType=
|
||
#SlurmctldDebug=info
|
||
SlurmctldLogFile=/var/log/slurmctld.log
|
||
#SlurmdDebug=info
|
||
SlurmdLogFile=/var/log/slurmd.log
|
||
#
|
||
#
|
||
# COMPUTE NODES
|
||
NodeName=tishcpu[1-2],tishgpu[1-2] CPUs=2 State=UNKNOWN
|
||
PartitionName=tishpartition Nodes=ALL Default=YES MaxTime=INFINITE State=UP
|
||
|
||
#Копируем этот конфиг в машины tishcpu2, tishgpu1, tishgpu2
|
||
\end{lstlisting}
|
||
\subsection{Конфигурация munge}
|
||
MUNGE — это инструмент для аутентификации, который используется для обеспечения безопасности в кластерах.
|
||
|
||
Ниже приведены шаги конфигурации ключей munge для конфигурации машин.
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Копируем файл ключа MUNGE с узла tishgpu1 на все машины кластера.
|
||
# Этот файл необходим для аутентификации в кластере.
|
||
|
||
root@tishcpu2:/tmp# chown munge:munge ./munge.key
|
||
# Изменяем владельца и группу файла ключа на пользователя и группу MUNGE.
|
||
# Это необходимо для корректной работы службы MUNGE.
|
||
|
||
root@tishcpu2:/tmp# ls -l
|
||
# Проверяем содержимое текущей директории, чтобы убедиться, что файл ключа имеет нужные права:
|
||
# -rw-------: доступ только для владельца.
|
||
|
||
root@tishcpu2:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key
|
||
# Переименовываем существующий ключ в `munge_old.key` для резервного копирования.
|
||
|
||
root@tishcpu2:/tmp# mv munge.key /etc/munge/munge.key
|
||
# Перемещаем новый файл ключа в директорию /etc/munge и задаём ему правильное имя.
|
||
|
||
root@tishcpu2:/etc/munge# ls
|
||
# Проверяем содержимое директории /etc/munge:
|
||
# Убедились, что есть два файла: новый ключ (`munge.key`) и резервный ключ (`munge_old.key`).
|
||
|
||
# Переходим на другой узел (tishgpu1), повторяем процесс.
|
||
aritytishgpu1:/tmp$ su
|
||
Password:
|
||
root@tishgpu1:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key
|
||
# Создаём резервную копию существующего ключа.
|
||
|
||
root@tishgpu1:/tmp# chown munge:munge munge.key
|
||
# Изменяем владельца нового ключа на пользователя MUNGE.
|
||
|
||
root@tishgpu1:/tmp# mv /tmp/munge.key /etc/munge/munge.key
|
||
# Перемещаем новый ключ в директорию /etc/munge.
|
||
|
||
# На узле tishgpu2 повторяем процесс.
|
||
root@tishgpu2:/tmp# chown munge:munge munge.key
|
||
# Меняем владельца файла ключа.
|
||
|
||
root@tishgpu2:/tmp# mv /etc/munge/munge.key /etc/munge/munge_old.key
|
||
# Резервируем старый ключ.
|
||
|
||
root@tishgpu2:/tmp# mv munge.key /etc/munge/munge.key
|
||
# Перемещаем новый ключ на место старого.
|
||
|
||
root@tishgpu2:/tmp# ls -l /etc/munge
|
||
# Проверяем права и владельца ключей в директории /etc/munge:
|
||
# Убедились, что оба ключа принадлежат пользователю `munge` и имеют доступ только для владельца.
|
||
|
||
# Вносим изменения в конфигурацию SLURM:
|
||
# Меняем механизм отслеживания процессов с `cgroup` на `linuxproc` в файле конфигурации /etc/slurm/slurm.conf.
|
||
|
||
# Перезапускаем службы, чтобы применить изменения:
|
||
sudo systemctl restart munge
|
||
# Перезапускаем службу MUNGE для применения нового ключа.
|
||
|
||
sudo systemctl restart slurmd
|
||
# Перезапускаем демон SLURM для рабочих узлов.
|
||
|
||
sudo systemctl restart slurmctld
|
||
# Перезапускаем демон SLURM для управляющего узла.
|
||
|
||
sudo systemctl status munge
|
||
sudo systemctl status slurmd
|
||
sudo systemctl status slurmctld
|
||
# Проверяем статус всех служб, чтобы убедиться в их корректной работе.
|
||
|
||
\end{lstlisting}
|
||
|
||
\subsection{Конфигурация OpenMPI}
|
||
|
||
OpenMPI (Open Message Passing Interface) — это высокопроизводительная, гибкая и открытая реализация стандарта MPI (Message Passing Interface). MPI — это стандарт для взаимодействия между процессами в распределённых и параллельных вычислительных системах, таких как кластеры и суперкомпьютеры.
|
||
|
||
Для обмена сообщениями узлы должны обменяться публичными ключами и каждый имел прямой доступ к друг другу через \smalltexttt{ssh}.
|
||
|
||
Чтобы произвести обмен ключами необходимо выполнить следующие команды:
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
# Создаём новую пару SSH-ключей:
|
||
ssh-keygen
|
||
|
||
# Копируем публичный SSH-ключ на удалённый узел:
|
||
# - ssh-copy-id: утилита для добавления публичного ключа на удалённый сервер.
|
||
# - aritytishcpu1: имя пользователя и адрес узла, куда копируется ключ.
|
||
# После выполнения этой команды ключ будет добавлен в файл authorized_keys на удалённом сервере,
|
||
# что позволит подключаться по SSH без ввода пароля.
|
||
ssh-copy-id aritytishcpu1
|
||
\end{lstlisting}
|
||
|
||
Для установки библиотеки OpenMPI необходимо выполнить следующие пакеты:
|
||
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
sudo apt install openmpi-bin # установить на все узлы
|
||
sudo apt install openmpi-dev # установить на 1 узел где будет разработка приложения
|
||
\end{lstlisting}
|
||
|
||
\newpage
|
||
\subsection{Постановка задачи и прототип решения}
|
||
|
||
\subsubsection*{Описание задачи}
|
||
Необходимо разработать параллельное приложение, задействующее вычислительные ресурсы двух CPU-узлов и двух CUDA-узлов, используя механизм OpenMPI, выполняющее анализ временных рядов исторических данных о стоимости Bitcoin.
|
||
|
||
Задача разбита на два этапа:
|
||
\begin{enumerate}
|
||
\item \textbf{Этап 1. Агрегация данных}: для временного ряда исторических данных о стоимости Bitcoin (исходные данные содержат информацию по каждым 10 секундам) необходимо выполнить группировку по дням и для каждого дня вычислить среднюю цену как математическое ожидание значений Low и High, а также минимальные и максимальные значения Open и Close.
|
||
\item \textbf{Этап 2. Поиск интервалов изменения цены}: на основе дневных агрегированных данных необходимо выявить интервалы дат (начиная с начальной даты в наборе данных), в которых средняя дневная цена изменилась не менее чем на 10\% относительно начала интервала. Для каждого интервала необходимо вывести начальную и конечную даты, а также минимальные и максимальные значения Open и Close за все дни внутри интервала.
|
||
\end{enumerate}
|
||
|
||
\subsubsection*{Описание входных данных}
|
||
В задаче используется файл в формате CSV с историческими данными о стоимости Bitcoin\cite{kaggle}. В качестве разделителя используется запятая. Во входном файле заданы следующие поля:
|
||
\begin{enumerate}
|
||
\item \textbf{Timestamp} - временная метка Unix в секундах.
|
||
\item \textbf{Open} - цена открытия за период.
|
||
\item \textbf{High} - максимальная цена за период.
|
||
\item \textbf{Low} - минимальная цена за период.
|
||
\item \textbf{Close} - цена закрытия за период.
|
||
\item \textbf{Volume} - объём торгов (может быть пустым).
|
||
\end{enumerate}
|
||
|
||
\subsubsection*{Прототип решения на Python}
|
||
Для отладки алгоритма был создан прототип на языке Python. Код прототипа представлен ниже:
|
||
|
||
\begin{lstlisting}[language=Python, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
import pandas as pd
|
||
|
||
# Загрузка данных
|
||
df = pd.read_csv("data.csv")
|
||
df['Timestamp'] = pd.to_datetime(df['Timestamp'], unit='s', utc=True)
|
||
|
||
# Вычисление средней цены
|
||
df['Avg'] = (df['Low'] + df['High']) / 2
|
||
|
||
# Группировка по дням и агрегация
|
||
df_days = (
|
||
df.groupby(df["Timestamp"].dt.date)
|
||
.agg(
|
||
Avg=("Avg", "mean"),
|
||
OpenMin=("Open", "min"),
|
||
OpenMax=("Open", "max"),
|
||
CloseMin=("Close", "min"),
|
||
CloseMax=("Close", "max"),
|
||
)
|
||
.reset_index()
|
||
)
|
||
|
||
# Поиск интервалов изменения цены на 10%
|
||
intervals = []
|
||
start_idx = 0
|
||
price_base = df_days.loc[start_idx, "Avg"]
|
||
|
||
for i in range(1, len(df_days)):
|
||
price_now = df_days.loc[i, "Avg"]
|
||
change = abs(price_now - price_base) / price_base
|
||
|
||
if change >= 0.10:
|
||
interval = df_days.loc[start_idx:i]
|
||
|
||
intervals.append({
|
||
"start_date": df_days.loc[start_idx, "Timestamp"],
|
||
"end_date": df_days.loc[i, "Timestamp"],
|
||
"min_open": interval["OpenMin"].min(),
|
||
"max_open": interval["OpenMax"].max(),
|
||
"min_close": interval["CloseMin"].min(),
|
||
"max_close": interval["CloseMax"].max(),
|
||
"start_avg": price_base,
|
||
"end_avg": price_now,
|
||
"change": change,
|
||
})
|
||
|
||
start_idx = i + 1
|
||
if start_idx >= len(df_days):
|
||
break
|
||
price_base = df_days.loc[start_idx, "Avg"]
|
||
|
||
df_intervals = pd.DataFrame(intervals)
|
||
\end{lstlisting}
|
||
|
||
\subsubsection*{Увеличение объёма данных}
|
||
Исходные данные содержат информацию по каждой минуте и имеют размер около 360 МБ. При тестировании параллельной реализации обработка таких данных занимала слишком мало времени, что не позволяло достоверно оценить эффективность параллельных вычислений и преимущества использования GPU.
|
||
|
||
Для решения этой проблемы был разработан скрипт \smalltexttt{upsample.py}, выполняющий линейную интерполяцию данных. Алгоритм работы скрипта следующий:
|
||
|
||
\begin{enumerate}
|
||
\item Для каждой пары соседних записей $(t_1, o_1, h_1, l_1, c_1, v_1)$ и $(t_2, o_2, h_2, l_2, c_2, v_2)$ вычисляется временной интервал $\Delta t = t_2 - t_1$.
|
||
\item Интервал делится на $n = \Delta t / \text{step}$ равных частей, где $\text{step}$ - новый временной шаг (10 секунд).
|
||
\item Для каждой промежуточной точки $i \in [0, n)$ вычисляются интерполированные значения с помощью линейной интерполяции:
|
||
\begin{align*}
|
||
\alpha &= i / n \\
|
||
t_i &= t_1 + i \cdot \text{step} \\
|
||
o_i &= o_1 + (o_2 - o_1) \cdot \alpha \\
|
||
h_i &= h_1 + (h_2 - h_1) \cdot \alpha \\
|
||
l_i &= l_1 + (l_2 - l_1) \cdot \alpha \\
|
||
c_i &= c_1 + (c_2 - c_1) \cdot \alpha \\
|
||
v_i &= v_1 + (v_2 - v_1) \cdot \alpha
|
||
\end{align*}
|
||
\end{enumerate}
|
||
|
||
В результате применения интерполяции данные были преобразованы из формата "каждая минута" в формат "каждые 10 секунд", что увеличило объём данных в 6 раз - с примерно 360 МБ до 2.3 ГБ. Такой объём данных позволяет наглядно продемонстрировать эффективность параллельных вычислений и преимущества использования GPU-ускорения.
|
||
|
||
\newpage
|
||
\subsection{Параллельная реализация на CPU}
|
||
|
||
\subsubsection*{Проблема последовательной обработки}
|
||
При профилировании первоначальной реализации было выявлено, что операция чтения и парсинга CSV-файла размером 2.3 ГБ занимает значительную часть времени выполнения программы. Последовательное чтение такого объёма данных на одном узле приводило к неэффективному использованию вычислительных ресурсов кластера, так как остальные узлы простаивали в ожидании данных.
|
||
|
||
\subsubsection*{Параллельное чтение с перекрытием}
|
||
Для решения этой проблемы было реализовано параллельное чтение файла, при котором каждый MPI-ранк читает только свою часть файла. Алгоритм работает следующим образом:
|
||
|
||
\begin{enumerate}
|
||
\item \textbf{Вычисление диапазонов байт}: размер файла делится на части пропорционально долям, указанным в переменной окружения \smalltexttt{DATA\_READ\_SHARES}. Для каждого ранка вычисляется диапазон байт $[\text{start}, \text{end})$, который он должен прочитать.
|
||
|
||
\item \textbf{Добавление перекрытия}: к каждому диапазону добавляется перекрытие размером \smalltexttt{READ\_OVERLAP\_BYTES} (по умолчанию 128 КБ). Это необходимо для корректной обработки строк CSV, которые могут быть разделены на границах диапазонов:
|
||
\begin{align*}
|
||
\text{start}_\text{adj} &= \max(0, \text{start} - \text{overlap}) \\
|
||
\text{end}_\text{adj} &= \min(\text{file\_size}, \text{end} + \text{overlap})
|
||
\end{align*}
|
||
|
||
\item \textbf{Обработка границ строк}:
|
||
\begin{itemize}
|
||
\item Ранк 0 пропускает заголовок CSV (первую строку) и начинает парсинг со второй строки.
|
||
\item Ранки $1 \ldots n-1$ пропускают неполную строку в начале своего диапазона, начиная парсинг с первого символа новой строки после \texttt{\textbackslash n}.
|
||
\item Ранк $n-1$ (последний) читает до конца файла, остальные ранки заканчивают чтение на последнем символе \texttt{\textbackslash n} перед концом диапазона.
|
||
\end{itemize}
|
||
\end{enumerate}
|
||
|
||
Такой подход обеспечивает равномерное распределение нагрузки по чтению между узлами кластера и исключает дублирование или потерю данных на границах диапазонов.
|
||
|
||
\subsubsection*{Агрегация данных по периодам}
|
||
После параллельного чтения каждый ранк имеет свой набор записей \smalltexttt{Record}. Агрегация выполняется локально на каждом ранке:
|
||
|
||
\begin{enumerate}
|
||
\item Записи последовательно обрабатываются, для каждой записи вычисляется индекс периода:
|
||
$$\text{period} = \lfloor \text{timestamp} / \text{AGGREGATION\_INTERVAL} \rfloor$$
|
||
|
||
\item Для каждого периода накапливаются следующие статистики:
|
||
\begin{itemize}
|
||
\item Сумма средних цен: $\sum_{i} (Low_i + High_i) / 2$
|
||
\item Минимальное и максимальное значение Open: $\min(Open_i)$, $\max(Open_i)$
|
||
\item Минимальное и максимальное значение Close: $\min(Close_i)$, $\max(Close_i)$
|
||
\item Количество записей в периоде: $count$
|
||
\end{itemize}
|
||
|
||
\item При смене периода статистики сохраняются в структуру \smalltexttt{PeriodStats}, и начинается накопление для следующего периода.
|
||
\end{enumerate}
|
||
|
||
Агрегация может выполняться на CPU (последовательная обработка) или на GPU (параллельная обработка с использованием CUDA). При недоступности GPU автоматически выполняется fallback на CPU-версию.
|
||
|
||
\subsubsection*{Удаление граничных периодов}
|
||
Из-за параллельного чтения с перекрытием первый и последний периоды каждого ранка могут содержать неполные данные. Например, если период охватывает временной интервал $[t_1, t_2)$, а ранк прочитал записи только начиная с $t_1 + \delta$, то статистики для этого периода будут искажены.
|
||
|
||
Для устранения этой проблемы применяется функция \smalltexttt{trim\_edge\_periods}:
|
||
\begin{itemize}
|
||
\item Ранк 0 удаляет только последний период (первый период гарантированно полный, так как чтение начинается с начала файла).
|
||
\item Ранки $1 \ldots n-2$ удаляют первый и последний периоды.
|
||
\item Ранк $n-1$ удаляет только первый период (последний период гарантированно полный, так как чтение идёт до конца файла).
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Параллельный поиск интервалов изменения цены}
|
||
После агрегации каждый ранк имеет список периодов \smalltexttt{PeriodStats}, упорядоченных по времени. Для параллельного поиска интервалов используется следующий алгоритм:
|
||
|
||
\begin{enumerate}
|
||
\item \textbf{Приём данных от предыдущего ранка}: ранк $i > 0$ ожидает от ранка $i-1$ информацию о незавершённом интервале. Если предыдущий ранк передал начало интервала, текущий ранк продолжает его обработку.
|
||
|
||
\item \textbf{Локальная обработка периодов}: ранк последовательно обходит свои периоды, проверяя условие изменения цены:
|
||
$$\text{change} = \frac{|\text{avg}_\text{current} - \text{avg}_\text{start}|}{\text{avg}_\text{start}} \geq 0.10$$
|
||
|
||
Если условие выполнено, интервал завершается и сохраняется в результаты. Начинается новый интервал.
|
||
|
||
\item \textbf{Передача данных следующему ранку}: если у ранка остался незавершённый интервал (не достигнуто изменение на 10\%), информация о начале этого интервала передаётся ранку $i+1$ через MPI.
|
||
\end{enumerate}
|
||
|
||
Такой подход обеспечивает корректность параллельного поиска интервалов: интервалы, пересекающие границы данных между ранками, корректно обрабатываются через передачу состояния по цепочке.
|
||
|
||
\subsubsection*{Сбор результатов}
|
||
После завершения локальной обработки ранк 0 собирает найденные интервалы от всех остальных ранков через MPI, сортирует их по времени начала и записывает в выходной файл \smalltexttt{result.csv}.
|
||
|
||
\newpage
|
||
\subsection{GPU-ускорение агрегации данных}
|
||
|
||
\subsubsection*{Общий алгоритм GPU-агрегации}
|
||
Агрегация данных на GPU реализована в модуле \smalltexttt{gpu\_plugin.cu} и использует библиотеку CUB (CUDA Unbound) для эффективной параллельной обработки. Общий алгоритм состоит из следующих шагов:
|
||
|
||
\begin{enumerate}
|
||
\item \textbf{Копирование данных на GPU}: массивы timestamp, open, high, low, close копируются из оперативной памяти CPU в память GPU.
|
||
|
||
\item \textbf{Вычисление индексов периодов}: для каждой записи параллельно вычисляется индекс периода:
|
||
$$\text{period\_id}_i = \lfloor \text{timestamp}_i / \text{AGGREGATION\_INTERVAL} \rfloor$$
|
||
Используется простое ядро с одномерной сеткой блоков.
|
||
|
||
\item \textbf{Run-Length Encoding (RLE)}: применяется операция RLE из библиотеки CUB для нахождения уникальных последовательных периодов и подсчёта количества записей в каждом периоде. На выходе получаем:
|
||
\begin{itemize}
|
||
\item Массив уникальных периодов: $[\text{period}_0, \text{period}_1, \ldots, \text{period}_{n-1}]$
|
||
\item Массив длин последовательностей: $[\text{count}_0, \text{count}_1, \ldots, \text{count}_{n-1}]$
|
||
\end{itemize}
|
||
|
||
\item \textbf{Exclusive Scan}: применяется префиксная сумма (exclusive scan) к массиву длин для вычисления смещений начала каждого периода в исходном массиве данных:
|
||
$$\text{offset}_i = \sum_{j=0}^{i-1} \text{count}_j$$
|
||
|
||
\item \textbf{Агрегация по периодам}: для каждого периода параллельно вычисляются статистики (среднее значение, минимумы и максимумы). Используется одно из двух ядер в зависимости от настроек (см. следующий раздел).
|
||
|
||
\item \textbf{Копирование результатов обратно на CPU}: агрегированные статистики копируются из памяти GPU обратно в оперативную память CPU.
|
||
\end{enumerate}
|
||
|
||
\subsubsection*{Два варианта ядер агрегации}
|
||
Для агрегации по периодам реализовано два варианта CUDA-ядер, оптимизированных для разных сценариев использования:
|
||
|
||
\textbf{1. Блочное ядро (Block Kernel):}
|
||
|
||
Используется когда \smalltexttt{USE\_BLOCK\_KERNEL=1}. Оптимизировано для случая, когда в каждом периоде много записей (большой \smalltexttt{AGGREGATION\_INTERVAL}).
|
||
|
||
Алгоритм работы:
|
||
\begin{itemize}
|
||
\item Один блок потоков обрабатывает один период.
|
||
\item Потоки внутри блока параллельно обрабатывают записи периода, каждый поток накапливает локальные статистики.
|
||
\item Используется shared memory для промежуточного хранения результатов.
|
||
\item Атомарные операции (\texttt{atomicAdd}, \texttt{atomicMin}, \texttt{atomicMax}) используются для объединения локальных результатов потоков.
|
||
\item Первый поток блока записывает финальный результат в глобальную память.
|
||
\end{itemize}
|
||
|
||
Преимущества: эффективное использование параллелизма внутри периода, минимизация обращений к глобальной памяти за счёт shared memory.
|
||
|
||
\textbf{2. Простое ядро (Simple Kernel):}
|
||
|
||
Используется когда \smalltexttt{USE\_BLOCK\_KERNEL=0}. Оптимизировано для случая, когда периодов много, но в каждом периоде мало записей (малый \smalltexttt{AGGREGATION\_INTERVAL}).
|
||
|
||
Алгоритм работы:
|
||
\begin{itemize}
|
||
\item Один поток обрабатывает один период полностью.
|
||
\item Поток последовательно обходит все записи своего периода, накапливая статистики.
|
||
\item Не используется shared memory и атомарные операции.
|
||
\item Результат сразу записывается в глобальную память.
|
||
\end{itemize}
|
||
|
||
Преимущества: отсутствие overhead на синхронизацию потоков и атомарные операции, эффективно при большом количестве независимых периодов.
|
||
|
||
\textbf{Выбор ядра:}
|
||
|
||
Выбор между ядрами осуществляется через переменную окружения \smalltexttt{USE\_BLOCK\_KERNEL}:
|
||
\begin{itemize}
|
||
\item Блочное ядро предпочтительно при агрегации по дням/часам (86400 или 3600 секунд) - много записей в каждом периоде.
|
||
\item Простое ядро предпочтительно при агрегации по минутам/секундам (60 или 10 секунд) - мало записей в каждом периоде, но много периодов.
|
||
\end{itemize}
|
||
|
||
\newpage
|
||
\subsection{Конфигурация через переменные окружения}
|
||
|
||
Все настройки параллельного приложения вынесены в переменные окружения, которые задаются в SLURM-скрипте \smalltexttt{run.slurm}. Это обеспечивает гибкость настройки без необходимости перекомпиляции программы.
|
||
|
||
\subsubsection*{Описание переменных окружения}
|
||
|
||
\begin{itemize}
|
||
\item \textbf{DATA\_PATH} - полный путь к CSV-файлу с входными данными. Файл должен быть доступен на всех узлах кластера (рекомендуется использовать общую директорию, например, через NFS).
|
||
|
||
Пример: \smalltexttt{/mnt/shared/supercomputers/data/data\_10s.csv}
|
||
|
||
\item \textbf{DATA\_READ\_SHARES} - доли данных для каждого ранка при параллельном чтении файла, разделённые запятыми. Позволяет неравномерно распределить нагрузку по чтению, если узлы имеют разную производительность.
|
||
|
||
Пример: \smalltexttt{10,11,13,14} означает, что файл будет разделён на части пропорционально $10:11:13:14$. Если количество значений не совпадает с количеством ранков, используется равномерное распределение.
|
||
|
||
\item \textbf{READ\_OVERLAP\_BYTES} - размер перекрытия в байтах при параллельном чтении файла. Необходим для корректной обработки строк CSV на границах диапазонов. Значение должно быть достаточным для размещения хотя бы одной полной строки CSV.
|
||
|
||
Значение по умолчанию: \smalltexttt{131072} (128 КБ)
|
||
|
||
\item \textbf{AGGREGATION\_INTERVAL} - интервал агрегации в секундах. Определяет размер временного периода, по которому группируются данные.
|
||
|
||
Типичные значения:
|
||
\begin{itemize}
|
||
\item \smalltexttt{60} - агрегация по минутам
|
||
\item \smalltexttt{600} - агрегация по 10 минутам
|
||
\item \smalltexttt{3600} - агрегация по часам
|
||
\item \smalltexttt{86400} - агрегация по дням
|
||
\end{itemize}
|
||
|
||
\item \textbf{USE\_CUDA} - флаг использования GPU для агрегации данных. Если установлен в \smalltexttt{1}, программа попытается использовать GPU. Если GPU недоступен или флаг установлен в \smalltexttt{0}, используется CPU-версия агрегации.
|
||
|
||
Значения: \smalltexttt{0} (отключено) или \smalltexttt{1} (включено)
|
||
|
||
\item \textbf{USE\_BLOCK\_KERNEL} - выбор варианта CUDA-ядра для GPU-агрегации (действует только при \smalltexttt{USE\_CUDA=1}). Определяет, какое ядро будет использоваться для параллельной обработки на GPU.
|
||
|
||
Значения:
|
||
\begin{itemize}
|
||
\item \smalltexttt{0} - использовать простое ядро (один поток на период)
|
||
\item \smalltexttt{1} - использовать блочное ядро (один блок на период)
|
||
\end{itemize}
|
||
|
||
Рекомендации по выбору:
|
||
\begin{itemize}
|
||
\item Для больших интервалов агрегации (дни, часы) - \smalltexttt{USE\_BLOCK\_KERNEL=1}
|
||
\item Для малых интервалов агрегации (минуты, секунды) - \smalltexttt{USE\_BLOCK\_KERNEL=0}
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
Пример конфигурации в \smalltexttt{run.slurm}:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
export DATA_PATH="/mnt/shared/supercomputers/data/data_10s.csv"
|
||
export DATA_READ_SHARES="10,11,13,14"
|
||
export READ_OVERLAP_BYTES=131072
|
||
export AGGREGATION_INTERVAL=60
|
||
export USE_CUDA=1
|
||
export USE_BLOCK_KERNEL=0
|
||
\end{lstlisting}
|
||
|
||
\newpage
|
||
\subsection{Структура проекта}
|
||
Исходный проект содержит в себе следующие зависимости:
|
||
\begin{itemize}
|
||
\item CUDA-Toolkit 12.8;
|
||
\item OpenMPI 3.
|
||
\end{itemize}
|
||
|
||
Можно выделить следующие основные сущности:
|
||
\begin{itemize}
|
||
\item \smalltexttt{run.slurm} - SLURM-скрипт для запуска параллельного приложения на 4 узлах с настройкой переменных окружения (путь к данным, доли данных для каждого ранка, интервал агрегации, использование CUDA). Исходный текст файла представлен в \hyperref[1]{Приложение A};
|
||
\item \smalltexttt{Makefile} - файл системы сборки, описывающий компиляцию C++ исходников с помощью mpic++ и компиляцию CUDA-плагина с помощью nvcc, а также правила запуска и очистки. Исходный текст файла представлен в \hyperref[2]{Приложение Б};
|
||
\item \smalltexttt{src/main.cpp} - основная MPI-программа: координирует выполнение параллельного чтения CSV данных, агрегацию данных по временным периодам (на CPU или GPU), поиск интервалов изменения цены и запись результатов. Исходный текст файла представлен в \hyperref[3]{Приложение В};
|
||
\item \smalltexttt{src/csv\_loader.cpp}, \smalltexttt{src/csv\_loader.hpp} - модуль параллельной загрузки CSV-файла: каждый MPI-ранк читает свою часть файла с перекрытием для обработки границ строк, парсит записи Bitcoin данных. Исходный текст файла представлен в \hyperref[4]{Приложение Г};
|
||
\item \smalltexttt{src/aggregation.cpp}, \smalltexttt{src/aggregation.hpp} - модуль агрегации временных рядов на CPU: группирует записи по временным интервалам, вычисляет среднее значение (Low+High)/2, минимумы и максимумы Open/Close за каждый период. Исходный текст файла представлен в \hyperref[5]{Приложение Д};
|
||
\item \smalltexttt{src/gpu\_loader.cpp}, \smalltexttt{src/gpu\_loader.hpp} - модуль динамической загрузки GPU-плагина: проверяет доступность GPU, загружает функции из \smalltexttt{libgpu\_compute.so}, преобразует данные из AoS в SoA для передачи на GPU. Исходный текст файла представлен в \hyperref[6]{Приложение Е};
|
||
\item \smalltexttt{src/gpu\_plugin.cu} - CUDA-модуль агрегации данных на GPU: использует библиотеку CUB для RLE и scan операций, реализует два ядра агрегации (блочное для больших интервалов и простое для множества малых периодов). Исходный текст файла представлен в \hyperref[7]{Приложение Ж};
|
||
\item \smalltexttt{src/intervals.cpp}, \smalltexttt{src/intervals.hpp} - модуль параллельного поиска интервалов изменения цены: каждый ранк обрабатывает свою часть периодов, передаёт незавершённые интервалы следующему ранку через MPI, собирает результаты на ранке 0. Исходный текст файла представлен в \hyperref[8]{Приложение З};
|
||
\item \smalltexttt{src/utils.cpp}, \smalltexttt{src/utils.hpp} - вспомогательный модуль: чтение переменных окружения, вычисление диапазонов байт для параллельного чтения файла, удаление граничных периодов, получение размера файла. Исходный текст файла представлен в \hyperref[9]{Приложение И};
|
||
\item \smalltexttt{src/period\_stats.hpp} - заголовочный файл с определением структуры \smalltexttt{PeriodStats}, хранящей агрегированные статистики за один временной период (среднее значение, минимумы и максимумы Open/Close). Исходный текст файла представлен в \hyperref[10]{Приложение К};
|
||
\item \smalltexttt{src/record.hpp} - заголовочный файл с определением структуры \smalltexttt{Record} для хранения одной записи из CSV-файла Bitcoin (timestamp, open, high, low, close, volume). Исходный текст файла представлен в \hyperref[11]{Приложение Л};
|
||
\item \smalltexttt{data/data\_10s.csv} - текстовый файл с входными данными о стоимости Bitcoin по каждым 10 секундам в формате CSV;
|
||
\item \smalltexttt{result.csv} - выходной файл с найденными интервалами изменения цены не менее чем на 10\%.
|
||
\end{itemize}
|
||
|
||
Также в рамках проекта используется система автоматизированной сборки. Для сборки и запуска проекта необходимо выполнить следующие команды:
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
make
|
||
make run
|
||
\end{lstlisting}
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ЗАКЛЮЧЕНИЕ}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ЗАКЛЮЧЕНИЕ}
|
||
В ходе выполнения лабораторной работы были решены задачи, направленные на освоение параллельных вычислений с использованием разнородных типов вычислительных ресурсов. В результате работы удалось:
|
||
|
||
\begin{itemize}
|
||
\item Создать виртуальные машины, обеспечивающие выполнение задач как на GPU-узлах, так и на CPU-узлах;
|
||
\item Настроить сеть для обеспечения стабильной связи между хост-системой и виртуальными узлами;
|
||
\item Реализовать параллельное приложение с использованием механизма OpenMPI, задействующее ресурсы разнородных узлов;
|
||
\item Изучить технологии CUDA, OpenMPI, Slurm.
|
||
\end{itemize}
|
||
|
||
Были изучены технологии OpenMPI, CUDA Toolkit и библиотека CUB. В рамках работы было разработано параллельное приложение на языке C++, использующее разнородный вид вычислительных ресурсов для анализа временных рядов исторических данных о стоимости Bitcoin.
|
||
|
||
Разработанная программа выполняет параллельную агрегацию временных рядов и поиск интервалов значительного изменения цены. Ключевые особенности реализации:
|
||
|
||
\begin{itemize}
|
||
\item Параллельное чтение CSV-файла размером 2.3 ГБ с использованием техники перекрытия диапазонов для корректной обработки границ строк.
|
||
\item Гибридная агрегация данных, поддерживающая вычисления как на CPU (последовательная обработка), так и на GPU (параллельная обработка с использованием CUDA).
|
||
\item Два варианта CUDA-ядер: блочное ядро для больших интервалов агрегации (дни, часы) и простое ядро для малых интервалов (минуты, секунды).
|
||
\item Динамическая загрузка GPU-плагина через dlopen, позволяющая запускать приложение на узлах без GPU без перекомпиляции.
|
||
\item Параллельный поиск интервалов изменения цены с передачей незавершённых интервалов между ранками через MPI.
|
||
\item Гибкая конфигурация через переменные окружения, позволяющая настраивать параметры работы без изменения исходного кода.
|
||
\end{itemize}
|
||
|
||
Для наглядной демонстрации эффективности параллельных вычислений был разработан скрипт линейной интерполяции данных, увеличивший объём исходного набора данных с 360 МБ до 2.3 ГБ. Это позволило достоверно оценить преимущества параллельной обработки и GPU-ускорения.
|
||
|
||
|
||
|
||
В рамках работы получены практические знания о гетерогенных вычислительных системах и реализовано полнофункциональное параллельное приложение, эффективно использующее ресурсы разнородных вычислителей для обработки больших объёмов временных рядов.
|
||
|
||
|
||
\newpage
|
||
\addcontentsline{toc}{section}{Список литературы}
|
||
|
||
\vspace{-1.5cm}
|
||
\begin{thebibliography}{0}
|
||
\bibitem{kaggle}
|
||
Zielak - Bitcoin Historical Data // kaggle URL: https://www.kaggle.com/datasets/mczielinski/bitcoin-historical-data (дата обращения: 10.01.2026).
|
||
\bibitem{hyperv}
|
||
Hyper-V Documentation // Microsoft URL: https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/ (дата обращения: 10.01.2026).
|
||
\bibitem{ubuntuDocs}
|
||
Ubuntu OS Docs // Ubuntu URL: https://ubuntu.com/server/docs (дата обращения: 10.01.2026).
|
||
\bibitem{newVmSwitch}
|
||
New-VMSwitch: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/new-vmswitch?view=windowsserver2025-ps (дата обращения: 10.01.2026).
|
||
\bibitem{newNetIpAddress}
|
||
New-NetIPAddress: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/nettcpip/new-netipaddress?view=windowsserver2025-ps (дата обращения: 10.01.2026).
|
||
\bibitem{newNetNat}
|
||
New-NetNat: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/netnat/New-NetNat?view=windowsserver2016-ps (дата обращения: 10.01.2026).
|
||
\bibitem{addNetNatStaticMapping}
|
||
Add-NetNatStaticMapping: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/ru-ru/powershell/module/netnat/add-netnatstaticmapping?view=windowsserver2022-ps (дата обращения: 10.01.2026).
|
||
\bibitem{getVmHostPartitionableGpu}
|
||
Get-VMHostPartitionableGpu: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/get-vmhostpartitionablegpu?view=windowsserver2025-ps (дата обращения: 10.01.2026).
|
||
\bibitem{setVm}
|
||
Set-VM: Документация PowerShell // Microsoft 365 URL: https://learn.microsoft.com/en-us/powershell/module/hyper-v/set-vm?view=windowsserver2025-ps (дата обращения: 10.01.2026).
|
||
\bibitem{rfc1094}
|
||
RFC 1094: NFS: Network File System Protocol Specification // Sun Microsystems, Inc. URL: https://datatracker.ietf.org/doc/html/rfc1094 (дата обращения: 10.01.2026).
|
||
\end{thebibliography}
|
||
\newpage
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ А}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ А}
|
||
\label{1}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#!/bin/bash
|
||
#SBATCH --job-name=btc
|
||
#SBATCH --nodes=4
|
||
#SBATCH --ntasks=4
|
||
#SBATCH --cpus-per-task=2
|
||
#SBATCH --output=out.txt
|
||
|
||
# Путь к файлу данных (должен существовать на всех узлах)
|
||
export DATA_PATH="/mnt/shared/supercomputers/data/data_10s.csv"
|
||
|
||
# Доли данных для каждого ранка (сумма определяет пропорции)
|
||
export DATA_READ_SHARES="10,11,13,14"
|
||
|
||
# Размер перекрытия в байтах для обработки границ строк
|
||
export READ_OVERLAP_BYTES=131072
|
||
|
||
# Интервал агрегации в секундах (60 = минуты, 600 = 10 минут, 86400 = дни)
|
||
export AGGREGATION_INTERVAL=60
|
||
|
||
# Использовать ли CUDA для агрегации (0 = нет, 1 = да)
|
||
export USE_CUDA=1
|
||
|
||
# Использовать ли блочное ядро (быстрее для больших интервалов, 0 = нет, 1 = да)
|
||
export USE_BLOCK_KERNEL=0
|
||
|
||
cd /mnt/shared/supercomputers/build
|
||
mpirun -np $SLURM_NTASKS ./bitcoin_app
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Б}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Б}
|
||
\label{2}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
CXX = mpic++
|
||
CXXFLAGS = -std=c++17 -O2 -Wall -Wextra -Wno-cast-function-type -fopenmp
|
||
|
||
NVCC = nvcc
|
||
NVCCFLAGS = -O3 -std=c++17 -arch=sm_86 -Xcompiler -fPIC
|
||
|
||
SRC_DIR = src
|
||
BUILD_DIR = build
|
||
|
||
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
|
||
OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS))
|
||
|
||
TARGET = $(BUILD_DIR)/bitcoin_app
|
||
|
||
PLUGIN_SRC = $(SRC_DIR)/gpu_plugin.cu
|
||
PLUGIN = $(BUILD_DIR)/libgpu_compute.so
|
||
|
||
all: $(PLUGIN) $(TARGET)
|
||
|
||
$(BUILD_DIR):
|
||
mkdir -p $(BUILD_DIR)
|
||
|
||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR)
|
||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||
|
||
$(TARGET): $(OBJS)
|
||
$(CXX) $(CXXFLAGS) $^ -o $@ -ldl
|
||
|
||
$(PLUGIN): $(PLUGIN_SRC) | $(BUILD_DIR)
|
||
$(NVCC) $(NVCCFLAGS) -shared $< -o $@
|
||
|
||
clean:
|
||
rm -rf $(BUILD_DIR)
|
||
|
||
run: all
|
||
sbatch run.slurm
|
||
|
||
.PHONY: all clean run
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ В}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ В}
|
||
\label{3}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include <mpi.h>
|
||
#include <iostream>
|
||
#include <vector>
|
||
#include <iomanip>
|
||
|
||
#include "csv_loader.hpp"
|
||
#include "record.hpp"
|
||
#include "period_stats.hpp"
|
||
#include "aggregation.hpp"
|
||
#include "intervals.hpp"
|
||
#include "utils.hpp"
|
||
#include "gpu_loader.hpp"
|
||
|
||
int main(int argc, char** argv) {
|
||
MPI_Init(&argc, &argv);
|
||
double total_start = MPI_Wtime();
|
||
|
||
int rank, size;
|
||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||
MPI_Comm_size(MPI_COMM_WORLD, &size);
|
||
|
||
// Проверяем доступность GPU
|
||
bool use_cuda = get_use_cuda();
|
||
bool have_gpu = gpu_is_available();
|
||
bool use_gpu = use_cuda && have_gpu;
|
||
|
||
std::cout << "Rank " << rank
|
||
<< ": USE_CUDA=" << use_cuda
|
||
<< ", GPU available=" << have_gpu
|
||
<< ", using " << (use_gpu ? "GPU" : "CPU")
|
||
<< std::endl;
|
||
|
||
// Параллельное чтение данных
|
||
double read_start = MPI_Wtime();
|
||
std::vector<Record> records = load_csv_parallel(rank, size);
|
||
double read_time = MPI_Wtime() - read_start;
|
||
|
||
std::cout << "Rank " << rank
|
||
<< ": read " << records.size() << " records"
|
||
<< " in " << std::fixed << std::setprecision(3) << read_time << " sec"
|
||
<< std::endl;
|
||
|
||
// Агрегация по периодам
|
||
double agg_start = MPI_Wtime();
|
||
std::vector<PeriodStats> periods;
|
||
|
||
if (use_gpu) {
|
||
int64_t interval = get_aggregation_interval();
|
||
if (!aggregate_periods_gpu(records, interval, periods)) {
|
||
std::cerr << "Rank " << rank << ": GPU aggregation failed, falling back to CPU" << std::endl;
|
||
periods = aggregate_periods(records);
|
||
}
|
||
} else {
|
||
periods = aggregate_periods(records);
|
||
}
|
||
|
||
double agg_time = MPI_Wtime() - agg_start;
|
||
|
||
std::cout << "Rank " << rank
|
||
<< ": aggregated " << periods.size() << " periods"
|
||
<< " [" << (periods.empty() ? 0 : periods.front().period)
|
||
<< ".." << (periods.empty() ? 0 : periods.back().period) << "]"
|
||
<< " in " << std::fixed << std::setprecision(3) << agg_time << " sec"
|
||
<< std::endl;
|
||
|
||
// Удаляем крайние периоды (могут быть неполными из-за параллельного чтения)
|
||
trim_edge_periods(periods, rank, size);
|
||
|
||
std::cout << "Rank " << rank
|
||
<< ": after trim " << periods.size() << " periods"
|
||
<< " [" << (periods.empty() ? 0 : periods.front().period)
|
||
<< ".." << (periods.empty() ? 0 : periods.back().period) << "]"
|
||
<< std::endl;
|
||
|
||
// Параллельное построение интервалов
|
||
IntervalResult iv_result = find_intervals_parallel(periods, rank, size);
|
||
|
||
std::cout << "Rank " << rank
|
||
<< ": found " << iv_result.intervals.size() << " intervals"
|
||
<< ", compute " << std::fixed << std::setprecision(6) << iv_result.compute_time << " sec"
|
||
<< ", wait " << iv_result.wait_time << " sec"
|
||
<< std::endl;
|
||
|
||
// Сбор интервалов на ранке 0
|
||
double collect_wait = collect_intervals(iv_result.intervals, rank, size);
|
||
|
||
if (rank == 0) {
|
||
std::cout << "Rank 0: collected " << iv_result.intervals.size() << " total intervals"
|
||
<< ", wait " << std::fixed << std::setprecision(3) << collect_wait << " sec"
|
||
<< std::endl;
|
||
}
|
||
|
||
// Запись результатов в файл (только ранк 0)
|
||
if (rank == 0) {
|
||
double write_start = MPI_Wtime();
|
||
write_intervals("result.csv", iv_result.intervals);
|
||
double write_time = MPI_Wtime() - write_start;
|
||
|
||
std::cout << "Rank 0: wrote result.csv"
|
||
<< " in " << std::fixed << std::setprecision(3) << write_time << " sec"
|
||
<< std::endl;
|
||
}
|
||
|
||
// Вывод общего времени выполнения
|
||
MPI_Barrier(MPI_COMM_WORLD);
|
||
double total_time = MPI_Wtime() - total_start;
|
||
if (rank == 0) {
|
||
std::cout << "Total execution time: "
|
||
<< std::fixed << std::setprecision(3)
|
||
<< total_time << " sec" << std::endl;
|
||
}
|
||
|
||
MPI_Finalize();
|
||
return 0;
|
||
}
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Г}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Г}
|
||
\label{4}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include "csv_loader.hpp"
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#include <iostream>
|
||
#include <stdexcept>
|
||
|
||
bool parse_csv_line(const std::string& line, Record& record) {
|
||
if (line.empty()) {
|
||
return false;
|
||
}
|
||
|
||
std::stringstream ss(line);
|
||
std::string item;
|
||
|
||
try {
|
||
// timestamp
|
||
if (!std::getline(ss, item, ',') || item.empty()) return false;
|
||
record.timestamp = std::stod(item);
|
||
|
||
// open
|
||
if (!std::getline(ss, item, ',') || item.empty()) return false;
|
||
record.open = std::stod(item);
|
||
|
||
// high
|
||
if (!std::getline(ss, item, ',') || item.empty()) return false;
|
||
record.high = std::stod(item);
|
||
|
||
// low
|
||
if (!std::getline(ss, item, ',') || item.empty()) return false;
|
||
record.low = std::stod(item);
|
||
|
||
// close
|
||
if (!std::getline(ss, item, ',') || item.empty()) return false;
|
||
record.close = std::stod(item);
|
||
|
||
// volume
|
||
if (!std::getline(ss, item, ',')) return false;
|
||
// Volume может быть пустым или содержать данные
|
||
if (item.empty()) {
|
||
record.volume = 0.0;
|
||
} else {
|
||
record.volume = std::stod(item);
|
||
}
|
||
|
||
return true;
|
||
} catch (const std::exception&) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
std::vector<Record> load_csv_parallel(int rank, int size) {
|
||
std::vector<Record> data;
|
||
|
||
// Читаем настройки из переменных окружения
|
||
std::string data_path = get_data_path();
|
||
std::vector<int> shares = get_data_read_shares();
|
||
int64_t overlap_bytes = get_read_overlap_bytes();
|
||
|
||
// Получаем размер файла
|
||
int64_t file_size = get_file_size(data_path);
|
||
|
||
// Вычисляем диапазон байт для этого ранка
|
||
ByteRange range = calculate_byte_range(rank, size, file_size, shares, overlap_bytes);
|
||
|
||
// Открываем файл и читаем нужный диапазон
|
||
std::ifstream file(data_path, std::ios::binary);
|
||
if (!file.is_open()) {
|
||
throw std::runtime_error("Cannot open file: " + data_path);
|
||
}
|
||
|
||
// Переходим к началу диапазона
|
||
file.seekg(range.start);
|
||
|
||
// Читаем данные в буфер
|
||
int64_t bytes_to_read = range.end - range.start;
|
||
std::vector<char> buffer(bytes_to_read);
|
||
file.read(buffer.data(), bytes_to_read);
|
||
int64_t bytes_read = file.gcount();
|
||
|
||
file.close();
|
||
|
||
// Преобразуем в строку для удобства парсинга
|
||
std::string content(buffer.data(), bytes_read);
|
||
|
||
// Находим позицию начала первой полной строки
|
||
size_t parse_start = 0;
|
||
if (rank == 0) {
|
||
// Первый ранк: пропускаем заголовок (первую строку)
|
||
size_t header_end = content.find('\n');
|
||
if (header_end != std::string::npos) {
|
||
parse_start = header_end + 1;
|
||
}
|
||
} else {
|
||
// Остальные ранки: начинаем с первого \n (пропускаем неполную строку)
|
||
size_t first_newline = content.find('\n');
|
||
if (first_newline != std::string::npos) {
|
||
parse_start = first_newline + 1;
|
||
}
|
||
}
|
||
|
||
// Находим позицию конца последней полной строки
|
||
size_t parse_end = content.size();
|
||
if (rank != size - 1) {
|
||
// Не последний ранк: ищем последний \n
|
||
size_t last_newline = content.rfind('\n');
|
||
if (last_newline != std::string::npos && last_newline > parse_start) {
|
||
parse_end = last_newline;
|
||
}
|
||
}
|
||
|
||
// Парсим строки
|
||
size_t pos = parse_start;
|
||
while (pos < parse_end) {
|
||
size_t line_end = content.find('\n', pos);
|
||
if (line_end == std::string::npos || line_end > parse_end) {
|
||
line_end = parse_end;
|
||
}
|
||
|
||
std::string line = content.substr(pos, line_end - pos);
|
||
|
||
// Убираем \r если есть (Windows line endings)
|
||
if (!line.empty() && line.back() == '\r') {
|
||
line.pop_back();
|
||
}
|
||
|
||
Record record;
|
||
if (parse_csv_line(line, record)) {
|
||
data.push_back(record);
|
||
}
|
||
|
||
pos = line_end + 1;
|
||
}
|
||
|
||
return data;
|
||
}
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Д}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Д}
|
||
\label{5}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include "aggregation.hpp"
|
||
#include "utils.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <cstdint>
|
||
#include <limits>
|
||
#include <vector>
|
||
|
||
std::vector<PeriodStats> aggregate_periods(const std::vector<Record>& records) {
|
||
const int64_t interval = get_aggregation_interval();
|
||
|
||
std::vector<PeriodStats> result;
|
||
if (records.empty()) return result;
|
||
|
||
struct PeriodAccumulator {
|
||
double avg_sum = 0.0;
|
||
double open_min = std::numeric_limits<double>::max();
|
||
double open_max = std::numeric_limits<double>::lowest();
|
||
double close_min = std::numeric_limits<double>::max();
|
||
double close_max = std::numeric_limits<double>::lowest();
|
||
int64_t count = 0;
|
||
|
||
void add(const Record& r) {
|
||
const double avg = (r.low + r.high) / 2.0;
|
||
avg_sum += avg;
|
||
open_min = std::min(open_min, r.open);
|
||
open_max = std::max(open_max, r.open);
|
||
close_min = std::min(close_min, r.close);
|
||
close_max = std::max(close_max, r.close);
|
||
++count;
|
||
}
|
||
};
|
||
|
||
PeriodIndex current_period =
|
||
static_cast<PeriodIndex>(records[0].timestamp) / interval;
|
||
|
||
PeriodAccumulator acc;
|
||
acc.add(records[0]);
|
||
|
||
for (size_t i = 1; i < records.size(); ++i) {
|
||
const Record& r = records[i];
|
||
const PeriodIndex period =
|
||
static_cast<PeriodIndex>(r.timestamp) / interval;
|
||
|
||
if (period != current_period) {
|
||
PeriodStats stats;
|
||
stats.period = current_period;
|
||
stats.avg = acc.avg_sum / static_cast<double>(acc.count);
|
||
stats.open_min = acc.open_min;
|
||
stats.open_max = acc.open_max;
|
||
stats.close_min = acc.close_min;
|
||
stats.close_max = acc.close_max;
|
||
stats.count = acc.count;
|
||
result.push_back(stats);
|
||
|
||
current_period = period;
|
||
acc = PeriodAccumulator{};
|
||
}
|
||
|
||
acc.add(r);
|
||
}
|
||
|
||
// последний период
|
||
PeriodStats stats;
|
||
stats.period = current_period;
|
||
stats.avg = acc.avg_sum / static_cast<double>(acc.count);
|
||
stats.open_min = acc.open_min;
|
||
stats.open_max = acc.open_max;
|
||
stats.close_min = acc.close_min;
|
||
stats.close_max = acc.close_max;
|
||
stats.count = acc.count;
|
||
result.push_back(stats);
|
||
|
||
return result;
|
||
}
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Е}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Е}
|
||
\label{6}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include "gpu_loader.hpp"
|
||
#include <dlfcn.h>
|
||
#include <iostream>
|
||
#include <cstdint>
|
||
|
||
// Структура результата GPU (должна совпадать с gpu_plugin.cu)
|
||
struct GpuPeriodStats {
|
||
int64_t period;
|
||
double avg;
|
||
double open_min;
|
||
double open_max;
|
||
double close_min;
|
||
double close_max;
|
||
int64_t count;
|
||
};
|
||
|
||
// Типы функций из GPU плагина
|
||
using gpu_is_available_fn = int (*)();
|
||
|
||
using gpu_aggregate_periods_fn = int (*)(
|
||
const double* h_timestamps,
|
||
const double* h_open,
|
||
const double* h_high,
|
||
const double* h_low,
|
||
const double* h_close,
|
||
int num_ticks,
|
||
int64_t interval,
|
||
GpuPeriodStats** h_out_stats,
|
||
int* out_num_periods
|
||
);
|
||
|
||
using gpu_free_results_fn = void (*)(GpuPeriodStats*);
|
||
|
||
static void* get_gpu_lib_handle() {
|
||
static void* h = dlopen("./libgpu_compute.so", RTLD_NOW | RTLD_LOCAL);
|
||
return h;
|
||
}
|
||
|
||
bool gpu_is_available() {
|
||
void* h = get_gpu_lib_handle();
|
||
if (!h) return false;
|
||
|
||
auto fn = reinterpret_cast<gpu_is_available_fn>(dlsym(h, "gpu_is_available"));
|
||
if (!fn) return false;
|
||
|
||
return fn() != 0;
|
||
}
|
||
|
||
bool aggregate_periods_gpu(
|
||
const std::vector<Record>& records,
|
||
int64_t aggregation_interval,
|
||
std::vector<PeriodStats>& out_stats)
|
||
{
|
||
if (records.empty()) {
|
||
out_stats.clear();
|
||
return true;
|
||
}
|
||
|
||
void* h = get_gpu_lib_handle();
|
||
if (!h) {
|
||
std::cerr << "GPU: Failed to load libgpu_compute.so" << std::endl;
|
||
return false;
|
||
}
|
||
|
||
auto aggregate_fn = reinterpret_cast<gpu_aggregate_periods_fn>(
|
||
dlsym(h, "gpu_aggregate_periods"));
|
||
auto free_fn = reinterpret_cast<gpu_free_results_fn>(
|
||
dlsym(h, "gpu_free_results"));
|
||
|
||
if (!aggregate_fn || !free_fn) {
|
||
std::cerr << "GPU: Failed to load functions from plugin" << std::endl;
|
||
return false;
|
||
}
|
||
|
||
int num_ticks = static_cast<int>(records.size());
|
||
|
||
// Конвертируем AoS в SoA
|
||
std::vector<double> timestamps(num_ticks);
|
||
std::vector<double> open(num_ticks);
|
||
std::vector<double> high(num_ticks);
|
||
std::vector<double> low(num_ticks);
|
||
std::vector<double> close(num_ticks);
|
||
|
||
for (int i = 0; i < num_ticks; i++) {
|
||
timestamps[i] = records[i].timestamp;
|
||
open[i] = records[i].open;
|
||
high[i] = records[i].high;
|
||
low[i] = records[i].low;
|
||
close[i] = records[i].close;
|
||
}
|
||
|
||
// Вызываем GPU функцию
|
||
GpuPeriodStats* gpu_stats = nullptr;
|
||
int num_periods = 0;
|
||
|
||
int result = aggregate_fn(
|
||
timestamps.data(),
|
||
open.data(),
|
||
high.data(),
|
||
low.data(),
|
||
close.data(),
|
||
num_ticks,
|
||
aggregation_interval,
|
||
&gpu_stats,
|
||
&num_periods
|
||
);
|
||
|
||
if (result != 0) {
|
||
std::cerr << "GPU: Aggregation failed with code " << result << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// Конвертируем результат в PeriodStats
|
||
out_stats.clear();
|
||
out_stats.reserve(num_periods);
|
||
|
||
for (int i = 0; i < num_periods; i++) {
|
||
PeriodStats ps;
|
||
ps.period = gpu_stats[i].period;
|
||
ps.avg = gpu_stats[i].avg;
|
||
ps.open_min = gpu_stats[i].open_min;
|
||
ps.open_max = gpu_stats[i].open_max;
|
||
ps.close_min = gpu_stats[i].close_min;
|
||
ps.close_max = gpu_stats[i].close_max;
|
||
ps.count = gpu_stats[i].count;
|
||
out_stats.push_back(ps);
|
||
}
|
||
|
||
// Освобождаем память
|
||
free_fn(gpu_stats);
|
||
|
||
return true;
|
||
}
|
||
\end{lstlisting}
|
||
\newpage
|
||
|
||
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Ж}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Ж}
|
||
\label{7}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include <cuda_runtime.h>
|
||
#include <cub/cub.cuh>
|
||
#include <cstdint>
|
||
#include <cfloat>
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <ctime>
|
||
#include <string>
|
||
#include <sstream>
|
||
#include <iomanip>
|
||
|
||
// ============================================================================
|
||
// Структуры данных
|
||
// ============================================================================
|
||
|
||
// Результат агрегации одного периода
|
||
struct GpuPeriodStats {
|
||
int64_t period;
|
||
double avg;
|
||
double open_min;
|
||
double open_max;
|
||
double close_min;
|
||
double close_max;
|
||
int64_t count;
|
||
};
|
||
|
||
// ============================================================================
|
||
// Вспомогательные функции
|
||
// ============================================================================
|
||
|
||
static double get_time_ms() {
|
||
struct timespec ts;
|
||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0;
|
||
}
|
||
|
||
#define CUDA_CHECK(call) do { \
|
||
cudaError_t err = call; \
|
||
if (err != cudaSuccess) { \
|
||
printf("CUDA error at %s:%d: %s\n", __FILE__, __LINE__, cudaGetErrorString(err)); \
|
||
return -1; \
|
||
} \
|
||
} while(0)
|
||
|
||
// ============================================================================
|
||
// Kernel: вычисление period_id для каждого тика
|
||
// ============================================================================
|
||
|
||
__global__ void compute_period_ids_kernel(
|
||
const double* __restrict__ timestamps,
|
||
int64_t* __restrict__ period_ids,
|
||
int n,
|
||
int64_t interval)
|
||
{
|
||
int idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (idx < n) {
|
||
period_ids[idx] = static_cast<int64_t>(timestamps[idx]) / interval;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// Kernel: агрегация одного периода (один блок на период)
|
||
// ============================================================================
|
||
|
||
__global__ void aggregate_periods_kernel(
|
||
const double* __restrict__ open,
|
||
const double* __restrict__ high,
|
||
const double* __restrict__ low,
|
||
const double* __restrict__ close,
|
||
const int64_t* __restrict__ unique_periods,
|
||
const int* __restrict__ offsets,
|
||
const int* __restrict__ counts,
|
||
int num_periods,
|
||
GpuPeriodStats* __restrict__ out_stats)
|
||
{
|
||
int period_idx = blockIdx.x;
|
||
if (period_idx >= num_periods) return;
|
||
|
||
int offset = offsets[period_idx];
|
||
int count = counts[period_idx];
|
||
|
||
// Используем shared memory для редукции внутри блока
|
||
__shared__ double s_avg_sum;
|
||
__shared__ double s_open_min;
|
||
__shared__ double s_open_max;
|
||
__shared__ double s_close_min;
|
||
__shared__ double s_close_max;
|
||
|
||
// Инициализация shared memory первым потоком
|
||
if (threadIdx.x == 0) {
|
||
s_avg_sum = 0.0;
|
||
s_open_min = DBL_MAX;
|
||
s_open_max = -DBL_MAX;
|
||
s_close_min = DBL_MAX;
|
||
s_close_max = -DBL_MAX;
|
||
}
|
||
__syncthreads();
|
||
|
||
// Локальные аккумуляторы для каждого потока
|
||
double local_avg_sum = 0.0;
|
||
double local_open_min = DBL_MAX;
|
||
double local_open_max = -DBL_MAX;
|
||
double local_close_min = DBL_MAX;
|
||
double local_close_max = -DBL_MAX;
|
||
|
||
// Каждый поток обрабатывает свою часть тиков
|
||
for (int i = threadIdx.x; i < count; i += blockDim.x) {
|
||
int tick_idx = offset + i;
|
||
double avg = (low[tick_idx] + high[tick_idx]) / 2.0;
|
||
local_avg_sum += avg;
|
||
local_open_min = min(local_open_min, open[tick_idx]);
|
||
local_open_max = max(local_open_max, open[tick_idx]);
|
||
local_close_min = min(local_close_min, close[tick_idx]);
|
||
local_close_max = max(local_close_max, close[tick_idx]);
|
||
}
|
||
|
||
// Редукция с использованием атомарных операций
|
||
atomicAdd(&s_avg_sum, local_avg_sum);
|
||
atomicMin(reinterpret_cast<unsigned long long*>(&s_open_min),
|
||
__double_as_longlong(local_open_min));
|
||
atomicMax(reinterpret_cast<unsigned long long*>(&s_open_max),
|
||
__double_as_longlong(local_open_max));
|
||
atomicMin(reinterpret_cast<unsigned long long*>(&s_close_min),
|
||
__double_as_longlong(local_close_min));
|
||
atomicMax(reinterpret_cast<unsigned long long*>(&s_close_max),
|
||
__double_as_longlong(local_close_max));
|
||
|
||
__syncthreads();
|
||
|
||
// Первый поток записывает результат
|
||
if (threadIdx.x == 0) {
|
||
GpuPeriodStats stats;
|
||
stats.period = unique_periods[period_idx];
|
||
stats.avg = s_avg_sum / static_cast<double>(count);
|
||
stats.open_min = s_open_min;
|
||
stats.open_max = s_open_max;
|
||
stats.close_min = s_close_min;
|
||
stats.close_max = s_close_max;
|
||
stats.count = count;
|
||
out_stats[period_idx] = stats;
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// Простой kernel для агрегации (один поток на период)
|
||
// Используется когда периодов много и тиков в каждом мало
|
||
// ============================================================================
|
||
|
||
__global__ void aggregate_periods_simple_kernel(
|
||
const double* __restrict__ open,
|
||
const double* __restrict__ high,
|
||
const double* __restrict__ low,
|
||
const double* __restrict__ close,
|
||
const int64_t* __restrict__ unique_periods,
|
||
const int* __restrict__ offsets,
|
||
const int* __restrict__ counts,
|
||
int num_periods,
|
||
GpuPeriodStats* __restrict__ out_stats)
|
||
{
|
||
int period_idx = blockIdx.x * blockDim.x + threadIdx.x;
|
||
if (period_idx >= num_periods) return;
|
||
|
||
int offset = offsets[period_idx];
|
||
int count = counts[period_idx];
|
||
|
||
double avg_sum = 0.0;
|
||
double open_min = DBL_MAX;
|
||
double open_max = -DBL_MAX;
|
||
double close_min = DBL_MAX;
|
||
double close_max = -DBL_MAX;
|
||
|
||
for (int i = 0; i < count; i++) {
|
||
int tick_idx = offset + i;
|
||
double avg = (low[tick_idx] + high[tick_idx]) / 2.0;
|
||
avg_sum += avg;
|
||
open_min = min(open_min, open[tick_idx]);
|
||
open_max = max(open_max, open[tick_idx]);
|
||
close_min = min(close_min, close[tick_idx]);
|
||
close_max = max(close_max, close[tick_idx]);
|
||
}
|
||
|
||
GpuPeriodStats stats;
|
||
stats.period = unique_periods[period_idx];
|
||
stats.avg = avg_sum / static_cast<double>(count);
|
||
stats.open_min = open_min;
|
||
stats.open_max = open_max;
|
||
stats.close_min = close_min;
|
||
stats.close_max = close_max;
|
||
stats.count = count;
|
||
out_stats[period_idx] = stats;
|
||
}
|
||
|
||
|
||
// ============================================================================
|
||
// Проверка доступности GPU
|
||
// ============================================================================
|
||
|
||
extern "C" int gpu_is_available() {
|
||
int n = 0;
|
||
cudaError_t err = cudaGetDeviceCount(&n);
|
||
if (err != cudaSuccess) return 0;
|
||
return (n > 0) ? 1 : 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// Главная функция агрегации на GPU
|
||
// ============================================================================
|
||
|
||
extern "C" int gpu_aggregate_periods(
|
||
const double* h_timestamps,
|
||
const double* h_open,
|
||
const double* h_high,
|
||
const double* h_low,
|
||
const double* h_close,
|
||
int num_ticks,
|
||
int64_t interval,
|
||
GpuPeriodStats** h_out_stats,
|
||
int* out_num_periods)
|
||
{
|
||
if (num_ticks == 0) {
|
||
*h_out_stats = nullptr;
|
||
*out_num_periods = 0;
|
||
return 0;
|
||
}
|
||
|
||
std::ostringstream output;
|
||
double total_start = get_time_ms();
|
||
|
||
// ========================================================================
|
||
// Шаг 1: Выделение памяти и копирование данных на GPU
|
||
// ========================================================================
|
||
double step1_start = get_time_ms();
|
||
|
||
double* d_timestamps = nullptr;
|
||
double* d_open = nullptr;
|
||
double* d_high = nullptr;
|
||
double* d_low = nullptr;
|
||
double* d_close = nullptr;
|
||
int64_t* d_period_ids = nullptr;
|
||
|
||
size_t ticks_bytes = num_ticks * sizeof(double);
|
||
|
||
CUDA_CHECK(cudaMalloc(&d_timestamps, ticks_bytes));
|
||
CUDA_CHECK(cudaMalloc(&d_open, ticks_bytes));
|
||
CUDA_CHECK(cudaMalloc(&d_high, ticks_bytes));
|
||
CUDA_CHECK(cudaMalloc(&d_low, ticks_bytes));
|
||
CUDA_CHECK(cudaMalloc(&d_close, ticks_bytes));
|
||
CUDA_CHECK(cudaMalloc(&d_period_ids, num_ticks * sizeof(int64_t)));
|
||
|
||
CUDA_CHECK(cudaMemcpy(d_timestamps, h_timestamps, ticks_bytes, cudaMemcpyHostToDevice));
|
||
CUDA_CHECK(cudaMemcpy(d_open, h_open, ticks_bytes, cudaMemcpyHostToDevice));
|
||
CUDA_CHECK(cudaMemcpy(d_high, h_high, ticks_bytes, cudaMemcpyHostToDevice));
|
||
CUDA_CHECK(cudaMemcpy(d_low, h_low, ticks_bytes, cudaMemcpyHostToDevice));
|
||
CUDA_CHECK(cudaMemcpy(d_close, h_close, ticks_bytes, cudaMemcpyHostToDevice));
|
||
|
||
double step1_ms = get_time_ms() - step1_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 2: Вычисление period_id для каждого тика
|
||
// ========================================================================
|
||
double step2_start = get_time_ms();
|
||
|
||
const int BLOCK_SIZE = 256;
|
||
int num_blocks = (num_ticks + BLOCK_SIZE - 1) / BLOCK_SIZE;
|
||
|
||
compute_period_ids_kernel<<<num_blocks, BLOCK_SIZE>>>(
|
||
d_timestamps, d_period_ids, num_ticks, interval);
|
||
CUDA_CHECK(cudaGetLastError());
|
||
CUDA_CHECK(cudaDeviceSynchronize());
|
||
|
||
double step2_ms = get_time_ms() - step2_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 3: RLE (Run-Length Encode) для нахождения уникальных периодов
|
||
// ========================================================================
|
||
double step3_start = get_time_ms();
|
||
|
||
int64_t* d_unique_periods = nullptr;
|
||
int* d_counts = nullptr;
|
||
int* d_num_runs = nullptr;
|
||
|
||
CUDA_CHECK(cudaMalloc(&d_unique_periods, num_ticks * sizeof(int64_t)));
|
||
CUDA_CHECK(cudaMalloc(&d_counts, num_ticks * sizeof(int)));
|
||
CUDA_CHECK(cudaMalloc(&d_num_runs, sizeof(int)));
|
||
|
||
// Определяем размер временного буфера для CUB
|
||
void* d_temp_storage = nullptr;
|
||
size_t temp_storage_bytes = 0;
|
||
|
||
cub::DeviceRunLengthEncode::Encode(
|
||
d_temp_storage, temp_storage_bytes,
|
||
d_period_ids, d_unique_periods, d_counts, d_num_runs,
|
||
num_ticks);
|
||
|
||
CUDA_CHECK(cudaMalloc(&d_temp_storage, temp_storage_bytes));
|
||
|
||
cub::DeviceRunLengthEncode::Encode(
|
||
d_temp_storage, temp_storage_bytes,
|
||
d_period_ids, d_unique_periods, d_counts, d_num_runs,
|
||
num_ticks);
|
||
CUDA_CHECK(cudaGetLastError());
|
||
|
||
// Копируем количество уникальных периодов
|
||
int num_periods = 0;
|
||
CUDA_CHECK(cudaMemcpy(&num_periods, d_num_runs, sizeof(int), cudaMemcpyDeviceToHost));
|
||
|
||
cudaFree(d_temp_storage);
|
||
d_temp_storage = nullptr;
|
||
|
||
double step3_ms = get_time_ms() - step3_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 4: Exclusive Scan для вычисления offsets
|
||
// ========================================================================
|
||
double step4_start = get_time_ms();
|
||
|
||
int* d_offsets = nullptr;
|
||
CUDA_CHECK(cudaMalloc(&d_offsets, num_periods * sizeof(int)));
|
||
|
||
temp_storage_bytes = 0;
|
||
cub::DeviceScan::ExclusiveSum(
|
||
d_temp_storage, temp_storage_bytes,
|
||
d_counts, d_offsets, num_periods);
|
||
|
||
CUDA_CHECK(cudaMalloc(&d_temp_storage, temp_storage_bytes));
|
||
|
||
cub::DeviceScan::ExclusiveSum(
|
||
d_temp_storage, temp_storage_bytes,
|
||
d_counts, d_offsets, num_periods);
|
||
CUDA_CHECK(cudaGetLastError());
|
||
|
||
cudaFree(d_temp_storage);
|
||
|
||
double step4_ms = get_time_ms() - step4_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 5: Агрегация периодов
|
||
// ========================================================================
|
||
double step5_start = get_time_ms();
|
||
|
||
GpuPeriodStats* d_out_stats = nullptr;
|
||
CUDA_CHECK(cudaMalloc(&d_out_stats, num_periods * sizeof(GpuPeriodStats)));
|
||
|
||
// Выбор ядра через переменную окружения USE_BLOCK_KERNEL
|
||
const char* env_block_kernel = std::getenv("USE_BLOCK_KERNEL");
|
||
if (env_block_kernel == nullptr) {
|
||
printf("Error: Environment variable USE_BLOCK_KERNEL is not set\n");
|
||
return -1;
|
||
}
|
||
bool use_block_kernel = std::atoi(env_block_kernel) != 0;
|
||
|
||
if (use_block_kernel) {
|
||
// Блочное ядро: один блок на период, потоки параллельно обрабатывают тики
|
||
// Лучше для больших интервалов с множеством тиков в каждом периоде
|
||
aggregate_periods_kernel<<<num_periods, BLOCK_SIZE>>>(
|
||
d_open, d_high, d_low, d_close,
|
||
d_unique_periods, d_offsets, d_counts,
|
||
num_periods, d_out_stats);
|
||
} else {
|
||
// Простое ядро: один поток на период
|
||
// Лучше для множества периодов с малым количеством тиков в каждом
|
||
int agg_blocks = (num_periods + BLOCK_SIZE - 1) / BLOCK_SIZE;
|
||
aggregate_periods_simple_kernel<<<agg_blocks, BLOCK_SIZE>>>(
|
||
d_open, d_high, d_low, d_close,
|
||
d_unique_periods, d_offsets, d_counts,
|
||
num_periods, d_out_stats);
|
||
}
|
||
|
||
|
||
CUDA_CHECK(cudaGetLastError());
|
||
CUDA_CHECK(cudaDeviceSynchronize());
|
||
|
||
double step5_ms = get_time_ms() - step5_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 6: Копирование результатов на CPU
|
||
// ========================================================================
|
||
double step6_start = get_time_ms();
|
||
|
||
GpuPeriodStats* h_stats = new GpuPeriodStats[num_periods];
|
||
CUDA_CHECK(cudaMemcpy(h_stats, d_out_stats, num_periods * sizeof(GpuPeriodStats),
|
||
cudaMemcpyDeviceToHost));
|
||
|
||
double step6_ms = get_time_ms() - step6_start;
|
||
|
||
// ========================================================================
|
||
// Шаг 7: Освобождение GPU памяти
|
||
// ========================================================================
|
||
double step7_start = get_time_ms();
|
||
|
||
cudaFree(d_timestamps);
|
||
cudaFree(d_open);
|
||
cudaFree(d_high);
|
||
cudaFree(d_low);
|
||
cudaFree(d_close);
|
||
cudaFree(d_period_ids);
|
||
cudaFree(d_unique_periods);
|
||
cudaFree(d_counts);
|
||
cudaFree(d_offsets);
|
||
cudaFree(d_num_runs);
|
||
cudaFree(d_out_stats);
|
||
|
||
double step7_ms = get_time_ms() - step7_start;
|
||
|
||
// ========================================================================
|
||
// Итого
|
||
// ========================================================================
|
||
double total_ms = get_time_ms() - total_start;
|
||
|
||
// Формируем весь вывод одной строкой
|
||
output << " GPU aggregation (" << num_ticks << " ticks, interval=" << interval << " sec, kernel=" << (use_block_kernel ? "block" : "simple") << "):\n";
|
||
output << " 1. Malloc + H->D copy: " << std::fixed << std::setprecision(3) << std::setw(7) << step1_ms << " ms\n";
|
||
output << " 2. Compute period_ids: " << std::setw(7) << step2_ms << " ms\n";
|
||
output << " 3. RLE (CUB): " << std::setw(7) << step3_ms << " ms (" << num_periods << " periods)\n";
|
||
output << " 4. Exclusive scan: " << std::setw(7) << step4_ms << " ms\n";
|
||
output << " 5. Aggregation kernel: " << std::setw(7) << step5_ms << " ms (" << (use_block_kernel ? "block" : "simple") << ")\n";
|
||
output << " 6. D->H copy: " << std::setw(7) << step6_ms << " ms\n";
|
||
output << " 7. Free GPU memory: " << std::setw(7) << step7_ms << " ms\n";
|
||
output << " GPU TOTAL: " << std::setw(7) << total_ms << " ms\n";
|
||
|
||
// Выводим всё одним принтом
|
||
printf("%s", output.str().c_str());
|
||
fflush(stdout);
|
||
|
||
*h_out_stats = h_stats;
|
||
*out_num_periods = num_periods;
|
||
|
||
return 0;
|
||
}
|
||
|
||
// ============================================================================
|
||
// Освобождение памяти результатов
|
||
// ============================================================================
|
||
|
||
extern "C" void gpu_free_results(GpuPeriodStats* stats) {
|
||
delete[] stats;
|
||
}
|
||
\end{lstlisting}
|
||
\newpage
|
||
|
||
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ З}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ З}
|
||
\label{8}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include "intervals.hpp"
|
||
#include "utils.hpp"
|
||
#include <mpi.h>
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <fstream>
|
||
#include <iomanip>
|
||
#include <sstream>
|
||
#include <ctime>
|
||
#include <limits>
|
||
|
||
// Вспомогательная структура для накопления min/max в интервале
|
||
struct IntervalAccumulator {
|
||
PeriodIndex start_period;
|
||
double start_avg;
|
||
double open_min;
|
||
double open_max;
|
||
double close_min;
|
||
double close_max;
|
||
|
||
void init(const PeriodStats& p) {
|
||
start_period = p.period;
|
||
start_avg = p.avg;
|
||
open_min = p.open_min;
|
||
open_max = p.open_max;
|
||
close_min = p.close_min;
|
||
close_max = p.close_max;
|
||
}
|
||
|
||
void update(const PeriodStats& p) {
|
||
open_min = std::min(open_min, p.open_min);
|
||
open_max = std::max(open_max, p.open_max);
|
||
close_min = std::min(close_min, p.close_min);
|
||
close_max = std::max(close_max, p.close_max);
|
||
}
|
||
|
||
Interval finalize(const PeriodStats& end_period, double change) const {
|
||
Interval iv;
|
||
iv.start_period = start_period;
|
||
iv.end_period = end_period.period;
|
||
iv.start_avg = start_avg;
|
||
iv.end_avg = end_period.avg;
|
||
iv.change = change;
|
||
iv.open_min = std::min(open_min, end_period.open_min);
|
||
iv.open_max = std::max(open_max, end_period.open_max);
|
||
iv.close_min = std::min(close_min, end_period.close_min);
|
||
iv.close_max = std::max(close_max, end_period.close_max);
|
||
return iv;
|
||
}
|
||
};
|
||
|
||
// Упакованная структура PeriodStats для MPI передачи (8 doubles)
|
||
struct PackedPeriodStats {
|
||
double period; // PeriodIndex as double
|
||
double avg;
|
||
double open_min;
|
||
double open_max;
|
||
double close_min;
|
||
double close_max;
|
||
double count; // int64_t as double
|
||
double valid; // флаг валидности (1.0 = valid, 0.0 = invalid)
|
||
|
||
void pack(const PeriodStats& ps) {
|
||
period = static_cast<double>(ps.period);
|
||
avg = ps.avg;
|
||
open_min = ps.open_min;
|
||
open_max = ps.open_max;
|
||
close_min = ps.close_min;
|
||
close_max = ps.close_max;
|
||
count = static_cast<double>(ps.count);
|
||
valid = 1.0;
|
||
}
|
||
|
||
PeriodStats unpack() const {
|
||
PeriodStats ps;
|
||
ps.period = static_cast<PeriodIndex>(period);
|
||
ps.avg = avg;
|
||
ps.open_min = open_min;
|
||
ps.open_max = open_max;
|
||
ps.close_min = close_min;
|
||
ps.close_max = close_max;
|
||
ps.count = static_cast<int64_t>(count);
|
||
return ps;
|
||
}
|
||
|
||
bool is_valid() const { return valid > 0.5; }
|
||
void set_invalid() { valid = 0.0; }
|
||
};
|
||
|
||
IntervalResult find_intervals_parallel(
|
||
const std::vector<PeriodStats>& periods,
|
||
int rank, int size,
|
||
double threshold)
|
||
{
|
||
IntervalResult result;
|
||
result.compute_time = 0.0;
|
||
result.wait_time = 0.0;
|
||
|
||
if (periods.empty()) {
|
||
if (rank < size - 1) {
|
||
PackedPeriodStats invalid;
|
||
invalid.set_invalid();
|
||
MPI_Send(&invalid, 8, MPI_DOUBLE, rank + 1, 0, MPI_COMM_WORLD);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
double compute_start = MPI_Wtime();
|
||
|
||
size_t process_until = (rank == size - 1) ? periods.size() : periods.size() - 1;
|
||
|
||
IntervalAccumulator acc;
|
||
size_t start_idx = 0;
|
||
bool have_pending_interval = false;
|
||
|
||
if (rank > 0) {
|
||
double wait_start = MPI_Wtime();
|
||
|
||
PackedPeriodStats received;
|
||
MPI_Recv(&received, 8, MPI_DOUBLE, rank - 1, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||
|
||
result.wait_time = MPI_Wtime() - wait_start;
|
||
compute_start = MPI_Wtime();
|
||
|
||
if (received.is_valid()) {
|
||
PeriodStats prev_period = received.unpack();
|
||
|
||
for (start_idx = 0; start_idx < periods.size(); start_idx++) {
|
||
if (periods[start_idx].period > prev_period.period) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (start_idx < process_until) {
|
||
acc.init(prev_period);
|
||
have_pending_interval = true;
|
||
|
||
for (size_t i = start_idx; i < process_until; i++) {
|
||
acc.update(periods[i]);
|
||
|
||
double change = std::abs(periods[i].avg - acc.start_avg) / acc.start_avg;
|
||
|
||
if (change >= threshold) {
|
||
result.intervals.push_back(acc.finalize(periods[i], change));
|
||
have_pending_interval = false;
|
||
|
||
start_idx = i + 1;
|
||
if (start_idx < process_until) {
|
||
acc.init(periods[start_idx]);
|
||
have_pending_interval = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (process_until > 0) {
|
||
acc.init(periods[0]);
|
||
have_pending_interval = true;
|
||
start_idx = 0;
|
||
}
|
||
}
|
||
} else {
|
||
if (process_until > 0) {
|
||
acc.init(periods[0]);
|
||
have_pending_interval = true;
|
||
start_idx = 0;
|
||
}
|
||
}
|
||
|
||
if (rank == 0 && have_pending_interval) {
|
||
for (size_t i = 1; i < process_until; i++) {
|
||
acc.update(periods[i]);
|
||
|
||
double change = std::abs(periods[i].avg - acc.start_avg) / acc.start_avg;
|
||
|
||
if (change >= threshold) {
|
||
result.intervals.push_back(acc.finalize(periods[i], change));
|
||
have_pending_interval = false;
|
||
|
||
start_idx = i + 1;
|
||
if (start_idx < process_until) {
|
||
acc.init(periods[start_idx]);
|
||
have_pending_interval = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rank == size - 1 && have_pending_interval && !periods.empty()) {
|
||
const auto& last_period = periods.back();
|
||
double change = std::abs(last_period.avg - acc.start_avg) / acc.start_avg;
|
||
result.intervals.push_back(acc.finalize(last_period, change));
|
||
}
|
||
|
||
result.compute_time = MPI_Wtime() - compute_start;
|
||
|
||
if (rank < size - 1) {
|
||
PackedPeriodStats to_send;
|
||
|
||
if (have_pending_interval) {
|
||
PeriodStats start_period;
|
||
start_period.period = acc.start_period;
|
||
start_period.avg = acc.start_avg;
|
||
start_period.open_min = acc.open_min;
|
||
start_period.open_max = acc.open_max;
|
||
start_period.close_min = acc.close_min;
|
||
start_period.close_max = acc.close_max;
|
||
start_period.count = 0;
|
||
to_send.pack(start_period);
|
||
} else if (periods.size() >= 2) {
|
||
to_send.pack(periods[periods.size() - 2]);
|
||
} else {
|
||
to_send.set_invalid();
|
||
}
|
||
|
||
MPI_Send(&to_send, 8, MPI_DOUBLE, rank + 1, 0, MPI_COMM_WORLD);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
double collect_intervals(
|
||
std::vector<Interval>& local_intervals,
|
||
int rank, int size)
|
||
{
|
||
double wait_time = 0.0;
|
||
|
||
if (rank == 0) {
|
||
for (int r = 1; r < size; r++) {
|
||
double wait_start = MPI_Wtime();
|
||
|
||
int count;
|
||
MPI_Recv(&count, 1, MPI_INT, r, 1, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||
|
||
if (count > 0) {
|
||
std::vector<double> buffer(count * 9);
|
||
MPI_Recv(buffer.data(), count * 9, MPI_DOUBLE, r, 2, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
|
||
|
||
for (int i = 0; i < count; i++) {
|
||
Interval iv;
|
||
iv.start_period = static_cast<PeriodIndex>(buffer[i * 9 + 0]);
|
||
iv.end_period = static_cast<PeriodIndex>(buffer[i * 9 + 1]);
|
||
iv.open_min = buffer[i * 9 + 2];
|
||
iv.open_max = buffer[i * 9 + 3];
|
||
iv.close_min = buffer[i * 9 + 4];
|
||
iv.close_max = buffer[i * 9 + 5];
|
||
iv.start_avg = buffer[i * 9 + 6];
|
||
iv.end_avg = buffer[i * 9 + 7];
|
||
iv.change = buffer[i * 9 + 8];
|
||
local_intervals.push_back(iv);
|
||
}
|
||
}
|
||
|
||
wait_time += MPI_Wtime() - wait_start;
|
||
}
|
||
|
||
std::sort(local_intervals.begin(), local_intervals.end(),
|
||
[](const Interval& a, const Interval& b) {
|
||
return a.start_period < b.start_period;
|
||
});
|
||
} else {
|
||
int count = static_cast<int>(local_intervals.size());
|
||
MPI_Send(&count, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
|
||
|
||
if (count > 0) {
|
||
std::vector<double> buffer(count * 9);
|
||
for (int i = 0; i < count; i++) {
|
||
const auto& iv = local_intervals[i];
|
||
buffer[i * 9 + 0] = static_cast<double>(iv.start_period);
|
||
buffer[i * 9 + 1] = static_cast<double>(iv.end_period);
|
||
buffer[i * 9 + 2] = iv.open_min;
|
||
buffer[i * 9 + 3] = iv.open_max;
|
||
buffer[i * 9 + 4] = iv.close_min;
|
||
buffer[i * 9 + 5] = iv.close_max;
|
||
buffer[i * 9 + 6] = iv.start_avg;
|
||
buffer[i * 9 + 7] = iv.end_avg;
|
||
buffer[i * 9 + 8] = iv.change;
|
||
}
|
||
MPI_Send(buffer.data(), count * 9, MPI_DOUBLE, 0, 2, MPI_COMM_WORLD);
|
||
}
|
||
}
|
||
|
||
return wait_time;
|
||
}
|
||
|
||
std::string period_index_to_datetime(PeriodIndex period) {
|
||
int64_t interval = get_aggregation_interval();
|
||
time_t ts = static_cast<time_t>(period) * interval;
|
||
struct tm* tm_info = gmtime(&ts);
|
||
|
||
std::ostringstream oss;
|
||
oss << std::setfill('0')
|
||
<< (tm_info->tm_year + 1900) << "-"
|
||
<< std::setw(2) << (tm_info->tm_mon + 1) << "-"
|
||
<< std::setw(2) << tm_info->tm_mday << " "
|
||
<< std::setw(2) << tm_info->tm_hour << ":"
|
||
<< std::setw(2) << tm_info->tm_min << ":"
|
||
<< std::setw(2) << tm_info->tm_sec;
|
||
|
||
return oss.str();
|
||
}
|
||
|
||
void write_intervals(const std::string& filename, const std::vector<Interval>& intervals) {
|
||
std::ofstream out(filename);
|
||
|
||
out << std::fixed << std::setprecision(2);
|
||
out << "start_datetime,end_datetime,open_min,open_max,close_min,close_max,start_avg,end_avg,change\n";
|
||
|
||
for (const auto& iv : intervals) {
|
||
out << period_index_to_datetime(iv.start_period) << ","
|
||
<< period_index_to_datetime(iv.end_period) << ","
|
||
<< iv.open_min << ","
|
||
<< iv.open_max << ","
|
||
<< iv.close_min << ","
|
||
<< iv.close_max << ","
|
||
<< iv.start_avg << ","
|
||
<< iv.end_avg << ","
|
||
<< std::setprecision(6) << iv.change << "\n";
|
||
}
|
||
}
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ И}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ И}
|
||
\label{9}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#include "utils.hpp"
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#include <stdexcept>
|
||
#include <numeric>
|
||
|
||
int get_num_cpu_threads() {
|
||
const char* env_threads = std::getenv("NUM_CPU_THREADS");
|
||
int num_cpu_threads = 1;
|
||
if (env_threads) {
|
||
num_cpu_threads = std::atoi(env_threads);
|
||
if (num_cpu_threads < 1) num_cpu_threads = 1;
|
||
}
|
||
return num_cpu_threads;
|
||
}
|
||
|
||
std::string get_env(const char* name) {
|
||
const char* env = std::getenv(name);
|
||
if (!env) {
|
||
throw std::runtime_error(std::string("Environment variable not set: ") + name);
|
||
}
|
||
return std::string(env);
|
||
}
|
||
|
||
std::string get_data_path() {
|
||
return get_env("DATA_PATH");
|
||
}
|
||
|
||
std::vector<int> get_data_read_shares() {
|
||
std::vector<int> shares;
|
||
std::stringstream ss(get_env("DATA_READ_SHARES"));
|
||
std::string item;
|
||
while (std::getline(ss, item, ',')) {
|
||
shares.push_back(std::stoi(item));
|
||
}
|
||
return shares;
|
||
}
|
||
|
||
int64_t get_read_overlap_bytes() {
|
||
return std::stoll(get_env("READ_OVERLAP_BYTES"));
|
||
}
|
||
|
||
int64_t get_aggregation_interval() {
|
||
return std::stoll(get_env("AGGREGATION_INTERVAL"));
|
||
}
|
||
|
||
bool get_use_cuda() {
|
||
return std::stoi(get_env("USE_CUDA")) != 0;
|
||
}
|
||
|
||
int64_t get_file_size(const std::string& path) {
|
||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||
if (!file.is_open()) {
|
||
throw std::runtime_error("Cannot open file: " + path);
|
||
}
|
||
return static_cast<int64_t>(file.tellg());
|
||
}
|
||
|
||
ByteRange calculate_byte_range(int rank, int size, int64_t file_size,
|
||
const std::vector<int>& shares, int64_t overlap_bytes) {
|
||
std::vector<int> effective_shares;
|
||
if (shares.size() == static_cast<size_t>(size)) {
|
||
effective_shares = shares;
|
||
} else {
|
||
effective_shares.assign(size, 1);
|
||
}
|
||
|
||
int total_shares = std::accumulate(effective_shares.begin(), effective_shares.end(), 0);
|
||
int64_t bytes_per_share = file_size / total_shares;
|
||
|
||
int64_t base_start = 0;
|
||
for (int i = 0; i < rank; i++) {
|
||
base_start += bytes_per_share * effective_shares[i];
|
||
}
|
||
|
||
int64_t base_end = base_start + bytes_per_share * effective_shares[rank];
|
||
|
||
ByteRange range;
|
||
|
||
if (rank == 0) {
|
||
range.start = 0;
|
||
range.end = std::min(base_end + overlap_bytes, file_size);
|
||
} else if (rank == size - 1) {
|
||
range.start = std::max(base_start - overlap_bytes, static_cast<int64_t>(0));
|
||
range.end = file_size;
|
||
} else {
|
||
range.start = std::max(base_start - overlap_bytes, static_cast<int64_t>(0));
|
||
range.end = std::min(base_end + overlap_bytes, file_size);
|
||
}
|
||
|
||
return range;
|
||
}
|
||
|
||
void trim_edge_periods(std::vector<PeriodStats>& periods, int rank, int size) {
|
||
if (periods.empty()) return;
|
||
|
||
if (rank == 0) {
|
||
periods.pop_back();
|
||
} else if (rank == size - 1) {
|
||
periods.erase(periods.begin());
|
||
} else {
|
||
periods.pop_back();
|
||
periods.erase(periods.begin());
|
||
}
|
||
}
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ К}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ К}
|
||
\label{10}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#pragma once
|
||
#include <cstdint>
|
||
|
||
using PeriodIndex = int64_t;
|
||
|
||
// Агрегированные данные за один период
|
||
struct PeriodStats {
|
||
PeriodIndex period; // индекс периода (timestamp / AGGREGATION_INTERVAL)
|
||
double avg; // среднее значение (Low + High) / 2 по всем записям
|
||
double open_min; // минимальный Open за период
|
||
double open_max; // максимальный Open за период
|
||
double close_min; // минимальный Close за период
|
||
double close_max; // максимальный Close за период
|
||
int64_t count; // количество записей, по которым агрегировали
|
||
};
|
||
\end{lstlisting}
|
||
|
||
|
||
\newpage
|
||
\begin{center}
|
||
\section*{ПРИЛОЖЕНИЕ Л}
|
||
\end{center}
|
||
\addcontentsline{toc}{section}{ПРИЛОЖЕНИЕ Л}
|
||
\label{11}
|
||
\begin{lstlisting}[language=sh, basicstyle=\ttfamily\scriptsize, escapeinside={{*@}{@*}}]
|
||
#pragma once
|
||
#include <cstdint>
|
||
|
||
struct Record {
|
||
double timestamp;
|
||
double open;
|
||
double high;
|
||
double low;
|
||
double close;
|
||
double volume;
|
||
};
|
||
\end{lstlisting}
|
||
|
||
\end{document}
|