1 / 86

Основы параллельного программирования с использованием MPI Лекция 7

Основы параллельного программирования с использованием MPI Лекция 7. Немнюгин Сергей Андреевич Санкт-Петербургский государственный университет физический факультет кафедра вычислительной физики. Лекция 7. Аннотация

darcie
Download Presentation

Основы параллельного программирования с использованием MPI Лекция 7

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Основы параллельного программирования с использованием MPIЛекция 7 Немнюгин Сергей Андреевич Санкт-Петербургский государственный университет физический факультет кафедра вычислительной физики

  2. Лекция 7 Аннотация В этой лекции рассматриваются методы работы с пользовательскими типами данных. Пользовательские типы позволяют обойти некоторые ограничения MPI. Разъясняются такие вопросы, как построение карты типа, его регистрация и аннулирование. Обсуждаются примеры использования пользовательских типов в MPI-программах. Рассматриваются операции упаковки и распаковки данных. Рассматриваются также виртуальные топологии, которыми могут быть наделены группы процессов, относящихся к MPI-программе. Основное внимание уделяется декартовой топологии. Приводятся примеры. 2008

  3. План лекции • Проблемы организации данных при обмене сообщениями. • Пользовательские типы. Карта типа, правила использования типа. • Операции упаковки/распаковки данных. • Виртуальные топологии. Декартова топология и топология графа. 2008

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

  5. Пользовательские типы Сообщение в MPI представляет собой массив однотипных данных, элементы которого расположены в последовательных ячейках памяти. Такая структура не всегда удобна. Иногда возникает необходимость в пересылке разнотипных данных или фрагментов массивов, содержащих элементы, расположенные не в последовательных ячейках. Так бывает, например, при программировании вычислений, связанных с операциями с матрицами и векторами, а также в других ситуациях. Эта проблема легко решается при программировании с PVM. В PVM используется концепция «бабушкиной посылки». В MPI-программе для пересылки данных, расположенных не последовательно или для пересылки неоднородных данных придется создать производный тип данных. 2008

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

  7. Пользовательские типы Решать проблему пересылки разнотипных данных или данных, расположенных не в последовательных ячейках памяти, можно разными средствами. Можно «уложить» элементы исходного массива во вспомогательный массив так, чтобы данные располагались непрерывно. Это неудобно и требует дополнительных затрат памяти и процессорного времени. Можно различные элементы данных пересылать по отдельности. Это медленный и неудобный способ обмена. Более эффективным решением является использование производных типов данных. 2008

  8. Пользовательские типы Производные типы данных создаются во время выполнения программы (а не во время ее трансляции), как правило, перед их использованием. Создание типа  двухступенчатый процесс, который состоит из двух шагов: конструирование типа; регистрация типа. После завершения работы с производным типом, он аннулируется. При этом все производные от него типы остаются и могут использоваться дальше, пока и они не будут уничтожены. Коммуникаторы, группы, типы данных, распределенные операции, запросы, атрибуты коммуникаторов, обработчики ошибок  все это объекты, которые (если они были созданы в процессе работы программы) должны удаляться. 2008

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

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

  11. Пользовательские типы Конструкторы производных типов Подпрограмма MPI_Type_struct является наиболее общим конструктором типа в MPI – с ней программист может использовать полное описание каждого элемента типа. Если пересылаемые данные содержат подмножество элементов массива, такая детальная информация не нужна, поскольку у всех элементов массива один и тот же базовый тип. MPI предлагает три конструктора, которые можно использовать в такой ситуации: MPI_Type_сontiguous, MPI_Type_vector и MPI_Type_indexed. Первый из них создает производный тип, элементы которого являются непрерывно расположенными элементами массива. Второй создает тип, элементы которого расположены на одинаковых расстояниях друг от друга. Третий создает тип, содержащий произвольные элементы. 2008

  12. Пользовательские типы • Векторный тип создается конструктором 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. • Этот идентификатор назначается программистом. Исходные данные • здесь однотипные. 2008

  13. Пользовательские типы Схема расположения данных в векторном типе 2008

  14. Пользовательские типы Пример Параметры векторного типа: count = 2; stride = 4; blocklen = 3; oldtype = double; карта нового типа: {(double, 0), (double, 1), (double, 2), (double, 4), (double, 5), (double, 6)} 2008

  15. Пользовательские типы При вызове подпрограммы MPI_Type_hvector смещения задаются в байтах: int MPI_Type_hvector(int count, int blocklen, MPI_Aint stride, MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_Type_hvector(count, blocklen, stride, oldtype, newtype, ierr) Смысл и назначение параметров этой подпрограммы совпадают с Назначением параметров подпрограммы MPI_Type_vector, только значение параметра stride задается в байтах. 2008

  16. Пользовательские типы • Конструктором структурного типа является подпрограмма • 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. 2008

  17. Пользовательские типы Схема расположения данных в структурном типе 2008

  18. Пользовательские типы Пример создания структурного производного типа: 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); Задание В качестве упражнения попробуйте построить карту нового типа newtype для этого примера. Вопрос Достаточно ли для этого информации, содержащейся в приведенном фрагменте программы? 2008

  19. Пользовательские типы • При создании индексированного типа блоки располагаются по адресам • с разным смещением и его можно считать обобщением векторного • типа. Конструктором индексированного типа является подпрограмма: • int MPI_Type_indexed(int count, int blocklens[], int • indices[], MPI_Datatype oldtype, MPI_Datatype *newtype) • MPI_Type_indexed(count, blocklens, indices, oldtype, newtype, • ierr) • Ее входные параметры: • count количество блоков, одновременно длина массивов indices и blocklens; • blocklens количество элементов в каждом блоке; • indices смещение каждого блока, которое задается в количестве ячеек базового типа (целочисленный массив); • oldtype базовый тип. • Выходным параметром является идентификатор производного типа • newtype. 2008

  20. Пользовательские типы В языке Fortran роль смещений играют значения индексов массива. Смещение в этом случае отсчитывается относительно первого элемента массива. Численные значения смещений в картах типа отсчитываются от нуля, а значения индексов в Fortran по умолчанию отсчитываются от единицы. Для согласования обеих систем отсчета иногда удобно описывать массивы следующим образом: real vector_a(0:99) В этом случае значение индекса массива равно значению смещения. 2008

  21. Пользовательские типы Подпрограмма MPI_Type_hindexed также является конструктором индексированного типа, однако смещения indices задаются в байтах: int MPI_Type_hindexed(int count, int blocklens[], MPI_Aint indices[], MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_Type_hindexed(count, blocklens, indices, oldtype, newtype, ierr) 2008

  22. Пользовательские типы • Подпрограмма MPI_Type_contiguous используется для создания типа • данных с непрерывным расположением элементов: • int MPI_Type_contiguous(int count, MPI_Datatype oldtype, • MPI_Datatype *newtype) • MPI_Type_contiguous(count, oldtype, newtype, ierr) • Ее входные параметры: • count счетчик повторений; • oldtype базовый тип. • Выходной параметр newtype идентификатор нового типа. Эта • подпрограмма фактически создает описание массива. 2008

  23. Пользовательские типы Пример MPI_Datatype a; float b[10]; … MPI_Type_contiguous(10, MPI_REAL, &a); MPI_Type_commit(&a); MPI_Send(b, 1, a,…); MPI_Type_free(&a); 2008

  24. Пользовательские типы • Тип данных, соответствующий подмассиву многомерного массива, • можно создать с помощью подпрограммы: • int MPI_Type_create_subarray(int ndims, int *sizes, int • *subsizes, int *starts, int order, MPI_Datatype oldtype, • MPI_Datatype *newtype) • MPI_Type_create_subarray(ndims, sizes, subsizes, starts, • order, oldtype, newtype, ierr) • Входные параметры: • ndims размерность массива; • sizes количество элементов типа oldtype в каждом измерении полного массива; • subsizes количество элементов типа oldtype в каждом измерении подмассива; • starts стартовые координаты подмассива в каждом измерении; • order флаг, задающий переупорядочение; • oldtype базовый тип. 2008

  25. Пользовательские типы Новый тип newtype является выходным параметром этой подпрограммы. Существуют и другие конструкторы производных типов, но они используются значительно реже. 2008

  26. Пользовательские типы Регистрация и удаление производных типов С помощью вызова подпрограммы 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) Предопределенные (базовые) типы данных не могут быть аннулированы. 2008

  27. Пользовательские типы Вспомогательные подпрограммы Размер типа datatype в байтах (объем памяти, занимаемый одним элементом данного типа) можно определить с помощью вызова подпрограммы MPI_Type_size: int MPI_Type_size(MPI_Datatype datatype, int *size) MPI_Type_size(datatype, size, ierr) Выходным параметром является размер size. 2008

  28. Пользовательские типы Количество элементов данных в одном объекте типа datatype (его экстент) можно определить с помощью вызова подпрограммы MPI_Type_extent: int MPI_Type_extent(MPI_Datatype datatype, MPI_Aint *extent) MPI_Type_extent(datatype, extent, ierr) Выходной параметр extent. Смещения могут даваться относительно базового адреса, значение которого содержится в константе MPI_BOTTOM. 2008

  29. Пользовательские типы Адрес (address) по заданному положению (location) можно определить с помощью подпрограммы MPI_Address: int MPI_Address(void *location, MPI_Aint *address) MPI_Address(location, address, ierr) Эта подпрограмма может понадобиться в программе на языке Fortran. В C, вообще говоря, есть собственные средства для определения адреса. 2008

  30. Пользовательские типы • С помощью подпрограммы MPI_Type_get_contents можно определить • фактические параметры, использованные при создании производного • типа: • int MPI_Type_get_contents(MPI_Datatype datatype, int • max_integers, int max_addresses, int max_datatypes, int • *integers, MPI_Aint *addresses, MPI_Datatype *datatypes) • MPI_Type_get_contents(datatype, max_integers, max_addresses, • max_datatypes, integers, addresses, datatypes, ierr) • Входные параметры: • datatype идентификатор типа; • max_integers количество элементов в массиве integers; • max_addresses количество элементов в массиве addresses; • max_datatypes количество элементов в массиве datatypes. 2008

  31. Пользовательские типы • Выходные параметры: • integers содержит целочисленные аргументы, использованные при конструировании указанного типа; • addresses содержит аргументы address, использованные при конструировании указанного типа; • datatypes — содержит аргументы datatype, использованные при конструировании указанного типа. 2008

  32. Пользовательские типы Пример 1 program main_mpi include 'mpif.h' parameter (n = 50) real arr(n, n, n), b(9, 9, 9) integer slice1, slice2, slice3, sizeofreal integer rank, ierr, status(MPI_STATUS_SIZE) integer tag, cnt, vcount, blocklen, stride, count cnt = 1 tag = 0 vcount = 9 blocklen = 1 call MPI_Init(ierr) call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) 2008

  33. Пользовательские типы if (rank.eq.0) then call MPI_Type_extent(MPI_REAL, sizeofreal, ierr) stride = 2 call MPI_Type_vector(vcount, blocklen, stride, MPI_REAL, & slice1, ierr) stride = n * sizeofreal call MPI_Type_hvector(vcount, blocklen, stride, slice1, & slice2, ierr) stride = n * n * sizeofreal call MPI_Type_hvector(vcount, blocklen, stride, slice2, & slice3, ierr) call MPI_Type_commit(slice3, ierr) call MPI_Send(arr(1, 3, 2), cnt, slice3, 1, tag, & MPI_COMM_WORLD, ierr) call MPI_Type_free(slice3) call MPI_Type_free(slice2) call MPI_Type_free(slice1) 2008

  34. Пользовательские типы else if (rank.eq.1) then count = vcount * vcount * vcount call MPI_Recv(b, count, MPI_REAL, 0, tag, MPI_COMM_WORLD,& status, ierr) print *, b end if call MPI_Finalize(ierr) end 2008

  35. Пользовательские типы В этой программе строятся производные типы slice1, slice2 и slice3, соответствующие сечениям исходного трехмерного массива A. Вопрос и задание Каким? Попробуйте самостоятельно определить производные структуры данных. Между процессами в сечении выполняется блокирующий двухточечный обмен сообщениями. 2008

  36. Пользовательские типы Пример 2 – создание структурного типа #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; 2008

  37. Пользовательские типы Пример 2 – создание структурного типа #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; 2008

  38. Пользовательские типы 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); 2008

  39. Пользовательские типы if (myrank == 0) { indata.a = 3.14159; indata.b = 2.71828; indata.n = 2002; 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; } 2008

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

  41. Пользовательские типы Пример 3 – создание структурного типа в программе на языке Fortran program main_mpi include 'mpif.h' integer rank, tag, cnt, ierr, status(MPI_STATUS_SIZE) parameter (bufsize=100) character rcvbuf1(bufsize) integer newtype integer blocks, position integer disp(2), blen(2), type(2) integer address(2) integer data1, data3 complex data2, data4 cnt = 1 tag = 0 call MPI_Init(ierr) call MPI_Comm_rank(MPI_COMM_WORLD, rank, ierr) 2008

  42. Пользовательские типы if(rank == 0) then blen(1) = 1 blen(2) = 1 type(1) = MPI_INTEGER type(2) = MPI_COMPLEX blocks = 2 call MPI_Address(data1, address(1), ierr) disp(1) = address(1) call MPI_Address(data2, address(2), ierr) disp(2) = address(2) call MPI_Type_struct(blocks, blen, disp, type, newtype, ierr) call MPI_Type_commit(newtype, ierr) data1 = 3 data2 = (1., 3.) call MPI_Send(MPI_BOTTOM, cnt, newtype, 1, tag, & MPI_COMM_WORLD, ierr) print *, "process ", rank, " send ", data1, " and ", data2 call MPI_TYPE_Free(newtype, ierr) 2008

  43. Пользовательские типы else position = 0 call MPI_Recv(rcvbuf1, bufsize, MPI_PACKED, 0, tag, & MPI_COMM_WORLD, status, ierr) call MPI_Unpack(rcvbuf1, bufsize, position, data3, 1,& MPI_INTEGER, MPI_COMM_WORLD, ierr) call mpi_unpack(rcvbuf1, bufsize, position, data4, 1, & MPI_COMPLEX, MPI_COMM_WORLD, ierr) print *, "process ", rank, " received ", data3, " and ", data4 end if call MPI_Finalize(ierr) stop end 2008

  44. Пользовательские типы Результат выполнения этой программы выглядит так: # mpirun –np 2 a.out process 0 send 3 and (1.,3.) process 1 received 3 and (1.,3.) В программе используется одна из подпрограмм упаковки и распаковки (MPI_Unpack). 2008

  45. Операции упаковки данных 2008

  46. Операции упаковки данных Упаковка и распаковка, выполняемая подпрограммами MPI_Pack и MPI_Unpack, является альтернативным методом группировки данных. Подпрограмма MPI_Pack позволяет явным образом хранить произвольные (в том числе и расположенные не в последовательных ячейках) данные в непрерывной области памяти (буфере передачи). Подпрограмму MPI_Unpack используют для копирования данных из буфера приёма в произвольные (в том числе и не расположенные непрерывно) ячейки памяти. 2008

  47. Операции упаковки данных Сообщения передаются по коммуникационной сети, связывающей узлы вычислительной системы. Сеть работает медленно, поэтому, чем меньше в параллельной программе обменов, тем меньше потери на пересылку данных. С учетом этого полезен механизм, который позволял бы вместо отправки трех разных значений тремя сообщениями, отправлять их все вместе. Такие механизмы есть: это параметр count в подпрограммах обмена, производные типы данных и подпрограммы MPI_Pack и MPI_Unpack. 2008

  48. Операции упаковки данных С помощью аргумента count в подпрограммах MPI_Send, MPI_Recv, MPI_Bcast и MPI_Reduce можно отправить в одном сообщении несколько однотипных элементов данных. Для этого элементы данных должны находиться в непрерывно расположенных ячейках памяти. Если элементы данных  простые переменные, они могут не находиться в последовательных ячейках памяти. В этом случае можно использовать производные типы данных или упаковку. Подпрограмма упаковки может вызываться несколько раз перед передачей сообщения, содержащего упакованные данные, а подпрограмма распаковки в этом случае также будет вызываться несколько раз после выполнения приема. Для извлечения каждой порции данных применяется новый вызов. При распаковке данных текущее положение указателя в буфере сохраняется. 2008

  49. Операции упаковки данных • Интерфейс подпрограммы MPI_Pack: • int MPI_Pack(void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outcount, int *position, MPI_Comm comm) • MPI_Pack(inbuf, incount, datatype, outbuf, outcount, position, comm, ierr) • При вызове incount элементов указанного типа выбираются из входного буфера и упаковываются в выходном буфере, начиная с положения position. Входные параметры: • inbuf начальный адрес входного буфера; • incount количество входных данных; • datatype тип каждого входного элемента данных; • outcount размер выходного буфера в байтах; • position текущее положение в буфере в байтах; • comm коммуникатор для упакованного сообщения. • Выходным параметром является стартовый адрес выходного буфера outbuf. 2008

  50. Операции упаковки данных • Подпрограмма распаковки данных: • 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, ierr) • Входные параметры: • inbuf стартовый адрес входного буфера; • insize размер входного буфера в байтах; • position текущее положение в байтах; • outcount количество данных, которые должны быть распакованы; • datatype тип каждого выходного элемента данных; • comm коммуникатор для упаковываемого сообщения. • Выходной параметр outbuf стартовый адрес выходного буфера. 2008

More Related