Московский Энергетический Институт (Технический Университет) Реферат

advertisement
Московский Энергетический Институт
(Технический Университет)
Кафедра Прикладной Математики
Реферат
на тему «Пособие по MPI»
Выполнил:
Группа:
Москва 2007
Ясенков Е.М.
А-13-03
1. MPI_Init, MPI_Finalize, “Hello, World” с анализом результата MPI_Init
Функция MPI_Init() применяется для инициализации среды выполнения MPIпрограммы. Параметрами функции являются количество аргументов в командной строке и
адрес указателя на массив символов текста самой командной строки.
Функция MPI_Finalize() применяется для завершения выполнения MPIпрограммы.
Как результат, можно отметить, что структура параллельной программы,
разработанная с использованием MPI, должна иметь следующий вид:
#include "mpi.h"
int main(int argc, char *argv[])
{
// программный код без использования функций MPI
MPI_Init(&argc, &argv);
// программный код с использованием функций MPI
MPI_Finalize();
// программный код без использования функций MPI
return 0;
}
Все функции MPI (кроме MPI_Wtime и MPI_Wtick) возвращают в качестве своего
значения код завершения. При успешном выполнении функции возвращаемый код равен
MPI_SUCCESS. Другие значения кода завершения свидетельствуют об обнаружении тех или
иных ошибочных ситуаций в ходе выполнения функций. Для выяснения типа обнаруженной
ошибки используются предопределенные именованные константы, среди которых:
MPI_ERR_BUFFER — неправильный указатель на буфер;
MPI_ERR_TRUNCATE — сообщение превышает размер приемного буфера;
MPI_ERR_COMM — неправильный коммуникатор;
MPI_ERR_RANK — неправильный ранг процесса и др.
Далее следует пример программы “Hello, World” с анализом результата MPI_Init():
#include "mpi.h"
int main(int argc, char* argv[])
{
int ProcRank, Error;
Error = MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &ProcRank);
printf("Hello, World! from process %2d... ", ProcRank);
if (Error == MPI_SUCCESS)
printf("Ошибок нет\n");
else if (Error == MPI_ERR_RANK)
printf("Ошибка - неправильный ранг процесса");
else if (Error == MPI_ERR_UNKNOWN)
printf("Неизвестная ошибка");
MPI_Finalize();
return 0;
}
Результат выполнения программы на 5 процессах:
Hello,
Hello,
Hello,
Hello,
Hello,
World!
World!
World!
World!
World!
from
from
from
from
from
process
process
process
process
process
2...
3...
4...
0...
1...
Ошибок
Ошибок
Ошибок
Ошибок
Ошибок
нет
нет
нет
нет
нет
--------------------------------------------------------------------------2
2. MPI_Send, MPI_Recv, анализ структуры MPI_Status
Функция MPI_Send применяется для передачи сообщения:
int MPI_Send(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm), где
 buf — адрес буфера памяти, в котором располагаются данные отправляемого
сообщения;
 count — количество элементов данных в сообщении;
 type — тип элементов данных пересылаемого сообщения;
 dest — ранг процесса, которому отправляется сообщение;
 tag — значение-тег, используемое для идентификации сообщения;
 comm — коммуникатор, в рамках которого выполняется передача данных.
Функция MPI_Recv применяется для получения сообщения:
int MPI_Recv(void *buf, int count, MPI_Datatype type, int source, int tag, MPI_Comm comm,
MPI_Status *status), где
 buf, count, type — буфер памяти для приема сообщения, назначение каждого
отдельного параметра соответствует описанию в MPI_Send;
 source — ранг процесса, от которого должен быть выполнен прием сообщения;
 tag — тег сообщения, которое должно быть принято для процесса;
 comm — коммуникатор, в рамках которого выполняется передача данных;
 status – указатель на структуру данных с информацией о результате выполнения
