| |
Как было отмечено в основной спецификации MPI, интерфейс нарушает стандарты несколькими путями. Это вызывало некоторые проблемы в программах на языке ФОРТРАН77 и стало более значимым для программ на ФОРТРАН90, поэтому пользователи должны быть осторожны при использовании новых возможностей ФОРТРАН 90. Эти нарушения изначально были адаптированы, но впоследствии возвращены с связи их важностью для возможности использования MPI. Остальная часть этой главы детально обсуждает потенциальные проблемы. Она заменяет обсуждение привязок ФОРТРАН оригинальной спецификации MPI (для ФОРТРАН90, но не ФОРТРАН77). Итак, следующие возможности MPI несовместимы с ФОРТРАН90:
Проблемы из-за жесткого определения типов. Все функции MPI с аргументами выбора ассоциируют реальные аргументы различных типов ФОРТРАНa с одним и тем же фиктивным аргументом. Это не допускалось ФОРТРАН77, а в ФОРТРАН90 допустимо только в случае перегрузки функции для каждого типа. В Си эти проблемы решались использованием формального аргумента void*.
Этот фрагмент технически неверен и может вызвать ошибку во время компиляции:
integer i(5) real x(5) ... call mpi_send(x, 5, MPI_REAL, ...) call mpi_send(i, 5, MPI_INTEGER, ...)На практике, компиляторы редко делают что-то кроме выдачи предупреждения, хотя считается, что компиляторы ФОРТРАН90 скорее всего вернут ошибку.
Так же для ФОРТРАНa технически недопустима передача скалярного аргумента в виде массива. Поэтому, следующий фрагмент кода может вызвать ошибку, так как аргумент buf для MPI_SEND объявлен как assumed-size массив типа <type> buf(*).
integer a call mpi_send(a, 1, MPI_INTEGER, ...)
Совет пользователям: В случае, если вы наткнулись на одну из проблем, связанных с проверками типов, вы можете избавиться от нее, используя флаг компилятора, компилируя по отдельности или используя реализацию MPI с расширенной поддержкой ФОРТРАНa, как описано в главе 8.2.4. В качестве альтернативы, которая будет работать с переменными, являющимися локальными по отношению с функции (но не к аргументам функции) можно использовать ключевое слово EQUIVALENCE для создания другой переменной с типом, приемлемым для компилятора.[]
Проблемы, связанные с копированием данных и последовательностями. В MPI заложена неявная идея того, что непрерывный кусок памяти доступен через линейное адресное пространство. MPI копирует данные в память и из нее. Программа MPI определяет местонахождение памяти предоставляя адреса и смещения в памяти. В языке Си правила ассоциации последовательностей и указатели представляют всю низкоуровневую структуру.
В ФОРТРАН90 данные пользователя не обязательно расположены непрерывно. Например, кусок массива A(1:N:2) включает только элементы массива A с индексами 1,3,5,.... То же справедливо и для массивов указателей, которые ссылаются на такой кусок. Большинство компиляторов стараются, чтобы массив в качестве фиктивного аргумента находился в непрерывной памяти если он объявлен с явным размером (например, B(N)) или имеет assumed size (например, B(*)). Если необходимо, они реализуют это копированием массива в непрерывную память. И ФОРТРАН77, и ФОРТРАН90 оговорены позволять такое копирование, но немногие компиляторы ФОРТРАН90 это делают. Технически, стандарты ФОРТРАН должны позволять содержать массивы во фрагментированной памяти.
Так как фиктивные буферные аргументы MPI - assumed-size arrays, это приводит к серьезным проблемам с неблокирующими вызовами: компилятор после возврата копирует временный массив обратно, а MPI продолжает копировать данные в память, содержавшую его. Например, следующий фрагмент:
real a(100) call MPI_IRECV(a(1:100:2), MPI_REAL, 50, ...)
Так как первый аргумент для MPI_IRECV - assumed-size array ( <type> buf(*)), секция массива a(1:100:2) перед передачей MPI_IRECV копируется во временный массив в непрерывной памяти. MPI_IRECV возвращается сразу и данные копируются обратно в массив a. Позже MPI может начать запись по адресам освобожденной памяти. Копирование также является проблемой и для MPI_ISEND, так как временная память может быть освобождена до того, как все данные будут оттуда переданы.
Большинство компиляторов ФОРТРАН90 не делают копию, если аргумент целиком является массивом явной формы, assumed-size array или ``простой'' секцией (например, A(1:N)) подобного массива. (Понятие ``простой'' секции мы определим в следующем параграфе). Также, многие компиляторы в этом отношении интерпретируют динамические (allocatable) массивы так же, как и массивы явной формы (хотя нам известен один, который так не делает). Тем не менее, это не так для assumed-shape и массивов указателей; так как они могут быть фрагментированы, часто производится копирование. Это тот случай, который вызывает проблемы с MPI, как описано в предыдущем параграфе.
``Простая'' секция массива формально определяется следующим образом:
name ( [:,]... [<subscript>]:[<subscript>] [,<subscript>]... )Это значит, существует ноль или более измерений, которые выбираются целиком, затем одно измерение выбираемое без шага, затем ноль или более измерений, выбираемых простым индексом. Примеры:
A(1:N), A(:,N), A(:,1:N,1), A(1:6,N), A(:,:,1:N)Благодаря ориентированному по колонкам индексированию ФОРТРАН, где первый индекс изменяется быстрее, простая секция массива также будет непрерывной. 1
Та же проблема может быть и с в случае со скалярным аргументом. Некоторые компиляторы, даже для ФОРТРАН77 делают копию некоторых скалярных аргументов по умолчанию в вызванной процедуре. Это может вызвать проблему, проиллюстрированную в следующем примере:
call user1(a,rq) call MPI\_WAIT(rq,status,ierr) write (*,*) a subroutine user1(buf,request) call MPI\_IRECV(buf,...,request,...) end
Если a скопировано, MPI_IRECV после завершения связи изменит копию и не изменит само a.
Заметьте, что копирование почти обязательно произойдет для аргумента, который представляет нетривиальное выражение (с как минимум, одним оператором или вызовом функции), секцией, не выбирающей непрерывную часть своего предка (например, A(1:n:2)), указатель, чья ссылка - такое выражение, или массив неявной формы, который (прямо или косвенно) ассоциируется с такой секцией.
Если есть опция компилятора, запрещающая копирование аргументов при вызовах и возвратах процедур, она должна быть включена.
Если компилятор делает копии при вызове процедур для аргументов, являющихся массивами явной формы или assumed-size arrays, простых секций таких массивов, скаляров, и у компилятора нет опций, запрещающих это, он не может быть использован а приложениях, использующих MPI_GET_ADDRESS, или любые неблокирующие функций MPI. Если компилятор копирует скалярные аргументы в вызванной процедуре и нет опции, запрещающей это, такой компилятор не может быть использован в приложениях, использующих ссылки на память между вызовами процедур как в указанном примере.
Особые константы. MPI требует набор особых ``констант'',
которые не могут быть реализованы как нормальные константы языка ФОРТРАН, в том
числе MPI_BOTTOM, MPI_STATUS_IGNORE,
MPI_IN_PLACE,
MPI_STATUSES_IGNORE и MPI_ERRCODES_IGNORE. В языке Си это
реализовано как константные указатели, обычно NULL, и используются
там, где прототип получает указатель на переменную, а не саму переменную.
В ФОРТРАН реализация таких особых констант может потребовать использование конструкций, выходящих за пределы стандарта ФОРТРАН. Использование особых значений для констант (например, определение их через ключевое слово parameter) также невозможно, так как реализация не может отличить эти значения от нормальных данных. Обычно эти константы определены как предопределенные статические переменные (например, переменная, определенная в объявленном в MPI блоке COMMON), полагаясь на то, что компилятор передает данные по адресу. Внутри процедуры адрес может быть получен некоторым механизмом за рамками стандарта ФОРТРАН (например, расширениями ФОРТРАНa или реализацией этой функции на Си).
Порожденные типы ФОРТРАН90. MPI не поддерживает явно передачу порожденных типов ФОРТРАН90 фиктивным аргументам с выбором. И в самом деле, для реализаций MPI, предоставляющих явные интерфейсы через модуль mpi , компилятор отклонит такой тип еще во время компиляции. Даже когда явные интерфейсы не даются, пользователи должны знать, что ФОРТРАН90 не дает гарантии ассоциации последовательности для порожденных типов. Например, массив порожденных типов, состоящих из двух элементов может быть реализован как массив первых элементов, за которым следует массив вторых элементов. Здесь может помочь использование атрибута SEQUENCE.
Следующий фрагмент показывает один из возможных путей передачи порожденных типов в ФОРТРАН. Пример предполагает, что данные передаются по адресу.
type mytype integer i real x double precision d end type mytype type(mytype) foo integer blocklen(3), type(3) integer(MPI\ADDRESS_KIND) disp(3), base call MPI_GET_ADDRESS(foo%i, disp(1), ierr) call MPI_GET_ADDRESS(foo%x, disp(2), ierr) call MPI_GET_ADDRESS(foo%d, disp(3), ierr) base = disp(1) disp(1) = disp(1) - base disp(2) = disp(2) - base disp(3) = disp(3) - base blocklen(1) = 1 blocklen(2) = 1 blocklen(3) = 1 type(1) = MPI_INTEGER type(2) = MPI_REAL type(3) = MPI_DOUBLE_PRECISION call MPI_TYPE_CREATE_STRUCT(3, blocklen, disp, type, newtype, ierr) call MPI_TYPE_COMMIT(newtype, ierr) ! не очень-то хорошо пересылать foo%i вместо foo, но для скалярных ! объектов типа mytype это работает call MPI_SEND(foo%i, 1, newtype, ...)Проблемы с регистровой оптимизацией. MPI содержит операции, которые могут быть спрятаны от кода пользователя и исполняться параллельно с ним, с доступом к той же памяти, что и код пользователя. Примеры включают передачу данных для MPI_IRECV. Оптимизатор компилятора считает, что он может определить периоды, когда копия переменной может находиться в регистре без перезагрузки из памяти или записи в нее. Когда программа пользователя работает с регистровой копией, а скрытая операция работает с памятью, возникает проблема. Эта глава обсуждает подводные камни регистровой оптимизации.
Когда переменная для процедуры ФОРТРАНa является локальной (то есть не модуль или блок COMMON), компилятор считает, что она не может быть изменена вызванной процедурой, если это не реальный аргумент вызова. В наиболее распространенной конвенции компоновщика от процедуры ожидается, что она сохранит и восстановит определенные регистры. Поэтому, оптимизатор будет предполагать, что регистр, содержавший верную копию такой переменной до вызова будет содержать ее и при возврате.
Обычно это не влияет на пользователей. Но в случае если в программе пользователя буферный аргумент для MPI_SEND, MPI_RECV и др. использует имя, которое скрывает настоящий аргумент, пользователь должен обратить внимание на эту главу. Один из примеров - MPI_BOTTOM с MPI_Datatype, содержащим абсолютный адрес. Другой способ - создание типа данных, который использует одну переменную как метку и работает с другими используя MPI_GET_ADDRESS для определения их смещения от метки. Переменная-метка будет единственной, упомянутой в вызове. Также следует уделить внимание случаю, когда используются те операции MPI, которые выполняются параллельно приложению пользователя.
Следующий пример показывает, что разрешено делать компиляторам ФОРТРАН.
Этот исходный код ... может быть скомпилирован как: call MPI_GET_ADDRESS(buf, call MPI_GET_ADDRESS(buf,...) bufaddr, ierror) call MPI_TYPE_CREATE_STRUCT(1, call MPI_TYPE_CREATE_STRUCT(...) 1,bufaddr, MPI_REAL,type, error) call MPI_TYPE_COMMIT(type, call MPI_TYPE_COMMIT(...) ierror) val_old = buf register = buf val_old = register call MPI_RECV(MPI_BOTTOM,1, call MPI_RECV(MPI_BOTTOM,...) type,...) val_new = buf val_new = register
Компилятор не помечает регистр недействительным, так как не может определить изменение значения buf функцией MPI_RECV. Доступ к buf скрыт использованием MPI_GET_ADDRESS и MPI_BOTTOM.
Следующий пример иллюстрирует экстремальные, но допустимые возможности.
Исходный код скомпилирован как или как: call MPI_IRECV(buf, call MPI_IRECV(buf, call MPI_IRECV(buf, ..req) ..req) ..req) register = buf b1 = buf call MPI_WAIT(req,..) call MPI_WAIT(req,..) call MPI_WAIT(req,..) b1 = buf b1 := register
MPI_WAIT в параллельном потоке изменяет buf между вызовом MPI_IRECV и завершением MPI_WAIT. Но компилятор не видит возможности изменения buf после возврата MPI_IRECV и может загрузить buf раньше, чем указано в исходном коде. Он не видит причин не использовать регистр для хранения buf до вызова MPI_WAIT. Он также может поменять порядок следования операций, как в случае справа.
Для предотвращения изменения порядка команд или хранения буфера в регистре есть две возможности построения реализации кроссплатформенного кода на ФОРТРАН:
call DD(buf) call MPI_RECV(MPI_BOTTOM,...) call DD(buf)
со скомпилированной отдельно
subroutine DD(buf) integer buf end
(будем считать, что buf имеет тип INTEGER). Таким же образом компилятор можно удержать от ссылки на переменную через вызов функции MPI.
В случае неблокирующего вызова, как и в случае с MPI_WAIT, никакие ссылки на буфер не допускаются до проверки завершения переноса данных. Таким образом, в этом случае дополнительный вызов MPI не является необходимым, то есть вызов MPI_WAIT в примере может быть изменен так:
call MPI_WAIT(req,..) call DD(buf)
В будущем, атрибут VOLATILE рассматривается для ФОРТРАН2000 и должен будет придать буферу или переменной необходимые свойства, но он будет подавлять регистровую оптимизацию для любого кода, содержащего буфер или переменную.
В языке Си, функции, которые могут изменить переменные, не являющиеся ее аргументами не будут вызывать проблем с регистровой оптимизацией. Это так, потому что получение указателей на объекты хранения используя оператор & и последующие ссылки на объект с использованием указателя - часть языка. Компилятор Си понимает такой подтекст, поэтому, в общем случае проблемы быть не должно. Тем не менее, есть компиляторы с опциональной агрессивной оптимизацией, которые могут быть не столь безопасны.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |