Основы параллельного программирования mpi

Основы MPI

Прочитал статью «Основы MPI для «чайников»» и понял, что статья новичка способна отпугнуть.

Теория

Начнем с начала

Первое время не было единого стандарта (API) для параллельных вычислений и программистам приходилось писать для каждого кластера архитектурно-специфический код. Но, как известно, программисты люди рациональные и быстро было решено организовать стандарты (самые известные — MPI, OpenMP).

MPI — Message Passing Interface. Это специфический API, который реализуют производители кластеров для того, чтобы можно было легко переносить программы с кластера на кластер не изменяя ни байта исходного кода(!).
Параллельная программа должна эффективно использовать вычислительные мощности и коммуникационную среду. В MPI вся работа по распределению нагрузки на узлы и сеть ложатся на программиста и для максимальной производительности необходимо знать особенности конкретного кластера. MPI очень элегантно решает вопрос топологии сети: имеются понятия коммуникаторов — группы процессов, которые можно пронумеровать в соответствии с топологией сети (для этого используется функция MPI_Cart_create, которая позволяет задать любую топологию от решётки до гиперкуба).

Целесообразность распараллеливания

Некоторые примеры в учебных пособиях весьма синтетические — в них считается какой-нибудь ряд в пределах стандартного типа (например, double), что на практике вычисляется за время много меньшее того, которое тратится на инициализацию и передачу чего-либо по сети (вычисление числа pi в double на двух компьютерах с Gigabit Ethernet примерно в два раза медленнее вычисления на одном компьютере). Однако, MPI позволяет использовать многоядерные процессоры (что почему-то многие забывают), а между ядрами скорость передачи совершенно другого порядка, поэтому всегда нужно знать архитектуру и топологию системы.

Читайте также:  Языки программирования как отличить

Практика

Про теорию можно много писать, но лучше постигать теорию соразмерно с практикой.
Для начала установим какую-нибудь реализацию MPI на свой компьютер. Одной из самых распространённых реализаций MPI является MPICH (MPI Chameleon).

Установка

В убунте устанавливается в одну строчку:

sudo apt-get install mpich2

Напишем простенькую программку, которая ничего полезного не делает:

int main ( int argc, char* argv[])
int errCode;

if (myRank == 0)
printf( «It works!\n» );
>

MPI_Finalize();
return 0;
>

* This source code was highlighted with Source Code Highlighter .

Скомпилируем эту программку:

И (если еще не настроили демон mpd) получим сообщение о том, что демон mpd не запущен.

mpiexec: cannot connect to local mpd (/tmp/mpd2.console_valery); possible causes:
1. no mpd is running on this host
2. an mpd is running but was started without a «console» (-n option)
In case 1, you can start an mpd on this host with:
mpd &
and you will be able to run jobs just on this host.
For more details on starting mpds on a set of hosts, see
the MPICH2 Installation Guide.

При попытке запустить mpd будет сказано об отсутствии настроек (почему, собственно, не запускается демон)

configuration file /home/valery/.mpd.conf not found
A file named .mpd.conf file must be present in the user’s home
directory (/etc/mpd.conf if root) with read and write access
only for the user, and must contain at least a line with:
MPD_SECRETWORD=
One way to safely create this file is to do the following:
cd $HOME
touch .mpd.conf
chmod 600 .mpd.conf
and then use an editor to insert a line like
MPD_SECRETWORD=mr45-j9z
into the file. (Of course use some other secret word than mr45-j9z.)

Секретное слово нужно только для подключения узлов. Если мы будем подключать ещё компьютеры, то надо будет и на них ставить MPICH и надо будет занести узел в список узлов, а также не будет лишним настроить подключение по ssh с использованием ключей (для общения с узлами).
Если всё сделано правильно, то получим примерно такой вывод:

MPI_Init — обязательна для вызова, так как выполняет инициализацию библиотеки MPI.
MPI_COMM_WORLD — идентификатор глобального коммуникатора, содержащего все процессы.
MPI_Comm_rank — возвращает идентификатор (номер, ранг) процесса в рамках заданного коммуникатора.

