Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В

..pdf
Скачиваний:
240
Добавлен:
24.05.2014
Размер:
1.69 Mб
Скачать

Функция 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

Соседние файлы в предмете Программирование