операции приема данных.
Структура MPI_Status (взято из файла mpi.h):
typedef struct MPI_Status {
int count; // необязательное поле - количество элементов данных в сообщении
int cancelled; // необязательное поле
int MPI_SOURCE; // ранг процесса–отправителя принятого сообщения;
int MPI_TAG; // тэг принятого сообщения
int MPI_ERROR; // код ошибки
} MPI_Status;
В следующем примере процесс 0 посылает сообщение из 12 символов процессу 1
и тот также в ответ шлет сообщение процессу 0:
# include "mpi.h"
# include <stdio.h>
int main(int argc, char** argv)
{
int numtasks, rank, rc, dest, source, tag = 1;
char* outmsg1 = "Hello first\0";
char* outmsg2 = "Hello zero\0";
char inmsg[12];
MPI_Status stat;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == 0){
dest = 1;
source = 1;
rc = MPI_Send(outmsg1, 12, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
rc = MPI_Recv(inmsg, 12, MPI_CHAR, source, tag, MPI_COMM_WORLD, &stat);
printf("Process %d received message: \"%s\"\n", rank, inmsg);
}
3
else if(rank == 1){
dest = 0;
source = 0;
rc = MPI_Send(outmsg2, 12, MPI_CHAR, dest, tag, MPI_COMM_WORLD);
rc = MPI_Recv(inmsg, 12, MPI_CHAR, source, tag, MPI_COMM_WORLD, &stat);
printf("Process %d received message: \"%s\"\n", rank, inmsg);
}
MPI_Finalize();
return 0;
}
Результат выполнения программы на 2 процессах:
Process 1 received message: "Hello first"
Process 0 received message: "Hello zero"
----------------------------------------------------------------------------------------------------------------3. Разрешение взаимной блокировки Send/Recv с помощью функции MPI_Sendrecv
Пример взаимной блокировки этих двух функций:
Допустим, у нас есть несколько процессов, которые взаимодействуют следующим
образом:
Каждый процесс содержит следующий код:
for(i=0; i<n; i++)
{
MPI_Send(…);
}
for(i=0; i<n; i++)
{
MPI_Recv(…);
}
Если запустить эти процессы, мы получим тупик, так как процессы зайдут в
функцию MPI_Send() и будут ждать ее завершения. А для ее завершения необходимо
выполнение функции MPI_Recv, которая никогда не выполнится.
Чтобы избежать взаимной блокировки можно выполнить взаимный обмен
данными между процессами, используя совмещенную операцию MPI_Sendrecv.
int MPI_Sendrecv(void *sbuf, int scount, MPI_Datatype stype, int dest, int stag, void *rbuf, int
count, MPI_Datatype rtype, int source, int rtag, MPI_Comm comm, MPI_Status *status), где
 sbuf, scount, stype, dest, stag — параметры передаваемого сообщения;
 rbuf, rcount, rtype, source, rtag — параметры принимаемого сообщения;
 comm — коммуникатор, в рамках которого выполняется передача данных;
 status — структура данных с информацией о результате выполнения операции.
Тогда каждый процесс будет содержать следующий код:
4
for(i=0; i<n; i++)
{
MPI_Sendrecv(…);
}
-----------------------------------------------------------------------4. Пример использования MPI_Waitall, MPI_Waitany
Процедура MPI_Waitall:
int MPI_Waitall(int count, MPI_Request *requests, MPI_Status *statuses), где
–
count - число идентификаторов;
–
requests - массив идентификаторов асинхронного приема или передачи;
–
OUT statuses - параметры сообщений.
Выполнение процесса блокируется до тех пор, пока все операции обмена,
ассоциированные с указанными идентификаторами, не будут завершены. Если во время
одной или нескольких операций обмена возникли ошибки, то поле ошибки в элементах
массива statuses будет установлено в соответствующее значение.
Пример. Сеть из 4 узлов с круговой топологией. Каждый узел посылает двум
соседям сообщения и ждет ответа от них. Работа каждого узла не завершится до тех пор,
пока все 4 операции не будут выполнены.
# include <mpi.h>
# include <stdio.h>
int main(int argc, char** argv)
{
int numtasks, rank, next, prev, buf[2], tag1 = 1, tag2 = 2;
MPI_Request reqs[4];
MPI_Status stats[4];
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
prev = rank - 1;
next = rank + 1;
if (rank == 0) prev = numtasks - 1;
if (rank == (numtasks - 1)) next = 0;
MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);
MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);
MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);
MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);
MPI_Waitall(4, reqs, stats);
printf("Node %d: all ok!\n", rank);
MPI_Finalize();
}
Результат выполнения программы на 4 процессах:
Node
Node
Node
Node
1:
3:
2:
0:
all
all
all
all
ok!
ok!
ok!
ok!
Процедура MPI_Waitany:
int MPI_Waitany(int count, MPI_Request *requests, int *index, MPI_Status *status), где
5
–
count - число идентификаторов;
–
requests - массив идентификаторов асинхронного приема или передачи;
–
OUT index - номер завершенной операции обмена;
–
OUT status - параметры сообщений.
Выполнение процесса блокируется до тех пор, пока какая-либо операция обмена,
ассоциированная с указанными идентификаторами, не будет завершена. Если несколько
операций могут быть завершены, то случайным образом выбирается одна из них. Параметр
index содержит номер элемента в массиве requests, содержащего идентификатор
завершенной операции.
Пример. Изменим предыдущий пример используя функцию MPI_Waitany вместо
MPI_Waitall.
# include <mpi.h>
# include <stdio.h>
int main(int argc, char** argv)
{
int numtasks, rank, next, prev, buf[2], tag1 = 1, tag2 = 2;
int *i;
MPI_Request reqs[4];
MPI_Status stats[4];
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
prev = rank - 1;
next = rank + 1;
if (rank == 0) prev = numtasks - 1;
if (rank == (numtasks - 1)) next = 0;
MPI_Irecv(&buf[0], 1, MPI_INT, prev, tag1, MPI_COMM_WORLD, &reqs[0]);
MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);
MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]);
MPI_Isend(&rank, 1, MPI_INT, next, tag1, MPI_COMM_WORLD, &reqs[3]);
MPI_Waitany(4, reqs, &i, stats);
printf("Node %d: all ok!\n", rank);
MPI_Finalize();
}
Результат выполнения программы на 4 процессах:
Node
Node
Node
Node
1:
3:
2:
0:
all
all
all
all
ok!
ok!
ok!
ok!
-------------------------------------------------5. Пример использования MPI_Bcast
Broadcast - передача сообщения от одного процесса ко всем остальным процессам группы,
включая его самого. Схематически broadcast можно изобразить следующим образом:
6
int MPI_Bcast(void *buff, int count, MPI_Datatype datatype, int root, MPI_Comm comm), где
 buff - адрес начала буфера, хранящего передаваемое сообщение
 count - количество передаваемых элементов
 datatype - тип передаваемых элементов
 root - номер корневого процесса, т.е. номер процесса от которого будет передаваться
сообщение всем остальным процессам
 comm - идентификатор группы
Пример. Каждый не root процесс в буфере sbuf хранит строку "I am not root", а
root-процесс кладет себе в буфер sbuf строку "Hello from root" и рассылает ее при помощи
MPI_Bcast всем остальным. В результате, у каждого не root процесса в буфере оказывается
именно это сообщение.
# include <mpi.h>
# include <stdio.h>
# include <string.h>
int main(int argc, char** argv)
{
int numtasks, rank, root;
char sbuf[20];
strcpy(sbuf, "I am not root\0");
root = 1;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if(rank == root)
strcpy(sbuf, "Hello from root\0");
MPI_Bcast(sbuf, 16, MPI_CHAR, root, MPI_COMM_WORLD);
if(rank == root) strcpy(sbuf, "I am root\0");
printf("I am %d. Message received: %s\n", rank, sbuf);
MPI_Finalize();
return 0;
}
Результат выполнения программы на 5 процессах:
I
I
I
I
I
am
am
am
am
am
0.
3.
4.
1.
2.
Message
Message
Message
Message
Message
received:
received:
received:
received:
received:
Hello from
Hello from
Hello from
I am root
Hello from
root
root
root
root
-------------------------------------------------------------------------6. Пример использования MPI_Gatherv и MPI_Scatterv
Функция MPI_Gatherv:
int MPI_Gatherv (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int
*recvcnts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm), где