Почему выводим на экран только при ранге, равном 0? Просто этот процесс как раз соответствует по умолчанию тому, который имеет доступ к консоли того терминала, с которого производился запуск. Мы можем использовать и любой другой, но так просто удобнее.

Вместо вывода

Можно написать параллельную программу не имея почти никаких знаний о параллельном программировании, но написание эффективных программ является трудоёмким процессом выбора алгоритма и его реализации, подгонки под систему и так далее. Но если начать писать простенькие программки и при этом читать спецификации и литературу о вычислительных системах (об их архитектуре, коммуникационных средах и прочем), то со временем, %username%, будешь способен подчинить себе даже такие страшные машины как те, которые представлены в списке топ-500

Источник

Часть 1. MPI — Введение и первая программа

В этом цикле статей речь пойдет о параллельном программировании.

В бой. Введение

Довольно часто самые сложные алгоритмы требуют огромного количества вычислительных ресурсов в реальных задачах, когда программист пишет код в стандартном его понимании процедурного или Объектно Ориентированного Программирования(ООП), то для особо требовательных алгоритмических задач, которые работают с большим количеством данных и требуют минимизировать время выполнения задачи, необходимо производить оптимизацию.

В основном используют 2 типа оптимизации, либо их смесь: векторизация и распараллеливание
вычислений. Чем же они отличаются?

Вычисления производятся на процессоре, процессор пользуется специальными «хранилищами» данных называемыми регистрами. Регистры процессора напрямую подключены к логическим элементам и требуют гораздо меньшее время для выполнения операций над данными, чем данные из оперативной памяти, а тем более на жестком диске, так как для последних довольно большую часть времени занимает пересылка данных. Так же в процессорах существует область памяти называемая Кэшем, в нем хранятся те значения, которые в данный момент участвуют в вычислениях или будут участвовать в них в ближайшее время, то есть самые важные данные.

Задача оптимизации алгоритма сводится к тому, чтобы правильно выстроить последовательность операций и оптимально разместить данные в Кэше, минимизировав количество возможных пересылок данных из памяти.

Так а чем отличаются векторизация и параллельные вычисления? Векторизация — позволяет выполнять операции над данными целыми векторами. Например для С++: если мы используем AVX инструкции, то у нас есть вектора размером 256 бит, куда мы можем поместить float32 элементы. Получится, что мы можем собрать 2 вектора по (256 / 32) = 8 float32 значений. После чего выполнить над ними одну операцию за такт, хотя если бы мы этого не сделали, то вполне возможно, что аналогичные вычисления прошли бы за 8 операций, а то и больше если брать в учет вычисления индексов конкретных элементов. Однако есть одна проблема: компиляторы нынче довольно умны и довольно хорошо справляются с подобной оптимизацией и сами, поэтому большая часть сил уходит на то чтобы правильно организовать данные.

Параллелизация же позволяет пользоваться не только вычислительными возможностями одного процессора, а множеством процессоров, в том числе и в системах с распределенной памятью, и т.п. В подобных системах как правило намного больше вычислительных мощностей, но этим всем нужно уметь управлять и правильно организовывать параллельную работу на конкретных потоках.

Основная часть

В настоящее время существует много технологий которые позволяют наиболее легко организовать параллельные вычисления, одна из самых популярных на текущий момент это MPI.
MPI — Message Passing Interface (интерфейс передачи сообщений). Название носит смысловой характер, что будет ярко выражено в последующих статьях, а пока все что требуется знать — основной способ взаимодействия параллельных процессов в такой системе — передача сообщений между ними. Это означает что наши потоки будут между собой общаться при обработке данных, а как это уже вопрос реализации.

Существует два основных стиля создания параллельных программ: MIMD(Multiple Instruction Multiple Data — Множественный поток Инструкций, Множественный поток Данных) и SPMD(Single Program Multiple Data — Одна Программа, Множественный поток Данных).

