Pull to refresh
0

Обмен данными с использованием MPI. Работа с библиотекой MPI на примере Intel® MPI Library

Reading time 9 min
Views 36K


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

Мы приведем краткое описание того, как организован обмен данными в параллельных приложениях на основе MPI, а также ссылки на внешние источники с более подробным описанием. В практической части вы найдете описание всех этапов разработки демонстрационного MPI-приложения «Hello World», начиная с настройки необходимого окружения и заканчивая запуском самой программы.

MPI (Message Passing Interface)


MPI — интерфейс передачи сообщений между процессами, выполняющими одну задачу. Он предназначен, в первую очередь, для систем с распределенной памятью (MPP) в отличие от, например, OpenMP. Распределенная (кластерная) система, как правило, представляет собой набор вычислительных узлов, соединенных высокопроизводительными каналами связи (например, InfiniBand).

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

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

Основная операция в MPI — это передача сообщений. В MPI реализованы практически все основные коммуникационные шаблоны: двухточечные (point-to-point), коллективные (collective) и односторонние (one-sided).

Работа с MPI


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

Настройка кластерного окружения


Для экспериментов нам понадобится пара вычислительный узлов (желательно со схожими характеристиками). Если под руками нет двух серверов, всегда можно воспользоваться cloud-сервисами.

Для демонстрации я выбрал сервис Amazon Elastic Compute Cloud (Amazon EC2). Новым пользователям Amazon предоставляет пробный год бесплатного использования серверами начального уровня.

Работа с Amazon EC2 интуитивно понятна. В случае возникновения вопросов, можно обратиться к подробной документации (на англ.). При желании можно использовать любой другой аналогичный сервис.

Создаем два рабочих виртуальных сервера. В консоли управления выбираем EC2 Virtual Servers in the Cloud, затем Launch Instance (под «Instance» подразумевается экземпляр виртуального сервера).

Следующим шагом выбираем операционную систему. Intel MPI Library поддерживает как Linux, так и Windows. Для первого знакомства с MPI выберем OC Linux. Выбираем Red Hat Enterprise Linux 6.6 64-bit или SLES11.3/12.0.
Выбираем Instance Type (тип сервера). Для экспериментов нам подойдет t2.micro (1 vCPUs, 2.5 GHz, Intel Xeon processor family, 1 GiB оперативной памяти). Как недавно зарегистрировавшемуся пользователю, мне такой тип можно было использовать бесплатно — пометка «Free tier eligible». Задаем Number of instances: 2 (количество виртуальных серверов).

После того, как сервис предложит нам запустить Launch Instances (настроенные виртуальные сервера), сохраняем SSH-ключи, которые понадобятся для связи с виртуальными серверами извне. Состояние виртуальных серверов и IP адреса для связи с серверами локального компьютера можно отслеживать в консоли управления.

Важный момент: в настройках Network & Security / Security Groups необходимо создать правило, которым мы откроем порты для TCP соединений, — это нужно для менеджера MPI-процессов. Правило может выглядеть так:
Type: Custom TCP Rule
Protocol: TCP
Port Range: 1024-65535
Source: 0.0.0.0/0

В целях безопасности можно задать и более строгое правило, но для нашего демонстрационного примера достаточно этого.

Здесь можно прочитать инструкции о том, как связаться с виртуальными серверами с локального компьютера (на англ.).
Для связи с рабочими серверами c компьютера на Windows я использовал Putty, для передачи файлов — WinSCP. Здесь можно прочитать инструкции по их настройке для работы с сервисами Amazon (на англ.).

Следующий шаг — настройка SSH. Для того, чтобы настроить беспарольный SSH с авторизацией по публичным ключам, необходимо выполнить следующие действия:
  1. На каждом из хостов запускаем утилиту ssh-keygen — она создаст в $HOME/.ssh директории пару из приватного и публичного ключей;
  2. Берем содержимое публичного ключа (файл с расширением .pub) с одного сервера и добавляем его в файл $HOME/.ssh/authorized_keys на другом сервере;
  3. Проделаем эту процедуру для обоих серверов;
  4. Попробуем присоединиться по SSH с одного сервера на другой и обратно, чтобы проверить корректность настройки SSH. При первом соединении может потребоваться добавить публичный ключ удаленного хоста в список $HOME/.ssh/known_hosts.

Настройка MPI библиотеки


Итак, рабочее окружение настроено. Время установить MPI.
В качестве демонстрационного варианта возьмем 30-дневную trial-версию Intel MPI Library (~300МБ). При желании можно использовать другие реализации MPI, например, MPICH. Последняя доступная версия Intel MPI Library на момент написания статьи 5.0.3.048, ее и возьмем для экспериментов.

