Московский Энергетический Институт (Технический Университет) Кафедра Прикладной Математики Реферат на тему «Пособие по 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