Вычислительный центр им. А.А. Дородницына РАН    

Раздел виртуального курса
Параллельное программирование в интерфейсе MPI


Производные типы данных MPI


Содержание

  1. Цели и предварительные требования
  2. Зачем использовать производные типы данных?
    2.1 Основные типы данных MPI
    2.2 Мотивация
  3. Что такое производные типы данных?
    3.1 Общие типы данных и карты типов
    3.2 Диапазон типа данных
  4. Как и когда использовать производные типы данных?
    4.1 Когда использовать
    4.2 Как использовать
    4.3 Правила согласования
  5. Ключевые моменты

Литература


1. Цели и предварительные требования

Цель этого материала состоит в том, чтобы познакомить Вас с понятием производные типы данных MPI. После завершения изучения этого материала, Вы должны знать чем являются производные типы данных, а также зачем, как и когда использовать их. Упражннения на C дадут Вам практический опыт. Чтобы узнать больше о производных типах данных обратитесь к ссылкам, помещенным в конце этого материала.

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

В качестве предварительных условий для выполнения этого материала, вы должны быть знакомы с материалом Основы программирования в MPI, и знать основные типы данных в MPI.

ПРЕДУПРЕЖДЕНИЕ

MPI-2 вводит несколько новых функций для использования при создании производных типов данных. Они не описаны в данном модуле. Описание в модуле удовлетворяет MPI-1. Код, который использует эти функции, должен работать в любой реализации MPI, поскольку в стандарте требуется обратная совместимость. Тем не менее, MPI-2 резко осуждает использование функций, которые были заменены, поэтому каждый, кто пишет код, который будет использован только в реализации стандарта MPI-2 должен будет использовать эти новые функции.
В настоящее время (2003 год), у нас нет свободно распространяемых стабильных реализаций MPI-2.


2. Зачем использовать производные типы данных?

2.1 Основные типы данных MPI

Напомним, кратко, основные типы данных MPI, показанные ниже:

Зная заданные здесь типы данных и их число, Вы можете отправлять сообщения с непрерывными (смежными) данными одного и того же типа.


2.2 Мотивация

Связана с тем, что Вам может понадобиться определение:

Некоторые возможные решения напрашиваются сами:

Вообще говоря, тем не менее, эти решения медленны, неуклюжи, и расточительны по памяти. Использование MPI_BYTE или MPI_PACKED может также привести к созданию программы, которая не будет переносимой на неоднородную систему машин.

Идея производных типы данных MPI должна обеспечить переносимый и эффективный способ передачи сообщения, состоящего из нескольких несмежных или смешанных типов данных. Производные типы данных MPI обеспечивают более простой, более чистый, более изящный и эффективный способ обработать этот тип данных, которые являются обычными в приложениях. Хотя Вы и можете прожить без производных типов данных, Вы не сможете сделать это легко.

[ ПРЕДУПРЕЖДЕНИЕ: в некоторых реализациях MPI использование производных типов данных не является настолько эффективным, как оно должно быть, в частности в терминах времени ожидания по операциям ввода/вывода. Как только Вы заполучите правильно работающий код вашей программы, Вам может захотеться преобразовать некоторые из производных типов данных в горячих точках вашего кода к MPI_BYTE или MPI_PACKED. Сравните синхронизацию переделанного вами кода и кода, использующего производные типы данных MPI, и выберите тот код, который даст Вам оптимальную эффективность работы. ]


3. Что такое производные типы данных?

Производные типы данных – это типы данных, которые построены из основных типов данных MPI. Для лучшего понимания того, что Вам необходимо для построения такого типа данных, Вам необходимо понимать общее понятие типа данных MPI, а также нечто, называемого картой типа(typemap).

3.1 Общие типы данных и карты типов

Формально выражаясь, стандарт MPI определяет общий тип данных как объект, который определяет две вещи:

Наилегчайший способ представить такой объект есть последовательность пар основных типов данных и смещений. MPI называет эту последовательность картой типов (Typemap).

Typemap = {(type0, disp0), (type1, disp1), ..., (typeN, dispN)}

Эти смещения заданы относительно буфера, который этот тип данных описывает. Например, MPI_INT является предопределенным управителем (handle) для типа данных с картой типов {(int,0)} с одним вводом типа int с нулевым смещением. Другие основные типы данных определяются аналогично. В соответствии с этим способом, производные типы данных являются общими типами данных с более чем одной парой (type, disp).


3.2 Диапазон типа данных

Вышеприведеннного материала достаточно, чтобы ответить на вопрос "Что такое производные типы данных?" Время от времени, однако, Вам может понадобиться немного больше знания для успешного применения более сложных производных типов данных. В частности, полезно понимание понятия диапазона (extent) производного типа данных. Для этого нам понадобятся дальнейшие определения.

