Опубликован: 11.10.2012 | Уровень: для всех | Доступ: платный | ВУЗ: Санкт-Петербургский государственный университет
Лекция 7:

Виртуальные топологии

< Лекция 6 || Лекция 7: 12 || Лекция 8 >
Аннотация: В данной лекции описывается работа в MPI с виртуальными топологиями, а также разбирается работа с пользовательскими типами данных.

Презентацию к данной лекции Вы можете скачать здесь.

Пользовательские типы

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

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

В MPI-программе для решения этой проблемы придется создать производный тип данных.

Пример:

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


Решать проблему пересылки разнотипных данных или данных, расположенных не в последовательных ячейках памяти, можно разными средствами.

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

Можно различные элементы данных пересылать по отдельности. Это медленный и неудобный способ обмена.

Более эффективным решением является использование производных типов данных.

Производные типы данных создаются во время выполнения программы (а не во время ее трансляции), как правило, перед их использованием.

Создание типа - двухступенчатый процесс, который состоит из двух шагов:

  1. конструирование типа.
  2. регистрация типа.

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

Коммуникаторы, группы, типы данных, определённые пользователем операции, атрибуты коммуникаторов, обработчики ошибок - все это примеры объектов, которые (если они были созданы в процессе работы программы) должны удаляться.

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

Смещения отсчитываются относительно начала буфера обмена и определяют те элементы данных, которые будут участвовать в обмене. Не требуется, чтобы они были упорядочены (например, по возрастанию или по убыванию).

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

Последовательность пар (тип, смещение) называется картой типа:


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

Конструкторы производных типов

Подпрограмма MPI_Type_struct является наиболее общим конструктором типа в MPI - программист может использовать полное описание каждого элемента типа.

Если пересылаемые данные содержат подмножество элементов массива, такая детальная информация не нужна (у всех элементов один и тот же базовый тип). В MPI есть три конструктора, которые можно использовать в такой ситуации:

  • MPI_Type_сontiguous - создает производный тип, элементы которого являются непрерывно расположенными элементами массива.
  • MPI_Type_vector - создает тип, элементы которого расположены на одинаковых расстояниях друг от друга.
  • MPI_Type_indexed - создает тип, содержащий произвольные элементы.

Векторный тип создается конструктором MPI_Type_vector:

      int MPI_Type_vector(int count, int blocklen, int stride, MPI_Datatype oldtype, MPI_Datatype *newtype)
            MPI_Type_vector(count, blocklen, stride, oldtype, newtype, ierr)    
      

Входные параметры:

  • count - количество блоков (неотрицательное целое значение);
  • blocklen - длина каждого блока (количество элементов, неотрицательное целое);
  • stride - количество элементов, расположенных между началом предыдущего и началом следующего блока ("гребенка");
  • oldtype - базовый тип.

Выходной параметр - идентификатор нового типа newtype. Этот идентификатор назначается программистом. Исходные данные здесь однотипные.

Схема расположения данных в новом типе


Пример:

  • count = 2;
  • stride = 4;
  • blocklen = 3;
  • oldtype = double;

карта нового типа:

{(double, 0), (double, 1), (double, 2), (double, 4), (double, 5), (double, 6)}

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

  int MPI_Type_struct(int count, int blocklengths[], MPI_Aint indices[], MPI_Datatype oldtypes[], 
  MPI_Datatype *newtype)
  MPI_Type_struct(count, blocklengths, indices, oldtypes, newtype, ierr)
 

Ее входные параметры:

  • count - задает количество элементов в производном типе, а также длину массивов oldtypes, indices и blocklengths;
  • blocklengths - количество элементов в каждом блоке (массив);
  • indices - смещение каждого блока в байтах (массив);
  • oldtypes - тип элементов в каждом блоке (массив).

Выходной параметр - идентификатор производного типа newtype.

Схема расположения данных в структурном типе


Пример создания структурного производного типа:

 blen[0] = 1;
 indices[0] = 0;
 oldtypes[0] = MPI_INT;
 blen[1] = 1;
 indices[1] = &data.b — &data;
 oldtypes[1] = MPI_CHAR;
 blen[2] = 1;
 indices[2] = sizeof(data);
 oldtypes[2] = MPI_FLOAT;
 MPI_Type_struct(3, blen, indices, oldtypes, &newtype);

Регистрация и удаление производных типов

С помощью вызова подпрограммы MPI_Type_commit производный тип datatype, сконструированный программистом, регистрируется. После этого он может использоваться в операциях обмена:

  int MPI_Type_commit(MPI_Datatype *datatype)
      MPI_Type_commit(datatype, ierr)
        

Аннулировать производный тип datatype можно с помощью вызова подпрограммы

  MPI_Type_free:
    int MPI_Type_free(MPI_Datatype *datatype)
      MPI_Type_free(datatype, ierr
        

Предопределенные (базовые) типы данных не могут быть аннулированы.

Пример – создание структурного типа

  #include "mpi.h"
  #include <stdio.h>
  struct newtype {
  float a;
  float b;
  int n;
  };
  int main(int argc,char *argv[])
  {
  int myrank;
  MPI_Datatype NEW_MESSAGE_TYPE;
  int block_lengths[3];
  MPI_Aint displacements[3];
  MPI_Aint addresses[4];
  MPI_Datatype typelist[3];
  int blocks_number;
  struct newtype indata;
  int tag = 0;
  MPI_Status status;
  
  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
  typelist[0] = MPI_FLOAT;
  typelist[1] = MPI_FLOAT;
  typelist[2] = MPI_INT;
  block_lengths[0] = block_lengths[1] = block_lengths[2] = 1;
  MPI_Address(&indata, &addresses[0]);
  MPI_Address(&(indata.a), &addresses[1]);
  MPI_Address(&(indata.b), &addresses[2]);
  MPI_Address(&(indata.n), &addresses[3]);
  displacements[0] = addresses[1] — addresses[0];
  displacements[1] = addresses[2] — addresses[0];
  displacements[2] = addresses[3] — addresses[0];
  blocks_number = 3;
  MPI_Type_struct(blocks_number, block_lengths, displacements, typelist, &NEW_MESSAGE_TYPE);
  MPI_Type_commit(&NEW_MESSAGE_TYPE);
  
  if (myrank == 0)
  {
  indata.a = 3.14159;
  indata.b = 2.71828;
  indata.n = 2010;
  MPI_Send(&indata, 1,NEW_MESSAGE_TYPE, 1, tag, MPI_COMM_WORLD);
  printf("Process %i send: %f %f %i\n", myrank, indata.a, indata.b, indata.n);
  }
  else
  {
  MPI_Recv(&indata, 1, NEW_MESSAGE_TYPE, 0, tag, MPI_COMM_WORLD, &status);
  printf("Process %i received: %f %f %i, status %s\n", myrank, indata.a, indata.b, indata.n, status.MPI_ERROR);
  }
  MPI_Type_free(&NEW_MESSAGE_TYPE);
  MPI_Finalize();
  return 0;
  }
  

Как работает эта программа

Задаются типы членов производного типа.

Задаётся количество элементов каждого типа.

Вычисляются адреса элементов типа indata и определяются смещения трех членов производного типа относительно адреса первого, для которого смещение равно 0. Располагая этой информацией, можно определить производный тип, что и делается с помощью подпрограмм MPI_Type_struct и MPI_Type_commit .

Созданный таким образом производный тип можно использовать в любых операциях обмена.

< Лекция 6 || Лекция 7: 12 || Лекция 8 >
Константин Белюстин
Константин Белюстин
Украина, г. Киев
Андрей Частухин
Андрей Частухин
Россия