Если сильно упростить, то первая модель берет разные исходные коды программы и выполняет их на разных потоках, а вторая модель берет один исходный код и запускает его на всех выделенных потоках. Программы написанные под стиль MIMD довольно сложно отлаживаются из-за своей архитектуры, поэтому чаще используется модель SPMD. В MPI возможно использовать и то и то, но по стандарту(в зависимости от реализации, разумеется) используется модель SPMD.

Установка

Поскольку MPI — библиотека, то необходимо ее прилинковать к компилятору. В каждой среде это делается по своему и в зависимости от компилятора. Лично я работал на Ubuntu Budgie 20.04 LTS и расскажу инструкцию по установке именно для нее.

В командной строке вводим следующие команды:

[user-name]$ sudo apt-get update [user-name]$ sudo apt-get install gcc [user-name]$ sudo apt-get install mpich

Первая команда обновляет менеджер пакетов, вторая устанавливает компилятор GCC, если его нет в системе, третья программа устанавливает компилятор через который мы собственно и будем работать с C\С++&MPI кодом.

Первые шаги.

MPI-программа — это множество параллельных взаимодействующих процессов, которые работают каждый в своей выделенной области памяти. По сути это N независимых программ, которые общаются между собой в ходе работы. В MPI большинство типов данных уже переопределены и начинаются с аббревиатуры MPI_[Name], далее это будет понятно.

Для понимания того что происходит дальше нужно определиться с несколькими терминами:

Коммуникатор — объект через который общается определенная группа порожденных процессов. В С++/С это тип данных MPI_Comm. Коммуникатор может объединять несколько процессов путем передачи сообщений между ними, при этом коммуникаторов может быть несколько, группы которые они образуют могут как не пересекаться, так пересекаться частично. При старте программы все процессы работают под единым коммуникатором с именем MPI_COMM_WORLD. Кроме него существуют еще коммуникаторы MPI_COMM_SELF, MPI_COMM_NULL, которые содержат только текущий процесс и ни одного процесса соответственно.

Сообщение — набор данных некоторого типа, который передается при коммуникации процессов. Каждое сообщение имеет несколько атрибутов, в частности номер процесса-отправителя, получателя, идентификатор сообщения, коммуникатор и тег.

Тег сообщения — целое не отрицательное число от 0 до 32767(В зависимости от реализации. Максимально возможная величина тега хранится в константе MPI_TAG_UB).

В данном примере не будем использовать пересылку данных, а лишь ознакомимся с основами процедур MPI.ения возможности использования большинства процедур MPI в любой программе должны быть следующие процедуры:

int MPI_Init(int *argc, char ***argv); int MPI_Finalize(void);

Первая процедура предназначения для инициализации параллельной части программы, все другие процедуры, кроме некоторых особых, могут быть вызваны только после вызова процедуры MPI_Init, принимает эта функция аргументы командной строки, через которые система может передавать процессу некоторые параметры запуска. Вторая процедура же предназначена для завершения параллельной части программы.

Для того чтобы проверить работу программы реализуем самую примитивную программку на С++ с применением MPI.

#include #include "mpi.h" int main(int argc, char **argv)

Чтобы запустить эту программу сохраним нашу запись в файле с любым названием и расширением *.cpp, после чего выполним следующие действия в консоли (В моем случае код лежит в файле main.cpp):

[user-name]$ mpic++ main.cpp -o main [user-name]$ mpiexec -n 2 ./main 

Первая команда скомпилирует нашу MPI-программу, а вторая команда позволяет ее запустить. Заметим, что мы передаем параметры -n 2 во второй строке, зачем это? Таким образом мы сообщаем исполнителю, что нужно запустить 2 параллельных процесса.

Программа просто напечатает несколько строк в зависимости от количества процессов которое вы укажете. Не стоит пугаться если строки «Before . » и «After . « будут отображаться не по одному разу, в зависимости от реализации MPI программа может работать параллельно и вне процедур Init-Finalize.

Итог

В этой краткой статье мы на примере наипростейшей программы научились запускать файлы C++ с MPI кодом и разобрались что за зверь вообще MPI и с чем его едят. В дальнейших туториалах мы рассмотрим уже более полезные программы и наконец перейдем ко коммуникации между процессами.

Источник

Оцените статью