lb(Typemap) = min(disp0, disp1, ..., dispN)

ub(Typemap) = max(disp0 + sizeof(type0), disp1 + sizeof(type1), ..., dispN + sizeof(typeN))

extent(Typemap) = ub(Typemap) - lb(Typemap) + pad

Здесь
lb устанавливает нижнюю границу карты типа Typemap. Вы можете представлять ее как расположение первого байта, описанного этим типом данных.
ub устанавливает верхнюю границу карты типа Typemap. Это расположение последнего байта, описанного этим типом данных.
sizeof – есть размер основного типа данных в байтах. (Заметим: это верно для основных типов данных.)
Диапазон extent – есть разница между этими двумя установками, возможно увеличенная на величину pad, чтобы удовлетворить требованиям выравнивания. Некоторые языки, подобно C, требуют, чтобы их типы данных были выравнены в памяти некоторым особым способом. Обычно, они требуют, чтобы адрес переменной (в байтах) был множителем ее длины (в байтах). MPI использует pad, чтобы учесть это, так что диапазон типа данных есть промежуток от первого байта до последнего байта, занятого входами в этом datatype, округленном так, чтобы удовлетворить требованиям выравнивания. Для всех основных типов данных, подобных MPI_DOUBLE, MPI_INT, это – просто число байтов в них.

Рассмотрим пример производного типа данных. Предположим, диапазоны extent(double) = 8, extent(int) = 4, – и машина требует, чтобы числа сточностью doubles были выровнены по 8-байтным границам. Если производный тип данных имеет карту типа typemap = {(int,0) (double,4)}, то осюда следует, что lb = min(0,4) = 0 и = max(0+4,4+8) = 12. Однако, поскольку числа с точностью doubles должны быть выровнены по 8-байтным границам, диапазн extent этого производного типа данных равен 16, а не 12. Существуют вызовы MPI, чтобы получить значения lb, ub и extent. Таким образом, вам нет нужды беспокоиться о том, как некая частная машина или язык располагают данные в памяти.

Здесь указан синтаксис для функции диапазона extent:

где


4. Как и когда использовать производные типы данных?

4.1 Когда использовать

Когда Вы хотите создать тип данных в C, Вы делаете это, объявляя тип данных перед выполнением любых инструкций. Ваши объявления читаются компилятором, который устанавливает память для вашего типа данных. Напротив, производные типы данных MPI создаются во время выполнения через вызовы функций из библиотеки MPI. Поскольку производные типы данных MPI часто используются, чтобы отправить или получить типы данных C, то в типичном сценарии, Вы сначала объявляете вашы типы данных C. Позже, во время выполнения вашей программы между вызовами MPI_INIT и MPI_FINALIZE, Вы создаете и используете ваши производные типы данных MPI.


4.2 Как использовать

Перед тем как использовать производный тип данных, вы должны создать его. Здесь указаны шаги, которые вы при этом проходите:

  1. Создайте тип данных.
  2. Разместите тип данных.
  3. Используйте тип данных.
  4. Освободите тип данных.
Вам следует создать и разместить тип данных перед его использованием. С другой стороны, если вы создали и разместили тип данных, то от вас не требуется использовать или освобождать его.


Пройдем по этим шагам более подробно:

4.2.1 Создайте тип данных

Как говорилось выше, производные типы данных – типы данных, которые сформированы из основных типов данных MPI. Карты памяти (typemaps) – наиболее общий способ сделать это, но они не очень удобны, если мы имеем большое количество входов. К счастью, MPI обеспечивает множество функций, чтобы создать общие типы данных из основных типов данных, не будучи обязанным создавать typemap. Новые определения типа данных строятся на основе существующих типов данных (или производных или основных) используя вызов или рекурсивную серию вызовов функций, описанных ниже:

Смежный (непрерывный):

Вызовы MPI_Type_contiguous создают новый тип данных посредством замены существующего типа данных в смежных расположениях.

где

Векторный:

Вызовы MPI_Type_vector, подобно вызовам MPI_Type_contiguous, создают новый тип данных посредством дублирования существующего; тем не менее, MPI_Type_vector позволяет учитывает промежутки в смещении. Такие промежутки – множители степени существующего типа данных.

где

Пример:

[IMAGE]

Иллюстрирует вызов с count = 2, blocklength = 3, и stride = 5

Сделайте следующее:

Используя бумагу и карандаш, проиллюстрируйте вызов:

MPIERROR = MPI_Type_vector(2, 4, 4, OLDTYPE, NEWTYPE)

