Источник: [http://www.root-ua.info/articles.php?id=100]

Распределенное программирование с использованием MPI Часть 2

Автор: Вадим Хохлов


Инициализация библиотеки

Параллельное1 приложение состоит из нескольких процессов или задач, которые выполняются одновременно. Библиотека поддержки MPI самостоятельно создает указанное число процессов. Когда программа получает управление, все процессы уже созданы. Можно указать, что на одном процессоре следует запустить несколько задач. В этом случае их праллельное выполнение будет эмулироваться. Для проведения расчетов процессы объединяются в группы. Каждый процесс в группе имеет уникальный номер. Обычно процесс с номером 0 считается главным и управляет остальными.
Особенностью MPI является понятие области связи (communication domains). При запуске приложения все процессы помещаются в одну область связи. В дальнейшем можно создавать новые области на основе существующих. Все области связи имеют независимую систему нумерации процессов. Программисту доступен коммуникатор - описатель области связи. Большинство функций библиотеки в качесте одного из параметров принимают коммуникатор, который ограничивает сферу их действия указанной областью. Начальная область связи, которая создается автоматически при старте, использует коммуникатор MPI_COMM_WORLD.
Прототипы большинства функций библиотеки объявлены в файле "mpi.h". Существует несколько функций, которые должны вызываться любой распределенной программой (см. табл.1).
Табл. 1. Функции инициализации и деинициализации библиотеки MPI
Табл. 1. Функции инициализации и деинициализации библиотеки MPI
Функция Описание
MPI_Init(int *argc, char ***argv)Выполняет инициализацию библиотеки. В качестве параметров получает указатели
на стандартные параметры функции main
MPI_Finalize()Нормальное завершение библиотеки. Функцию необходимо вызывать перед завершением программы

Библиотека также содержит набор информационных функций. В табл. 2 представлены две из них.
Табл. 2. Информационные функции MPI
Табл. 2. Информационные функции MPI
Функция Описание
MPI_Comm_size(MPI_Comm comm, int *size) Записывает в аргумент size размер группы, т.е. общее количество процессов, выполняющихся в указанной области связи comm
MPI_Comm_rank(MPI_Comm comm, int *rank) В параметр rank помещает номер процесса, вызвавшего функцию

Теперь мы можем написать простейшую распределенную программу:

/*файл first.c */
#include
/* прототипы функций MPI */
#include "mpi.h"
int main(int argc, char **argv)
{
int myrank, size;
/* Инициализируем библиотеку */
MPI_Init (&argc, &argv);
/* определяем число процессов */
MPI_Comm_size (MPI_COMM_WORLD, &size);
/* Определяем номер процесса */
MPI_Comm_rank (MPI_COMM_WORLD, &myrank);
/* выводим полученную информацию */
printf("Number %d size %d\n", myrank, size);
/* Закрываем библиотеку */
MPI_Finalize();
return 0;
}

Компиляция и запуск параллельных программ

Для компиляции C-программ, использующих MPICH, следует использовать скрипт mpicc. Он запускается с теми же параметрами, что и компилятор C и добавляет параметры, необходимые для подключения библиотеки:

mpicc -o first first.c

Для запуска программы используется скрипт mpirun. Ему необходимо указать число создаваемых процессов и название исполняемого файла:

mpirun -np 4 ./first

После запуска Вы увидите на экране следующее:

Number 3 size 4
Number 0 size 4
Number 2 size 4
Number 1 size 4

Порядок выполения процессов неопределен. Поэтому и строки на экране будут появлятся в произвольном порядке. Чтобы убедиться в этом, несколько раз запустите программу.

Передача и прием сообщений

Как уже отмечалось, одним из способов взаимодействия распределенных процессов является передача сообщений. MPI (Message Passing Interface) определяет множество функций, с помощью которых задачи могут принимать и передавать сообщения. Каждое сообщение имеет набор атрибутов - номер процесса-отправителя, номер процесса-получателя и идентификатор сообщения. Идентификатором сообщения является целое число, лежащее в диапазоне от 0 до 32767.

Две самые простые функции для приема/передачи представлены в табл. 3.
Табл. 3. Простейшие функции MPI приема/передачи
Табл. 3. Простейшие функции MPI приема/передачи
ФункцияОписание
int MPI_Send(void* buf, int count, PI_Datatype datatype, int dest, int msgtag, MPI_Comm comm) Выполняет блокирующая посылку сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы сообщения расположены подряд в буфере buf. Значение count может быть нулем. Тип передаваемых элементов datatype должен указываться с помощью предопределенных констант типа. Разрешается передавать сообщение самому себе.
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source,int msgtag, MPI_comm
comm, MPI_Status *status)
Выполняет прием сообщения с идентификатором msgtag от процесса source с блокировкой. Число элементов в принимаемом сообщении не должно превосходить значения count. Если число принятых элементов меньше значения count, то гарантируется, что в буфере buf изменятся только элементы, соответствующие элементам принятого сообщения.


Данные функции выполняют блокирующую приемо-передачу, т.е. при их использовнии управление вызывающему процессу возвращается только после того, как данные приняты или переданы (или скопированы во временный буфер).

Типы данных в MPI

Функциям MPI необходимо указывать тип передаваемых данных. Это связано с тем, что процессы распределенной программы могут одновременно выполняться на машинах, имеющих разную архитектуру, порядок байт и представление данных. Поэтому все функции приема и передачи в MPI оперируют не количеством передаваемых байт, а количеством ячеек, тип которых задается параметром функции. В MPI используется специальный тип - MPI_Datatype - тип "описатель типов", каждая его переменная описывает для MPI один тип. Для каждого базового типа данных, имеющегося в используемом языке программирования, в библиотеке MPI определены соответсвующие константы 2.

Например, в табл. 4 представлены константы, определенные для языка C.
Табл. 4. Константы MPI и типы данных C
Табл. 4. Константы MPI и типы данных C
Константы MPI Тип в C
MPI_CHAR signed char
MPI_SHORT signed short
MPI_INT signed int
MPI_LONG signed long int
MPI_UNSIGNED_CHAR unsigned char
MPI_UNSIGNED_SHORT unsigned short
MPI_UNSIGNED_INT unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLElong double

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

/* Файл mpi2.c */
#include
#include "mpi.h"
int main(int argc, char **argv)
{
int me;
char message[20];
MPI_Status status;

MPI_Init (&argc, &argv);
MPI_Comm_rank (MPI_COMM_WORLD, &me);
if (me ==0 ) /* это главный процесс */
{
strcpy (message, "Hello, there");
/* пошлем процессу 1 строку символов; сообщение имеет номер 99*/
MPI_Send(message, strlen(message) + 1, MPI_CHAR, 1, 99, MPI_COMM_WORLD);
}
else /* это вспомогательный процесс*/
{
/* Ждем от процесса 0 сообщения 99, содержащего строку символов */
MPI_Recv(message, 20, MPI_CHAR, 0, 99, MPI_COMM_WORLD, &status);
printf("proc %d receiveds :%s\n", me, message);
}

MPI_Finalize();
return 0;
}

Программа рассчитана только на два процесса, поэтому ее надо запускать следующим образом:

mpirun -np 2 ./mpi2

На экран она выведет следующее:

proc 1 receiveds :Hello, there

Если скрипту mpirun указать ключ -l, то перед каждой выводимой строкой будет печататься номер процесса, который напечатал эту строку:

mpirun -l -np 2 ./mpi2
1: proc 1 receiveds :Hello, there

Заключение

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


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

2В MPI существуют механизмы создания пользовательских типов данных. Они будут рассмотрены в следующих статьях.