Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В
..pdfФункция MPI_TYPE_FREE маркирует объекты типа данных, связанные с datatype для удаления и установки типа данных в MPI_DATATYPE_NULL. Любой обмен, который использует этот тип данных, будет завершен нормально. Производные типы данных, которые произошли от удаленного типа, не меняются.
Пример 3.18. Пример использования MPI_TYPE_COMMIT.
INTEGER type1, type2
CALL MPI_TYPE_CONTIGUOUS(5, MPI_REAL, type1, ierr)
!создан новый объект типа данных
CALL MPI_TYPE_COMMIT(type1, ierr)
!теперь type1 может быть использован для обмена type2 = type1
!type2 может быть использован для обмена
CALL MPI_TYPE_VECTOR(3, 5, 4, MPI_REAL, type1, ierr)
!создан новый необъявленный объект типа
CALL MPI_TYPE_COMMIT(type1, ierr)
! теперь type1 может быть использован снова для обмена
Удаление типа данных не воздействует на другой тип, который был построен от удаленного типа. Система ведет себя как если бы аргументы входного типа данных были переданы конструктору производного типа данных по значению.
3.10.5. Использование универсальных типов данных Пример 3.19. Пример использования производных типов.
CALL MPI_TYPE_CONTIGUOUS( 2, MPI_REAL, type2, ...) CALL MPI_TYPE_CONTIGUOUS( 4, MPI_REAL, type4, ...) CALL MPI_TYPE_CONTIGUOUS( 2, type2, type22, ...)
...
CALL MPI_SEND( a, 4, MPI_REAL, ...) CALL MPI_SEND( a, 2, type2, ...) CALL MPI_SEND( a, 1, type22, ...) CALL MPI_SEND( a, 1, type4, ...)
...
CALL MPI_RECV( a, 4, MPI_REAL, ...) CALL MPI_RECV( a, 2, type2, ...) CALL MPI_RECV( a, 1, type22, ...) CALL MPI_RECV( a, 1, type4, ...)
91
Каждой передаче соответствует операция приема. Тип данных может описывать перекрывающиеся элементы. Использование такого типа в операциях приема неверно. Предположим, что выполнена опе-
рация MPI_RECV (buf, count, datatype, dest, tag, comm, status), где карта типа имеет n элементов. Принятое сообщение не обязано ни заполнять весь буфер, ни заполнять число ячеек, которое кратно n. Может быть принято любое число k базисных элементов, где 0 ≤ k ≤ count · n. Номер полученных базисных элементов может быть получен из статуса с помощью функции MPI_GET_ELEMENTS.
Ранее определенная функция MPI_GET_COUNT имеет различное поведение. Она возвращает количество полученных «элементов верхнего уровня», то есть количество «копий» типа данных. В предыдущем примере MPI_GET_COUNT может возвратить любое целое число k, где 0 ≤ k ≤ count · n. Если MPI_GET_COUNT возвращает k, тогда число принятых базисных элементов (и значение, возвращенное MPI_GET_ELEMENTS) есть n · k. Если число полученных базисных элементов не кратно n, то есть операция приема не получила общее число «копий» datatype, то MPI_GET_COUNT возвращает значение
MPI_UNDEFINED.
MPI_GET_ELEMENTS ( status, datatype, count)
IN |
status |
возвращает статус операции приема (статус) |
IN |
datatype |
тип данных операции приема (дескриптор) |
OUT |
count |
число принятых базисных элементов (целое) |
int MPI_Get_elements (MPI_Status *status, MPI_Datatype datatype, int *count)
MPI_GET_ELEMENTS (STATUS, DATATYPE, COUNT, IERROR) INTEGER STATUS(MPI_STATUS_SIZE), DATATYPE, COUNT, IERROR
int MPI::Status::Get_elements(const MPI::Datatype& datatype) const
Пример 3.20. Использование MPI_GET_COUNT,MPI_GET_ELEMENT
CALL MPI_TYPE_CONTIGUOUS(2, MPI_REAL, Type2, ierr)
CALL MPI_TYPE_COMMIT(Type2, ierr)
CALL MPI_COMM_RANK(comm, rank, ierr)
IF(rank.EQ.0) THEN
CALL MPI_SEND(a, 2, Type2, 1, 0, comm, ierr)
CALL MPI_SEND(a, 3, Type2, 1, 0, comm, ierr)
ELSE
CALL MPI_RECV(a, 2, Type2, 0, 0, comm, stat, ierr)
CALL MPI_GET_COUNT(stat, Type2, i, ierr)
!возвращает i=1
92
CALL MPI_GET_ELEMENTS(stat, Type2, i, ierr)
!возвращает i=2
CALL MPI_RECV(a, 2, Type2, 0, 0, comm, stat, ierr)
CALL MPI_GET_COUNT(stat,Type2,i,ierr)
!возвращает i=MPI_UNDEFINED
CALL MPI_GET_ELEMENTS(stat, Type2, i, ierr) ! возвращает i=3
END IF
Функция MPI_GET_ELEMENTS также может использоваться после операции probe, чтобы найти число элементов в опробованном сообщении. Заметим, что две функции MPI_GET_COUNT и MPI_GET_ELEMENTS возвращают то же самое значение, когда они используются с базисным типом данных.
3.10.6. Примеры Пример 3.21. Передача и прием части трехмерного массива.
REAL a(100,100,100), e(9,9,9)
INTEGER oneslice, twoslice, threeslice, sizeofreal, myrank, ierr
INTEGER status(MPI_STATUS_SIZE)
!извлекает часть a(1:17:2, 3:11, 2:10)и запоминает ее в e(:,:,:). CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr)
!создает тип данных для секции 1D
CALL MPI_TYPE_VECTOR( 9, 1, 2, MPI_REAL, oneslice, ierr) ! создает тип данных для секции 2D
CALL MPI_TYPE_HVECTOR(9, 1, 100*sizeofreal, oneslice, twoslice, ierr) ! создает тип данных для секции в целом
CALL MPI_TYPE_HVECTOR( 9, 1, 100*100*sizeofreal, twoslice, threeslice, ierr) CALL MPI_TYPE_COMMIT( threeslice, ierr)
CALL MPI_SENDRECV(a(1,3,2), 1, threeslice, myrank, 0,e, 9*9*9, REAL, myrank, 0,MPI_COMM_WORLD, status, ierr)
Пример 3.22. Копирование нижней треугольной части матрицы
REAL a(100,100), b(100,100)
INTEGER disp(100), blocklen(100), ltype, myrank, ierr
INTEGER status(MPI_STATUS_SIZE)
!копирует нижнюю треугольную часть массива a в нижнюю
!треугольную часть массива b
CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank)
!вычисляет начало и размер каждого столбца
93
DO i=1, 100
disp(i) = 100*(i-1) + i block(i) = 100-i
END DO
! создает тип данных для нижней треугольной части
CALL MPI_TYPE_INDEXED(100,block, disp, MPI_REAL, ltype, ierr) CALL MPI_TYPE_COMMIT(ltype, ierr)
CALL MPI_SENDRECV(a, 1, ltype, myrank, 0, b, 1, ltype, myrank, 0, MPI_COMM_WORLD,status, ierr)
Пример 3.23. Транспонирование матрицы.
REAL a(100,100), b(100,100)
INTEGER row, xpose, sizeofreal, myrank, ierr INTEGER status(MPI_STATUS_SIZE)
! транспонирование матрицы a в матрицу b
CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr)
! создание типа данных для одной строки
CALL MPI_TYPE_VECTOR( 100, 1, 100, MPI_REAL, row, ierr)
!создание типа данных для матрицы с расположением по строкам
CALL MPI_TYPE_HVECTOR( 100, 1, sizeofreal, row, xpose, ierr) CALL MPI_TYPE_COMMIT( xpose, ierr)
!посылка матрицы с расположением по строкам
!и получение матрицы с расположением по столбцам
CALL MPI_SENDRECV( a, 1, xpose, myrank, 0, b, 100*100,
MPI_REAL, myrank, 0, MPI_COMM_WORLD, status,ierr)
Пример 3.24. Другой способ транспонирования матрицы.
REAL a(100,100), b(100,100)
INTEGER disp(2), blocklen(2), type(2), row, row1, sizeofreal INTEGER myrank, ierr
INTEGER status(MPI_STATUS_SIZE)
CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) ! транспонирование матрицы a в матрицу b
CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr) ! создание типа данных для одной строки
CALL MPI_TYPE_VECTOR( 100, 1, 100, MPI_REAL, row, ierr) ! создание типа данных для одной строки
disp(1) = 0
disp(2) = sizeofreal type(1) = row type(2) = MPI_UB blocklen(1) = 1 blocklen(2) = 1
94
CALL MPI_TYPE_STRUCT( 2, blocklen, disp, type, row1, ierr) CALL MPI_TYPE_COMMIT( row1, ierr)
! посылка 100 строк и получение с расположением по столбцам
CALL MPI_SENDRECV( a, 100, row1, myrank, 0, b, 100*100, MPI_REAL, myrank, 0, MPI_COMM_WORLD, status, ierr)
Пример 3.25. Посылка массива структур.
struct Partstruct |
|
||
{ |
int |
class; |
/* класс частицы */ |
|
double d[6]; |
/* координаты частицы */ |
|
}; |
char |
b[7]; |
/* некоторая дополнительная информация */ |
|
|
|
|
struct Partstruct |
particle[1000]; |
||
int |
|
i, dest, rank; |
|
MPI_Comm comm; |
|||
|
|
|
/* построение типа данных описываемой структуры */ |
MPI_Datatype Particletype; |
|||
MPI_Datatype type[3] = {MPI_INT, MPI_DOUBLE, MPI_CHAR}; |
|||
int |
blocklen[3] = {1, 6, 7}; |
||
MPI_Aint |
disp[3]; |
||
int |
base; |
|
/* вычисление смещений компонент структуры */ MPI_Address( particle, disp);
MPI_Address( particle[0].d, disp+1); MPI_Address( particle[0].b, disp+2); base = disp[0];
for (i=0; i <3; i++) disp[i] -= base;
MPI_Type_struct( 3, blocklen, disp, type, &Particletype); MPI_Type_commit( &Particletype);
MPI_Send( particle, 1000, Particletype, dest, tag, comm);
Пример 3.26. Обработка объединений.
union { |
|
|
|
int |
ival; |
|
float |
fval; |
|
} u[1000] |
|
int |
utype; |
|
MPI_Datatype type[2]; |
||
int |
blocklen[2] = {1,1}; |
|
MPI_Aint |
disp[2]; |
|
MPI_Datatype MPI_utype[2]; |
||
MPI_Aint |
i,j; |
/* вычисляет тип данных MPI для каждого возможного типа union; считаем, что значения в памяти union выровнены по левой границе.*/
95
MPI_Address( u, &i); MPI_Address( u+1, &j); disp[0] = 0;
disp[1] = j-i; type[1] = MPI_UB; type[0] = MPI_INT;
MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[0]);
type[0] = MPI_FLOAT;
MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[1]);
for(i=0; i<2; i++) MPI_Type_commit(&mpi_utype[i]); /* фактический обмен */
MPI_Send(u, 1000, MPI_utype[utype], dest, tag, comm);
3.11. УПАКОВКА И РАСПАКОВКА
Некоторые существующие библиотеки для передачи сообщений обеспечивают функции pack/unpack для передачи несмежных данных. При этом пользователь явно пакует данные в смежный буфер перед их посылкой и распаковывает смежный буфер при приеме. Производные типы данных, которые описаны в параграфе 3.10, позволяют избежать упаковки и распаковки. Пользователь описывает размещение данных, которые должны быть посланы или приняты, и коммуникационная библиотека прямо обращается в несмежный буфер.
Процедуры pack/unpack обеспечивают совместимость с предыдущими библиотеками. К тому же они обеспечивают некоторые возможности, которые другим образом недоступны в MPI. Например, сообщение может быть принято в нескольких частях, где приемная операция, сделанная на поздней части, может зависеть от содержания первой части. Другое удобство состоит в том, что исходящее сообщение может быть явно буферизовано в предоставленном пользователю пространстве, превышая возможности системной политики буферизации. Доступность операций pack и unpack облегчает развитие дополнительных коммуникационных библиотек, расположенных на верхнем уровне MPI.
Операция MPI_PACK пакует сообщение в буфер посылки, описанный аргументами inbuf, incount, datatype в буферном пространстве, описанном аргументами outbuf и outsize. Входным буфером может быть любой коммуникационный буфер, разрешенный в MPI_SEND. Выходной буфер есть смежная область памяти, содержащая outsize
96
байтов, начиная с адреса outbuf (длина подсчитывается в байтах, а не элементах, как если бы это был коммуникационный буфер для сооб-
щения типа MPI_PACKED).
MPI_PACK (inbuf, incount, datatype, outbuf, outsize, position, comm)
IN |
inbuf |
начало входного буфера (альтернатива) |
IN |
incount |
число единиц входных данных (целое) |
IN |
datatype |
тип данных каждой входной единицы (дескриптор) |
OUT |
outbuf |
начало выходного буфера (альтернатива) |
IN |
outsize |
размер выходного буфера в байтах (целое) |
INOUT |
position |
текущая позиция в буфере в байтах (целое) |
IN |
comm |
коммуникатор для упакованного сообщения (дескриптор) |
int MPI_Pack(void* inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm)
MPI_PACK(INBUF, INCOUNT, DATATYPE, OUTBUF, OUTSIZE, POSITION, COMM, IERROR)
<type> INBUF(*), OUTBUF(*)
INTEGER INCOUNT, DATATYPE, OUTSIZE, POSITION, COMM, IERROR
void MPI::Datatype::Pack (const void* inbuf, int incount, void *outbuf, int outsize, int& position, const MPI::Comm &comm) const
Входное значение position есть первая ячейка в выходном буфере, которая должна быть использована для упаковки. рosition инкрементируется размером упакованного сообщения, выходное значение рosition есть первая ячейка в выходном буфере, следующая за ячейками, занятыми упакованным сообщением. Аргумент comm – коммуникатор, используемый для передачи упакованного сообщения.
Функция MPI_UNPACK распаковывает сообщение в приемный буфер, описанный аргументами outbuf, outcount, datatype из буферного пространства, описанного аргументами inbuf и insize.
Выходным буфером может быть любой буфер, разрешенный в MPI_RECV. Входной буфер есть смежная область памяти, содержащая insize байтов, начиная с адреса inbuf. Входное значение position есть первая ячейка во входном буфере, занятом упакованным сообщением. рosition инкрементируется размером упакованного сообщения, так что выходное значение рosition есть первая ячейка во входном буфере после ячеек, занятых сообщением, которое было упаковано. сomm есть коммуникатор для приема упакованного сообщения.
97
MPI_UNPACK (inbuf, insize, position, outbuf, outcount, datatype, comm)
IN |
inbuf |
начало входного буфера (альтернатива) |
IN |
insize |
размер входного буфера в байтах (целое) |
INOUT |
position |
текущая позиция в байтах (целое) |
OUT |
outbuf |
начало выходного буфера (альтернатива) |
IN |
outcount |
число единиц для распаковки (целое) |
IN |
datatype |
тип данных каждой выходной единицы данных (дескрип- |
|
comm |
тор) |
IN |
коммуникатор для упакованных сообщений (дескриптор) |
int MPI_Unpack(void* inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm)
MPI_UNPACK(INBUF, INSIZE, POSITION, OUTBUF, OUTCOUNT, DATATYPE, COMM, IERROR)
<type> INBUF(*), OUTBUF(*)
INTEGER INSIZE, POSITION, OUTCOUNT, DATATYPE, COMM, IERROR
void MPI::Datatype::Unpack (const void* inbuf, int insize, void *outbuf, int outcount, int& position, const MPI::Comm& comm) const
Чтобы понять поведение pack и unpack, предположим, что часть данных сообщения есть последовательность, полученная конкатенацией последующих значений, посланных в сообщении. Операция pack сохраняет эту последовательность в буферном пространстве, как при посылке сообщения в этот буфер. Операция unpack обрабатывает последовательность из буферного пространства, как при приеме сообщения из этого буфера. Несколько сообщений могут быть последовательно упакованы в один объект. Это достигается несколькими последовательными связанными обращениями к MPI_PACK, где первый вызов обеспечивает position = 0, и каждый последующий вызов вводит значение position, которое было выходом для предыдущего вызова, и то же самое значение для outbuf, outcount и comm. Этот упакованный объект содержит эквивалентную информацию, которая хранилась бы в сообщении по одной передаче с буфером передачи, который есть «конкатенация» индивидуальных буферов передачи.
Упакованный объект может быть послан типом MPI_PACKED. Любая парная или коллективная коммуникационная функция может быть использована для передачи последовательности байтов, которая формирует упакованный объект, из одного процесса в другой. Этот упакованный объект также может быть получен любой приемной операцией, поскольку типовые правила соответствия ослаблены для сообщений, посланных с помощью MPI_PACKED.
98
Сообщение, посланное с любым типом может быть получено с помощью MPI_PACKED. Такое сообщение может быть распаковано обращением к MPI_UNPACK.
Упакованный объект может быть распакован в несколько последовательных сообщений. Это достигается несколькими последовательными обращениями к MPI_UNPACK, где первое обращение обеспечивает position = 0, и каждый последовательный вызов вводит значение position, которое было выходом предыдущего обращения, и то же самое значение для inbuf, insize и comm.
Конкатенация двух упакованных объектов не обязательно является упакованным объектом; подстрока упакованного объекта также не обязательно есть упакованный объект. Поэтому нельзя производить конкатенацию двух упакованных объектов и затем распаковывать результат как один упакованный объект; ни распаковывать подстроку упакованного объекта, как отдельный упакованный объект. Каждый упакованный объект, который был создан соответствующей последовательностью операций упаковки или регулярными send, обязан быть распакован как объект последовательностью связанных распаковывающих обращений. Следующий вызов позволяет пользователю выяснить, сколько пространства нужно для упаковки объекта, что позволяет управлять распределением буферов.
Обращение к MPI_PACK_SIZE(incount, datatype, comm, size)
возвращает в size верхнюю границу по инкременту в position, которая создана обращением к MPI_PACK(inbuf, incount, datatype, outbuf, outcount, position, comm).
MPI_PACK_SIZE(incount, datatype, comm, size)
IN |
incount |
аргумент count для упакованного вызова (целое) |
IN |
datatype |
аргумент datatype для упакованного вызова (дескриптор) |
IN |
comm |
аргумент communicator для упакованного вызова (дескриптор) |
OUT size |
верхняя граница упакованного сообщения в байтах (целое) |
int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size)
MPI_PACK_SIZE(INCOUNT, DATATYPE, COMM, SIZE, IERROR) INTEGER INCOUNT, DATATYPE, COMM, SIZE, IERROR
int MPI::Datatype::Pack_size(int incount, const MPI::Comm& comm) const
99
Пример 3.27. Пример использования MPI_PACK.
int position, i, j, a[2]; char buff[1000];
MPI_Comm_rank(MPI_COMM_WORLD, &myrank); / * код отправителя */
if (myrank == 0)
{position = 0;
MPI_Pack(&i,1,MPI_INT,buff,1000, &position,MPI_COMM_WORLD); MPI_Pack(&j,1,MPI_INT,buff,1000,&position, MPI_COMM_WORLD); MPI_Send( buff, position, MPI_PACKED, 1, 0, MPI_COMM_WORLD);
}
else /* код получателя */ MPI_Recv( a, 2, MPI_INT, 0, 0, MPI_COMM_WORLD)
Пример 3.28. Более сложный пример.
int position, i; float a[1000]; char buff[1000]
MPI_Comm_rank(MPI_Comm_world, &myrank);
if (myrank == 0) / * код отправителя */
{int len[2]; MPI_Aint disp[2];
MPI_Datatype type[2], newtype;
/* построение типа данных для i с последующими a[0]...a[i-1] */
len[0] = 1; len[1] = i; MPI_Address( &i, disp); MPI_Address( a, disp+1);
type[0] = MPI_INT; type[1] = MPI_FLOAT; MPI_Type_struct( 2, len, disp, type, &newtype); MPI_Type_commit( &newtype);
/* упаковка i с последующими a[0]...a[i-1]*/
position = 0;
MPI_Pack(MPI_BOTTOM,1, newtype,buff,1000, &position, MPI_COMM_WORLD);
/* посылка */
MPI_Send( buff, position, MPI_PACKED, 1, 0, MPI_COMM_WORLD) /* можно заменить последние три строки
MPI_Send( MPI_BOTTOM, 1, newtype, 1, 0, MPI_COMM_WORLD); */
}
100