sendbuf - Адрес, по которому находятся отправляемые данные
sendcount - Число отправляемых данных (в каждом процессе — свое)
sendtype - Тип отправляемых данных
recvcounts - Целочисленный массив размера, равного числу процессов в
коммуникаторе; каждый элемент этого массива содержит число элементов,
принимаемых от процесса с рангом, равным номеру этого элемента.
displs - Целочисленный массив размера, равного числу процессов в коммуникаторе. iтый элемент определяет смещение (в элементах) относительно адреса recvbuf, по
7




которому разместить данные, приходящие от процесса с рангом i. (имеет значение
только в процессе с рангом root)
recvtype - Тип принимаемых данных (имеет значение только в процессе с рангом
root)
root - ранг процесса, который принимает данные.
comm - communicator (handle)
OUT recvbuf - Адрес, по которому принимаются данные. (имеет значение только в
процессе с рангом root)
Пример. Здесь каждый процесс посылает 100 чисел типа int корневому процессу,
но каждое множество (100 элементов) размещается с некоторым шагом (stride) относительно
конца размещения предыдущего множества. Чтобы получить этот эффект нужно
использовать MPI_GATHERV и аргумент displs. Полагаем, что stride > 100.
MPI_Comm comm;
int gsize,sendarray[100];
int root, *rbuf, stride;
int *displs,i,*rcounts;
...
MPI_Comm_size(comm, &gsize);
rbuf = (int *)malloc(gsize*stride*sizeof(int));
displs = (int *)malloc(gsize*sizeof(int));
rcounts = (int *)malloc(gsize*sizeof(int));
for (i=0; i<gsize; ++i) {
displs[i] = i*stride;
rcounts[i] = 100;
}
MPI_Gatherv(sendarray, 100, MPI_INT, rbuf, rcounts, displs,
MPI_INT, root, comm);
Функция MPI_Scatterv:
MPI_SCATTERV(sendbuf, sendcounts, displs, sendtype, recvbuf, recvcount, ecvtype,
root, comm)
IN
IN
IN
IN
OUT
IN
IN
sendbuf
sendcounts
адрес буфера посылки (альтернатива, используется только корневым
процессом)
целочисленный массив (размера группы), определяющий число
элементов, для отправки каждому процессу
целочисленный массив (размера группы). Элемент i указывает
displs смещение (относительно sendbuf, из которого берутся данные для
процесса take the i)
sendtype тип элементов посылающего буфера (дескриптор)
recvbuf адрес принимающего буфера (альтернатива)
recvcount число элементов в посылающем буфере (целое)
recvtype тип данных элементов принимающего буфера (дескриптор)
IN
root номер посылающего процесса (целое)
IN
comm коммуникатор (дескриптор)
8
Пример. Корневой процесс рассылает множества из 100 чисел типа int остальным
процессам, но множества размещены в посылающем буфере с шагом stride, поэтому нужно
использовать MPI_Scatterv. Полагаем stride > 100.
MPI_Comm comm;
int gsize,*sendbuf;
int root, rbuf[100], i, *displs, *scounts;
...
MPI_Comm_size(comm, &gsize);
sendbuf = (int*)malloc(gsize*stride*sizeof(int));
...
displs = (int*)malloc(gsize*sizeof(int));
scounts = (int*)malloc(gsize*sizeof(int));
for (i=0; i<gsize; ++i) {
displs[i] = i*stride;
scounts[i] = 100;
}
MPI_Scatterv(sendbuf, scounts, displs, MPI_INT, rbuf, 100,
MPI_INT, root, comm);
9
Download