next up previous contents
Next: Коллективные взаимодействия процессов Up: Парные межпроцессные обмены Previous: Устаревшие функции   Contents

Упаковка и распаковка

Некоторые существующие библиотеки для передачи сообщений обеспечивают функции вида
pack/unpack (распаковка и упаковка) для передачи несмежных данных. При этом пользователь явно пакует данные в смежный буфер перед их посылкой и распаковывает смежный буфер при приеме. Производные типы данных, которые описаны в разделе 3.12, позволяют в большинстве случаев избежать упаковки и распаковки. Пользователь описывает размещение данных, которые должны быть посланы или приняты и коммуникационная библиотека прямо обращается в несмежный буфер. Процедуры pack/unpack обеспечивают совместимость с предыдущими библиотеками. К тому же они обеспечивают некоторые возможности, которые другим образом недоступны в MPI. Например, сообщение может быть принято в нескольких частях, где приемная операция, выполняемая для поздней части, может зависеть от содержания первой части. Другое удобство состоит в том, что исходящее сообщение может быть явно буферизовано в предоставленном пользователю пространстве, превышая таким образом возможности системной политики буферизации. Наконец, доступность операций pack и unpack облегчает развитие дополнительных коммуникационных библиотек, расположенных на верхнем уровне MPI.

Синтаксис функции MPI_PACK представлен ниже.

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

Операция MPI_PACK пакует сообщение в буфер посылки, описанный аргументами inbuf,
incount, datatype
в буферном пространстве, описанном аргументами outbuf и outsize. Входным буфером может быть любой коммуникационный буфер, разрешенный в MPI_SEND. Выходной буфер есть смежная область памяти, содержащая outsize байтов, начиная с адреса outbuf (длина подсчитывается в байтах, а не в элементах, как если бы это был коммуникационный буфер для сообщения типа MPI_PACKED).

Входное значение position есть первая ячейка в выходном буфере, которая должна быть использована для упаковки. рosition инкрементируется размером упакованного сообщения и выходное значение рosition есть первая ячейка в выходном буфере, следующая за ячейками, занятыми упакованным сообщением. Аргумент comm есть коммуникатор, который будет использован для передачи упакованного сообщения.

Синтаксис функции MPI_UNPACK представлен ниже.

MPI_UNPACK(inbuf, insize, position, outbuf, outcount, datatype, comm)

IN inbuf начало входного буфера (альтернатива)
IN insize размер входного буфера в байтах (целое)
INOUT position текущая позиция в байтах (целое)
OUT outbuf начало выходного буфера (альтернатива)
IN outcount число единиц для распаковки (целое)
IN datatype тип данных каждой выходной единицы данных (дескриптор)
IN comm коммуникатор для упакованных сообщений (дескриптор)

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

Функция MPI_UNPACK распаковывает сообщение в приемный буфер, описанный аргументами outbuf, outcount, datatype из буферного пространства, описанного аргументами inbuf и insize. Выходным буфером может быть любой коммуникационный буфер, разрешенный в MPI_RECV. Входной буфер есть смежная область памяти, содержащая insize байтов, начиная с адреса inbuf. Входное значение position есть первая ячейка во входном буфере, занятом упакованным сообщением. рosition инкрементируется размером упакованного сообщения, так что выходное значение рosition есть первая ячейка во входном буфере после ячеек, занятых сообщением, которое было упаковано. сomm есть коммуникатор для приема упакованного сообщения.

Совет пользователям: Укажем на разницу между MPI_RECV и MPI_UNPACK: в MPI_RECV аргумент count описывает максимальное число единиц, которое может быть получено. Действительное же число определяется длиной входного сообщения. В MPI_UNPACK аргумент count описывает действительное число единиц, которые распакованы; ``размер'' соответствующего сообщения инкрементируется в position. Причина для этого изменения состоит в том, что ``размер входного сообщения'' не предопределен, поскольку пользователь решает, сколько распаковывать; да и нелегко определить ``размер сообщения'' по числу распакованных единиц. Фактически в неоднородной системе это число не может быть определено априори.[]

Чтобы понять поведение pack и unpack, удобно предположить, что часть данных сообщения есть последовательность, полученная конкатенацией последующих значений, посланных в сообщении. Операция pack сохраняет эту последовательность в буферном пространстве, как при посылке сообщения в этот буфер. Операция unpack обрабатывает последовательность из буферного пространства, как при приеме сообщения из этого буфера. (Полезно вспомнить о внутренних файлах языка ФОРТРАН или о sscanf в языке Си для подобной функции).

Несколько сообщений могут быть последовательно упакованы в один упакованный объект (packing unit). Это достигается несколькими последовательными связанными обращениями к MPI_PACK, где первый вызов обеспечивает position = 0, и каждый последующий вызов вводит значение position, которое было выходом для предыдущего вызова, и то же самое значение для outbuf, outcount и comm. Этот упакованный объект теперь содержит эквивалентную информацию, которая хранилась бы в сообщении по одной передаче с буфером передачи, который является ``конкатенацией'' индивидуальных буферов передачи.

Упакованный объект может быть послан операцией MPI_PACKED. Любая парная или коллективная коммуникационная функция может быть использована для передачи последовательности байтов, которая формирует упакованный объект, из одного процесса в другой. Этот упакованный объект также может быть получен любой приемной операцией: типовые правила соответствия ослаблены для сообщений, посланных с помощью MPI_PACKED.

