Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В.. Программирование для многопроцессорных систем в стандарте MPI -. Организация вычислений в многопроцессорных системах
Скачать 1.61 Mb.
|
3.10.4. Объявление и удаление объектов типа данных Объекты типов данных должны быть объявлены перед их исполь- зованием в коммуникации. Объявленный тип данных может быть ис- пользован как аргумент в конструкторах типов данных. Базисные ти- пы данных объявлять не нужно, поскольку они предопределены. MPI_TYPE_COMMIT(datatype) INOUT datatype тип данных, который объявлен (дескриптор) int MPI_Type_commit(MPI_Datatype *datatype) MPI_TYPE_COMMIT(DATATYPE, IERROR) INTEGER DATATYPE, IERROR void MPI::Datatype::Commit ( ) Операция commit объявляет тип данных, то есть формально опи- сывает коммуникационный буфер, но не содержимое этого буфера. Поэтому после того, как тип данных объявлен, он может быть много- кратно использован, чтобы передавать изменяемое содержимое буфе- ра или различных буферов с различными стартовыми адресами. MPI_TYPE_FREE (datatype) INOUT datatype тип данных, который освобождается (дескриптор) int MPI_Type_free(MPI_Datatype *datatype) MPI_TYPE_FREE (DATATYPE, IERROR) INTEGER DATATYPE, IERROR void MPI::Datatype::Free ( ) 91 Функция MPI_TYPE_FREE маркирует объекты типа данных, связанные с datatype для удаления и установки типа данных в MPI_DATATYPE_NULL . Любой обмен, который использует этот тип данных, будет завершен нормально. Производные типы данных, которые произошли от удаленного типа, не меняются. Пример 3.18. Пример использования MPI_TYPE_COMMIT. INTEGER type1, type2 CALL MPI_TYPE_CONTIGUOUS(5, MPI_REAL, type1, ierr) ! создан новый объект типа данных CALL MPI_TYPE_COMMIT(type1, ierr) ! теперь type1 может быть использован для обмена type2 = type1 ! type2 может быть использован для обмена CALL MPI_TYPE_VECTOR(3, 5, 4, MPI_REAL, type1, ierr) ! создан новый необъявленный объект типа CALL MPI_TYPE_COMMIT(type1, ierr) ! теперь type1 может быть использован снова для обмена Удаление типа данных не воздействует на другой тип, который был построен от удаленного типа. Система ведет себя как если бы ар- гументы входного типа данных были переданы конструктору произ- водного типа данных по значению. 3.10.5. Использование универсальных типов данных Пример 3.19. Пример использования производных типов. CALL MPI_TYPE_CONTIGUOUS( 2, MPI_REAL, type2, ...) CALL MPI_TYPE_CONTIGUOUS( 4, MPI_REAL, type4, ...) CALL MPI_TYPE_CONTIGUOUS( 2, type2, type22, ...) CALL MPI_SEND( a, 4, MPI_REAL, ...) CALL MPI_SEND( a, 2, type2, ...) CALL MPI_SEND( a, 1, type22, ...) CALL MPI_SEND( a, 1, type4, ...) CALL MPI_RECV( a, 4, MPI_REAL, ...) CALL MPI_RECV( a, 2, type2, ...) CALL MPI_RECV( a, 1, type22, ...) CALL MPI_RECV( a, 1, type4, ...) 92 Каждой передаче соответствует операция приема. Тип данных может описывать перекрывающиеся элементы. Использование такого типа в операциях приема неверно. Предположим, что выполнена опе- рация MPI_RECV ( buf, count, datatype, dest, tag, comm, status ), где карта типа имеет n элементов. Принятое сообщение не обязано ни за- полнять весь буфер, ни заполнять число ячеек, которое кратно n . Мо- жет быть принято любое число k базисных элементов, где 0 ≤ k ≤ count · n . Номер полученных базисных элементов может быть полу- чен из статуса с помощью функции MPI_GET_ELEMENTS Ранее определенная функция MPI_GET_COUNT имеет различное поведение. Она возвращает количество полученных «элементов верхнего уровня», то есть количество «копий» типа данных. В преды- дущем примере MPI_GET_COUNT может возвратить любое целое число k , где 0 ≤ k ≤ count · n . Если MPI_GET_COUNT возвращает k , тогда число принятых базисных элементов (и значение, возвращенное MPI_GET_ELEMENTS ) есть n · k . Если число полученных базисных элементов не кратно n , то есть операция приема не получила общее число «копий» datatype , то MPI_GET_COUNT возвращает значение MPI_UNDEFINED MPI_GET_ELEMENTS ( status, datatype, count) IN status возвращает статус операции приема (статус) IN datatype тип данных операции приема (дескриптор) OUT count число принятых базисных элементов (целое) int MPI_Get_elements (MPI_Status *status, MPI_Datatype datatype, int *count) MPI_GET_ELEMENTS (STATUS, DATATYPE, COUNT, IERROR) INTEGER STATUS(MPI_STATUS_SIZE), DATATYPE, COUNT, IERROR int MPI::Status::Get_elements(const MPI::Datatype& datatype) const Пример 3.20. Использование MPI_GET_COUNT,MPI_GET_ELEMENT CALL MPI_TYPE_CONTIGUOUS(2, MPI_REAL, Type2, ierr) CALL MPI_TYPE_COMMIT(Type2, ierr) CALL MPI_COMM_RANK(comm, rank, ierr) IF(rank.EQ.0) THEN CALL MPI_SEND(a, 2, Type2, 1, 0, comm, ierr) CALL MPI_SEND(a, 3, Type2, 1, 0, comm, ierr) ELSE CALL MPI_RECV(a, 2, Type2, 0, 0, comm, stat, ierr) CALL MPI_GET_COUNT(stat, Type2, i, ierr) ! возвращает i=1 93 CALL MPI_GET_ELEMENTS(stat, Type2, i, ierr) ! возвращает i=2 CALL MPI_RECV(a, 2, Type2, 0, 0, comm, stat, ierr) CALL MPI_GET_COUNT(stat,Type2,i,ierr) ! возвращает i=MPI_UNDEFINED CALL MPI_GET_ELEMENTS(stat, Type2, i, ierr) ! возвращает i=3 END IF Функция MPI_GET_ELEMENTS также может использоваться после операции probe , чтобы найти число элементов в опробованном сообщении. Заметим, что две функции MPI_GET_COUNT и MPI_GET_ELEMENTS возвращают то же самое значение, когда они используются с базисным типом данных. 3.10.6. Примеры Пример 3.21. Передача и прием части трехмерного массива. REAL a(100,100,100), e(9,9,9) INTEGER oneslice, twoslice, threeslice, sizeofreal, myrank, ierr INTEGER status(MPI_STATUS_SIZE) ! извлекает часть a(1:17:2, 3:11, 2:10)и запоминает ее в e(:,:,:). CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr) ! создает тип данных для секции 1D CALL MPI_TYPE_VECTOR( 9, 1, 2, MPI_REAL, oneslice, ierr) ! создает тип данных для секции 2D CALL MPI_TYPE_HVECTOR(9, 1, 100*sizeofreal, oneslice, twoslice, ierr) ! создает тип данных для секции в целом CALL MPI_TYPE_HVECTOR( 9, 1, 100*100*sizeofreal, twoslice, threeslice, ierr) CALL MPI_TYPE_COMMIT( threeslice, ierr) CALL MPI_SENDRECV(a(1,3,2), 1, threeslice, myrank, 0,e, 9*9*9, REAL, myrank, 0,MPI_COMM_WORLD, status, ierr) Пример 3.22. Копирование нижней треугольной части матрицы REAL a(100,100), b(100,100) INTEGER disp(100), blocklen(100), ltype, myrank, ierr INTEGER status(MPI_STATUS_SIZE) ! копирует нижнюю треугольную часть массива a в нижнюю ! треугольную часть массива b CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) ! вычисляет начало и размер каждого столбца 94 DO i=1, 100 disp(i) = 100*(i-1) + i block(i) = 100-i END DO ! создает тип данных для нижней треугольной части CALL MPI_TYPE_INDEXED(100,block, disp, MPI_REAL, ltype, ierr) CALL MPI_TYPE_COMMIT(ltype, ierr) CALL MPI_SENDRECV(a, 1, ltype, myrank, 0, b, 1, ltype, myrank, 0, MPI_COMM_WORLD,status, ierr) Пример 3.23. Транспонирование матрицы. REAL a(100,100), b(100,100) INTEGER row, xpose, sizeofreal, myrank, ierr INTEGER status(MPI_STATUS_SIZE) ! транспонирование матрицы a в матрицу b CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr) ! создание типа данных для одной строки CALL MPI_TYPE_VECTOR( 100, 1, 100, MPI_REAL, row, ierr) ! создание типа данных для матрицы с расположением по строкам CALL MPI_TYPE_HVECTOR( 100, 1, sizeofreal, row, xpose, ierr) CALL MPI_TYPE_COMMIT( xpose, ierr) ! посылка матрицы с расположением по строкам ! и получение матрицы с расположением по столбцам CALL MPI_SENDRECV( a, 1, xpose, myrank, 0, b, 100*100, MPI_REAL, myrank, 0, MPI_COMM_WORLD, status,ierr) Пример 3.24. Другой способ транспонирования матрицы. REAL a(100,100), b(100,100) INTEGER disp(2), blocklen(2), type(2), row, row1, sizeofreal INTEGER myrank, ierr INTEGER status(MPI_STATUS_SIZE) CALL MPI_COMM_RANK(MPI_COMM_WORLD, myrank) ! транспонирование матрицы a в матрицу b CALL MPI_TYPE_EXTENT( MPI_REAL, sizeofreal, ierr) ! создание типа данных для одной строки CALL MPI_TYPE_VECTOR( 100, 1, 100, MPI_REAL, row, ierr) ! создание типа данных для одной строки disp(1) = 0 disp(2) = sizeofreal type(1) = row type(2) = MPI_UB blocklen(1) = 1 blocklen(2) = 1 95 CALL MPI_TYPE_STRUCT( 2, blocklen, disp, type, row1, ierr) CALL MPI_TYPE_COMMIT( row1, ierr) ! посылка 100 строк и получение с расположением по столбцам CALL MPI_SENDRECV( a, 100, row1, myrank, 0, b, 100*100, MPI_REAL, myrank, 0, MPI_COMM_WORLD, status, ierr) Пример 3.25. Посылка массива структур. struct Partstruct { int class; /* класс частицы */ double d[6]; /* координаты частицы */ char b[7]; /* некоторая дополнительная информация */ }; struct Partstruct particle[1000]; int i, dest, rank; MPI_Comm comm; /* построение типа данных описываемой структуры */ MPI_Datatype Particletype; MPI_Datatype type[3] = {MPI_INT, MPI_DOUBLE, MPI_CHAR}; int blocklen[3] = {1, 6, 7}; MPI_Aint disp[3]; int base; /* вычисление смещений компонент структуры */ MPI_Address( particle, disp); MPI_Address( particle[0].d, disp+1); MPI_Address( particle[0].b, disp+2); base = disp[0]; for (i=0; i <3; i++) disp[i] -= base; MPI_Type_struct( 3, blocklen, disp, type, &Particletype); MPI_Type_commit( &Particletype); MPI_Send( particle, 1000, Particletype, dest, tag, comm); Пример 3.26. Обработка объединений. union { int ival; float fval; } u[1000] int utype; MPI_Datatype type[2]; int blocklen[2] = {1,1}; MPI_Aint disp[2]; MPI_Datatype MPI_utype[2]; MPI_Aint i,j; /* вычисляет тип данных MPI для каждого возможного типа union; считаем, что значения в памяти union выровнены по левой границе.*/ 96 MPI_Address( u, &i); MPI_Address( u+1, &j); disp[0] = 0; disp[1] = j-i; type[1] = MPI_UB; type[0] = MPI_INT; MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[0]); type[0] = MPI_FLOAT; MPI_Type_struct(2, blocklen, disp, type, &mpi_utype[1]); for(i=0; i<2; i++) MPI_Type_commit(&mpi_utype[i]); /* фактический обмен */ MPI_Send(u, 1000, MPI_utype[utype], dest, tag, comm); 3.11. УПАКОВКА И РАСПАКОВКА Некоторые существующие библиотеки для передачи сообщений обеспечивают функции pack/unpack для передачи несмежных дан- ных. При этом пользователь явно пакует данные в смежный буфер пе- ред их посылкой и распаковывает смежный буфер при приеме. Произ- водные типы данных, которые описаны в параграфе 3.10, позволяют избежать упаковки и распаковки. Пользователь описывает размеще- ние данных, которые должны быть посланы или приняты, и коммуни- кационная библиотека прямо обращается в несмежный буфер. Процедуры pack/unpack обеспечивают совместимость с преды- дущими библиотеками. К тому же они обеспечивают некоторые воз- можности, которые другим образом недоступны в MPI. Например, со- общение может быть принято в нескольких частях, где приемная опе- рация, сделанная на поздней части, может зависеть от содержания первой части. Другое удобство состоит в том, что исходящее сообще- ние может быть явно буферизовано в предоставленном пользователю пространстве, превышая возможности системной политики буфериза- ции. Доступность операций pack и unpack облегчает развитие допол- нительных коммуникационных библиотек, расположенных на верх- нем уровне MPI. Операция MPI_PACK пакует сообщение в буфер посылки, опи- санный аргументами inbuf, incount, datatype в буферном пространст- ве, описанном аргументами outbuf и outsize Входным буфером может быть любой коммуникационный буфер, разрешенный в MPI_SEND Выходной буфер есть смежная область памяти, содержащая outsize 97 байтов, начиная с адреса outbuf (длина подсчитывается в байтах, а не элементах, как если бы это был коммуникационный буфер для сооб- щения типа MPI_PACKED ). 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) 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 Входное значение position есть первая ячейка в выходном буфере, которая должна быть использована для упаковки. рosition инкремен- тируется размером упакованного сообщения, выходное значение рosition есть первая ячейка в выходном буфере, следующая за ячей- ками, занятыми упакованным сообщением. Аргумент comm – комму- никатор, используемый для передачи упакованного сообщения. Функция MPI_UNPACK распаковывает сообщение в приемный буфер, описанный аргументами outbuf, outcount, datatype из буфер- ного пространства, описанного аргументами inbuf и insize Выходным буфером может быть любой буфер, разрешенный в MPI_RECV . Входной буфер есть смежная область памяти, содержа- щая insize байтов, начиная с адреса inbuf . Входное значение position есть первая ячейка во входном буфере, занятом упакованным сообще- нием. рosition инкрементируется размером упакованного сообщения, так что выходное значение рosition есть первая ячейка во входном буфере после ячеек, занятых сообщением, которое было упаковано. сomm есть коммуникатор для приема упакованного сообщения. 98 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) 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 Чтобы понять поведение pack и unpack , предположим, что часть данных сообщения есть последовательность, полученная конкатена- цией последующих значений, посланных в сообщении. Операция pack сохраняет эту последовательность в буферном пространстве, как при посылке сообщения в этот буфер. Операция unpack обрабатывает по- следовательность из буферного пространства, как при приеме сооб- щения из этого буфера. Несколько сообщений могут быть последова- тельно упакованы в один объект. Это достигается несколькими после- довательными связанными обращениями к MPI_PACK , где первый вызов обеспечивает position = 0 , и каждый последующий вызов вво- дит значение position , которое было выходом для предыдущего вызо- ва, и то же самое значение для outbuf, outcount и comm. Этот упако- ванный объект содержит эквивалентную информацию, которая хра- нилась бы в сообщении по одной передаче с буфером передачи, кото- рый есть «конкатенация» индивидуальных буферов передачи. Упакованный объект может быть послан типом MPI_PACKED Любая парная или коллективная коммуникационная функция может быть использована для передачи последовательности байтов, которая формирует упакованный объект, из одного процесса в другой. Этот упакованный объект также может быть получен любой приемной опе- рацией, поскольку типовые правила соответствия ослаблены для со- общений, посланных с помощью MPI_PACKED 99 Сообщение, посланное с любым типом может быть получено с помощью MPI_PACKED . Такое сообщение может быть распаковано обращением к MPI_UNPACK Упакованный объект может быть распакован в несколько последо- вательных сообщений. Это достигается несколькими последователь- ными обращениями к MPI_UNPACK , где первое обращение обеспе- чивает position = 0, и каждый последовательный вызов вводит значе- ние position , которое было выходом предыдущего обращения, и то же самое значение для inbuf, insize и comm Конкатенация двух упакованных объектов не обязательно является упакованным объектом; подстрока упакованного объекта также не обязательно есть упакованный объект. Поэтому нельзя производить конкатенацию двух упакованных объектов и затем распаковывать ре- зультат как один упакованный объект; ни распаковывать подстроку упакованного объекта, как отдельный упакованный объект. Каждый упакованный объект, который был создан соответствующей последо- вательностью операций упаковки или регулярными send , обязан быть распакован как объект последовательностью связанных распаковы- вающих обращений. Следующий вызов позволяет пользователю вы- яснить, сколько пространства нужно для упаковки объекта, что позво- ляет управлять распределением буферов. Обращение к MPI_PACK_SIZE ( incount, datatype, comm, size ) возвращает в size верхнюю границу по инкременту в position , которая создана обращением к MPI_PACK ( inbuf, incount, datatype, outbuf, outcount, position, comm ). 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 100 Пример 3.27. Пример использования 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.28. Более сложный пример. 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); */ } 101 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 Контрольные вопросы к 3.2 1. Что такое атрибуты сообщения? 2. Возвращает ли функция MPI_Send код ошибки, номер процесса, которому адресована передача? 3. Происходит ли возврат из функции MPI_Send, когда можно повторно исполь- зовать параметры данной функции или когда сообщение покинет процесс, или когда сообщение принято адресатом или когда адресат инициировал приём данного сообщения? 4. Может ли значение переменной count быть равным нулю? 5. Можно ли в качестве значения тэга в команде посылки передать значение но- мера процесса в коммуникаторе? 6. Может ли длина буфера получателя быть большей, чем длина принимаемого сообщения? А меньшей? 7. Каков диапазон значений принимаемых константами MPI_ANY_SOURCE, MPI_ANY_TAG? 8. Можно ли в операции посылки использовать MPI_ANY_SOURCE? Почему? 9. Как определить номер процесса-отправителя, если при приеме используются MPI_ANY_SOURCE, MPI_ANY_TAG? 10. Означает ли возврат из функции MPI_Recv, что произошла ошибка? 11. Нужно ли перед вызовом функции MPI_Recv обратиться к функции MPI_Get_Count? 12. Можно ли использовать функцию MPI_Recv, если не известен отправитель сообщения или тэг сообщения? 13. Как определить длину переданного сообщения? 14. Всегда ли status.MPI_SOURCE= SOURCE, status.MPI_TAG= MPI_TAG? Контрольные вопросы к 3.3 1. Сформулируйте основные правила соответствия типов данных. 2. Правильна ли программа в примере 3.1, если отправитель использует четы- рехбайтовое представление для действительных чисел, а получатель – вось- мибайтовое? 3. Объясните, почему пример 3.3 корректен, хотя посылаем 40 элементов, а в операции приема указываем 60 элементов? 4. Как изменить программу в примере 3.2, чтобы она стала корректна? 102 5. Влечет ли обмен в MPI преобразование типов данных при их несоответствии? 6. Являются ли MPI–программы, в которых смешаны языки, переносимыми? Контрольные вопросы к 3.4 1. Что такое стандартный коммуникационный режим? 2. Буферизуется ли исходящее сообщение при стандартном коммуникационном режиме? 3. В каком случае использование функций MPI_Send и MPI_Recv приведет к дедлоку? 4. Перечислите три дополнительных коммуникационных режима. 5. Сколько операций приема существует для трех разных режимов передачи? 6. Почему потребовалось введение различных коммуникационных режимов? 7. Почему посылка в стандартном режиме коммуникации является локальной операцией? 8. Что произойдет, если при буферизованной посылке недостаточно места для сообщения? 9. В чем основное различие между буферизованным и стандартным режимами? 10. Можно ли рассматривать синхронный режим коммуникаций как способ син- хронизации процессов? 11. В чем основное различие между синхронным и стандартным режимами? 12. В чем основное различие между стандартным и режимом по готовности? Контрольные вопросы к 3.5 1. Перечислите основные свойства парного обмена, которые гарантирует пра- вильная реализация MPI. 2. Что произойдет при выполнении программы из примера 3.4. если не будет выполняться свойство очередности? 3. Что произойдет при выполнении программы из примера 3.5. если не будет выполняться свойство продвижения обмена? 4. Гарантируют ли свойства парного обмена, что программа с передачей сооб- щений детерминирована? 5. В каких ситуациях недостаток буферного пространства ведет к дедлоку? 6. Как разрешить ситуацию дедлока в примере 3.7? 7. В каком случае возникнет ситуация дедлока в примере 3.8? Контрольные вопросы к 3.6. 1. Необходимы ли функции, описанные в данном разделе, при стандартном коммуникационном режиме? Почему? 2. Что произойдет при вызове функции MPI_Buffer_attach, если недостаточно памяти для буфера? 3. Сколько буферов можно присоединить к процессу за один вызов MPI_Buffer_attach? 4. В каком режиме должны быть посланы сообщения для использования буфера в MPI_Buffer_attach? 5. Когда функция MPI_Buffer_detach может быть вызвана? 103 Контрольные вопросы к 3.7. 1. В чем различие между блокирующим и неблокирующим обменом? 2. Что такое скрытые запросы? 3. Верен ли вызов функции MPI_IRecv(buf, 1, MPI_INT, 3, tag, comm, &status); для того, чтобы принять одно целое число от процесса 3 (считаем, что все пе- ременные описаны корректно, значение переменной tag – правильное)? 4. Определите понятие “завершение операции посылки” для разных коммуника- ционных режимов. 5. Определите понятие “завершение операции приема” для разных коммуника- ционных режимов. 6. Что такое активный и неактивный дескриптор? 7. В чем различие использования MPI_Wait и MPI_Test? 8. Означает ли возврат из функции MPI_Wait, что все процессы дошли до барье- ра или отправитель вернулся из функции MPI_Send, или получатель вернулся из функции MPI_Recv, или закончилась асинхронно запущенная операция? 9. Как изменится код программы в примере 3.10, если вместо MPI_Wait исполь- зовать MPI_Test? 10. Какие значения имеет status в примере 3.10 при вызове MPI_Wait? 11. Какие свойства неблокирующего парного обмена должны быть гарантирова- ны правильной реализацией MPI? 12. Что произойдет при выполнении программы из примера 3.11, если не будет выполняться свойство очередности? 13. Что произойдет при выполнении программы из примера 3.12, если не будет выполняться свойство продвижение обмена? 14. Закончилась одна или все из асинхронно запущенных операций, ассоцииро- ванных с указанными в списке параметров идентификаторами requests, если значение параметра flag равно 1 при возврате из функции MPI_TestAll? 15. В чем различие использования функций MPI_Waitany, MPI_Waitall, MPI_Waitsome? 16. В чем различие использования функций MPI_Waitany, MPI_Testany? 17. Если несколько операций из массива активных запросов могут завершиться одновременно при вызове MPI_Waitany, какой номер процесса будет выбран? 18. Что означает код MPI_ERR_Pending в статусе при выполнении функции MPI_Waitall? 19. Почему код, в примере 3.14 с использованием MPI_Waitsome дает правиль- ный результат, в отличие от кода в примере 3.13 с использованием MPI_Waitany, при котором имеет место невозможность обмена? Контрольные вопросы к 3.8 1. В каком случае возникает необходимость проверять входные сообщения без их реального приема? 2. Какая функция позволяет отменить ждущие сообщения? В каких ситуациях это необходимо? 3. В чем различие в использовании MPI_Probe и MPI_Iprobe? 104 4. Что может произойти при выполнении кода в примере 3.15, если не исполь- зовать блокируемую пробу для ожидания входного сообщения? 5. Что произойдет при выполнении программы в примере 3.15, если количество процессов будет 1 , 2, больше 3? 6. Как изменить код программы в примере 3.16, чтобы она была корректна и выполнялась для количества процессов больше 3? 7. Как завершить операцию обмена, которую маркирует функция MPI_Cancel? 8. Как определить, что операция обмена была отменена функцией MPI_Cancel? Контрольные вопросы к 3.9 1. Может ли быть получателем и отправителем один и тот же процесс в комби- нируемой операции send–receive? 2. Может ли быть получено сообщение обычной операцией приема, если оно по- слано операцией send–receive? 3. Какой режим обменов осуществляет операция send–receive – блокирующий или неблокирующий? 4. В чем отличие функций MPI_Sendrecv и MPI_Sendrecv_replace? 5. В каком случае может понадобиться "фиктивный" отправитель или получа- тель для коммуникаций? 6. Каков результат обмена с процессом, который имеет значение MPI_PROC_NULL? 7. Каковы значения статуса и тэга, если прием происходит от процесса, который имеет значение MPI_PROC_NULL? Контрольные вопросы к 3.10 1. Как осуществить обмен данными, которые не непрерывно расположены в па- мяти, не используя производные типы данных? 2. Почему лучше один раз вызвать функцию приемопередачи со сложным ти- пом, чем много раз с простым? 3. Есть ли различие при пересылке массива следующими двумя способами: MPI_Send( a, 16, MPI_INT, ... ); MPI_Send( a, 1, intArray16, ... ); если int a[16]; MPI_Datatype intArray16; MPI_Type_contiguous( 16, MPI_INT, &intArray16 ) MPI_Type_commit( &intArray16 )? 4. Опишите структуру данных, которая создается при вызове: MPI_Type_vector( 5, 1, 7, MPI_DOUBLE, &newtype ); 5. Эквивалентны ли следующие обращения ( n – произвольно): MPI_Type_contiguous (count, oldtype, newtype); MPI_Type_vector (count, 1, 1, oldtype, newtype); MPI_Type_vector (1, count, n, oldtype, newtype); 6. В чем различие между производными типами, созданными двумя способами? MPI_Type_vector ( 5, 1, 7, MPI_DOUBLE, &newtype ); MPI_Type_hvector( 5, 1, 7, MPI_DOUBLE, &newtype ); 105 7. Опишите параметры функции MPI_Type_struct(3, block_lengths, displs, typelist,message_typ); которая позволит создать новый тип данных для осуществления передачи данных следующего типа: typedef struct { float a; float b; int n; } INDATA_TYPE 8. Какая операция в С эквивалентна вызову функции MPI_Address? 9. Функция MPI_Type_size возвращает размер элемента в байтах или в количе- стве элементов старого типа, содержащегося в новом? 10. Нужно ли перед вызовом функции MPI_Type_struct обратиться к функции MPI_Type_commit? 11. Когда и почему необходимо использовать функцию MPI_Type_free? 12. Обьясните разные результаты, полученные функциями MPI_GET_COUNT и MPI_GET_ELEMENTS в примере 3.20. 13. Эквивалентен ли вызов MPI_SEND (buf, count, datatype, dest, tag, comm) вызовам MPI_Type_contiguous(count, datatype, newtype) MPI_Type_commit(newtype) MPI_Send(buf, 1, newtype, dest, tag, comm)? 14. Сколько производных типов данных создается в примере 3.21? Предложите вариант эквивалентного кода, заменив MPI_TYPE_HVECTOR на MPI_TYPE_VECTOR. 15. Предложите другие способы задания типа, эквивалентные по результату функции MPI_TYPE_INDEXED в примере 3.22. 16. Какое значение получаем при вызове CALL MPI_TYPE_EXTENT ( MPI_REAL, sizeofreal, ierr) в примере 3.23? 17. Сравните программы в примерах 3.23 и 3.24 для решения одной и той же за- дачи транспонирования матриц. Какой вариант предпочтительнее? Почему? 18. Как выполнить посылку массива структур из примера 3.25, не используя MPI_Type_struct? 19. Как измениться код в примере 3.26, если в union будет полей больше? Контрольные вопросы к 3.11 1. Какие возможности обеспечивают функции pack/unpack, которые недоступны в MPI другим способом? 2. Укажите разницу при использовании параметра count в разных функциях: MPI_Recv (…, count, …) и MPI_Unpack (…,count,…) . 3. Можно ли распаковывать результат как один упакованный объект, если пе- ред посылкой происходила упаковка двух объектов? 4. Предложите вариант посылки данных в примере 3.26 без использования MPI_Pack. 5. Почему в примере 3.27 не нужно вызывать функцию MPI_Unpack после приема результата? 6. Почему в пример 3.28 используется две функции MPI_Unpack при приеме, если при посылке использовали одну функцию MPI_Pack? 106 Задания для самостоятельной работы 3.1. Напишите программу, в которой каждый процесс MPI печатает “Hello Word from process i for n”, где i – номер процесса в MPI_COMM_WORLD, а n – размер MPI_COMM_WORLD. 3.2. Напишите программу, в которой определено только два процесса. В од- ном из них осуществляется генерация случайных чисел, количество которых за- дает пользователь. Второй процесс получает эти числа, складывает их, посылает результат в первый процесс. Результат выводится на экран. 3.3. Напишите программу, которая рассылает данные от процесса с номером 0 всем другим процессам по конвейеру. Этот означает, что процесс i должен при- нять данные от i-1 и послать их процессу i + 1, и так до тех пор, пока не будет достигнут последний процесс. Процесс с номером 0 читает данные (целые) от пользователя, пока не будет введено отрицательное целое число. 3.4.Выполните задание 3.3, замкнув вычисления по кругу, т.е. последний про- цесс передает данные нулевому, который выводит их на экран. 3.5.Выполните задание 3.3, используя обмен MPI_Sendrecv. 3.6. Напишите программу, которая проверяет, в каком порядке осуществляет- ся передача сообщений. Для этого можно допустить, что все процессы за исклю- чением процесса 0 посылают 100 сообщений процессу 0. Пусть процесс 0 распе- чатывает сообщения в порядке их приема, используя MPI_ANY_SOURCE и MPI_ANY_TAG в MPI_Recv. 3.7. Выполните задание 3.6, используя неблокируемый прием MPI_IRecv. 3.8. Напишите программу для измерения времени передачи вещественных данных двойной точности от одного процесса другому. Для усреднения наклад- ных расходов следует: повторить достаточное количество операций пересылок для получения времени в пределах долей секунды (образцовое решение делает 100000/size итераций для целых size), повторить тестирование несколько раз (на- пример, 10) и усреднить результаты. 3.9. Выполните задание 3.8, используя MPI_SSend. Сравните характеристики с теми, которые получаются при иcпользовании MPI_Send. 3.10. Выполните задание 3.8, используя MPI_BSend. Сравните характеристики с теми, которые получаются при иcпользовании MPI_Send. 3.11. Выполните задание 3.8, используя MPI_RSend. Сравните характеристики с теми, которые получаются при иcпользовании MPI_Send . 3.12. Выполните задание 3.8, используя MPI_ISend, MPI_IRecv, MPI_Wait. Сравните характеристики с теми, которые получаются при иcпользовании MPI_Send и MPI_Recv. Этот тест измеряет эффективную полосу пропускания и задержку, когда процессор одновременно посылает и принимает данные. 3.13. Напишите программу для измерения времени, необходимого для выпол- нения пересылки вектора из 1000 элементов MPI_DOUBLE со страйдом 24 между элементами. Используйте MPI_Type_vector для описания данных. Используйте те же приемы, как в задании 3.8 для усреднения вариаций и накладных расходов. 3.14. Выполните задание 3.13, используя MPI_Type_struct, чтобы сформиро- вать структуру со страйдом. Сравните результаты с результатами в 3.13. 107 3.15.Выполните задание 3.13, используя MPI_DOUBLE и цикл для самостоя- тельной упаковки и распаковки (то есть не используйте типы данных MPI). Срав- ните результаты с результатами в 3.13, 3.14. 3.16. Напишите программу, которая пересылает процессу (можно самому се- бе) часть трехмерного массива (задаются значения для каждой размерности), ис- пользуя производные типы данных MPI_Type_vector, MPI_Type_Hvector. 3.17. Выполните задание 3.16, используя MPI_Pack, MPI_Unpack. 3.18. Напишите программу, которая позволяет копировать нижнюю треуголь- ную часть массива A в нижнюю треугольную часть массива B, используя MPI_Type_indexed. 3.19. Напишите программу транспонирования матрицы, используя типы дан- ных MPI_Type_vector, MPI_Type_Hvector. 3.20. Напишите программу транспонирования матрицы, используя производ- ные типы данных MPI_Type_struct. |