Установим Intel MPI Library, следуя инструкциям встроенного инсталлятора (могут потребоваться привилегии суперпользователя).
$ tar xvfz l_mpi_p_5.0.3.048.tgz
$ cd l_mpi_p_5.0.3.048
$ ./install.sh

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

Для компиляции демонстрационной MPI-программы воспользуемся GNU C компилятором (gcc).
В стандартном наборе программ RHEL образа от Amazon его нет, поэтому необходимо его установить:
$ sudo yum install gcc

В качестве демонстрационной MPI-программы возьмем test.c из стандартного набора примеров Intel MPI Library (находится в папке intel/impi/5.0.3.048/test).
Для его компиляции первым шагом выставляем Intel MPI Library окружение:
$. /home/ec2-user/intel/impi/5.0.3.048/intel64/bin/mpivars.sh

Далее компилируем нашу тестовую программу с помощью скрипта из состава Intel MPI Library (все необходимые MPI зависимости при компиляции будут выставлены автоматически):
$ cd /home/ec2-user/intel/impi/5.0.3.048/test
$ mpicc -o test.exe ./test.c

Полученный test.exe копируем на второй узел:
$ scp test.exe ip-172-31-47-24:/home/ec2-user/intel/impi/5.0.3.048/test/

Прежде чем выполнять запуск MPI-программы, полезно будет сделать пробный запуск какой-нибудь стандартной Linux утилиты, например, 'hostname':
$ mpirun -ppn 1 -n 2 -hosts ip-172-31-47-25,ip-172-31-47-24 hostname
ip-172-31-47-25
ip-172-31-47-24

Утилита 'mpirun' — это программа из состава Intel MPI Library, предназначенная для запуска MPI-приложений. Это своего рода «запускальщик». Именно эта программа отвечает за запуск экземляра MPI-программы на каждом из узлов, перечисленных в ее аргументах.

Касательно опций, '-ppn' — количество запускаемых процессов на каждый узел, '-n' — общее число запускаемых процессов, '-hosts' — список узлов, где будет запущено указанное приложение, последний аргумент — путь к исполняемому файлу (это может быть и приложение без MPI).

В нашем примере с запуском утилиты hostname мы должны получить ее вывод (название вычислительного узла) с обоих виртуальных серверов, тогда можно утверждать, что менеджер MPI-процессов работает корректно.

«Hello World» с использованием MPI


В качестве демонстрационного MPI-приложения мы взяли test.c из стандартного набора примеров Intel MPI Library.

Демонстрационное MPI-приложение cобирает с каждого из параллельно запущенных MPI-процессов некоторую информацию о процессе и вычислительном узле, на котором он запущен, и распечатывает эту информацию на головном узле.

Рассмотрим подробнее основные составляющие типичной MPI-программы.

#include "mpi.h"
Подключение заголовочного файла mpi.h, который содержит объявления основных MPI-функций и констант.
Если для компиляции нашего приложения мы используем специальные скрипты из состава Intel MPI Library (mpicc, mpiicc и т.д.), то путь до mpi.h прописывается автоматически. В противном случае, путь до папки include придется задать при компиляции.

MPI_Init (&argc, &argv);
...
MPI_Finalize ();
Вызов MPI_Init() необходим для инициализации среды исполнения MPI-программы. После этого вызова можно использовать остальные MPI-функции.
Последним вызовом в MPI программе является MPI_Finalize(). В случае успешного завершения MPI-программы каждый из запущенных MPI-процессов делает вызов MPI_Finalize(), в котором осуществляется чистка внутренних MPI-ресурсов. Вызов любой MPI-функции после MPI_Finalize() недопустим.

Чтобы описать остальные части нашей MPI-программы необходимо рассмотреть основные термины используемые в MPI-программировании.

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

Кроме того в MPI существуют специальные объекты, называемые коммуникаторами (communicator), описывающие группы процессов. Каждый процесс в рамках одного коммуникатора имеет уникальный ранг. Один и тот же процесс может относиться к разным коммуникаторам и, соответственно, может иметь разные ранги в рамках разных коммуникаторов. Каждая операция пересылки данных в MPI должна выполняться в рамках какого-то коммуникатора. По умолчанию всегда создается коммуникатор MPI_COMM_WORLD, в который входят все имеющиеся процессы.

Вернемся к test.c:

MPI_Comm_size (MPI_COMM_WORLD, &size);
MPI_Comm_rank (MPI_COMM_WORLD, &rank);
MPI_Comm_size() вызов запишет в переменную size (размер) текущего MPI_COMM_WORLD коммуникатора (общее количество процессов, которое мы указали с mpirun опцией '-n').
MPI_Comm_rank() запишет в переменную rank (ранг) текущего MPI-процесса в рамках коммуникатора MPI_COMM_WORLD.

