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

в 7:43, , рубрики: Amazon EC2, HPC, intel, mpi, Блог компании Intel, Облачные вычисления, параллельное программирование, Программирование

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

В этом посте мы расскажем об организации обмена данными с помощью 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 %sn", 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 %sn", 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.

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

Автор: iTemko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js