Рассмотренные ранее коммуникационные операции позволяют посылать или получать последовательность элементов одного типа, занимающих смежные области памяти. При разработке параллельных программ иногда возникает потребность передавать данные разных типов (например, структуры) или данные, расположенные в несмежных областях памяти (части массивов, не образующих непрерывную последовательность элементов). MPI предоставляет два механизма эффективной пересылки данных в упомянутых выше случаях:
- путем создания производных типов для использования в коммуникационных операциях вместо предопределенных типов MPI;
- пересылку упакованных данных (процесс-отправитель упаковывает пересылаемые данные перед их отправкой, а процесс-получатель распаковывает их после получения).
В большинстве случаев оба эти механизма позволяют добиться желаемого результата, но в конкретных случаях более эффективным может оказаться либо один, либо другой подход.
Производные типы данных
Производные типы MPI не являются в полном смысле типами данных, как это понимается в языках программирования. Они не могут использоваться ни в каких других операциях, кроме коммуникационных. Производные типы MPI следует понимать как описатели расположения в памяти элементов базовых типов. Производный тип MPI представляет собой скрытый (opaque) объект, который специфицирует две вещи: последовательность базовых типов и последовательность смещений. Последовательность таких пар определяется как отображение (карта) типа:
Typemap = {(type0, disp0), ... , (typen-1,dispn-1)}.
Значения смещений не обязательно должны быть неотрицательными, различными и упорядоченными по возрастанию. Отображение типа вместе с базовым адресом начала расположения данных buf определяет коммуникационный буфер обмена. Этот буфер будет содержать n элементов, а i-й элемент будет иметь адрес buf+disp и иметь базовый тип type. Стандартные типы MPI имеют предопределенные отображения типов. Например, MPI_INT имеет отображение {(int,0)}.
Использование производного типа в функциях обмена сообщениями можно рассматривать как трафарет, наложенный на область памяти, которая содержит передаваемое или принятое сообщение.
Стандартный сценарий определения и использования производных типов включает следующие шаги:
1. Производный тип строится из предопределенных типов MPI и ранее определенных производных типов с помощью специальных функций-конструкторов MPI_Type_contiguous, MPI_Type_vector, MPI_Type_hvector, MPI_Type_indexed, MPI_Type_hindexed, MPI_Type_struct.
2. Новый производный тип регистрируется вызовом функции MPI_Type_commit. Только после регистрации новый производный тип можно использовать в коммуникационных подпрограммах и при конструировании других типов. Предопределенные типы MPI считаются зарегистрированными.
3. Когда производный тип становится ненужным, он уничтожается функцией MPI_Type_free.
Любой тип данных в MPI имеет две характеристики: протяженность и размер, выраженные в байтах:
1.
Протяженность типа определяет, сколько байт переменная данного типа
занимает в памяти. Эта величина может быть вычислена как:
адрес последней ячейки данных - адрес первой ячейки данных + длина последней
ячейки данных (опрашивается подпрограммой MPI_Type_extent).
2. Размер типа определяет количество реально передаваемых байт в коммуникационных операциях. Эта величина равна сумме длин всех базовых элементов определяемого типа (опрашивается подпрограммой MPI_Type_size).
Для простых типов протяженность и размер совпадают.
Функция MPI_Type_extent определяет протяженность элемента некоторого типа.
FORTRAN:
INTEGER DATATYPE, EXTENT, IERR
MPI_TYPE_EXTENT(DATATYPE, EXTENT, IERR)
C:
int MPI_Type_extent(MPI_Datatype datatype, MPI_Aint *extent)
Входные параметры:
datatype |
- |
тип данных. |
Выходные параметры:
extent |
- |
протяженность элемента заданного типа. |
Функция MPI_Type_size определяет "чистый" размер элемента некоторого типа (за вычетом пустых промежутков).
FORTRAN:
INTEGER DATATYPE, SIZE, IERR
MPI_TYPE_SIZE(DATATYPE, SIZE, IERR)
C:
int MPI_Type_size(MPI_Datatype datatype, int *size)
Входные параметры:
datatype |
- |
тип данных. |
Выходные параметры:
size |
- |
размер элемента заданного типа. |
Как отмечалось выше, для создания производных типов в MPI имеется набор специальных функций-конструкторов. Рассмотрим эти функции в последовательности от простого к сложному.
Самый простой конструктор типа MPI_Type_contiguous создает новый тип, элементы которого состоят из указанного числа элементов базового типа, занимающих смежные области памяти.
FORTRAN:
INTEGER COUNT, OLDTYPE, NEWTYPE, IERR
MPI_TYPE_CONTIGUOUS(COUNT, OLDTYPE, NEWTYPE, IERR)
C:
int MPI_Type_contiguous(int count, MPI_Datatype oldtype, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число элементов базового типа; |
oldtype |
- |
базовый тип данных. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Графическая интерпретация работы конструктора MPI_Type_contiguousа приведена на рис. 46.
Рис. 46. Графическая интерпретация работы конструктора MPI_Type_contiguous
Конструктор типа MPI_Type_vector создает тип, элемент которого представляет собой несколько равноудаленных друг от друга блоков из одинакового числа смежных элементов базового типа.
FORTRAN:
INTEGER COUNT, BLOCKLENGTH, STRIDE, OLDTYPE, NEWTYPE, IERR
MPI_TYPE_VECTOR(COUNT, BLOCKLENGTH, STRIDE, OLDTYPE, NEWTYPE, IERR)
C:
int MPI_Type_vector(int count, int blocklength, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число блоков; |
blocklength |
- |
число элементов базового типа в каждом блоке; |
stride |
- |
шаг между началами соседних блоков, измеренный числом элементов базового типа; |
oldtype |
- |
базовый тип данных. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Функция создает тип newtype, элемент которого состоит из count блоков, каждый из которых содержит одинаковое число blocklength элементов типа oldtype. Шаг stride между началом блока и началом следующего блока всюду одинаков и кратен протяженности представления базового типа. Графическая интерпретация работы конструктора MPI_Type_vector приведена на рис. 47.
Рис. 47. Графическая интерпретация работы конструктора MPI_Type_vector
Конструктор типа MPI_Type_hvector расширяет возможности конструктора MPI_Type_vector, позволяя задавать произвольный шаг между началами блоков в байтах.
FORTRAN:
INTEGER COUNT, BLOCKLENGTH, STRIDE, OLDTYPE, NEWTYPE, IERR
MPI_TYPE_HVECTOR(COUNT, BLOCKLENGTH, STRIDE, OLDTYPE, NEWTYPE, IERR)
C:
int MPI_Type_hvector(int count, int blocklength, MPI_Aint stride, MPI_Datatype oldtype, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число блоков; |
blocklength |
- |
число элементов базового типа в каждом блоке; |
stride |
- |
шаг между началами соседних блоков в байтах; |
oldtype |
- |
базовый тип данных. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Графическая интерпретация работы конструктора MPI_Type_hvector приведена на рис. 48.
Рис. 48. Графическая интерпретация работы конструктора MPI_Type_hvector
Конструктор типа MPI_Type_indexed является более универсальным конструктором по сравнению с MPI_Type_vector, так как элементы создаваемого типа состоят из произвольных по длине блоков с произвольным смещением блоков от начала размещения элемента. Смещения измеряются в элементах старого типа.
FORTRAN:
INTEGER COUNT, ARRAY_OF_BLOCKLENGTHS(*)
INTEGER ARRAY_OF_DISPLACEMENTS(*), OLDTYPE, NEWTYPE, IERR
MPI_TYPE_INDEXED(COUNT, ARRAY_OF_BLOCKLENGTHS, ARRAY_OF_DISPLACEMENTS, OLDTYPE, NEWTYPE, IERR)
C:
int MPI_Type_indexed(int count, int *array_of_blocklengths, int *array_of_displacements, MPI_Datatype oldtype, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число блоков; |
array_of_blocklengths |
- |
массив, содержащий число элементов базового типа в каждом блоке; |
array_of_displacements |
- |
массив смещений каждого блока от начала размещения элемента нового типа, смещения измеряются числом элементов базового типа; |
oldtype |
- |
базовый тип данных. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Эта функция создает тип newtype, каждый элемент которого состоит из count блоков, где i-ый блок содержит array_of_blocklengths[i] элементов базового типа и смещен от начала размещения элемента нового типа на array_of_displacements[i] элементов базового типа. Графическая интерпретация работы конструктора MPI_Type_indexedа приведена на рис. 49.
Рис. 49. Графическая интерпретация работы конструктора MPI_Type_indexed
Конструктор типа MPI_Type_hindexed идентичен конструктору MPI_Type_indexed за исключением того, что смещения измеряются в байтах.
FORTRAN:
INTEGER COUNT, ARRAY_OF_BLOCKLENGTHS(*)
INTEGER ARRAY_OF_DISPLACEMENTS(*), OLDTYPE, NEWTYPE, IERR
MPI_TYPE_HINDEXED(COUNT, ARRAY_OF_BLOCKLENGTHS, ARRAY_OF_DISPLACEMENTS, OLDTYPE, NEWTYPE, IERR)
C:
int MPI_Type_hindexed(int count, int *array_of_blocklengths, MPI_Aint *array_of_displacements, MPI_Datatype oldtype, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число блоков; |
array_of_blocklengths |
- |
массив, содержащий число элементов базового типа в каждом блоке; |
array_of_displacements |
- |
массив смещений каждого блока от начала размещения элемента нового типа, смещения измеряются в байтах; |
oldtype |
- |
базовый тип данных. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Элемент нового типа состоит из count блоков, где i-ый блок содержит array_of_blocklengths[i] элементов старого типа и смещен от начала размещения элемента нового типа на array_of_displacements[i] байт. Графическая интерпретация работы конструктора MPI_Type_hindexed приведена на рис. 50.
Рис. 50. Графическая интерпретация работы конструктора MPI_Type_hindexed
Конструктор типа MPI_Type_struct - самый универсальный из всех конструкторов типа. Создаваемый им тип является структурой, состоящей из произвольного числа блоков, каждый из которых может содержать произвольное число элементов одного из базовых типов и может быть смещен на произвольное число байтов от начала размещения структуры.
FORTRAN:
INTEGER COUNT, ARRAY_OF_BLOCKLENGTHS(*)
INTEGER ARRAY_OF_DISPLACEMENTS(*), ARRAY_OF_TYPES(*)
INTEGER NEWTYPE, IERR
MPI_TYPE_STRUCT(COUNT, ARRAY_OF_BLOCKLENGTHS, ARRAY_OF_DISPLACEMENTS, ARRAY_OF_TYPES, NEWTYPE, IERR)
C:
int MPI_Type_struct(int count, int *array_of_blocklengths, MPI_Aint *array_of_displacements, MPI_Datatype *array_of_types, MPI_Datatype *newtype)
Входные параметры:
count |
- |
число блоков; |
array_of_blocklength |
- |
массив, содержащий число элементов одного из базовых типов в каждом блоке; |
array_of_displacements |
- |
массив смещений каждого блока от начала размещения структуры, смещения измеряются в байтах; |
array_of_type |
- |
массив, содержащий тип элементов в каждом блоке. |
Выходные параметры:
newtype |
- |
новый производный тип данных. |
Функция создает тип newtype, элемент которого состоит из count блоков, где i-ый блок содержит array_of_blocklengths[i] элементов типа array_of_types[i]. Смещение i-ого блока от начала размещения элемента нового типа измеряется в байтах и задается в array_of_displacements[i].
Графическая интерпретация работы конструктора MPI_Type_struct приведена на рис. 51.
Рис. 51. Графическая интерпретация работы конструктора MPI_Type_struct
Функция MPI_Type_commit регистрирует созданный производный тип. Только после регистрации новый тип может использоваться в коммуникационных операциях.
FORTRAN:
INTEGER DATATYPE, IERR
MPI_TYPE_COMMIT(DATATYPE, IERR)
C:
int MPI_Type_commit(MPI_Datatype *datatype)
Входные параметры:
datatype |
- |
новый производный тип данных./td> |
Выходные параметры:
datatype |
- |
новый производный тип данных. |
Функция MPI_Type_free уничтожает описатель производного типа.
FORTRAN:
INTEGER DATATYPE, IERR
MPI_TYPE_FREE(DATATYPE, IERR)
C:
int MPI_Type_free(MPI_Datatype *datatype)
Входные параметры:
datatype |
- |
уничтожаемый производный тип данных. |
Выходные параметры:
datatype |
- |
уничтожаемый производный тип данных. |
Функция MPI_Type_free устанавливает описатель типа в состояние MPI_DATATYPE_NULL. Это не повлияет на выполняющиеся в данный момент коммуникационные операции с этим типом данных и на производные типы, которые ранее были определены через уничтоженный тип.
Для определения длины сообщения используются две функции: MPI_Get_count и MPI_Get_elements. Для сообщений из простых типов они возвращают одинаковое число. Подпрограмма MPI_Get_count возвращает число элементов типа datatype, указанного в операции получения. Если получено не целое число элементов, то она возвратит константу MPI_UNDEFINED (функция MPI_Get_count рассматривалась в разделе, посвященном коммуникационным операциям типа “точка-точка”).
Функция MPI_Get_elements возвращает число элементов простых типов, содержащихся в сообщении.
FORTRAN:
INTEGER STATUS(MPI_STATUS_SIZE), DATATYPE, COUNT, IERR
MPI_GET_ELEMENTS(STATUS, DATATYPE, COUNT, IERR)
C:
int MPI_Get_elements(MPI_Status *status, MPI_Datatype datatype, int *count)
Входные параметры:
status |
- |
статус сообщения; |
datatype |
- |
тип элементов сообщения. |
Выходные параметры:
count |
- |
число элементов простых типов, содержащихся в сообщении. |
Передача упакованных данных
Функция MPI_Pack упаковывает элементы предопределенного или производного типа MPI, помещая их побайтное представление в выходной буфер.
FORTRAN:
<type> INBUF(*), OUTBUF(*)
INTEGER INCOUNT, DATATYPE, OUTSIZE, POSITION, COMM, IERROR
MPI_PACK(INBUF, INCOUNT, DATATYPE, OUTBUF, OUTSIZE, POSITION, COMM, IERROR)
C:
int MPI_Pack(void* inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outsize, int *position, MPI_Comm comm)
Входные параметры:
inbuf |
- |
адрес начала области памяти с элементами, которые требуется упаковать; |
incount |
- |
число упаковываемых элементов; |
datatype |
- |
тип упаковываемых элементов; |
outsize |
- |
размер выходного буфера в байтах; |
comm |
- |
коммуникатор. |
Выходные параметры:
outbuf |
- |
адрес начала выходного буфера для упакованных данных; |
position |
- |
текущая позиция в выходном буфере в байтах. |
Функция MPI_Pack упаковывает incount элементов типа datatype из области памяти с начальным адресом inbuf. Результат упаковки помещается в выходной буфер с начальным адресом outbuf и размером outsize байт. Параметр position указывает текущую позицию в байтах, начиная с которой будут размещаться упакованные данные. На выходе из подпрограммы значение position увеличивается на число упакованных байт, указывая на первый свободный байт. Параметр comm при последующей посылке упакованного сообщения будет использован как коммуникатор.
Функция MPI_Unpack извлекает заданное число элементов некоторого типа из побайтного представления элементов во входном массиве.
FORTRAN:
<type> INBUF(*), OUTBUF(*)
INTEGER INSIZE, POSITION, OUTCOUNT, DATATYPE, COMM, IERROR
MPI_UNPACK(INBUF, INSIZE, POSITION, OUTBUF, OUTCOUNT, DATATYPE, COMM, IERROR)
C:
Int MPI_Unpack(void* inbuf, int insize, int *position, void *outbuf, int outcount, MPI_Datatype datatype, MPI_Comm comm)
Входные параметры:
inbuf |
- |
адрес начала входного буфера с упакованными данными; |
insize |
- |
размер входного буфера в байтах; |
position |
- |
текущая позиция во входном буфере в байтах; |
outcount |
- |
число извлекаемых элементов; |
datatype |
- |
тип извлекаемых элементов; |
comm |
- |
коммуникатор. |
Выходные параметры:
outbuf |
- |
адрес начала области памяти для размещения распакованных элементов; |
position |
- |
текущая позиция в входном буфере в байтах. |
Функция MPI_Unpack извлекает outcount элементов типа datatype из побайтного представления элементов в массиве inbuf, начиная с адреса position. После возврата из функции параметр position увеличивается на размер распакованного сообщения. Результат распаковки помещается в область памяти с начальным адресом outbuf.
Для посылки элементов разного типа из нескольких областей памяти их следует предварительно запаковать в один массив, последовательно обращаясь к функции упаковки MPI_Pack. При первом вызове функции упаковки параметр position, как правило, устанавливается в 0, чтобы упакованное представление размещалось с начала буфера. Для непрерывного заполнения буфера необходимо в каждом последующем вызове использовать значение параметра position, полученное из предыдущего вызова.
Упакованный буфер пересылается любыми коммуникационными операциями с указанием типа MPI_PACKED и коммуникатора comm, который использовался при обращениях к функции MPI_Pack.
Полученное упакованное сообщение распаковывается в различные массивы или переменные. Это реализуется последовательными вызовами функции распаковки MPI_Unpack с указанием числа элементов, которое следует извлечь при каждом вызове, и с передачей значения position, возвращенного предыдущим вызовом. При первом вызове функции параметр position следует установить в 0. В общем случае, при первом обращении должно быть установлено то значение параметра position, которое было использовано при первом обращении к функции упаковки данных. Очевидно, что для правильной распаковки данных очередность извлечения данных должна быть той же самой, как и при упаковке.
Функция MPI_Pack_size помогает определить размер буфера, необходимый для упаковки некоторого количества данных типа datatype.
FORTRAN:
INTEGER INCOUNT, DATATYPE, COMM, SIZE, IERROR
MPI_PACK_SIZE(INCOUNT, DATATYPE, COMM, SIZE, IERROR)
C:
int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size)
Входные параметры:
incount |
- |
число элементов, подлежащих упаковке; |
datatype |
- |
тип элементов, подлежащих упаковке; |
comm |
- |
коммуникатор. |
Выходные параметры:
size |
- |
размер сообщения в байтах после его упаковки. |
Первые три параметра функции MPI_Pack_size такие же, как у функции MPI_Pack. После обращения к функции параметр size будет содержать размер сообщения в байтах после его упаковки.