MPI_Get_processor_name (name, &namelen);
Вызов MPI_Get_processor_name() запишет в переменную name строковой идентификатор (название) вычислительного узла, на котором был запущен соответствующий процесс.

Собранная информация (ранг процесса, размерность MPI_COMM_WORLD, название процессора) далее посылается со всех ненулевых рангов на нулевой с помощью функции MPI_Send():
MPI_Send (&rank, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
MPI_Send (&size, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
MPI_Send (&namelen, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
MPI_Send (name, namelen + 1, MPI_CHAR, 0, 1, MPI_COMM_WORLD);

MPI_Send() функция имеет следующий формат:
MPI_Send(buf, count, type, dest, tag, comm)
buf — адрес буфера памяти, в котором располагаются пересылаемые данные;
count — количество элементов данных в сообщении;
type — тип элементов данных пересылаемого сообщения;
dest — ранг процесса-получателя сообщения;
tag — специальный тег для идентификации сообщений;
comm — коммуникатор, в рамках которого выполняется посылка сообщения.
Более подробное описание функции MPI_Send() и ее аргументов, а также других MPI-функций можно найти в MPI-стандарте (язык документации — английский).

На нулевом ранге принимаются сообщения, посланные остальными рангами, и печатаются на экран:
printf ("Hello world: rank %d of %d running on %s\n", rank, size, name);

for (i = 1; i < size; i++) {
    MPI_Recv (&rank, 1, MPI_INT, i, 1, MPI_COMM_WORLD, &stat);
    MPI_Recv (&size, 1, MPI_INT, i, 1, MPI_COMM_WORLD, &stat);
    MPI_Recv (&namelen, 1, MPI_INT, i, 1, MPI_COMM_WORLD, &stat);
    MPI_Recv (name, namelen + 1, MPI_CHAR, i, 1, MPI_COMM_WORLD, &stat);
    printf ("Hello world: rank %d of %d running on %s\n", rank, size, name);
}
Для наглядности нулевой ранг дополнительно печатает свои данные наподобие тех, что он принял с удаленных рангов.

MPI_Recv() функция имеет следующий формат:
MPI_Recv(buf, count, type, source, tag, comm, status)
buf, count, type — буфер памяти для приема сообщения;
source — ранг процесса, от которого должен быть выполнен прием сообщения;
tag — тег принимаемого сообщения;
comm — коммуникатор, в рамках которого выполняется прием данных;
status — указатель на специальную MPI-структуру данных, которая содержит информацию о результате выполнения операции приема данных.

В данной статье мы не будем углубляться в тонкости работы функций MPI_Send()/MPI_Recv(). Описание различных типов MPI-операций и тонкостей их работы — тема отдельной статьи. Отметим только, что нулевой ранг в нашей программе будет принимать сообщения от других процессов строго в определенной последовательности, начиная с первого ранга и по нарастающей (это определяется полем source в функции MPI_Recv(), которое изменяется от 1 до size).

Описанные функции MPI_Send()/MPI_Recv() — это пример так называемых двухточечных (point-to-point) MPI-операций. В таких операциях один ранг обменивается сообщениями с другим в рамках определенного коммуникатора. Существуют также коллективные (collective) MPI-операции, в которых в обмене данными могут участвовать более двух рангов. Коллективные MPI-операции — это тема для отдельной (и, возможно, не одной) статьи.

В результате работы нашей демонстрационной MPI-программы мы получим:
$ mpirun -ppn 1 -n 2 -hosts ip-172-31-47-25,ip-172-31-47-24 /home/ec2-user/intel/impi/5.0.3.048/test/test.exe
Hello world: rank 0 of 2 running on ip-172-31-47-25
Hello world: rank 1 of 2 running on ip-172-31-47-24


Вас заинтересовало рассказанное в этом посте и вы хотели бы принять участие в развитии технологии MPI? Команда разработчиков Intel MPI Library (г.Нижний Новгород) в данный момент активно ищет инженеров-соратников. Дополнительную информацию можно посмотреть на официальном сайте компании Intel и на сайте BrainStorage.

И, напоследок, небольшой опрос по поводу возможных тем для будущих публикаций, посвященных высокопроизводительным вычислениям.
Only registered users can participate in poll. Log in, please.
Список возможных тем для будущих публикаций, посвященных высокопроизводительным вычислениям
81.33% Технологии параллельного программирования 61
42.67% Инструменты Intel для анализа и повышения эффективности параллельных приложений 32
42.67% Инфраструктура кластерных систем 32
75 users voted. 10 users abstained.
Tags:
Hubs:
+22
Comments 9
Comments Comments 9

Articles

Information

Website
www.intel.ru
Registered
Founded
Employees
5,001–10,000 employees
Location
США
Representative
Анастасия Казантаева