Что это скажет о вызове MPI_Type_contiguous?

Предостережение

Этот конструктор типа данных и те, что будут описаны ниже, могут использоваться с объектами, которые можно размещать (C) , обеспечив то, чтобы полный объект был распределен сразу. Большой шаг в фактической памяти между частями, которые были распределены в разное время, не может быть предсказан. Таким образом, чтобы распределить такую матрицу на C, для которой MPI_Type_vector мог использоваться при определении типа данных, который представляет подматрицу, можно было бы распределить объект, размер которого равен числу строк, помноженное на число столбцов и помноженное на размер матричного элемента. Массив указателей на строки может быть установлен впоследствии.

H-векторный:

подобен MPI_Type_vector, за исключением того, что смещение определено в байтах. Функция C, MPI_Type_hvector, идентична MPI_Type_vector, данной выше, за тем исключением, что stride указан в байтах.

Проиндексированный:

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

где H-проиндексированный:

Подобен MPI_Type_indexed, за исключением того, что смещение определено в байтах. Функция C, MPI_Type_hindexed, идентична MPI_Type_indexed, данной выше, за исключением того, что array_of_displacements указан в байтах.

Структурный:

Когда Вы вызываете MPI_Type_struct, Вы можете собрать соединение различных типов данных, рассеянных во многих местах в памяти в один тип данных, который может использоваться для того, чтобы отправить сообщения. Это – самый общий тип данных и единственный, который позволяет больше одного типа данных на входе. Отметьте, что, если входные параметры – основные типы данных MPI, то вход есть только некая карта типа (typemap).

где

Если отношения к памяти среди элементов определены компилятором (C struct), то байтовые значения для массива array_of_displacements могут быть рассчитаны программистом. Однако, если эти элементы – независимо объявленные переменные, то должна использоваться функция MPI_ADDRESS для определения абсолютного адреса каждого элемента для использования в массиве array_of_displacements. Используя типы данных, содержащие абсолютные адреса в array_of_displacements, должен быть определен буферный адрес как MPI_BOTTOM.

Пример:

[IMAGE]

иллюстрирует вызов с:

count = 2
array_of_blocklengths[0] = 1
array_of_types[0] = MPI_INT
array_of_blocklengths[1] = 3
array_of_types[1] = MPI_DOUBLE

Предупреждение

Производные типы данных, определенные используя абсолютные смещения НЕ должны содержать переменные, которые не являются статическими (например, они находятся в стеке данных), если тип данных не определен и используется в пределах единственного вызова функции, переменные которой объявлены. Причина для этого – то, что, если контекст не идентичен, указатель вершины стека будет иметь различное значение после повторного входа в функцию, и абсолютные адреса, определенные ранее, будут недопустимы.

Сделайте это:

Используя бумагу и карандаш, проиллюстрируйте вызов MPI_Type_struct, где:

count = 2,
array_of_blocklengths[0] = 1,
array_of_types[0] = новый тип, проиллюстрированный выше,
array_of_blocklengths[1] = 2, и
array_of_types[1] = MPI_INT.


4.2.2 Разместите тип данных

Созданный тип данных должен быть размещен в системе прежде, чем его можно использовать в коммуникации. Созданный тип данных размещен с вызовом MPI_Type_commit. (Нет никакой потребности передавать основные типы данных; они предпереданы.) Затем его можно использовать в любом числе передач сообщений. Форма MPI_Type_commit такова:


4.2.3 Используйте тип данных

Производные типы данных могут использоваться во всех операциях отправки иполучения. Вы просто используете указатель для производного типа данных как параметр в операции отправки или получения вместо основного аргумента для типа данного. Ниже указан пример сегмента кода на С:

MPI_Type_vector(count, blocklength, stride, oldtype, &newtype);
MPI_Type_commit (&newtype);
MPI_Send(buffer, 1, newtype, dest, tag, comm);

Что случится, если бы Вы использовали производный тип данных и count в MPI_SEND больше, чем один? Будьте счастливы, это как раз то, что Вы бы ожидали: MPI_SEND действует, как будто это передавали новый тип данных, который является count-ом конкатенации для типа данных datatype.


4.2.4 Освободите тип данных

Наконец, существует дополнительная функция к MPI_Type_commit, а именно, MPI_TYPE_FREE, которая отмечает тип данных для освобождения. Форма MPI_TYPE_FREE такова:

Любые типы данных производные из освобожденного типа данных перестает действовать, когда его освобождают, как любая коммуникации, которая использует освобожденный тип данных в момент освобождения. datatype является как входным, так и выходным аргументом. Он возвращается как MPI_DATATYPE_NULL.


4.3 Правила согласования