Введение в использование MPI
Основы технологии MPI на примерах
Параллельное программирование — очень актуальное направление, т.к. большинство современных вычислительных устройств (включая телефоны) являются многоядерными или многопроцессорными. В предыдущей записи я публиковал учебник по OpenMP, однако OpenMP позволяет программировать только системы с общей памятью — в большей части, многоядерные компьютеры. Основной проблемой таких систем является плохая масштабируемость — не так легко и дешево увеличить число ядер в компьютере.
Другой класс параллельных ЭВМ — системы с распределенной памятью, к которым, в частности, относятся кластеры. Они представляют собой набор соединенных сетью компьютеров. Такая система гораздо лучше мастабируется — не составит особого труда купить и подключить в сеть дополнительную сотню компьютеров. Число вычислительных узлов в кластерах измеряется тысячами.
Кластерная архитектура
Взаимодействуют узлы кластера с помощью передачи сообщений по сети, поэтому стандартом программирования таких систем является MPI (Message Passing Interface). Библиотека MPI предоставляет программисту огромный выбор способов передать сообщение между узлами, в этой статье я постараюсь описать основные способы на простых примерах.
Содержание:
Ключевые особенности MPI
Допустим, есть у нас кластер. Чтобы программа начала на нем выполняться, ее необходимо скопировать на каждый узел, запустить и установить связь между процессами. Эту работу берет на себя утилита mpirun (под Linux) или mpiexec (под Windows), так например, чтобы запустить 5 процессов достаточно написать:
mpirun -np 5 path/your_mpi_program
Однако программа должна быть написана определенным образом. Вообще, технология MPI позволяет как использовать модель SPMD (Single Process, Multiple Data), так и MPMD [1], в этой статье я рассматривают только первый вариант. Далее по тексту узел и процесс будут означать одно и тоже, хотя на одном узле может быть создано несколько процессов (именно так я делаю при запуске примеров статьи, т.к. отлаживаю их на персональном компьютере). Это вводная статья, поэтому тут не пойдет речь о коммуникаторах, в которые могут группироваться процессы.
Суть SPMD заключается в том, что для решения задачи запускается множество одинаковых процессов. На приведенном выше рисунке видно, что пользователь (который вводит данные и хочет получить результат) взаимодействует только с одним узлом кластера. Такой узел называется root и логика его работы должна отличаться, ведь он должен не только взаимодействовать с пользователем, но и, получив исходные данные, выполнить их рассылку остальным процессам. Каждый процесс имеет свой номер (в MPI принят термин «ранг«) в рамках каждого коммуникатора, при этом у root ранг обычно равен нулю.
Процессы обмениваются сообщениями, каждое из которых помимо номеров процесса отправителя и получателя имеет тег, а также тип и количество передаваемых элементов. Тег представляет собой целое число, с помощью которого программа может отличать одни сообщения от других. В силу того, что сегодня мы рассмотрим очень простые примеры, тег нам не особо пригодится (можно было бы везде использовать, скажем, «ноль» вместо тега). Все поступающие процессу сообщения помещаются в очередь, из которой могут быть извлечены в соответствии с запрашиваемыми параметрами (тегом, отправителем и т.п.).
В связи с тем, что MPI-программа обладает множеством особенностей, компилироваться она должна специальным компилятором. Под Linux для этого используется mpic++, а под Windows можно применять расширение для Microsoft Visual Studio. Для сборки примеров статьи под Linux я использовал примерно следующую команду:
mpic++ main.cpp -o main
Разобравшись с особенностями технологии, перейдем к примерам. В этой статье мы будем пытаться посчитать сумму элементов массива — несмотря на простоту, эта задача позволяет продемонстрировать самые различные способы передачи данных между узлами.
Операции точка-точка MPI
Блокирующие операции — MPI_Send, MPI_Recv, MPI_Probe
Операции точка-точка позволяют передавать данные между парой узлов. Самые простые функции этого вида — MPI_Send и MPI_Recv, выполняющие передачу и прием сообщения, соответственно:
Функция MPI_Send выполняет передачу count элементов типа datatype, начиная с адреса определенного buf, процессу с номером dest в коммуникаторе comm. При этом сообщению присваивается некоторый тег.
Функция MPI_Recv выполняет прием, при этом она ожидает появления во входном буфере сообщения с заданными параметрами (выполнение программы не продолжится до того, как такое сообщение поступит). Функция также возвращает статус, который может содержать код ошибки.
Рассмотрим первый пример:
Все обращения к функциям MPI должны размещаться между MPI_Init и MPI_Finalize. В качестве коммуникатора в этом примере используется MPI_COMM_WORLD, который включает в себя все процессы, запущенные для текущей задачи с помощью mpirun. Каждый процесс получает свой ранг с помощью вызова MPI_Comm_rank, количество процессов в коммуникаторе возвращает функция MPI_Comm_size.
В теле программы явно выделяется два блока кода — один выполняется главным процессом, а другой — остальными. Главный процесс занимается вводом/выводом данных, и их распределением между остальными узлами. Все остальные процессы ожидают от главного часть массива, вычисляют сумму элементов и передают результат назад. Чтобы принять массив, дочерние процессы должны сначала выделить память, но для этого необходимо знать сколько именно элементов им будет передано — сделать это можно с помощью функции MPI_Probe, которая работает также как MPI_Send (блокирует процесс до поступления в очередь сообщения с заданными параметрами), но не удаляет сообщение из очереди, а лишь возвращает его статус. Структура типа MPI_Status содержит тип элементов и их количество.
Операции MPI_Send, MPI_Probe и MPI_Recv являются синхронными (блокирующими), т.к. останавливают выполнение основного потока процесса до тех пор, пока не выполнится какое-либо действие (данные не будут записаны в сокет или не поступит сообщение с требуемыми параметрами).
Асинхронные операции — MPI_Isend
Приведенный выше пример работает прекрасно, но бутылочным горлышком является нулевой процесс, т.к. перед тем как приступить к обработке своей части массива root должен передать данные всем остальным процессами. Немного улучшить картину можно с помощью неблокирующих (асинхронных) операций — одной из них является MPI_Isend.
Функции неблокирующего обмена имеют такие же аргументы как и их блокирующие аналоги, но в качестве дополнительного параметра принимают параметр типа MPI_Request. Чтобы нулевой процесс в приведенном выше примере работал асинхронно достаточно изменить лишь этот фрагмент кода:
Теперь функция передачи возвращает управление немедленно, сама же передача происходит параллельно с выполнением других команд процесса. Узнать о ходе выполнения асинхронной операции мы сможем с помощью специальных функций:
Функция MPI_Wait является блокирующей — она останавливает выполнение процесса до тех пор, пока передача, связанная с request не будет завершена. Функция MPI_Test является неблокирующей (не дожидается окончания выполнения операции) — о том была завершена операция или нет, она сигнализирует с помощью флага (true — завершена).
В нашем примере в использовании функций MPI_Wait и MPI_Test нет необходимости, т.к. синхронно выполняется сбор результатов вычислений процессов. Главный процесс инициирует передачу данных и приступает к обработке своей части массива, после этого синхронно ожидает поступления данных от каждого по очереди процесса. Кстати, асинхронной может быть не только передача, но и прием данных — функция MPI_Irecv.
Чтобы избежать ошибок, необходимо представлять как именно может быть реализована работа неблокирующих функций. Например, MPI_Isend инициирует передачу данных, которая выполняется в отдельном потоке параллельно. По окончанию передачи этот поток должен изменить переменную request. Это значит, что такой код вполне может привести к ошибкам:
Тут на каждой итерации цикла создается экземпляр переменной типа MPI_Request, инициируется передача и объект разрушается (память освобождается). Это значит, что поток, выполняющий передачу обратится к памяти, которая будет освобождена (и возможно уже повторно распределена для других целей), а значит — программа будет вести себя непредсказуемо. Вывод — объект request должен существовать до завершения асинхронной передачи.
Вызов функции MPI_Request_free необходим в случаях если не требуется ждать окончания асинхронной передачи и при этом хочется использовать один экземпляр MPI_Request — в нашем случае один и тот же объект используется для передачи данных всем дочерним процессам. За более детальной информацией о работе асинхронных операций MPI предлагаю обратиться к стандарту (раздел 3.7 Nonblocking Communication) [3].
Буферизованная передача — MPI_Bsend
В стандарте MPI сказано, что функция MPI_Send вернет управление после того, как сообщение будет записано в выходной буфер, я же выше писал что это сокет (пусть это не совсем точно) — стандарт в этом месте дает некоторую свободу, возможны различные реализации. Однако, «выходной буфер» в стандарте — это однозначно не область в оперативной памяти, т.к. для работы с буфером в ОЗУ предусмотрена другая функция — MPI_Bsend.
Функция MPI_Send записывает данные в сокет и только после этого возвращает управление. Функция MPI_Isend вернет управление сразу, но нам все равно нельзя будет изменять передаваемые данные, пока они не будут записаны в сокет. При этом, работа с сокетом выполняется медленнее, чем с оперативной памятью — поэтому ускорить работу программы в ряде случаев можно путем копирования данных в некоторый буфер в оперативной памяти и передачей данных из этого буфера. Изменять отправляемые данные мы сможем сразу после того, как они будут скопированы. Примерно так и работает функция MPI_Bsend. Сложность реализации такой операции вручную заключается также в том, что после отправки данных память из под буфера нужно освободить.
Чтобы выделенная область памяти использовалась в качестве буфера при передачи в буферизованном режиме — необходимо использовать функцию MPI_Buffer_attach. Буфер может состоять из нескольких «присоединенных» областей. В разделе 3.6.1 стандарта [3] говорится о том, что буфер может быть устроен как циклическая очередь сообщений, отправленные сообщения из буфера удаляются, позже на их место могут быть записаны другие сообщения.
При использовании буферизованного режима исходный код будет не сильно отличаться от приведенного выше, ниже приведен только фрагмент, которого коснулись изменения:
Перед использованием функции выделяется память под буфер, т.к. в этой памяти размещается очередь — следовательно есть некоторые издержки (размер которых зависит от конкретной реализации стандарта), поэтому необходимо выделять чуть больше памяти (MPI_BSEND_OVERHEAD). Выделенная область прикрепляется к буферу, затем используется MPI_Bsend (аргументы функции полностью совпадают с MPI_Send, буфер в качестве аргумента не передается, т.к. он является глобальным для процесса). После того, как все сообщения будут переданы — буфер можно отсоединить, а память освободить.
Коллективные операции. Пример использования MPI_Reduce
Коллективные операции выполняются всеми процессами указанного коммуникатора. Ниже приведена картинка из стандарта [3], на которой показана суть некоторых операций:
Коллективные операции MPI
Верхняя часть схемы иллюстрирует операцию MPI_Bcast, которая позволяет передать некоторые данные с одного узла кластера на все остальные. Нижняя — соответствует операциям MPI_Scatter и MPI_Gather. Если у нас на узле U есть массив из N элементов и его части необходимо передать на P узлов кластера — можно использовать функцию MPI_Scatter. Проблем не возникнет если N делится нацело на P, т.к. при выполнении MPI_Scatter все узлы получат одинаковое количество элементов. Обратную операцию выполняет MPI_Gather, т.е. собирает данные со всех P узлов на узел U.
Эти операции являются синхронными и используют MPI_Send (это закреплено стандартом), однако существуют асинхронные аналоги — MPI_Ibcast, MPI_Igather и MPI_Iscatter.
Операция MPI_Bcast теоретически (зависит от реализации библиотеки) может работать более эффективно и выполняться за (O(log(n))) операций вместо (O(n)).
Эффективная реализация MPI_Reduce и MPI_Bcast
На приведенной схеме цветом выделен узел, на котором находятся передаваемые данные. В начале работы такой узел один. После первой передачи данные есть уже на двух узлах, оба они могут участвовать в передачи. При реализации такой схемы для передачи данных на 1000 узлов будет достаточно 10 операций. Таким же образом может работать операция MPI_Reduce:
A more efficient implementation is achieved by taking advantage of associativity and using a logarithmic tree reduction. [3]
Операция MPI_Reduce не просто передает данные, но и выполняет над ними заданную операцию. В нашем примере применить ее можно вместо сбора результатов вычисления сумм:
Операция MPI_Reduce может выполняться не только над числами, но и над массивами (при этом будет применена к каждому его элементу отдельно).
Заключение
Целью статьи было продемонстрировать ряд функций библиотеки MPI и показать, что реализовать их вручную не так легко — этим привлечь внимание к библиотеке.
Пример с функцией MPI_Isend наглядно демонстрирует насколько сложно реализовать аналогичное поведение вручную. Дело не только в том, что передача выполняется в отдельном потоке. Сложно придумать механизм лучший, чем работа с MPI_Request, однако к объекту request может параллельно обращаться несколько потоков, поэтому все эти операции защищаются семафорами.
Еще более наглядным является пример с MPI_Bsend, т.к. вручную реализовать циклический буфер сообщений (который, кстати, тоже защищается семафорами) — не простая задача. Однако, в библиотеке MPI гораздо больше функций типа точка-точка. Различие между ними заключается в преставлении о «завершенной передаче», т.е. моменте когда блокирующая операция вернет управление или для асинхронной операции завершится MPI_Wait.
Примеры статьи являются полностью искусственными — именно по этой причине я не стал приводить таблицу с временем выполнения программы для различных вариантов реализации. Дело в том, что кластерная архитектура (а следовательно и MPI) подходит не всех типов задач — необходимо учитывать затраты на передачу данных. Так, для вычисления на кластере суммы элементов массива необходимо выполнить, порядка (O(n)) операций передачи, но (O(frac
)) операций сложения может быть выполнено параллельно. Трудоемкость вычислений будет оцениваться (O(n)) при любом количестве узлов кластера. Подробнее об оценке трудоемкости параллельных алгоритмов советую прочитать в книге Миллера [4].
Введение в технологии параллельного программирования (MPI)
Цель лекции: Лекция направлена на изучение общей методики разработки параллельных алгоритмов.
Видеозапись лекции — (объем — 134 МБ).
5.1. MPI: основные понятия и определения
Рассмотрим ряд понятий и определений, являющихся основополагающими для стандарта MPI .
5.1.1. Понятие параллельной программы
Под параллельной программой в рамках MPI понимается множество одновременно выполняемых процессов. Процессы могут выполняться на разных процессорах, но на одном процессоре могут располагаться и несколько процессов (в этом случае их исполнение осуществляется в режиме разделения времени). В предельном случае для выполнения параллельной программы может использоваться один процессор – как правило, такой способ применяется для начальной проверки правильности параллельной программы.
Каждый процесс параллельной программы порождается на основе копии одного и того же программного кода (модель SPMP). Данный программный код, представленный в виде исполняемой программы, должен быть доступен в момент запуска параллельной программы на всех используемых процессорах. Исходный программный код для исполняемой программы разрабатывается на алгоритмических языках C или Fortran с использованием той или иной реализации библиотеки MPI.
Количество процессов и число используемых процессоров определяется в момент запуска параллельной программы средствами среды исполнения MPI-программ и в ходе вычислений меняться не может (в стандарте MPI-2 предусматривается возможность динамического изменения количества процессов). Все процессы программы последовательно перенумерованы от 0 до p-1, где p есть общее количество процессов. Номер процесса именуется рангом процесса.
5.1.2. Операции передачи данных
Основу MPI составляют операции передачи сообщений. Среди предусмотренных в составе MPI функций различаются парные (point-to-point) операции между двумя процессами и коллективные (collective) коммуникационные действия для одновременного взаимодействия нескольких процессов.
Для выполнения парных операций могут использоваться разные режимы передачи, среди которых синхронный, блокирующий и др. – полное рассмотрение возможных режимов передачи будет выполнено в подразделе 5.3.
Как уже отмечалось ранее, стандарт MPI предусматривает необходимость реализации большинства основных коллективных операций передачи данных – см. подразделы 5.2 и 5.4.
5.1.3. Понятие коммуникаторов
Процессы параллельной программы объединяются в группы. Под коммуникатором в MPI понимается специально создаваемый служебный объект, объединяющий в своем составе группу процессов и ряд дополнительных параметров (контекст), используемых при выполнении операций передачи данных.
Как правило, парные операции передачи данных выполняются для процессов, принадлежащих одному и тому же коммуникатору. Коллективные операции применяются одновременно для всех процессов коммуникатора. Как результат, указание используемого коммуникатора является обязательным для операций передачи данных в MPI.
В ходе вычислений могут создаваться новые и удаляться существующие группы процессов и коммуникаторы. Один и тот же процесс может принадлежать разным группам и коммуникаторам. Все имеющиеся в параллельной программе процессы входят в состав создаваемого по умолчанию коммуникатора с идентификатором MPI_COMM_WORLD.
При необходимости передачи данных между процессами из разных групп необходимо создавать глобальный коммуникатор (intercommunicator).
Подробное рассмотрение возможностей MPI для работы с группами и коммуникаторами будет выполнено в подразделе 5.6.
5.1.4. Типы данных
При выполнении операций передачи сообщений для указания передаваемых или получаемых данных в функциях MPI необходимо указывать тип пересылаемых данных. MPI содержит большой набор базовых типов данных, во многом совпадающих с типами данных в алгоритмических языках C и Fortran. Кроме того, в MPI имеются возможности для создания новых производных типов данных для более точного и краткого описания содержимого пересылаемых сообщений.
Подробное рассмотрение возможностей MPI для работы с производными типами данных будет выполнено в подразделе 5.5.
5.1.5. Виртуальные топологии
Как уже отмечалось ранее, парные операции передачи данных могут быть выполнены между любыми процессами одного и того же коммуникатора, а в коллективной операции принимают участие все процессы коммуникатора. В этом плане, логическая топология линий связи между процессами имеет структуру полного графа (независимо от наличия реальных физических каналов связи между процессорами).
Вместе с этим (и это уже отмечалось в разделе 3), для изложения и последующего анализа ряда параллельных алгоритмов целесообразно логическое представление имеющейся коммуникационной сети в виде тех или иных топологий.
В MPI имеется возможность представления множества процессов в виде решетки произвольной размерности (см. подраздел 5.7). При этом, граничные процессы решеток могут быть объявлены соседними и, тем самым, на основе решеток могут быть определены структуры типа тор.
Кроме того, в MPI имеются средства и для формирования логических (виртуальных) топологий любого требуемого типа. Подробное рассмотрение возможностей MPI для работы с топологиями будет выполнено в подразделе 5.7.
И, наконец, последний ряд замечаний перед началом рассмотрения MPI:
- Описание функций и все приводимые примеры программ будут представлены на алгоритмическом языке C; особенности использования MPI для алгоритмического языка Fortran будут даны в п. 5.8.1,
- Краткая характеристика имеющихся реализаций библиотек MPI и общее описание среды выполнения MPI программ будут рассмотрены в п. 5.8.2,
- Основное изложение возможностей MPI будет ориентировано на стандарт версии 1.2 (MPI-1); дополнительные свойства стандарта версии 2.0 буду представлены в п. 5.8.3.
Приступая к изучению MPI, можно отметить, что, с одной стороны, MPI достаточно сложен – в стандарте MPI предусматривается наличие более 125 функций. С другой стороны, структура MPI является тщательно продуманной – разработка параллельных программ может быть начата уже после рассмотрения всего лишь 6 функций MPI. Все дополнительные возможности MPI могут осваиваться по мере роста сложности разрабатываемых алгоритмов и программ. Именное в таком стиле – от простого к сложному – и будет далее представлен весь учебный материал по MPI.
5.2. Введение в разработку параллельных программ с использованием MPI
5.2.1. Основы MPI
Приведем минимально-необходимый набор функций MPI, достаточный для разработки достаточно простых параллельных программ.
5.2.1.1 Инициализация и завершение MPI программ
Первой вызываемой функцией MPI должна быть функция:
для инициализации среды выполнения MPI-программы. Параметрами функции являются количество аргументов в командной строке и текст самой командной строки.
Последней вызываемой функцией MPI обязательно должна являться функция:
Как результат, можно отметить, что структура параллельной программы, разработанная с использованием MPI, должна иметь следующий вид:
- Файл mpi.h содержит определения именованных констант, прототипов функций и типов данных библиотеки MPI,
- Функции MPI_Init и MPI_Finalize являются обязательными и должны быть выполнены (и только один раз) каждым процессом параллельной программы,
- Перед вызовом MPI_Init может быть использована функция MPI_Initialized для определения того, был ли ранее выполнен вызов MPI_Init.
Рассмотренные примеры функций дают представление синтаксиса именования функций в MPI. Имени функции предшествует префикс MPI, далее следует одно или несколько слов названия, первое слово в имени функции начинается с заглавного символа, слова разделяются знаком подчеркивания. Названия функций MPI, как правило, поясняют назначение выполняемых функцией действий.
5.2.1.2 Определение количества и ранга процессов
Определение количества процессов в выполняемой параллельной программе осуществляется при помощи функции:
Для определения ранга процесса используется функция:
Как правило, вызов функций MPI_Comm_size и MPI_Comm_rank выполняется сразу после MPI_Init:
Использование технологии
параллельного программирования MPI-2
понедельник, 6 марта 2006 г.
Существуют задачи, не решаемые на серийных персональных компьютерах за приемлемое время[1], к примеру прогнозирование погоды, моделирование процессов разрушения в механике (crash-тесты).
Основной характеристикой при классификации параллельных вычислительных систем является способ организации памяти[2]. Все множество архитектур можно разделить на две группы: системы с общей памятью и системы с распределенной памятью.
При написании параллельных программ можно пользоваться коммуникационными библиотеками. Такие библиотеки реализуют методы запуска и управления параллельными процессами, обычно они содержат функции обмена данными между ветвями параллельной программы, функции синхронизации процессов. Существует много библиотек и интерфейсов параллельного программирования[3]. Соответственно типу организации памяти параллельных вычислителей, выделим два основных типа библиотек:
Библиотеки использующие модель общей памяти. Например, OpenMP — программный интерфейс для программирования компьютеров с общей памятью (симметричные мультипроцессоры).
Параллельные программы можно писать »вручную», непосредственно вставляя в нужные места вызовы коммуникационной библиотеки. Этот путь требует от программиста специальной подготовки. Альтернативой является использование систем автоматического и полуавтоматического распараллеливания последовательных программ[4]. Например, Adaptor — одна из реализаций спецификации High Performance Fortran (HPF) или BERT77 — средство автоматического распараллеливания Fortran-программ. Такие системы так же могут помочь пользователю выяснить, можно ли распараллелить данную задачу, оценить время ее выполнения, определить оптимальное число процессоров.
Кластерные системы приобретают все большую популярность. У этого класса параллельных вычислительных систем есть существенные преимущества перед другими архитектурами вычислителей.
В первую очередь это теоретическая возможность неограниченного наращивания производительности путем добавления новых узлов.
Гибкость технологии. Системы этого класса могут строится на разнородной аппаратной базе, иметь оптимальную для данной задачи топологию соединительной сети и допускают различные формы организации работы параллельных процессов.
Для программирования кластеров применяются библиотеки построенные по модели обмена сообщениями. Основным стандартом здесь является MPI : Message Passing Interface [5]. На настоящий момент существуют две основные версии этого стандарта: MPI-1 и MPI-2, причем MPI-1 является частным случаем MPI-2. Стандарт MPI-1 описывает статическое распараллеливание, т.е. количество параллельных процессов фиксировано. Он позволяет описывать обмены типа точка-точка, широковещательные(коллективные) обмены, групповые обмены а также позволяет организовывать топологии взаимодействия процессов. Стандарт MPI-2 помимо функциональности MPI-1 содержит возможность динамического порождения процессов и управления ими.
Разными коллективами разработчиков написано несколько программных пакетов, удовлетворяющих спецификациям MPI (MPICH, MPICH2, LAM, OpenMPI, HPVM, etc.). Существуют стандартные »привязки» MPI к языкам С, С++, Fortran 77/90, а также реализации почти для всех суперкомпьютерных платформ и сетей рабочих станций.
В данной работе для экспериментов был использован кластер на основе сети персональных компьютеров и библиотека MPICH2 [6]. Этот пакет можно получить и использовать бесплатно. В состав MPICH2 входит библиотека программирования, загрузчик приложений, утилиты.
Прежде всего необходимо скачать и установить на все узлы кластера пакет MPICH2. Исходные тексты или уже скомпилированные пакеты под разные платформы можно получить на сайте [6].
В данном случае MPICH2 v.1.0.3 собирался из исходных текстов для ОС FreeBSD v.5.3 на процессоре Intel Pentium III. Процесс инсталляции библиотеки описан в MPICH2 Installer’s Guide.
MPICH2 использует rsh (remote shell). Поэтому необходимо запустить на каждом узлеrshd (remote shell server) и согласовать права доступа, т.е. по команде rsh mynode система должна сразу »пускать» вас не спрашивая пароль. Для обеспечения более высокого уровня сетевой безопасности можно использовать ssh — OpenSSH remote login client.
Компиляция MPI-программы на языке С выполняется утилитой mpicc, представляющей собой надстройку над C-компилятором, установленным в данной ОС. Так же есть аналогичные утилиты для языков С++ — mpiCC и FORTRAN77 — mpif77.
Перед запуском »бинарника» myprog необходимо разослать его на все узлы кластера, причем локальный путь до myprog должен быть одинаковый на всех машинах, например — /usr/mpibin/myprog.
Вместо процедуры копирования программы на узлы можно использовать NFS (Network File System) :
- на головной машине запускаем NFS-сервер и открываем каталог с myprog
- на каждом рабочем узле кластера, монтируем NFS головной машины, используя единый для всех узлов локальный путь.
В отличии от MPICH1, который использовал только rsh, в случае MPICH2 процессы на узлах управляются специальным демоном mpd. Перед началом работы надо запустить его с головной машины на всех узлах при помощи mpdboot.
где hosts.mpd — текстовый файл со списком используемых узлов кластера.
Проверка состояния mpd выполняется при помощи mpdtrace:
Завершение работы mpd выполняется при помощи mpdexit
При выключении одного узла выключаются все остальные.
Запуск MPI-программы производится командой :
где N — начальное количество параллельных процессов.
После этого происходит запуск N копий MPI-программы myprog.
mpiexec сам распределяет процессы по узлам, »общаясь» с mpd, в данном случае нет надобности указывать список узлов как в mpirun для MPICH1.
Параллельная программа описывает некоторое количество процессов (веток параллельной программы) и порядок их взаимодействия. В статической модели стандарта MPI-1 количество таких веток фиксировано и задается при запуске программы.
MPI-программа начинается с вызова функции MPI_INIT(), которая включает процесс в среду MPI, и завершается вызовом функции MPI_FINALIZE(). При запуске каждая ветка параллельной программы получает MPI-идентификатор — ранг, который можно узнать при помощи функции MPI_COMM_RANK(). Для обмена данными между процессами в рамках MPI существует много разных функций: MPI_SEND() — обмен точка-точка, посылка сообщения для одного процесса, MPI_RECV() — обмен точка-точка, прием сообщения, MPI_BCAST() — широковещательная посылка один-всем, MPI_REDUCE() — сбор и обработка данных, посылка все-одному и еще много других.
Приведем пример статической MPI-программы вычисления числа как суммы ряда на языке FORTRAN77, текст программы [здесь].
Результаты работы программы, на кластере из двух PC на процессоре Pentium III 700MHz, сеть FAST ETHERNET 100Mbs, размер интервала, на котором считали сумму, равен 10 8
- один узел, время работы — 47 секунд
- два узла, время работы — 24 секунды
Теперь займемся динамическим порождением процессов. Стандарт MPI-2 предусматривает механизмы порождения новых ветвей из уже запущенных в процессе выполнения параллельные программы. В MPI-2 это происходит путем запуска файлов программ (аналогично функциям exec() стандарта POSIX.1) с помощью функции MPI_COMM_SPAWN() или MPI_COMM_SPAWN_MULTIPLE(), первая запускает заданное количество копий одной программы, вторая может запускать несколько разных программ.
Запущенные с помощью MPI_COMM_SPAWN процессы не принадлежат группе родителя (MPI_COMM_WORLD) и выполняются в отдельной среде. Порожденные процессы имеют свои ранги которые могут совпадать с рангами группы, в которой выполнялся родительский процесс. Обмен сообщениями между процессами родительской и дочерней групп происходит с использованием так называемого интеркоммуникатора, который возвращается процессу-родителю функцией MPI_COMM_SPAWN(). Дочерние процессы могут получить интеркоммуникатор группы родителя с помощью функции MPI_COMM_GET_PARENT(). Значение интеркоммуникатора используется функциями обмена сообщениями MPI_SEND(), MPI_RECV() и другими.
Приведем пример динамической MPI-программы на языке FORTRAN77. Здесь один родительский процесс порождает три дочерних, затем один дочерний процесс посылает остальным дочерним процессам широковещательное сообщение(MPI_BCAST()), после чего один дочерний процесс посылает сообщение родителю (MPI_SEND()) и принимает от него ответ (MPI_RECV()). Дочерние процессы запускаются родительским с параметром командной строки «—slave«, текст программы [здесь].
Параллельное программирование с использованием MPI
Лабораторная работа 1
Параллельное программирование с использованием MPI
Целью настоящей работы является освоение работы с библиотекой MPI в среде разработки Visual C++ и исследование способов создания параллельных программ, работающих на нескольких компьютерах, соединенных локальной сетью и использующих обмен сообщениями для коммуникации между своими частями, расположенными на разных рабочих станциях сети.
Как правило, программирование для сетевых кластеров отличается от привычной модели программирования для многозадачных систем, построенных на базе одного или множества процессоров. Часто в таких системах необходимо обеспечить только синхронизацию процессов, и нет нужды задумываться об особых способах обмена информацией между ними, т. к. данные обычно располагаются в общей разделяемой памяти. В сети реализация такого механизма затруднительна из-за высоких накладных расходов, связанных с необходимостью предоставлять каждому процессу копию одной и той же разделяемой памяти для работы. Поэтому, обычно, при программировании для сетевых кластеров используется SPMD-технология (Single Program – Multiple Data, одна программа – множественные данные). Идея SPMD в том, чтобы поделить большой массив информации между одинаковыми процессами, которые будут вести обработку своей части данных (рис. 1).
Рис. 1. Схема взаимодействия частей SPMD-программы
В случае SPMD-подхода достаточно рассылать время от времени процессам блоки данных, которые требуют трудоемкой обработки, а затем собирать результаты их работы. Если время обработки блока данных одной машиной значительно больше, чем время пересылки этого блока по сети, то сетевая кластерная система становится очень эффективной.
Именно такой подход используется в MPI. Здесь всегда есть основной процесс, который производит распределение данных по другим машинам, а после окончания вычислений собирает результаты и показывает их пользователю. Обычно процесс-мастер после распределения данных также выполняет обработку их части, чтобы использовать ресурсы системы наиболее эффективно.
Как уже было сказано выше, MPI является стандартом обмена сообщениями. Он обеспечивает низкоуровневые коммуникации и синхронизацию процессов в сетевом кластере.
По сути дела, каждое сообщение представляет собой пакет типизированных данных, который один процесс может отправить другому процессу или группе процессов. Все сообщения обладают идентификатором (не обязательно уникальным), который задается программистом и служит для идентификации типа сообщения на принимающей стороне.
MPI поставляется в виде библиотеки, подключаемой к среде программирования. Самыми распространенными являются библиотеки для языков Си и Fortran. Также частью MPI является резидент, который запускается на каждой машине кластера и, отвечая на запросы процессов, позволяет им взаимодействовать в сети, а также осуществляет начальный запуск всех процессов, участвующих в расчете и составляющих SPMD-программу, на указанных в файле конфигурации задачи машинах кластера.
Каждый MPI-процесс в пределах SPMD-программы уникально идентифицируется своим номером. Допускается объединять процессы в группы, которые могут быть вложенными. Внутри каждой группы все процессы перенумерованы, начиная с нуля. С каждой группой ассоциирован свой коммуникатор (уникальный идентификатор). Поэтому при осуществлении пересылки данных необходимо указать наряду с номером процесса и идентификатор группы, внутри которой производится эта пересылка. Все процессы изначально содержатся в группе с предопределенным идентификатором MPI_COMM_WORLD, который заводится для каждой запускаемой SPMD-программы (рис. 2). Разные SPMD-программы не знаю о существовании друг друга и обмен данными между ними невозможен.
Рис 2. Нумерация процессов и коммуникаторы в MPI
Рассмотрим основные функции библиотеки. Все они возвращают целое значение, которое либо равно константе MPI_SUCCESS, либо содержит код произошедшей ошибки.
Перед тем, как программа сможет работать с функциями библиотеки необходимо инициализировать MPI с помощью функции:
В качестве аргументов этой функции передаются параметры командной строки, поступающие в функцию main().
Если процессу MPI необходимо узнать свой порядковый номер в группе, то используется функция:
int MPI_Comm_rank (MPI_Comm, int *rank)
Ее второй параметр будет выходным, содержащим номер процесса в указанной в первом параметре группе.
Чтобы узнать размер группы (количество процессов в ней) необходимо применить функцию:
int MPI_Comm_size (MPI_Comm, int *ranksize)
Для окончания работы с MPI необходимо использовать функцию:
Перед тем, как начать рассмотрение функций передачи/приема сообщений, отметим, какие основные типы данных поддерживает MPI (таблица 1).
Таблица 1. Соответствие типов в MPI и языке Cи
Лекция 1 принципы построения параллельных вычислительных систем пути достижения параллелизма
5.2. Введение в разработку параллельных программ с использованием MPI
5.2.1. Основы MPI
5.2.1.1. Инициализация и завершение MPI-программ
Первой вызываемой функцией MPI должна быть функция:
int MPI_Init(int *argc, char ***argv),
где
- argc — указатель на количество параметров командной строки,
- argv — параметры командной строки,
применяемая для инициализации среды выполнения MPI-программы. Параметрами функции являются количество аргументов в командной строке и адрес указателя на массив символов текста самой командной строки.
^ Последней вызываемой функцией MPI обязательно должна являться функция:
Как результат, можно отметить, что структура параллельной программы, разработанная с использованием MPI, должна иметь следующий вид:
int main(int argc, char *argv[]) <
Следует отметить:
- файл mpi.h содержит определения именованных констант, прототипов функций и типов данных библиотеки MPI;
- функции MPI_Init и MPI_Finalize являются обязательными и должны быть выполнены (и только один раз) каждым процессом параллельной программы;
- перед вызовом MPI_Init может быть использована функция MPI_Initialized для определения того, был ли ранее выполнен вызов MPI_Init, а после вызова MPI_Finalize – MPI_Finalized 2) аналогичного предназначения.
Рассмотренные примеры функций дают представление синтаксиса именования функций в ^ MPI. Имени функции предшествует префикс MPI , далее следует одно или несколько слов названия, первое слово в имени функции начинается с заглавного символа, слова разделяются знаком подчеркивания. Названия функций MPI, как правило, поясняют назначение выполняемых функцией действий.
5.2.1.2. Определение количества и ранга процессов
Определение количества процессов в выполняемой параллельной программе осуществляется при помощи функции:
int MPI_Comm_size(MPI_Comm comm, int *size),
где
- comm — коммуникатор, размер которого определяется,
- size — определяемое количество процессов в коммуникаторе.
Для определения ранга процесса используется функция:
int MPI_Comm_rank(MPI_Comm comm, int *rank),
где
- comm — коммуникатор, в котором определяется ранг процесса,
- rank — ранг процесса в коммуникаторе.
Как правило, вызов функций MPI_Comm_size и MPI_Comm_rank выполняется сразу после MPI_Init для получения общего количества процессов и ранга текущего процесса:
int main(int argc, char *argv[]) <
int ProcNum, ProcRank;
Следует отметить:
- коммуникатор MPI_COMM_WORLD, как отмечалось ранее, создается по умолчанию и представляет все процессы выполняемой параллельной программы;
- ранг, получаемый при помощи функции MPI_Comm_rank, является рангом процесса, выполнившего вызов этой функции, т. е. переменная ProcRank примет различные значения у разных процессов.
5.2.1.3. Передача сообщений
Для передачи сообщения процесс— отправитель должен выполнить функцию:
int MPI_Send(void *buf, int count, MPI_Datatype type, int dest,
int tag, MPI_Comm comm),
где
- buf — адрес буфера памяти, в котором располагаются данные отправляемого сообщения;
- count — количество элементов данных в сообщении;
- type — тип элементов данных пересылаемого сообщения;
- dest — ранг процесса, которому отправляется сообщение;
- tag — значение-тег, используемое для идентификации сообщения;
- comm — коммуникатор, в рамках которого выполняется передача данных.
Для указания типа пересылаемых данных в MPI имеется ряд базовых типов, полный список которых приведен в табл. 5.1.
^ Таблица 5.1. Базовые (пpедопpеделенные) типы данных MPI для алгоритмического языка C
Следует отметить:
- отправляемое сообщение определяется через указание блока памяти (буфера), в котором это сообщение располагается. Используемая для указания буфера триада (buf, count, type) входит в состав параметров практически всех функций передачи данных;
- процессы, между которыми выполняется передача данных, в обязательном порядке должны принадлежать коммуникатору, указываемому в функции MPI_Send;
- параметр tag используется только при необходимости различения передаваемых сообщений, в противном случае в качестве значения параметра может быть использовано произвольное положительное целое число 3) (см. также описание функции MPI_Recv).
Сразу же после завершения функции MPI_Send процесс-отправитель может начать повторно использовать буфер памяти, в котором располагалось отправляемое сообщение. Также следует понимать, что в момент завершения функции MPI_Send состояние самого пересылаемого сообщения может быть совершенно различным: сообщение может располагаться в процессе-отправителе, может находиться в состоянии передачи, может храниться в процессе-получателе или же может быть принято процессом-получателем при помощи функции MPI_Recv. Тем самым, завершение функции MPI_Send означает лишь, что операция передачи начала выполняться и пересылка сообщения рано или поздно будет выполнена.
Пример использования функции будет представлен после описания функции MPI_Recv.
5.2.1.4. Прием сообщений
Для приема сообщения процесс-получатель должен выполнить функцию:
int MPI_Recv(void *buf, int count, MPI_Datatype type, int source,
int tag, MPI_Comm comm, MPI_Status *status),
где
- buf, count, type — буфер памяти для приема сообщения, назначение каждого отдельного параметра соответствует описанию в MPI_Send;
- source — ранг процесса, от которого должен быть выполнен прием сообщения;
- tag — тег сообщения, которое должно быть принято для процесса;
- comm — коммуникатор, в рамках которого выполняется передача данных;
- status – указатель на структуру данных с информацией о результате выполнения операции приема данных.
Следует отметить:
- буфер памяти должен быть достаточным для приема сообщения. При нехватке памяти часть сообщения будет потеряна и в коде завершения функции будет зафиксирована ошибка переполнения; с другой стороны, принимаемое сообщение может быть и короче, чем размер приемного буфера, в таком случае изменятся только участки буфера, затронутые принятым сообщением;
- типы элементов передаваемого и принимаемого сообщения должны совпадать;
- при необходимости приема сообщения от любого процесса— отправителя для параметра source может быть указано значение MPI_ANY_SOURCE (в отличие от функции передачи MPI_Send, которая отсылает сообщение строго определенному адресату);
- при необходимости приема сообщения с любым тегом для параметра tag может быть указано значение MPI_ANY_TAG (опять-таки, при использовании функции MPI_Send должно быть указано конкретное значение тега);
- в отличие от параметров «процесс-получатель» и «тег», параметр «коммуникатор» не имеет значения, означающего «любой коммуникатор»;
- параметр status позволяет определить ряд характеристик принятого сообщения:
- status.MPI_SOURCE — ранг процесса – отправителя принятого сообщения;
- status.MPI_TAG — тег принятого сообщения.
Приведенные значения MPI_ANY_SOURCE и MPI_ANY_TAG иногда называют джокерами.
Значение переменной status позволяет определить количество элементов данных в принятом сообщении при помощи функции:
int MPI_Get_count(MPI_Status *status, MPI_Datatype type,
где
- status — статус операции MPI_Recv;
- type — тип принятых данных;
- count — количество элементов данных в сообщении.
Вызов функции MPI_Recv не обязан быть согласованным со временем вызова соответствующей функции передачи сообщения MPI_Send – прием сообщения может быть инициирован до момента, в момент или после момента начала отправки сообщения.
По завершении функции MPI_Recv в заданном буфере памяти будет располагаться принятое сообщение. Принципиальный момент здесь состоит в том, что функция MPI_Recv является блокирующей для процесса-получателя, т.е. его выполнение приостанавливается до завершения работы функции. Таким образом, если по каким-то причинам ожидаемое для приема сообщение будет отсутствовать, выполнение параллельной программы будет блокировано.
5.2.1.5. Первая параллельная программа с использованием MPI
Рассмотренный набор функций оказывается достаточным для разработки параллельных программ 4) . Приводимая ниже программа является стандартным начальным примером для алгоритмического языка C.
Программа 5.1. Первая параллельная программа с использованием MPI
Параллельное программирование с использованием OpenMP и MPI
- 10 недель
от 8 до 12 часов в неделю
понадобится для освоения
3 зачётных единицы
для зачета в своем вузе
Курс даст базовые знания по многоядерным архитектурам, представит наиболее популярную технологию распараллеливания программ для многопроцессорных вычислительных систем с общей памятью и суперкомпьютеров OpenMP.
О курсе
Потребность решения сложных прикладных задач с большим объемом вычислений и принципиальная ограниченность максимального быстродействия «классических» – по схеме фон Неймана – ЭВМ привели к появлению многопроцессорных вычислительных систем (МВС) или суперкомпьютеров.
Широкое распространение параллельные вычисления приобрели с переходом компьютерной индустрии на массовый выпуск многоядерных процессоров с векторными расширениями. В настоящие время практически все устройства – от карманных гаджетов и до самых мощных суперкомпьютеров – оснащены многоядерными процессорами. И если вы пишете последовательную программу, не применив распределение работы между разными ядрами центрального процессора и не проведя векторизацию, то вы используете только часть вычислительных возможностей центрального процессора.
Курс познакомит с основными архитектурами МВС, с двумя стандартами (OpenMP и MPI), позволяющими писать параллельные программы для систем с общей и распределенной памятью. На простых примерах будут разобраны основные конструкции и способы распределения работы. Выполнение практических заданий позволит приобрести практические навыки создания параллельных программ.
Формат
- тематические видеолекции;
- материалы для самостоятельного изучения к каждому разделу курса;
- список литературы и дополнительных материалов по всему курсу;
- список необходимого программного обеспечения и инструкции по его установке;
- практические задания на программирование по соответствующим темам курса;
- тестовые задания на оценку (по 8–13 вопросов к каждому разделу курса).
Финальная оценка результатов обучения формируется на основе данных еженедельного контроля и итогового задания.
Курс рассчитан на 10 недель изучения. Недельная учебная нагрузка обучающихся по курсу составляет 8–12 часов. Общая трудоемкость курса – 3 зачетные единицы.
Информационные ресурсы
- Официальный сайт OpenMP ARB
- Спецификация OpenMP 4.0
- Примеры использования директив и функций с официального сайта
- Антонов А.С. Параллельное программирование с использованием технологии OpenMP.
- Материалы курса лекций «Параллельное программирование для многопроцессорных систем с общей и распределенной памятью», читаемого на механико-математическом факультете Томского государственного университета.
- Букатов А.А. Программирование многопроцессорных вычислительных систем / А.А. Букатов, В.Н. Дацюк, А.И. Жегуло. – Ростов н/Д: Издательство ООО «ЦВВР», 2003. – 208 с.
- Старченко А.В. Методы параллельных вычислений: учебник для вузов / А.В. Старченко, В.Н. Берцун. – Томск: Издательство Томского университета, 2013. – 224 с. (xn--80atcxa4d)
- Практикум по методам параллельных вычислений: учебник для вузов / А.В. Старченко, Е.А. Данилкин, В.И. Лаева [и др.]; под ред. А. В. Старченко. – М.: Издательство Моск. университета, 2010. – 199 с.
- Информационно аналитический центр по параллельным вычислениям
- Интернет-центр системы образовательных ресурсов в области суперкомпьютерного образования
- Список 50 наиболее мощных компьютеров СНГ
Требования
Для изучения данного курса требуются: опыт написания программ на любом языке, знание синтаксиса языка программирования C/С++, знание среды разработки MS Visual Studio. Курс ориентирован на студентов старших курсов бакалавриата и магистрантов.
Программа курса
Онлайн-курс состоит из девяти разделов:
Раздел 1. Введение в параллельный мир
1.1. История развития параллелизма в архитектуре ЭВМ
1.2. Иерархическая организация памяти в компьютере
1.3. Основные архитектуры многопроцессорных вычислительных систем. Их классификация
1.4. Обзор задач, требующих использования СуперЭВМ
1.5. Инструменты создания параллельных программ
Раздел 2. Основы OpenMP
2.1. Особенности программирования для систем с общей памятью. Понятие процесса, потока и многопоточности
2.2. Технология OpenMP, особенности и ее компоненты
2.3. Задание параллельной области и опции, влияющие на ее выполнение
2.4. Модель памяти. Классы переменных в OpenMP
2.5. Режимы выполнения многопоточных программ. Вложенный параллелизм
Раздел 3. Директивы распределения работы и синхронизации работы
3.1. Распараллеливание выполнения циклов
3.2. Распределение нескольких структурных блоков между потоками
3.3. Распределение работы на основе независимых задач
3.4. Синхронизация выполнения различных потоков. Простые директивы
3.5. Синхронизация выполнения различных потоков. Замки
Раздел 4. Векторные вычисления с помощью OpenMP0
4.1. Что такое векторизация и зачем она нужна
4.2. Векторизация исполняемого кода для современных процессоров
4.3. Новые возможности в стандарте OpenMP 4.0
4.4. Примеры использования векторизации
Раздел 5. Анализ и оптимизация программ с использованием современных программных пакетов
5.1. Основные опции компилятора Intel. Автоматическое распараллеливание
5.2. Основные возможности Intel Parallel Studio
5.3. Поиск ошибок работы с памятью с использованием Intel® Parallel Inspector
5.4. Профилирование программ с использованием Intel® Parallel Amplifier
Раздел 6. Системы с распределённой памятью. Основы MPI
6.1. Основные понятия модели передачи сообщений, MPI среди других средств разработки параллельных программ
6.2. Основные понятия и определения, состав MPI. Синтаксис функций MPI
6.3. Первая параллельная программа с использованием MPI
6.4. Ускорение работы параллельной программы
6.5. О построении параллельных алгоритмов
Раздел 7. Прием и передача сообщений между отдельными процессами
7.1. Обзор двухточечных обменов сообщениями
7.2. Блокирующие обмены
7.3. Неблокирующие обмены
7.4. Параллельные алгоритмы суммирования
7.5. Примеры параллельных программ, демонстрирующие использование функций MPI, изученных в разделе 7
Раздел 8. Коллективные операции
8.1. Обзор коллективных операций. Широковещательная рассылка
8.2. Функции сбора данных со всех процессов
8.3. Функции распределения данных по всем процессам
8.4. Функции редукции
8.5. Примеры параллельных программ, демонстрирующие использование функций MPI, изученных в разделе 8
Раздел 9. Производные типы данных. Группы и коммуникаторы
9.1. Создание и использование производных типов данных
9.2. Создание групп и коммуникаторов
9.3. Виртуальные топологии. Декартова топология. Организация пересылок данных в декартовой топологии
9.4. Виртуальные топологии. Топология графа
9.5. Примеры параллельных программ, демонстрирующие использование функций MPI, изученных в разделе 9
Раздел 10. Итоговая аттестация
Результаты обучения
В результате освоения курса студент должен уметь:
- Использовать особенности различных архитектур высокопроизводительных вычислительных систем;
- Создавать параллельные программы с использованием OpenMP и MPI;
- Используя полученные знания и навыки, ускорить работу существующих последовательных программ, создав на их основе параллельные программы.
Формируемые компетенции
Знать:
- Основные понятия параллельного программирования, структуру, методы и функции параллельных технологий программирования OpenMP, MPI.
- Возможные сферы применения параллельных технологий программирования при решении практических задач фундаментальной и прикладной математики
Уметь
- Разрабатывать программы для решения задач прикладного характера из различных разделов прикладной математики с использованием параллельных технологий.
Владеть
- Алгоритмами и эффективными технологиями параллельного программирования на основе стандарта OpenMP и MPI.
Знать:
- Основные понятия параллельного программирования, структуру, методы и функции параллельных технологий программирования OpenMP, MPI.
- Возможные сферы применения параллельных технологий программирования при решении практических задач фундаментальной и прикладной физики
Владеть:
- базовыми знаниями и навыками управления информацией посредством современных компьютерных технологий для решения исследовательских
- навыками исследований с помощью современной аппаратуры и информационных технологий
(09.04.04 — Программная инженерия)
Знать:
- Основные понятия параллельного программирования, структуру, методы и функции параллельных технологий программирования OpenMP, MPI
Уметь:
- Уметь выбирать подходящие параллельные методы, алгоритмы и подходы для решения задачи. Разрабатывать программы для решения задач прикладного характера из различных разделов прикладной математики с использованием параллельных технологий.
Владеть:
- Способностью к освоению современных информационных технологий в области параллельного программирования. Методами и эффективными технологиями параллельного программирования на основе стандарта OpenMP и MPI
(02.04.02 — Фундаментальная информатика и информационные технологии)
Знать
- Основные понятия параллельного программирования, структуру, методы и функции параллельных технологий программирования OpenMP, MPI
Уметь
- Уметь выбирать подходящие параллельные методы, алгоритмы и подходы для решения задачи/ Разрабатывать программы для решения задач прикладного характера из различных разделов прикладной математики с использованием параллельных технологий.
Владеть
- Способностью к освоению современных информационных технологий в области параллельного программирования. Методами и эффективными технологиями параллельного программирования на основе стандарта OpenMP и MPI.