Сообщение, посланное с любым типом (включая MPI_PACKED) могут быть получены с помощью MPI_PACKED. Такое сообщение может быть распаковано обращением к MPI_UNPACK.

Упакованный объект (или сообщение, созданное обычной ``типовой'' передачей) может быть распаковано в несколько последовательных сообщений. Это достигается несколькими последовательными обращениями к MPI_UNPACK, где первое обращение обеспечивает position = 0 и каждый последовательный вызов вводит значение position, которое было выходом предыдущего обращения, и то же самое значение для inbuf, insize и comm.

Конкатенация двух упакованных объектов не обязательно является упакованным объектом; подстрока упакованного объекта также не обязательно есть упакованный объект. Поэтому нельзя ни производить конкатенацию двух упакованных объектов и затем распаковывать результат как один упакованный объект; ни распаковывать подстроку упакованного объекта, как отдельный упакованный объект. Каждый упакованный объкт, который был создан соответствующей последовательностью операций упаковки или обычными send, обязан быть распакован как объект последовательностью связанных распаковывающих обращений.

Объяснение: Ограничение на ``атомарную'' (``atomic'') упаковку и распаковку упакованных объектов позволяет реализации добавлять в заголовок упакованных объектов дополнительную информацию, такую, как описание архитектуры отправителя (используется для преобразования типов в неоднородной среде).[]

Следующий вызов позволяет пользователю выяснить, сколько пространства нужно для упаковки объекта, что позволяет управлять распределением буферов.

Синтаксис функции MPI_PACK_SIZE представлен ниже.

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

Обращение к MPI_PACK_SIZE(incount, datatype, comm, size) возвращает в size верхнюю границу по инкременту в position, которая создана обращением к MPI_PACK(inbuf, incount, datatype, outbuf, outcount, position, comm).

Объяснение: Вызов возвращает верхнюю границу, а не точную границу, поскольку точный объем пространства, необходимый для упаковки сообщения, может зависеть от контекста (например, первое сообщение, упакованное в упакованный объект, может занимать больше объема).[]

Пример 3.38 Пример использования 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.39 Усложненный пример.

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); ***** */ } else /* myrank == 1 */ { /* код получателя */ MPI_Status status; /* прием */ MPI_Recv(buff, 1000, MPI_PACKED, 0, 0, &status); /* распаковка i */ position = 0; MPI_Unpack(buff, 1000, &position, &i, 1, MPI_INT, MPI_COMM_WORLD); /* распаковка a[0]...a[i-1] */ MPI_Unpack(buff, 1000, &position, a, i, MPI_FLOAT, MPI_COMM_WORLD); }

Пример 3.40 Каждый процесс посылает число, прибавляя знаки числа к корню, корень производит конкатенацию всех знаков в одну строку.

int count, gsize, counts[64], totalcount, k1, k2, k, displs[64], position, concat_pos; char chr[100], *lbuf, *rbuf, *cbuf; ... MPI_Comm_size(comm, &gsize); MPI_Comm_rank(comm, &myrank); /* создать локальный буфер для упаковки */ MPI_Pack_size(1, MPI_INT, comm, &k1); MPI_Pack_size(count, MPI_CHAR, comm, &k2); k = k1+k2; lbuf = (char *)malloc(k); /* упаковать count, */ position = 0; MPI_Pack(&count, 1, MPI_INT, lbuf, k, &position, comm); MPI_Pack(chr, count, MPI_CHAR, lbuf, k, &position, comm); if (myrank != root) /* собрать на корневом процессе размеры всех упакованных сообще-ний */ MPI_Gather(&position, 1, MPI_INT, NULL, NULL, NULL, root, comm); /* собрать на корневом процессе упакованные сообщения */ MPI_Gatherv(&buf, position, MPI_PACKED, NULL, NULL, NULL, NULL, root, comm); else { /* код корневого процесса */ /* собрать размеры всех упакованных сообщений */ MPI_Gather(&position, 1, MPI_INT, counts, 1, MPI_INT, root, comm); /* собрать все упакованные сообщения */ displs[0] = 0; for (i=1; i < gsize; i++) displs[i] = displs[i-1] + counts[i-1]; totalcount = dipls[gsize-1] + counts[gsize-1]; rbuf = (char *)malloc(totalcount); cbuf = (char *)malloc(totalcount); MPI_Gatherv(lbuf, position, MPI_PACKED, rbuf, counts, displs, MPI_PACKED, root, comm); /* распаковать все сообщения и соединить строки */ concat_pos = 0; for (i=0; i < gsize; i++) { position = 0; MPI_Unpack(rbuf+displs[i], totalcount-displs[i], &position, &count, 1, MPI_INT, comm); MPI_Unpack(rbuf+displs[i], totalcount-displs[i], &position, cbuf+concat_pos, count, MPI_CHAR, comm); concat_pos += count; } cbuf[concat_pos] = `\0'; }


next up previous contents
Next: Коллективные взаимодействия процессов Up: Парные межпроцессные обмены Previous: Устаревшие функции   Contents
Alex Otwagin 2002-12-10