Технология программирования MPI Антонов Александр Сергеевич, к.ф.-м.н., с.н.с. лаборатории Параллельных информационных технологий НИВЦ МГУ
MPI В операциях коллективного взаимодействия процессов участвуют все процессы коммуникатора! Как и для блокирующих процедур, возврат означает то, что разрешен свободный доступ к буферу приема или посылки. Сообщения, вызванные коллективными операциями, не пересекутся с другими сообщениями.
MPI Нельзя рассчитывать на синхронизацию процессов с помощью коллективных операций. Если какой-то процесс завершил свое участие в коллективной операции, то это не означает ни того, что данная операция завершена другими процессами коммуникатора, ни даже того, что она ими начата (если это возможно по смыслу операции).
MPI int MPI_Barrier(MPI_Comm comm) Работа процессов блокируется до тех пор, пока все оставшиеся процессы коммуникатора comm не выполнят эту процедуру. Все процессы должны вызвать MPI_Barrier, хотя реально исполненные вызовы различными процессами коммуникатора могут быть расположены в разных местах программы.
MPI int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm) Рассылка сообщения от процесса root всем процессам данного коммуникатора. Значения параметров count, datatype, root и comm должны быть одинаковыми у всех процессов.
MPI данные процессы
MPI int MPI_Gather(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int root, MPI_Comm comm) Сборка данных из массивов sbuf со всех процессов в буфере rbuf процесса root. Данные сохраняются в порядке возрастания номеров процессов.
MPI На процессе root существенными являются значения всех параметров, а на остальных процессах только значения параметров sbuf, scount, stype, root и comm. Значения параметров root и comm должны быть одинаковыми у всех процессов. Параметр rcount у процесса root обозначает число элементов типа rtype, принимаемых от каждого процесса.
MPI данные процессы
MPI int MPI_Gatherv(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int *rcounts, int *displs, MPI_Datatype rtype, int root, MPI_Comm comm) Сборка различного количества данных из массивов sbuf. Порядок расположения задает массив displs.
MPI rcounts – целочисленный массив, содержащий количество элементов, передаваемых от каждого процесса (индекс равен рангу адресата, длина равна числу процессов в коммуникаторе). displs – целочисленный массив, содержащий смещения относительно начала массива rbuf (индекс равен рангу адресата, длина равна числу процессов в коммуникаторе).
MPI int MPI_Scatter(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int root, MPI_Comm comm) Рассылка данных из массива sbuf процесса root в массивы rbuf всех процессов. Данные рассылаются в порядке возрастания номеров процессов.
MPI На процессе root существенными являются значения всех параметров, а на всех остальных процессах только значения параметров rbuf, rcount, rtype, source и comm. Значения параметров source и comm должны быть одинаковыми у всех процессов.
MPI данные процессы
MPI float sbuf[SIZE][SIZE], rbuf[SIZE]; if(rank == 0) for(i=0; i<SIZE; i++) for (j=0; j<SIZE; j++) sbuf[i][j]=…; if (numtasks == SIZE) MPI_Scatter(sbuf, SIZE, MPI_FLOAT, rbuf, SIZE, MPI_FLOAT, 0, MPI_COMM_WORLD);
MPI int MPI_Scatterv(void *sbuf, int *scounts, int *displs, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int root, MPI_Comm comm) Рассыл ка различного количества данных из массива sbuf. Начало рассылаемых порций задает массив displs.
MPI scounts – целочисленный массив, содержащий количество элементов, передаваемых каждому процессу (индекс равен рангу адресата, длина равна числу процессов в коммуникаторе). displs – целочисленный массив, содержащий смещения относительно начала массива sbuf (индекс равен рангу адресата, длина равна числу процессов в коммуникаторе).
MPI int MPI_Allgather(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm) Сборка данных из массивов sbuf со всех процессов в буфере rbuf каждого процесса. Данные сохраняются в порядке возрастания номеров процессов.
MPI данные процессы
MPI int MPI_Allgatherv(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int *rcounts, int *displs, MPI_Datatype rtype, MPI_Comm comm) Сборка на всех процессах различного количества данных из sbuf. Порядок расположения задает массив displs.
MPI int MPI_Alltoall(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm) Рассылка каждым процессом различных данных всем другим процессам. j -й блок данных i -го процесса попадает в i -й блок j -го процесса.
MPI данные процессы
MPI int MPI_Alltoallv(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm) Рассылка со всех процессов различного количества данных всем другим процессам.
MPI int MPI_Reduce(void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm) Выполнение count независимых глобальных операций op над соответствующими элементами массивов sbuf. Результат получается в массиве rbuf процесса root.
MPI Типы предопределенных глобальных операций: MPI_MAX, MPI_MIN – максимальное и минимальное значения; MPI_SUM, MPI_PROD – глобальная сумма и глобальное произведение; MPI_LAND, MPI_LOR, MPI_LXOR – логические И, ИЛИ, искл. ИЛИ; MPI_BAND, MPI_BOR, MPI_BXOR – побитовые И, ИЛИ, искл. ИЛИ
MPI int MPI_Allreduce(void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) Выполнение count независимых глобальных операций op над соответствующими элементами массивов sbuf. Результат получается в массиве rbuf каждого процесса.
MPI for(i=0; i<n; i++) s[i]=0.0; for(i=0; i<n; i++) for(j=0; j<m; j++) s[i]=s[i]+a[i][j]; MPI_Allreduce(s, r, n, MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);
MPI int MPI_Reduce_scatter(void *sbuf, void *rbuf, int *rcounts, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) В ыполнение rcounts(i) независимых глобальных операций op н ад соответствующими элементами массивов sbuf.
MPI Сначала выполняются глобальные операции, затем результат рассылается по процессам. i -й процесс получает rcounts(i) значений результата и помещает в массив rbuf.
MPI int MPI_Scan(void *sbuf, void *rbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) В ыполнение count независимых частичных глобальных операций op н ад соответствующими элементами массивов sbuf.
MPI i -й процесс выполняет глобальную операцию над соответствующими элементами массива sbuf процессов 0…i и помещает результат в массив rbuf. Окончательный результат глобальной операции получается в массиве rbuf последнего процесса.
MPI int MPI_Op_create (MPI_User_function *func, int commute, MPI_Op *op) Создание пользовательской глобальной операции. Если commute=1, то операция должна быть коммутативной и ассоциативной. Иначе порядок фиксируется по увеличению номеров процессов.
MPI typedef void MPI_User_function (void *invec, void *inoutvec, int *len, MPI_Datatype type) Интерфейс пользовательской функции. int MPI_Op_free(MPI_Op *op) Уничтожение пользовательской глобальной операции.
MPI Группа – упорядоченное множество процессов. Каждому процессу в группе сопоставлено целое число – ранг. MPI_GROUP_EMPTY – пустая группа. MPI_GROUP_NULL – значение, используемое для ошибочной группы. Новую группу можно создать только из уже существующих групп или коммуникаторов.
MPI Коммуникатор – контекст обмена группы. В операциях обмена используются только коммуникаторы! MPI_COMM_WORLD – коммуникатор для всех процессов приложения. MPI_COMM_NULL – значение, используемое для ошибочного коммуникатора. MPI_COMM_SELF – коммуникатор, включающий только вызвавший процесс.
MPI int MPI_Comm_group(MPI_Comm comm, MPI_Group *group) Получение группы group, соответствующей коммуникатору comm.
MPI int MPI_Group_incl(MPI_Group group, int n, int *ranks, MPI_Group *newgroup) Создание группы newgroup из n процессов группы group с рангами ranks(0),…,ranks(n-1), причем рангу ranks(i) в старой группе соответствует ранг i в новой группе. При n=0 создается пустая группа MPI_GROUP_EMPTY.
MPI int MPI_Group_excl(MPI_Group group, int n, int *ranks, MPI_Group *newgroup) Создание группы newgroup из процессов группы group, исключая процессы с рангами ranks(0),…,ranks(n-1), причем порядок процессов в новой группе соответствует порядку процессов в старой группе.
MPI int MPI_Group_intersection (MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) Создание группы newgroup из пересечения групп group1 и group2.
MPI int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) Создание группы newgroup из объединения групп group1 и group2.
MPI int MPI_Group_difference (MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) Создание группы newgroup из разности групп group1 и group2.
MPI int MPI_Group_free(MPI_Group *group) Уничтожение группы group. Переменная group принимает значение MPI_GROUP_NULL.
MPI int MPI_Group_size(MPI_Group group, int *size) int MPI_Group_rank(MPI_Group group, int *rank) Определение количества процессов и номера процесса в группе. Если процесс не входит в группу, то возвращается значение MPI_UNDEFINED.
MPI int MPI_Group_translate_ranks (MPI_Group group1, int n, int *ranks1, MPI_Group group2, int *ranks2) В массиве ranks2 возвращаются ранги в группе group2 процессов с рангами ranks1 в группе group1.
MPI int MPI_Group_compare(MPI_Group group1, MPI_Group group2, int *result) Сравнение групп group1 и group2. Если совпадают, то возвращается значение MPI_IDENT. Если отличаются только рангами процессов, то возвращается значение MPI_SIMILAR. Иначе возвращается значение MPI_UNEQAL.
MPI int MPI_Comm_dup(MPI_Comm comm, MPI_Comm *newcomm) Создание нового коммуникатора newcomm с той же группой процессов и атрибутами, что и у коммуникатора comm.
MPI int MPI_Comm_create(MPI_Comm comm, MPI_Group group, MPI_Comm *newcomm) Создание нового коммуникатора newcomm из коммуникатора comm с группой процессов group. Вызов должен стоять во всех процессах коммуникатора comm. На процессах, не принадлежащих group вернется значение MPI_COMM_NULL.
MPI MPI_Comm_group(MPI_COMM_WORLD, &group); for(i=0; i<nprocs/2; i++) ranks[i]=i; if (rank < nprocs/2) MPI_Group_incl(group, nprocs/2, ranks, &new_group); else MPI_Group_excl(group, nprocs/2, ranks, &new_group);
MPI MPI_Comm_create(MPI_COMM_WORLD, new_group, &new_comm); MPI_Allreduce(sbuf, rbuf, 1, MPI_INT, MPI_SUM, new_comm); MPI_Group_rank(new_group, &new_rank); printf("rank= %d newrank= %d rbuf= %d \n", rank, newrank, rbuf);
MPI int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm) Разбиение коммуникатора comm на несколько по числу значений параметра color. В одну подгруппу попадают процессы с одним значением color. Процессы с б Ольшим значением key получат больший ранг.
MPI Процессы, которые не должны войти в новые группы, указывают в качестве color константу MPI_UNDEFINED. Им в параметре newcomm вернется значение MPI_COMM_NULL. MPI_Comm_split(MPI_COMM_WORLD, rank%3, rank, new_comm)
MPI int MPI_Comm_free(MPI_Comm comm) Удаление коммуникатора comm. Переменной comm присваивается значение MPI_COMM_NULL.
MPI Топология – механизм сопоставления процессам альтернативной схемы адресации. В MPI топологии виртуальны, не связаны с физической топологией сети. Два типа топологий: декартова (прямоугольная решетка произвольной размерности) топология графа.
MPI int MPI_Cart_create(MPI_Comm comm, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_cart) Создание декартовой топологии comm_cart. ndims – размерность декартовой решетки, dims – число элементов в каждом измерении.
MPI periods – массив из ndims элементов, определяющий, является ли решетка периодической вдоль каждого измерения. reorder – при значении 1 системе разрешено менять порядок нумерации процессов. Подпрограмма должна быть вызвана всеми процессами коммуникатора. Некоторым процессам может вернуться значение MPI_COMM_NULL.
MPI int MPI_Dims_create(nnodes, ndims, dims) Определение размеров dims для каждой из ndims размерностей при создании декартовой топологии для nnodes процессов. dims(i) рассчитывается, если перед вызовом функции оно равно 0, иначе оставляется без изменений.
MPI Размеры по разным размерностям устанавливаются так, чтобы быть возможно близкими друг к другу. Перед вызовом функции значение nnodes должно быть кратно произведению ненулевых значений массива dims. Выходные значения массива dims, переопределенные функцией, будут упорядочены в порядке убывания.
MPI int MPI_Cart_coords(MPI_Comm comm, int rank, int maxdims, int *coords) Определение декартовых координат процесса по его рангу. Координаты возвращаются в массиве coords. Отсчет координат по каждому измерению начинается с 0.
MPI int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank) Определение ранга процесса по его декартовым координатам. Для периодических решеток координаты вне допустимых интервалов пересчитываются, для непериодических – ошибочны.
MPI int MPI_Cart_sub(MPI_Comm comm, int *dims, MPI_Comm *newcomm) Расщепление коммуникатора comm на подгруппы, соответствующие декартовым подрешеткам меньшей размерности. i –ый элемент массива равен 1, если i –ое измерение должно остаться в подрешетке.
MPI int MPI_Cartdim_get(MPI_Comm comm, int *ndims) Определение размерности декартовой топологии коммуникатора comm.
MPI int MPI_Cart_get(MPI_Comm comm, int maxdims, int *dims, int *periods, int *coords) Получение информации о декартовой топологии коммуникатора comm и координатах в ней вызвавшего процесса.
MPI int MPI_Cart_shift(MPI_Comm comm, int direction, int disp, int *source, int *dest) Получение номеров посылающего ( source ) и принимающего ( dest ) процессов в декартовой топологии коммуникатора comm для осуществления сдвига вдоль измерения direction на величину disp.
MPI Для периодических измерений осуществляется циклический сдвиг, для непериодических – линейный сдвиг. Для n -мерной декартовой решетки значение direction должно быть в пределах от 0 до n-1. Значения source и dest можно использовать, например, для обмена функцией MPI_Sendrecv.
MPI periods[0]=1; periods[1]=1; MPI_Cart_create(MPI_COMM_WORLD, 2, dims, periods, 1, &comm); MPI_Comm_rank(comm, &rank); MPI_Cart_coords(comm, rank, 2, coords); MPI_Cart_shift(comm, 0, shift, &source, &dest); MPI_Sendrecv_replace(a, 1, MPI_FLOAT, dest, 0, source, 0, comm, &status);
MPI int MPI_Graph_create(MPI_Comm comm, int nnodes, int *index, int *edges, int reorder, MPI_Comm *comm_graph) Создание топологии графа comm_graph. nnodes – число вершин графа, index(i) содержит суммарное количество соседей для первых i вершин.
MPI edges содержит упорядоченный список номеров процессов-соседей. reorder – при значении 1 системе разрешено менять порядок нумерации процессов. Подпрограмма должна быть вызвана всеми процессами коммуникатора. Некоторым процессам может вернуться значение MPI_COMM_NULL.
MPI nnodes=4 index=2, 3, 4, 6 edges=1, 3, 0, 3, 0, 2 0, , 30 Соседи Процесс
MPI int MPI_Graph_neighbors_count (MPI_Comm comm, int rank, int *nneighbors) Определение количества непосредственных соседей данной вершины графа.
MPI int MPI_Graph_neighbors(MPI_Comm comm, int rank, int max, int *neighbors) Определение непосредственных соседей данной вершины графа.
MPI int MPI_Graphdims_get(MPI_Comm comm, int *nnodes, int *nedges) Определение числа вершин и числа ребер графа.
MPI int MPI_Graph_get(MPI_Comm comm, int maxindex, int maxedges, int *index, int *edges) Определение информации о топологии графа.
MPI int MPI_Topo_test(MPI_Comm comm, int *type) Определение типа топологии, связанной с коммуникатором comm. MPI_GRAPH для графа MPI_CART для декартовой топологии MPI_UNDEFINED – нет связанной топологии.
MPI Сообщение – массив однотипных данных, расположенных в последовательных ячейках памяти. Для пересылки разнотипных данных можно использовать: Производные типы данных Упаковку данных
MPI Производные типы данных создаются во время выполнения программы с помощью подпрограмм-конструкторов. Создание типа: Конструирование типа Регистрация типа
MPI Производный тип данных характеризуется последовательностью базовых типов и набором значений смещения относительно начала буфера обмена. Смещения могут быть как положительными, так и отрицательными, не требуется их упорядоченность.
MPI int MPI_Type_contiguous(int count, MPI_Datatype type, MPI_Datatype *newtype) Создание нового типа данных newtype, состоящего из count последовательно расположенных элементов базового типа данных type.
MPI int MPI_Type_vector(int count, int blocklen, int stride, MPI_Datatype type, MPI_Datatype *newtype) Создание нового типа данных newtype, состоящего из count блоков по blocklen элементов базового типа данных type. Следующий блок начинается через stride элементов после начала предыдущего.
MPI count=2; blocklen=3; stride=5; MPI_Type_vector(count, blocklen, stride, MPI_DOUBLE, &newtype); Создание нового типа данных (тип элемента, количество элементов от начала буфера): {( MPI_DOUBLE, 0), ( MPI_DOUBLE, 1), ( MPI_DOUBLE, 2), ( MPI_DOUBLE, 5), ( MPI_DOUBLE, 6), ( MPI_DOUBLE, 7)}
MPI int MPI_Type_hvector(int count, int blocklen, MPI_Aint stride, MPI_Datatype type, MPI_Datatype *newtype) Создание нового типа данных newtype, состоящего из count блоков по blocklen элементов базового типа данных type. Следующий блок начинается через stride байт после начала предыдущего.
MPI int MPI_Type_indexed(int count, int *blocklens, int *displs, MPI_Datatype type, MPI_Datatype *newtype) Создание нового типа данных newtype, состоящего из count блоков по blocklens(i) элементов базового типа данных. i -й блок начинается через displs(i) элементов с начала буфера.
MPI for(i=0; i<n; i++){ blocklens[i]=n-i; displs[i]=(n+1)*i; } MPI_Type_indexed(n, blocklens, displs, MPI_DOUBLE, &newtype) Создание нового типа данных для описания верхнетреугольной матрицы.
MPI int MPI_Type_hindexed(int count, int *blocklens, MPI_Aint *displs, MPI_Datatype type, MPI_Datatype *newtype) Создание нового типа данных newtype, состоящего из count блоков по blocklens(i) элементов базового типа данных. i -й блок начинается через displs(i) байт с начала буфера.
MPI int MPI_Type_struct(int count, int *blocklens, MPI_Aint *displs, MPI_Datatype *types, MPI_Datatype *newtype) Создание структурного типа данных из count блоков по blocklens(i) элементов типа types(i). i -й блок начинается через displs(i) байт.
MPI blocklens[0]=3; blocklens[1]=2; types[0]=MPI_DOUBLE; types[1]=MPI_CHAR; displs[0]=0; displs[1]=24; MPI_Type_struct(2, blocklens, displs, types, &newtype); Создание нового типа данных (тип элемента, количество байт от начала буфера): {( MPI_DOUBLE, 0), ( MPI_DOUBLE, 8), ( MPI_DOUBLE, 16), ( MPI_CHAR, 24), ( MPI_CHAR, 25)}
MPI int MPI_Type_commit(MPI_Datatype *datatype) Регистрация созданного производного типа данных datatype. После регистрации этот тип данных можно использовать в операциях обмена.
MPI int MPI_Type_free(MPI_Datatype *datatype) Аннулирование производного типа данных datatype. datatype устанавливается в значение MPI_DATATYPE_NULL. Производные от datatype типы данных остаются. Предопределенные типы данных не могут быть аннулированы.
MPI int MPI_Type_size(MPI_Datatype datatype, int *size) Определение размера типа datatype в байтах (объема памяти, занимаемого одним элементом данного типа).
MPI MPI_Type_lb(MPI_Datatype datatype, MPI_Aint *displ) Определение смещения displ в байтах нижней границы элемента типа данных datatype от базового адреса.
MPI int MPI_Type_ub(MPI_Datatype datatype, MPI_Aint *displ) Определение смещения displ в байтах верхней границы элемента типа данных datatype от базового адреса.
MPI int MPI_Type_extent(MPI_Datatype datatype, MPI_Aint *extent) Определение диапазона типа datatype в байтах (разницы между верхней и нижней границами элемента данного типа).
MPI MPI_Address(void *location, MPI_Aint *address) Определение абсолютного байт-адреса address размещения массива location в оперативной памяти. Адрес отсчитывается от базового адреса, значение которого содержится в константе MPI_BOTTOM.
MPI blocklens[0]=1; blocklens[1]=1; types[0]=MPI_DOUBLE; types[1]=MPI_CHAR; MPI_Address(dat1, address[0]); displs[0]=address[0]; MPI_Address(dat2, address[1]); displs[1]=address[1]; MPI_Type_struct(2, blocklens, displs, types, &newtype); MPI_Type_commit(newtype); MPI_Send(MPI_BOTTOM, 1, newtype, dest, tag, MPI_COMM_WORLD);
MPI int MPI_Pack(void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm) Упаковка incount элементов типа datatype из массива inbuf в массив outbuf со сдвигом position байт. outbuf должен содержать хотя бы outsize байт.
MPI Параметр position увеличивается на число байт, равное размеру записи. Параметр comm указывает на коммуникатор, в котором в дальнейшем будет пересылаться сообщение.
MPI int MPI_Unpack(void *inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm) Распаковка из массива inbuf со сдвигом position байт в массив outbuf outcount элементов типа datatype.
MPI int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size) Определение необходимого объема памяти (в байтах) для упаковки incount элементов типа datatype.
MPI float a[10]; char b[10], buf[100]; position=0; if(rank == 0){ MPI_Pack(a, 10, MPI_FLOAT, buf, 100, &position, MPI_COMM_WORLD); MPI_Pack(b, 10, MPI_CHAR, buf, 100, &position, MPI_COMM_WORLD); MPI_BCAST(buf, 100, MPI_PACKED, 0, MPI_COMM_WORLD); }
MPI else{ MPI_Bcast(buf, 100, MPI_PACKED, 0, MPI_COMM_WORLD); position=0; MPI_Unpack(buf, 100, &position, &a, 10, MPI_FLOAT, MPI_COMM_WORLD); MPI_Unpack(buf, 100, &position, &b, 10, MPI_CHAR, MPI_COMM_WORLD); }
MPI Задания: написать программу, в которой операция глобального суммирования моделируется схемой сдваивания (каскадная схема) с использованием пересылок данных типа точка-точка. Сравнить эффективность такого моделирования с использованием функции MPI_Reduce.
MPI Задания: написать программу, в которой реализуется распределенное вычисление скалярного произведения двух больших векторов. Исследовать зависимость ускорения программы от числа процессоров для различных длин векторов.
MPI Литература 1. MPI: A Message-Passing Interface Standard (Version 1.1) ( 2. Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. СПб.: БХВ-Петербург, Антонов А.С. Параллельное программирование с использованием технологии MPI: Учебное пособие.-М.: Изд-во МГУ, Антонов А.С. Введение в параллельные вычисления (методическое пособие). М.: Изд-во Физического факультета МГУ, 2002.
MPI Литература 5. Букатов А.А., Дацюк В.Н., Жегуло А.И. Программирование многопроцессорных вычислительных систем. Ростов-на- Дону: Издательство ООО "ЦВВР", Шпаковский Г.И., Серикова Н.В. Программирование для многопроцессорных систем в стандарте MPI: Пособие. Минск: БГУ, Немнюгин С.А., Стесик О.Л. Параллельное программирование для многопроцессорных вычислительных систем. СПб.: БХВ-Петербург, Корнеев В.Д. Параллельное программирование в MPI. Новосибирск: Изд-во СО РАН, 2000.