...

среда, 11 марта 2015 г.

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


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


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





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

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


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


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




Рассмотрим на живом примере, как устроена типичная 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-процессов работает корректно.




В качестве демонстрационного 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.


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



Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.



This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

Отправить комментарий