Санкт-Петербургский государственный университет
С.А. Немнюгин
Средства программирования для многопроцессорных вычислит...
17 downloads
212 Views
3MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Санкт-Петербургский государственный университет
С.А. Немнюгин
Средства программирования для многопроцессорных вычислительных систем Intel Multicore Curriculum Initiative
Санкт-Петербург 2007
2
Данное пособие предназначено для использования в учебных курсах, посвященных средствам параллельного программирования для многопроцессорных вычислительных систем. Дается краткое введение в параллельное программирование, приводится описание моделей параллельного программирования и специфики проектирования параллельных алгоритмов и программ. Значительная часть пособия содержит основные сведения об инструментальных средствах параллельного программирования OpenMP и MPI. Описание строится по следующей схеме: модель параллельной программы, привязки к языкам программирования Фортран и C, описание программного интерфейса. Дается краткий обзор языка программирования Фортран 90. Пособие предназначено для студентов, аспирантов, а также всех тех, кто планирует использование данной технологии для решения своих задач. Оно может использоваться в качестве справочника по средствам параллельного программирования.
3
Оглавление I Введение
4
II OpenMP
18
III MPICH
31
IV Фортран 90
75
V Литература
88
4
I. Введение
5
Последовательная и параллельная модели программирования Модель программирования это совокупность приемов программирования, отвечающих архитектуре абстрактного компьютера, предназначенного для выполнения определенного класса алгоритмов. Модель программирования основана на определенном представлении о логической организации компьютера, его архитектуре. Существуют различные архитектуры, и одной из схем их классификации является таксономия Флинна, основанная на описании работы компьютера с потоками команд и данных (рис. 1 - 5).
Рис. 1. Параметры классификации Флинна
Рис. 2. Архитектура SISD (Single Instruction Stream Single Data Stream) один поток команд и один поток данных
Рис. 3. Архитектура SIMD (Single Instruction Stream Multiple Data Stream) один поток команд и несколько потоков данных
6
Рис. 4. Архитектура MISD (Multiple Instruction Stream Single Data Stream) несколько потоков команд и один поток данных
Рис. 5. Архитектура MIMD (Multiple Instruction Stream Multiple Data Stream) несколько потоков команд и несколько потоков данных Взаимодействие данных и команд может быть представлено информационным графом. Информационный граф описывает последовательность выполнения операций и взаимную зависимость между различными макрооперациями или блоками операций. Узлами информационного графа являются макрооперации, а однонаправленными дугами каналы обмена данными (рис. 6).
Рис. 6. Информационный граф программы
7 Каждый узел можно охарактеризовать парой (n, s), где n имя узла, s его размер. Размер узла определяется количеством элементарных операций, входящих в его состав. Дуга характеризуется парой (v, d), где v пересылаемые данные, а d время, затрачиваемое на ее пересылку. Информационный граф содержит линейные участки, а также многосвязные фрагменты (циклы). Предельные случаи информационного графа, топологически эквивалентные линейной последовательности макроопераций и лесу линейных последовательностей (рис. 7), соответствуют чистым последовательной и параллельной моделям вычислений.
Рис. 7. Предельные случай информационного графа программы Для последовательной модели программирования характерны следующие особенности: •
относительно невысокая производительность;
•
применение стандартных языков программирования;
• хорошая переносимость программ на уровне исходного кода. Параллельная модель программирования характеризуется: •
возможностью добиться более высокой производительности программ;
•
применением специальных приемов программирования;
•
применением специальных инструментов программирования;
•
более высокой трудоемкостью программирования;
• проблемами с переносимостью программ. При разработке параллельных приложений возникают и специфические для параллельной модели программирования проблемы: •
управление работой множества процессов;
•
организация межпроцессных пересылок данных;
•
вероятность тупиковых ситуаций (взаимных блокировок). Блокировки возникают, если процесс или поток ожидает доступ к ресурсу, заблокированному другим процессом, блокируя, в свою очередь, доступ к ресурсу, требуемому для продолжения работы второго процесса или потока;
•
нелокальный и динамический характер ошибок. Процессы или потоки выполняются на разных процессорах или ядрах. Загрузка вычислительных узлов изменяется с течением времени, поэтому для обеспечения корректной работы параллельной программы может потребоваться явная синхронизация;
•
утрата детерминизма (предсказуемого поведения программы в процессе ее исполнения). Параллельная программа может давать разные результаты при
8 повторных запусках даже без модификации кода и исходных данных. Это является следствием «гонок за данными» - одновременного и асинхронного доступа разных процессов или потоков к общим переменным; •
необходимость заботиться о масштабируемости программы (масштабируемость – это свойство вычислительной системы, компьютерной сети или процесса/процессов, которое заключается в сохранении способности эффективно работать при возрастании их сложности);
•
необходимость заботиться о сбалансированной загрузке вычислительных узлов.
Модели параллельного программирования В рамках параллельной модели программирования существуют различные подходы, ориентированные на разные архитектуры высокопроизводительных вычислительных систем и различные инструментальные средства. Перечислим некоторые из них.
Модель передачи сообщений Основные особенности данного подхода: •
Программа порождает несколько задач.
•
Каждой задаче присваивается свой уникальный идентификатор.
•
Взаимодействие осуществляется посредством отправки и приема сообщений.
•
Новые задачи могут создаваться во время выполнения параллельной программы, несколько задач могут выполняться на одном процессоре.
Модель параллелизма данных Основные особенности данного подхода: •
Одна операция применяется к множеству элементов структуры данных. Программа содержит последовательность таких операций.
•
"Зернистость" вычислений мала.
•
Программист должен указать транслятору, как данные следует распределить между задачами.
Модель общей памяти В модели общей (разделяемой) памяти задачи обращаются к общей памяти, имея общее адресное пространство и выполняя операции считывания/записи. Управление доступом к памяти осуществляется с помощью разных механизмов, таких, например, как семафоры. В рамках этой модели не требуется описывать обмен данными между задачами в явном виде. Это упрощает программирование. Вместе с тем особое внимание приходится уделять соблюдению детерминизма, таким явлениям, как «гонки за данными» и т. д.
Законы Амдала Законы Амдала составляют теоретическую основу оценок достижимой производительности параллельных программ. Они сформулированы для некоторой идеализированной модели
9 параллельных вычислений, в которой, например, не учитывается латентность (конечное время передачи данных) коммуникационной среды и т. д.
1-й закон Производительность вычислительной системы, состоящей из связанных между собой устройств, определяется самым медленным компонентом. 2-й закон Пусть время выполнения алгоритма на последовательной машине T1, причем Ts время выполнения последовательной части алгоритма, а Tp параллельной. Тогда при выполнении той же программы на идеальной параллельной машине, содержащей N процессорных элементов коэффициент ускорения: K=
где S =
T1 Ts + Tp 1 = = , T P T2 p S + Ts + N N
T Ts и P= p относительные доли последовательной и Ts + Tp Ts + Tp
параллельной частей (S + P = 1). Графическое представление закона Амдала дано на рис. 8. 3-й закон Пусть система состоит из N простых одинаковых процессорных элементов, тогда при любом режиме работы K ≤
1 P
Рис. 8. Зависимость ускорения от доли параллельного кода и числа процессоров в законе Амдала
10
Две парадигмы параллельного программирования На рис. 9 представлен информационный граф, показывающий, что в общем случае в программе присутствуют как параллелизм данных, так и параллелизм задач.
Рис. 9. Параллелизм задач и параллелизм данных
Параллелизм данных Наличие нескольких стрелок, связывающих соседние вершины информационного графа, означает применение одной операции сразу к нескольким элементам данных (массиву). Различные фрагменты такого массива могут обрабатываться на векторном процессоре или на разных процессорах параллельной вычислительной системы. Векторизация или распараллеливание в рамках данного подхода выполняются во время трансляции. В этом случае при разработке параллельного приложения от программиста требуется: •
задание опций векторной или параллельной оптимизации транслятору;
•
задание директив параллельной компиляции;
использование специализированных языков параллельных вычислений, библиотек подпрограмм, специально разработанных с учетом конкретной архитектуры компьютера и оптимизированных для этой архитектуры. Основные особенности данного подхода перечислены ниже: • обработкой данных управляет одна программа; • пространство имен является глобальным; • параллельные операции над элементами массива выполняются одновременно на всех доступных данной программе процессорах. При программировании на основе параллелизма данных часто используются специализированные языки или надстройки над языками DVM Fortran, HPF (High Perfomance Fortran) и другие. Реализация модели параллелизма данных требует поддержки параллелизма на уровне транслятора. Такую поддержку могут обеспечивать: • препроцессоры, использующие существующие последовательные трансляторы и специализированные библиотеки, с реализациями параллельных алгоритмических конструкций; •
11 •
•
предтрансляторы, которые выполняют предварительный анализ логической структуры программы, проверку зависимостей и ограниченную параллельную оптимизацию; распараллеливающие трансляторы, которые выявляют параллелизм в исходном коде программы и выполняют его преобразование в параллельные конструкции. Для того чтобы упростить преобразование, в исходный текст программы могут добавляться специальные директивы трансляции.
Параллелизм задач Петли, образованные в информационном графе «толстыми стрелками», соответствуют параллелизму задач, идея которого основана на разбиении вычислительной задачи на несколько относительно самостоятельных подзадач. Каждая подзадача выполняется на своем процессоре. Данный подход ориентирован на архитектуру MIMD. В рамках подхода, основанного на параллелизме задач, для каждой подзадачи пишется своя собственная программа на обычном языке программирования, чаще всего это Fortran и С. Подзадачи должны обмениваться результатами своей работы, получать исходные данные. Практически такой обмен осуществляется вызовом процедур специализированной библиотеки. Программист может контролировать распределение данных между различными процессорами и различными подзадачами, а также обмен данными. Проблемы, связанные с данным подходом: • повышенная трудоемкость разработки программы и ее отладки; • на программиста ложится вся ответственность за равномерную и сбалансированную загрузку процессоров параллельного компьютера; • программисту приходится минимизировать обмен данными между задачами, так как затраты времени на пересылку данных обычно относительно велики; • опасность возникновения тупиковых ситуаций, когда отправленное одной программой сообщение не приходит к месту назначения. Привлекательные особенности: •
большая гибкость и большая свобода, предоставляемая программисту в разработке программы, эффективно использующей ресурсы параллельного компьютера;
• возможность достижения максимального быстродействия. Основными инструментами программирования являются специализированные библиотеки (MPI - Message Passing Interface, PVM - Parallel Virtual Machines).
Разработка параллельного алгоритма Выделяют следующие этапы разработки параллельного алгоритма 1. Декомпозиция. На этом этапе выполняются анализ задачи и оценка возможности распараллеливания. Задача и связанные с ней данные разделяются на более мелкие части подзадачи и фрагменты структур данных. Особенности архитектуры конкретной вычислительной системы на данном этапе могут не учитываться. 2. Проектирование коммуникаций (обменов данными) между задачами. Определяются коммуникации, необходимые для пересылки исходных данных, промежуточных результатов выполнения подзадач, а также коммуникации, необходимые для управления работой подзадач. Выбираются методы коммуникации.
12 3. Укрупнение. Подзадачи объединяются в более крупные блоки, если это позволяет повысить эффективность алгоритма и снизить трудоемкость разработки. 4. Планирование вычислений. Распределение подзадач между процессорами. Основной критерий выбора способа размещения подзадач эффективное использование процессоров с минимальными затратами времени на обмены данными. Рассмотрим эти этапы подробнее.
Декомпозиция (сегментирование, расщепление) Существуют разные методы декомпозиции. Рассмотрим основные подходы.
Декомпозиция по данным Вначале сегментируются данные, затем алгоритм их обработки. Данные разбиваются на фрагменты приблизительно одинакового размера. С фрагментами данных связываются операции их обработки, из которых и формируются подзадачи. Затем определяются необходимые пересылки данных. Пересечение частей, на которые разбивается задача, должно быть сведено к минимуму, что позволяет избежать дублирования вычислений. Схема разбиения в дальнейшем может уточняться. Если необходимо для уменьшения числа обменов, допускается увеличение степени перекрытия подзадач. Сначала анализируются структуры данных наибольшего размера, либо те, обращение к которым происходит чаще всего. На разных стадиях расчета могут использоваться разные структуры данных, поэтому могут использоваться как статические, так и динамические схемы декомпозиции этих структур. Рекурсивная дихотомия Используется для разбиения области на подобласти, которым соответствует примерно одинаковая трудоемкость вычислений, а коммуникации сведены к минимуму. Область сначала разбивается на две части вдоль одного измерения. Разбиение повторяется рекурсивно в каждой новой подобласти столько раз, сколько потребуется для получения необходимого числа подзадач. Рекурсивная координатная дихотомия Может применяться к нерегулярным сеткам. Деление выполняется на каждом шаге вдоль того измерения, по которому сетка имеет наибольшую протяженность. Метод рекурсивной дихотомии графа Используется для нерегулярных сеток. В нем с помощью информации о топологии решетки минимизируется количество ребер, пересекающих границы подобластей. Это позволяет снизить количество коммуникаций.
Функциональная декомпозиция Вначале сегментируется вычислительный алгоритм, а затем уже под эту схему подгоняется схема декомпозиции данных. Метод функциональной декомпозиции может оказаться полезным в ситуации, где нет структур данных, которые очевидно могли бы быть распараллелены. Эффективность декомпозиции обеспечивается выполнением следующих рекомендаций: • количество подзадач после декомпозиции должно примерно на порядок превосходить количество процессоров; • следует избегать лишних вычислений и пересылок данных;
13
подзадачи должны быть примерно одинакового размера; • в идеале сегментация должна быть такой, чтобы с увеличением объема задачи количество подзадач также возрастало (при сохранении постоянным размера одной подзадачи). Размер подзадачи определяется зернистостью алгоритма. Мерой зернистости является количество операций в блоке. Выделяют три степени зернистости: •
1. Мелкозернистый параллелизм на уровне команд (менее 20 команд на блок, количество параллельно выполняемых подзадач от единиц до нескольких тысяч, средний масштаб параллелизма около 5 команд на блок). 2. Среднеблочный параллелизм на уровне процедур. Размер блока до 2000 команд. Выявление такого параллелизма сложнее реализовать, поскольку следует учитывать межпроцедурные зависимости. Требования к коммуникациям меньше, чем в случае параллелизма на уровне команд. 3. Крупноблочный параллелизм на уровне программ (задач). Соответствует выполнению независимых программ на параллельном компьютере. Крупноблочный параллелизм требует поддержки операционной системой. Важнейшим условием декомпозиции является независимость подзадач. Существуют следующие виды независимости: •
Независимость по данным данные, обрабатываемые одной частью программы, не модифицируются другой ее частью.
•
Независимость по управлению порядок выполнения частей программы может быть определен только во время выполнения программы (наличие зависимости по управлению предопределяет последовательность выполнения).
•
Независимость по ресурсам обеспечивается достаточным количеством компьютерных ресурсов.
Независимость по выводу возникает, если две подзадачи не производят запись в одну и ту же переменную, а независимость по вводу-выводу, если операторы ввода/вывода двух или нескольких подзадач не обращаются к одному файлу (или переменной). Полной независимости добиться обычно не удается. •
Проектирование коммуникаций Можно выделить следующие основные типы коммуникаций: •
локальные каждая подзадача связана с небольшим набором других подзадач;
•
глобальные каждая подзадача связана с большим числом других подзадач;
•
структурированные каждая подзадача и подзадачи, связанные с ней, образуют регулярную структуру (например, с топологией решетки);
•
неструктурированные подзадачи связаны произвольным графом;
•
статические схема коммуникаций не изменяется с течением времени;
•
динамические схема коммуникаций изменяется в процессе выполнения программы;
•
синхронные отправитель и получатель данных координируют обмен;
асинхронные обмен данными не координируется. Рекомендации по проектированию коммуникаций: •
14 •
• •
количество коммуникаций у подзадач должно быть примерно одинаковым, иначе приложение становится плохо масштабируемым; там, где это возможно, следует использовать локальные коммуникации; коммуникации должны быть, по возможности, параллельными.
Укрупнение На этапе укрупнения (агломерации) учитывается архитектура вычислительной системы и задачи, полученные на первых двух этапах, объединяются для того, чтобы их число соответствовало числу процессоров. При выполнении агломерации должны быть соблюдены следующие требования: • должны быть снижены накладные расходы на коммуникации; • если при укрупнении приходится дублировать вычисления или данные, это не должно приводить к потере производительности и масштабируемости программы; • результирующие задачи должны иметь примерно одинаковую трудоемкость; • должна быть сохранена масштабируемость; • должна быть сохранена возможность параллельного выполнения; • должна быть снижена стоимость трудоемкости разработки.
Планирование вычислений На этапе планирования определяют, на каких процессорах будут выполняться подзадачи. Основной критерий эффективности здесь минимизация времени выполнения программы. Стратегия размещения задач на процессорах строится на основе компромисса между требованием максимальной независимости выполняющихся задач (минимизация коммуникаций) и глобальным учетом состояния вычислений. Чаще всего применяются стратегии хозяин/работник, иерархические и децентрализованные стратегии. Хозяин/работник (Master/slave) Главная задача отвечает за размещение подчиненных задач (рис. 10). Подчиненная задача получает исходные данные для обработки от главной задачи и возвращает ей результат работы.
Рис. 10. Простая схема хозяин/работник
15
Иерархическая схема хозяин/работник Подчиненные задачи разделены на непересекающиеся подмножества и у каждого из этих подмножеств есть своя главная задача (рис. 11). Главные задачи подмножеств управляются одной "самой главной" задачей.
Рис. 11. Иерархическая схема хозяин/работник Децентрализованные схемы В этом случае главная задача отсутствует. Задачи обмениваются данными друг с другом, придерживаясь определенной стратегии (рис. 12). Это может быть случайный выбор объекта коммуникации или взаимодействие с небольшим числом ближайших соседей. В гибридной централизованно-распределенной схеме запрос посылается главной задаче, а она передает его подчиненным задачам, используя метод кругового планирования. Динамически сбалансированная загрузка может быть эффективно реализована, если учтены следующие соображения: • если каждый процессор выполняет одну подзадачу, длительность выполнения всей программы будет определяться самой «медленной» подзадачей, поэтому оптимальная производительность достигается, если все подзадачи имеют одинаковый размер; • сбалансированность может быть обеспечена посредством загрузки каждого процессора несколькими задачами.
16
Рис. 12. Децентрализованная схема
Многопоточные программы Поток (нить) представляет собой последовательный поток управления (последовательность команд) в рамках одной программы. При создании процесса порождается главный поток, выполняющий инициализацию процесса. Он же начинает выполнение команд. Поток и процесс соотносятся следующим образом: • процесс имеет главный поток, инициализирующий выполнение команд процесса; •
любой поток может порождать в рамках одного процесса другие потоки;
•
каждый поток имеет собственный стек;
• потоки, соответствующие одному процессу, имеют общие сегменты кода и данных. При разработке многопоточных приложений возникают те же проблемы, что и при разработке параллельных. Это: • гонки за данными; •
блокировки;
•
активные блокировки;
•
несбалансированность загрузки.
Гонки за данными Гонки за данными являются следствием зависимостей, когда несколько потоков модифицируют содержимое одной и той же области памяти. Наличие гонок за данными не всегда является очевидным. Они могут приводить к конфликтам двух типов: 1. конфликт «чтение-запись»; 2. конфликт «запись-запись». Имеются два способа борьбы с гонками за данными:
17 •
использование преимущественно локальных по отношению к потоку, а не разделяемых переменных;
управление доступом к разделяемым переменным с помощью различных средств синхронизации (они могут быть реализованы с помощью семафоров, событий, критических секций, взаимных блокировок - мьютексов). Гонки за данными могут быть скрыты синтаксисом языка программирования. Некоторые примеры приведены в табл. 1. •
Таблица 1. Примеры конструкций языка программирования, в которых могут возникать гонки за данными Поток 1 Поток 2 Причина возникновения гонок за данными Компилятор заменяет операцию += раздельными X += 1 X += 2 A[i] += 1 *p += 1 Func(1)
A[j] += 2 *q += 2 Func(2)
Add [abc], 1
add [abc], 2
операциями чтения и записи X Возможно совпадение значений индексов i и j Указатели p и q могут ссылаться на один адрес Func может суммировать значение аргумента и значение внутренней разделяемой переменной На уровне команд модификация [abc] заменяется раздельными операциями чтения и записи
Блокировки Блокировка (тупик) возникает, если поток ожидает выполнение условия, которое не может быть выполнено. Обычно возникновение тупиковой ситуации является следствием конкуренции потоков за ресурс, который удерживается одним из них. Условия возникновения тупика: • доступ к ресурсу эксклюзивен (возможен только одним потоком); • поток может удерживать ресурс, запрашивая другой; • ни один из конкурирующих потоков не может освободить запрашиваемый ресурс. Активной блокировкой называют ситуацию, когда поток не производит вычислений, но и не блокируется. Потоки пытаются преодолеть помеху, создаваемую другим потоком.
18
II. OpenMP
19 OpenMP стандарт программного интерфейса приложений для параллельных систем с общей памятью. Поддерживает языки C, C++, Фортран.
Модель программы в OpenMP
Рис. 12. Модель параллельной программы в OpenMP Модель параллельной программы в OpenMP можно сформулировать следующим образом: •
Программа состоит из последовательных и параллельных секций (рис. 12).
•
В начальный момент времени последовательные секции программы.
•
При входе в параллельную секцию выполняется операция fork, порождающая семейство нитей. Каждая нить имеет свой уникальный числовой идентификатор (главной нити соответствует 0). При распараллеливании циклов все параллельные нити исполняют один код. В общем случае нити могут исполнять различные фрагменты кода.
создается
главная
нить,
выполняющая
При выходе из параллельной секции выполняется операция join. Завершается выполнение всех нитей, кроме главной. OpenMP составляют следующие компоненты: • Директивы компилятора - используются для создания потоков, распределения работы между потоками и их синхронизации. Директивы включаются в исходный текст программы. • Подпрограммы библиотеки времени выполнения - используются для установки и определения атрибутов потоков. Вызовы этих подпрограмм включаются в исходный текст программы. • Переменные окружения - используются для управления поведением параллельной программы. Переменные окружения задаются для среды выполнения параллельной программы соответствующими командами (например, командами оболочки в операционных системах UNIX). Использование директив компилятора и подпрограмм библиотеки времени выполнения подчиняется правилам, которые различаются для разных языков программирования. Совокупность таких правил называется привязкой к языку. •
20
Привязка к языку Fortran В программах на языке Fortran директивы компилятора, имена подпрограмм и переменных окружения начинаются с OMP или OMP_. Формат директивы компилятора: {!|C|*}$OMP директива [оператор_1[, оператор_2, …]] Директива начинается в первой (фиксированный формат записи текста языка Fortran 77) или произвольной (свободный формат) позиции строки. Допускается продолжение директивы в следующей строке, в этом случае действует стандартное в данной версии языка правило для обозначения строки продолжения (непробельный символ в шестой позиции для фиксированного формата записи и амперсанд для свободного формата). Пример программы на языке Fortran с использованием OpenMP program omp_example integer i, k, N real*4 sum, h, x print *, "Please, type in N:" read *, N h = 1.0 / N sum = 0.0 C$OMP PARALLEL DO SCHEDULE(STATIC) REDUCTION(+:sum) do i = 1, N x = i * h sum = sum + 1.e0 * h / (1.e0 + x**2) enddo print *, 4.0 * sum end
Привязка к языку C В программах на языке C прагмы, имена функций и переменных окружения OMP начинаются с omp, omp_ или OMP_. Формат директивы: #pragma omp директива [оператор_1[, оператор_2, …]] В OpenMP-программе используется заголовочный файл omp.h. Пример программы на языке С с использованием OpenMP #include "omp.h" #include <stdio.h> double f(double x) { return 4.0 / (1 + x * x); } main () { const long N = 100000; long i; double h, sum, x; sum = 0;
21
h = 1.0 / N; #pragma omp parallel shared(h) { #pragma omp for private(x) reduction(+:sum) for (i = 0; i < N; i++) { x = h * (i + 0.5); sum = sum + f(x); } } printf("PI = %f\n", sum / N); }
Директивы OpenMP Далее приводится перечень директив OpenMP. Описания директив OpenMP ориентированы на спецификацию версии 2.5. parallel … end parallel Задает границы параллельной секции программы. С данной использоваться следующие операторы (их описание дается далее): •
private;
• •
shared; default;
•
firstprivate;
•
reduction;
•
if;
•
copyin;
•
num_threads.
директивой
могут
do цикл do end do #pragma omp for цикл for Задает границы цикла, исполняемого в параллельном режиме в языках Fortran и C соответственно. С данной директивой могут использоваться следующие операторы: •
private;
•
firstprivate;
22 •
lastprivate;
•
reduction;
• •
schedule; ordered;
•
nowait.
sections … end sections Обрамляет параллельную секцию программы. Вложенные секции программы, задаваемые директивами section, распределяются между нитями. С данной директивой могут использоваться следующие операторы: •
private;
•
firstprivate;
•
lastprivate;
•
reduction;
•
nowait.
section Определяет часть sections, которая выполняется одной нитью. single … end single Обрамляет блок программы, который должен выполняться одной нитью. С данной директивой могут использоваться следующие операторы: •
private;
•
firstprivate;
•
copyprivate;
•
nowait.
workshare … end workshare Делит блок на части, выполнение которых распределяется между нитями таким образом, что каждая часть выполняется один раз. Блок может содержать только следующие конструкции: • присваивания массивов; • скалярные присваивания; •
FORALL;
•
WHERE;
•
atomic;
23 •
critical;
•
parallel.
parallel do цикл do end parallel do Объединяет директивы parallel и do. parallel sections … end parallel sections Объединяет директивы parallel и sections. parallel workshare … end parallel workshare Объединяет директивы parallel и workshare. master … end master Обрамляет блок программы, который должен выполняться только главной нитью. critical[(блокировка)] … end critical[(блокировка)] Обрамляет блок программы, доступ к которому в любой момент времени может получить только одна нить (критическая секция). Блокировка – необязательное имя критической секции. Квадратные скобки указывают, что имя не является обязательным и может быть опущено. barrier Директива барьерной синхронизации нитей. Каждая нить, выполнение которой достигло данной точки, приостанавливает свое выполнение до тех пор, пока все нити не достигнут данной точки.
atomic Объявляет операцию атомарной (при выполнении атомарной операции одновременный доступ к памяти по записи разных нитей запрещен). Применяется только к оператору, непосредственно следующему после данной директивы. Он может иметь следующий вид:
24 •
•
•
•
x = x {+|-|*|/|.AND.|.OR.|.EQV.|.NEQV.} скалярное_выражение_не_содержащее_x x = скалярное_выражение_не_содержащее_x {+||*|/|.AND.|.OR.|.EQV.|.NEQV.} x x = {MAX|MIN|IAND|IOR|IEOR} (x, скалярное_выражение_не_содержащее_x) x = {MAX|MIN|IAND|IOR|IEOR} (скалярное_выражение_не_содержащее_x, x)
flush[(список переменных)] Данная директива задает точку синхронизации, в которой значения переменных, указанных в списке и видимых из данной нити, записываются в память. Этим обеспечивается согласование содержимого памяти, доступного разным нитям. ordered … end ordered Обеспечивает сохранение того порядка выполнения итераций цикла, который соответствует последовательному выполнению программы. threadprivate(список common-блоков) Определяет common-блоки, перечисленные в списке, локальными.
Операторы OpenMP Операторы OpenMP используются совместно с директивами. private(список переменных) Объявляет переменные из списка локальными. firstprivate(список переменных) Объявляет переменные из списка локальными и инициализирует их значениями из блока программы, предшествующего данной директиве. lastprivate(список переменных) Объявляет переменные из списка локальными и назначает им значения из того блока программы, который был выполнен последним.
copyprivate(список переменных) После завершения выполнения блока, заданного директивой single, значения локальных переменных из списка распределяются между остальными нитями.
25
nowait Отменяет барьерную синхронизацию при завершении выполнения параллельной секции. shared(список переменных) Объявляет переменные из списка общими для всех нитей. default(private|shared|none) Данный оператор позволяет изменить правила определения области видимости переменных, действующие по умолчанию. Вариант private используется только в языке Fortran. reduction(операция|встроенная функция: список переменных) Оператор приведения (редукции) значений локальных переменных из списка с помощью указанной операции или встроенной функции языка. Операция редукции применяется к нескольким значениям и возвращает одно значение. if(скалярное логическое выражение) Условный оператор. num_threads(скалярное целое выражение) Задает количество нитей. Альтернативный способ задания количества нитей обеспечивает переменная окружения OMP_NUM_THREADS. schedule(характер_распределения_итераций[, количество_итераций_цикла]) Данный оператор задает способ распределения итераций цикла между нитями: •
static – количество итераций цикла, передаваемых для выполнения каждой нити фиксировано и распределяется между нитями по принципу кругового планирования. Если количество итераций не указано, оно полагается равным 1;
•
dynamic – количество итераций цикла, передаваемых для выполнения каждой нити фиксировано. Очередная «порция» итераций передается освободившейся нити;
•
guided – количество итераций цикла, передаваемых для выполнения каждой нити постепенно уменьшается. Очередная «порция» итераций передается освободившейся нити;
•
runtime – тип распределения работы определяется во время выполнения программы, например, с помощью переменной окружения OMP_SCHEDULE.
copyin(список имен common-блоков) При выполнении этого оператора данные из главной нити копируются в локальные экземпляры common-блока в начале каждой параллельной секции. Имена задаются между символами «/ ».
26
Подпрограммы OpenMP Подпрограммы, формирующие среду выполнения параллельной программы Здесь и далее вначале приводится интерфейс подпрограмм OpenMP для языка C, затем для языка Fortran. void omp_set_num_threads(int threads); subroutine omp_set_num_threads(threads) integer threads Задает количество потоков (threads) при выполнении параллельных секций программы. int omp_get_num_threads(void); integer function omp_get_num_threads() Возвращает количество потоков, используемых для выполнения параллельной секции. int omp_get_max_threads(void); integer function omp_get_max_threads() Возвращает максимальное количество потоков, выполнения параллельных секций программы.
которые можно
использовать
int omp_get_thread_num(void); integer function omp_get_thread_num() Возвращает идентификатор нити, из которой вызывается данная функция. int omp_get_num_procs(void); integer function omp_get_num_procs() Возвращает количество процессоров, доступных в данный момент программе. int omp_in_parallel(void); logical function omp_in_parallel() Возвращает значение true при вызове из активной параллельной секции программы. void omp_set_dynamic(int threads); subroutine omp_set_dynamic(threads)
для
27
logical threads Включает или отключает возможность динамического назначения количества потоков при выполнении параллельной секции. По умолчанию эта возможность отключена. int omp_get_dynamic(void); logical function omp_get_dynamic() Возвращает значение true, если динамическое назначение количества потоков разрешено. void omp_set_nested(int nested); subroutine omp_set_nested(nested) integer nested Разрешает или запрещает вложенный параллелизм. По умолчанию вложенный параллелизм запрещен. int omp_get_nested(void); logical function omp_get_nested() Определяет, разрешен ли вложенный параллелизм.
Подпрограммы для работы с блокировками Блокировки используются для предотвращения эффектов, приводящих к непредсказуемому (недетерминированному) поведению программы. Среди таких эффектов, например, гонки за данными, когда более чем один поток имеет доступ к одной и той же переменной. void omp_init_lock(omp_lock_t *lock); subroutine omp_init_lock(lock) integer(kind = omp_lock_kind) :: lock Инициализирует блокировку, связанную с идентификатором lock, для использования в последующих вызовах подпрограмм. void omp_destroy_lock(omp_lock_t *lock); subroutine omp_destroy_lock(lock) integer(kind = omp_lock_kind) :: lock Переводит блокировку, связанную с идентификатором lock, в состояние неопределенности. void omp_set_lock(omp_lock_t *lock);
28
subroutine omp_set_lock(lock) integer(kind = omp_lock_kind) :: lock Переводит потоки из состояния выполнения в состояние ожидания до тех пор, пока блокировка, связанная с идентификатором lock, не окажется доступной. Поток становится владельцем доступной блокировки. void omp_unset_lock(omp_lock_t *lock); subroutine omp_unset_lock(lock) integer(kind = omp_lock_kind) :: lock После выполнения данного вызова поток перестает быть владельцем блокировки, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат вызова не определен. int omp_test_lock(omp_lock_t *lock); logical function omp_test_lock(lock) integer(kind = omp_lock_kind) :: lock Возвращает значение «истина», если блокировка связана с идентификатором lock. void omp_init_nest_lock(omp_nest_lock_t *lock); subroutine omp_init_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock Инициализирует вложенную блокировку, связанную с идентификатором lock. void omp_destroy_nest_lock(omp_nest_lock_t *lock); subroutine omp_destroy_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock Переводит вложенную блокировку, связанную с идентификатором lock, в состояние неопределенности. void omp_set_nest_lock(omp_nest_lock_t *lock); subroutine omp_set_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock Переводит выполняющиеся потоки в состояние ожидания до тех пор, пока вложенная блокировка, связанная с идентификатором lock, не станет доступной. Поток становится владельцем блокировки.
29
void omp_unset_nest_lock(omp_nest_lock_t *lock); subroutine omp_unset_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock Освобождает выполняющийся поток от владения вложенной блокировкой, связанной с идентификатором lock. Если поток не был владельцем блокировки, результат не определен. int omp_test_nest_lock(omp_nest_lock_t *lock); integer function omp_test_nest_lock(lock) integer(kind = omp_nest_lock_kind) :: lock Функция, позволяющая определить, связана ли вложенная блокировка с идентификатором lock. Если связана, возвращается значение счетчика, в противном случае возвращается значение 0.
Таймеры Для профилирования OpenMP программы можно использовать таймеры. double omp_get_wtime(void); double precision function omp_get_wtime() Возвращает время в секундах, прошедшее с произвольного момента в прошлом. Точка отсчета остается неизменной в течение всего времени выполнения программы. double omp_get_wtick(void); double precision function omp_get_wtick() Возвращает время в секундах, прошедшее между последовательными «тиками». Это время является мерой точности таймера.
Переменные окружения OpenMP Переменные окружения задаются следующим образом: •
export ПЕРЕМЕННАЯ=значение (в среде UNIX)
•
set ПЕРЕМЕННАЯ=значение (в среде Microsoft Windows)
OMP_NUM_THREADS Задает количество нитей при выполнении параллельных секций программы. OMP_SCHEDULE Задает способ распределения итераций циклов между нитями. Возможные значения: •
static;
•
dynamic;
30 •
guided.
Количество итераций (необязательный параметр) указывается после одного из этих ключевых слов, отделяясь от него запятой, например: export OMP_SCHEDULE=”static, 10” OMP_DYNAMIC Если этой переменной присвоено значение false, динамическое распределение итераций циклов между нитями запрещено, если true – разрешено. OMP_NESTED Если этой переменной присвоено значение false, вложенный параллелизм запрещен, если true – разрешен.
31
III. Интерфейс передачи сообщений MPI
32
Далее приводится описание свободно распространяемой версии MPI – MPICH 1.2.7, соответствующей спецификации MPI 1.
Модель параллельной программы в MPI В модели программирования MPI параллельная программа при запуске порождает несколько процессов, взаимодействующих между собой с помощью сообщений. Совокупность всех процессов, составляющих параллельное приложение, или их части, описывается специальной структурой, которая называется коммуникатором (областью взаимодействия). Каждому процессу в области взаимодействия назначается уникальный числовой идентификатор ранг, значение которого от 0 до np – 1 (np число процессов). Ранги, назначаемые одному и тому же процессу в разных коммуникаторах, вообще говоря, различны. Структура программы, написанной по схеме хозяин/работник, приведена ниже. program para … if (ранг процесса = рангу мастер-процесса) then код мастер-процесса else код подчиненного процесса (подчиненных процессов) endif end Каждый экземпляр программы уже в процессе своего выполнения определяет, является ли он мастер-процессом. Затем, в зависимости от результата этой проверки, выполняется одна из ветвей условного оператора. Первая ветвь соответствует мастер-задаче, а вторая подчиненной задаче. Способы взаимодействия между подзадачами определяются программистом. Перед использованием процедур передачи сообщений программа должна подключиться к системе обмена сообщениями. Подключение выполняется с помощью соответствующего вызова процедуры из библиотеки. В одних реализациях модели допускается только одно подключение, а в других несколько подключений к системе.
Сообщения Сообщение содержит пересылаемые данные и служебную информацию. Для того, чтобы передать сообщение, необходимо указать: ранг процесса-отправителя сообщения; адрес, по которому размещаются пересылаемые данные процесса-отправителя; тип пересылаемых данных; количество данных; ранг процесса, который должен получить сообщение; адрес, по которому должны быть размещены данные процессом-получателем. тег сообщения; идентификатор коммуникатора, описывающего область взаимодействия, внутри которой происходит обмен. Тег это задаваемое пользователем целое число от 0 до 32767, которое играет роль идентификатора сообщения и позволяет различать сообщения, приходящие от одного • • • • • • • •
33
процесса. Теги могут использоваться и для соблюдения определенного порядка приема сообщений. Прием сообщения начинается с подготовки буфера достаточного размера. В этот буфер записываются принимаемые данные. Операция отправки или приема сообщения считается завершенной, если программа может вновь использовать буферы сообщений.
Разновидности обменов сообщениями В MPI реализованы разные виды обменов. Прежде всего, это двухточечные (задействованы только два процесса) и коллективные (задействованы более двух процессов). Двухточечные обмены используются для организации локальных и неструктурированных коммуникаций. При выполнении глобальных операций используются коллективные обмены. Асинхронные коммуникации реализуются с помощью запросов о получении сообщений. Имеется несколько разновидностей двухточечного обмена. • •
• •
Блокирующие прием/передача приостанавливают выполнение процесса на время приема сообщения. Неблокирующие прием/передача выполнение процесса продолжается в фоновом режиме, а программа в нужный момент может запросить подтверждение завершения приема сообщения. Cинхронный обмен сопровождается уведомлением об окончании приема сообщения. Асинхронный обмен уведомлением не сопровождается.
Привязка к языку Fortran Имена подпрограмм и констант MPI в программах на языке Fortran начинаются с MPI_. При вызове подпрограмм коды завершения передаются через дополнительный параметр целого типа (находится на последнем месте в списке параметров подпрограммы). Код успешного завершения MPI_SUCCESS. Константы и другие объекты MPI описываются в файле mpif.h, который включается в MPI-программу с помощью оператора include. В некоторых подпрограммах используется переменная status, которая является массивом стандартного целого типа. Его размер MPI_STATUS_SIZE. При обращении к подпрограммам MPI используются типы данных MPI, для большинства из которых имеется соответствие базовым типам языка (см. табл. 1) Типы MPI_Datatype и MPI_Comm эмулируются стандартным целым типом языка Fortran (Integer). В программах на языке C используются библиотечные функции MPI, в программах на языке Fortran процедуры. Таблица 1. Типы данных MPI для языка Fortran Тип данных MPI
Тип данных Fortran
MPI_INTEGER
Integer
MPI_REAL
Real
34
Таблица 1. (продолжение) MPI_DOUBLE_PRECISION
Double precision
MPI_DOUBLE_COMPLEX
Double complex
MPI_COMPLEX
Complex
MPI_LOGICAL
Logical
MPI_CHARACTER
Character
MPI_BYTE
Нет соответствия
MPI_PACKED
Нет соответствия
Типы, которые имеются не во всех реализациях MPI MPI_INTEGER1
Integer*1
MPI_INTEGER2
Integer*2
MPI_INTEGER4
Integer*4
MPI_REAL4
Real*4
MPI_REAL8
Real*8
Привязка к языку C В программах на языке C имена подпрограмм имеют вид Класс_действие_подмножество или Класс_действие. В C++ подпрограмма является методом для определенного класса, имя имеет в этом случае вид MPI::Класс::действие_подмножество. Для некоторых действий введены стандартные наименования: Create создание нового объекта, Get получение информации об объекте, Set установка параметров объекта, Delete удаление информации, Is запрос о том, имеет ли объект указанное свойство. Имена констант MPI записываются в верхнем регистре. Их описания находятся в заголовочном файле mpi.h. Входные параметры функций передаются по значению, а выходные (и INOUT) — по ссылке. Соответствие типов MPI стандартным типам языка C приведено в табл. 2.
Коды завершения В MPI приняты стандартные соглашения о кодах завершения вызовов подпрограмм. Так, например, возвращаются значения MPI_SUCCESS при успешном завершении вызова и MPI_ERR_OTHER обычно при попытке повторного вызова процедуры MPI_Init. Вместо числовых кодов в программах обычно используют специальные именованные константы: •
MPI_ERR_BUFFER неправильный указатель на буфер;
•
MPI_ERR_COMM неправильный коммуникатор;
•
MPI_ERR_RANK неправильный ранг;
•
MPI_ERR_OP неправильная операция;
35 •
MPI_ERR_ARG неправильный аргумент;
•
MPI_ERR_UNKNOWN неизвестная ошибка;
•
MPI_ERR_TRUNCATE сообщение обрезано при приеме;
•
MPI_ERR_INTERN внутренняя ошибка. Обычно возникает, если системе не хватает памяти. Таблица 2. Типы данных MPI для языка C
Тип данных MPI
Тип данных C
MPI_CHAR
Signed char
MPI_SHORT
Signed short int
MPI_INT
Signed int
MPI_LONG
Signed long int
MPI_UNSIGNED_CHAR
unsigned char
MPI_UNSIGNED_SHORT
unsigned short int
MPI_UNSIGNED
unsigned int
MPI_UNSIGNED_LONG
unsigned long int
MPI_FLOAT
Float
MPI_DOUBLE
Double
MPI_LONG_DOUBLE
long double
MPI_BYTE
Нет соответствия
MPI_PACKED
Нет соответствия
Основные понятия MPI Коммуникатор представляет собой структуру, содержащую либо все процессы, исполняющиеся в рамках данного приложения, либо их подмножество. Процессы, принадлежащие одному и тому же коммуникатору, наделяются общим контекстом обмена. Операции обмена возможны только между процессами, связанными с общим контекстом, то есть, принадлежащие одному и тому же коммуникатору (рис. 13). Каждому коммуникатору присваивается идентификатор. В MPI есть несколько стандартных коммуникаторов: • MPI_COMM_WORLD – включает все процессы параллельной программы; • MPI_COMM_SELF – включает только данный процесс; • MPI_COMM_NULL – пустой коммуникатор, не содержит ни одного процесса. В MPI имеются процедуры, позволяющие создавать новые коммуникаторы, содержащие подмножества процессов.
36
Рис. 13. Коммуникатор Ранг процесса представляет собой уникальный числовой идентификатор, назначаемый процессу в том или ином коммуникаторе. Ранги в разных коммуникаторах назначаются независимо и имеют целое значение от 0 до число_процессов – 1 (рис. 14).
Рис. 14. Ранги процессов Тег (маркер) сообщения – это уникальный числовой идентификатор, который назначается сообщению и позволяет различать сообщения, если в этом есть необходимость. Если тег не требуется, вместо него можно использовать «джокер» MPI_ANY_TAG.
37
Типовая структура MPI-программы: program para … if (process = master) then master clause else slave clause endif end Различные подпрограммы MPI Подключение к MPI int MPI_Init(int *argc, char **argv) MPI_INIT(IERR) Аргументы argc и argv требуются только в программах на C, где они задают количество аргументов командной строки запуска программы и вектор этих аргументов. Данный вызов предшествует всем прочим вызовам подпрограмм MPI. Завершение работы с MPI int MPI_Finalize() MPI_FINALIZE(IERR) После вызова данной подпрограммы нельзя вызывать подпрограммы MPI. MPI_FINALIZE должны вызывать все процессы перед завершением своей работы. Определение размера области взаимодействия int MPI_Comm_size(MPI_Comm comm, int *size) MPI_COMM_SIZE(COMM., SIZE, IERR) Входные параметры: •
comm коммуникатор.
Выходные параметры: •
size количество процессов в области взаимодействия.
Определение ранга процесса int MPI_Comm_rank(MPI_Comm comm, int *rank)
38
MPI_COMM_RANK(COMM, RANK, IERR) Входные параметры: •
comm коммуникатор.
Выходные параметры: •
rank ранг процесса в области взаимодействия.
Определение имени узла, на котором выполняется данный процесс MPI_Get_processor_name(char *name, int *resultlen) MPI_GET_PROCESSOR_NAME(NAME, RESULTLEN, IERR) Выходные параметры: •
name идентификатор вычислительного MPI_MAX_PROCESSOR_NAME элементов;
•
resultlen длина имени.
узла.
Массив
не
менее
чем
из
Время, прошедшее с произвольного момента в прошлом double MPI_Wtime() MPI_WTIME(TIME, IERR)
Двухточечный обмен Участниками двухточечного обмена являются два процесса: процесс-отправитель и процессполучатель (рис. 14).
Рис. 14. Двухточечный обмен
39
Далее приводится описание интерфейса подпрограмм, реализующих разные виды двухточечного обмена. Стандартная блокирующая передача int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) MPI_SEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR) Входные параметры: •
buf адрес первого элемента в буфере передачи;
•
count количество элементов в буфере передачи;
•
datatype тип MPI каждого пересылаемого элемента;
•
dest ранг процесса-получателя сообщения (целое число от 0 до n – 1, где n число процессов в области взаимодействия);
•
tag тег сообщения;
•
comm коммуникатор;
•
ierr код завершения.
Стандартный блокирующий прием int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) MPI_RECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS, IERR) Входные параметры: •
count максимальное количество элементов в буфере приема. Фактическое их количество можно определить с помощью подпрограммы MPI_Get_count;
•
datatype тип принимаемых данных. Напомним о необходимости соблюдения соответствия типов аргументов подпрограмм приема и передачи;
•
source ранг источника. Можно использовать специальное значение MPI_ANY_SOURCE, соответствующее произвольному значению ранга. В программировании идентификатор, отвечающий произвольному значению параметра, часто называют "джокером". Этот термин будем использовать и мы;
•
tag тег сообщения или "джокер" MPI_ANY_TAG, соответствующий произвольному значению тега;
•
comm коммуникатор. При указании коммуникатора "джокеры" использовать нельзя.
Выходные параметры: •
buf начальный адрес буфера приема. Его размер должен быть достаточным, чтобы разместить принимаемое сообщение, иначе при выполнении приема произойдет сбой возникнет ошибка переполнения;
•
status статус обмена.
Если сообщение меньше, чем буфер приема, изменяется содержимое лишь тех ячеек памяти буфера, которые относятся к сообщению.
40
Определение размера полученного сообщения (count) int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count) MPI_GET_COUNT(STATUS, DATATYPE, COUNT, IERR) Аргумент datatype должен соответствовать типу данных, указанному в операции передачи сообщения. Синхронная передача int MPI_Ssend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) MPI_SSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR) Параметры этой подпрограммы совпадают с параметрами подпрограммы MPI_Send. Буферизованный обмен int MPI_Bsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) MPI_BSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR) Параметры совпадают с параметрами подпрограммы MPI_Send. Создание буфера int MPI_Buffer_attach(void *buf, size) MPI_BUFFER_ATTACH(BUF, SIZE, IERR) Выходной параметр: •
buf буфер размером size байтов.
В программах на языке Fortran роль буфера может играть массив. Этот массив должен быть описан в программе, его не следует использовать для других целей (например, в качестве первого аргумента подпрограммы MPI_Bsend). За один раз к процессу может быть подключен только один буфер. Отключение буфера int MPI_Buffer_detach(void *buf, int *size) MPI_BUFFER_DETACH(BUF, SIZE, IERR) Выходные параметры: • buf адрес; • size размер отключаемого буфера. Вызов данной подпрограммы блокирует работу процесса до тех пор, пока все сообщения, находящиеся в буфере, не будут обработаны. В языке C данный вызов не освобождает автоматически память, отведенную для буфера.
41
Передача по готовности int MPI_Rsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) MPI_RSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR) Параметры совпадают с параметрами подпрограммы MPI_Send. Блокирующая проверка доставки сообщения int MPI_Probe(int source, int *status)
tag,
MPI_Comm
comm,
MPI_Status
MPI_PROBE(SOURCE, TAG, COMM, STATUS, IERR) Входные параметры: •
source ранг источника или "джокер";
•
tag значение тега или "джокер";
•
comm коммуникатор.
Выходной параметр: •
status статус.
Неблокирующая проверка сообщения int MPI_Iprobe(int source, int tag, MPI_Comm comm, int *flag, MPI_Status *status) MPI_IPROBE(SOURCE, TAG, COMM, FLAG, STATUS, IERR) Входные параметры этой подпрограммы те же, что и у подпрограммы MPI_Probe. Выходные параметры: • flag флаг; • status статус. Если сообщение уже поступило и может быть принято, возвращается значение флага "истина". Прием и передача данных с блокировкой int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, int recvtag, MPI_Comm comm, MPI_Status *status) MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVBUF, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM, STATUS, IERR) Входные параметры: •
sendbuf начальный адрес буфера передачи;
•
sendcount количество передаваемых элементов;
•
sendtype тип передаваемых элементов;
•
dest ранг адресата;
•
sendtag тег передаваемого сообщения;
42 •
recvbuf начальный адрес буфера приема;
•
recvcount количество элементов в буфере приема;
•
recvtype тип элементов в буфере приема;
•
source ранг источника;
•
recvtag тег принимаемого сообщения;
•
comm коммуникатор.
Выходные параметры: •
recvbuf начальный адрес буфера приема;
•
status статус операции приема.
Прием, и передача используют один и тот же коммуникатор. Буферы передачи и приема не должны пересекаться, у них может быть разный размер, типы пересылаемых и принимаемых данных также могут различаться. Отправка и прием сообщения в блокирующем режиме с общим буфером для передачи и для приема int MPI_Sendrecv_replace(void *buf, int count, MPI_Datatype datatype, int dest, int sendtag, int source, int recvtag, MPI_Comm comm, MPI_Status *status) MPI_SENDRECV_REPLACE(BUF, COUNT, DATATYPE, DEST, SENDTAG, SOURCE, RECVTAG, COMM, STATUS, IERR) Входные параметры: •
count количество отправляемых данных и емкость буфера приема;
•
datatype тип данных в буфере приема и передачи;
•
dest ранг адресата;
•
sendtag тег передаваемого сообщения;
•
source ранг источника;
•
recvtag тег принимаемого сообщения;
•
comm коммуникатор.
Выходные параметры: •
buf начальный адрес буфера приема и передачи;
•
status статус.
Принимаемое сообщение не должно превышать по размеру отправляемое сообщение, а передаваемые и принимаемые данные должны быть одного типа. Последовательность приема и передачи выбирается системой автоматически. Инициализация неблокирующей стандартной передачи int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_ISEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERR) Входные параметры этой подпрограммы аналогичны аргументам подпрограммы MPI_Send.
43
Выходной параметр: •
request идентификатор операции.
Инициализация неблокирующей синхронной передачи данных int MPI_Issend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_ISSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERR) Параметры этой подпрограммы совпадают с параметрами подпрограммы MPI_Send. Неблокирующая буферизованная передача сообщения int MPI_Ibsend(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_IBSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERR) Неблокирующая передача "по готовности" int MPI_Irsend(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_request *request) MPI_IRSEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERR) Параметры всех подпрограмм неблокирующей передачи совпадают. Инициализация неблокирующего приема int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request) MPI_IRECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, REQUEST, IERR) Назначение аргументов здесь такое же, как и в предыдущих подпрограммах, за исключением того, что указывается ранг не адресата, а источника сообщения (source). Блокировка работы процесса до завершения приема или передачи сообщения int MPI_Wait(MPI_Request *request, MPI_Status *status) MPI_WAIT(REQUEST, STATUS, IERR) Входной параметр: •
request идентификатор операции обмена.
Выходной параметр: •
status статус выполненной операции.
Значение статуса для операции передачи сообщения можно получить вызовом подпрограммы MPI_Test_cancelled. Можно вызвать MPI_Wait с пустым или неактивным аргументом request. В этом случае операция завершается сразу же с пустым статусом.
44
Успешное выполнение подпрограммы MPI_Wait после вызова MPI_Ibsend подразумевает, что буфер передачи можно использовать вновь, то есть пересылаемые данные отправлены или скопированы в буфер, выделенный при вызове подпрограммы MPI_Buffer_attach. В этот момент уже нельзя отменить передачу. Если не будет зарегистрирован соответствующий прием, буфер нельзя будет освободить. В этом случае можно применить подпрограмму MPI_Cancel, которая освобождает память, выделенную подсистеме коммуникаций. Неблокирующая проверка завершения приема или передачи сообщения int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status) MPI_TEST(REQUEST, FLAG, STATUS, IERR) Входной параметр: •
request идентификатор операции обмена.
Выходные параметры: •
flag "истина", если операция, заданная идентификатором request, выполнена;
•
status статус выполненной операции.
Если при вызове MPI_Test используется пустой или неактивный аргумент request, операция возвращает значение флага "истина" и пустой статус. Проверка завершения всех обменов int MPI_Waitall(int count, MPI_Request requests[], MPI_Status statuses[]) MPI_WAITALL(COUNT, REQUESTS, STATUSES, IERR) Выполнение процесса блокируется до тех пор, пока все операции обмена, связанные с активными запросами в массиве requests, не будут выполнены. Возвращается статус этих операций. Статус обменов содержится в массиве statuses. count количество запросов на обмен (размер массивов requests и statuses). В результате выполнения подпрограммы MPI_Waitall запросы, сформированные неблокирующими операциями обмена, аннулируются, а соответствующим элементам массива присваивается значение MPI_REQUEST_NULL. Список может содержать пустые или неактивные запросы. Для каждого из них устанавливается пустое значение статуса. В случае неуспешного выполнения одной или более операций обмена подпрограмма MPI_Waitall возвращает код ошибки MPI_ERR_IN_STATUS и присваивает полю ошибки статуса значение кода ошибки соответствующей операции. Если операция выполнена успешно, полю присваивается значение MPI_SUCCESS, а если не выполнена, но и не было ошибки значение MPI_ERR_PENDING. Последний случай соответствует наличию запросов на выполнение операции обмена, ожидающих обработки. Неблокирующая проверка завершения обменов int MPI_Testall(int count, MPI_Request requests[], int *flag, MPI_Status statuses[]) MPI_TESTALL(COUNT, REQUESTS, FLAG, STATUSES, IERR)
45
При вызове возвращается значение флага (flag) "истина", если все обмены, связанные с активными запросами в массиве requests, выполнены. Если завершены не все обмены, флагу присваивается значение "ложь", а массив statuses не определен. count количество запросов. Каждому статусу, соответствующему активному запросу, присваивается значение статуса соответствующего обмена. Если запрос был сформирован операцией неблокирующего обмена, он аннулируется, а соответствующему элементу массива присваивается значение MPI_REQUEST_NULL. Каждому статусу, соответствующему пустому или неактивному запросу, присваивается пустое значение. Блокирующая проверка завершения любого числа обменов int MPI_Waitany(int count, MPI_Request requests[], int *index, MPI_Status *status) MPI_WAITANY(COUNT, REQUESTS, INDEX, STATUS, IERR) Выполнение процесса блокируется до тех пор, пока, по крайней мере, один обмен из массива запросов (requests) не будет завершен. Входные параметры: •
requests запрос;
•
count количество элементов в массиве requests, а выходные: status и index .
Выходные параметры: •
index индекс запроса (в языке C это целое число от 0 до count – 1, а в языке FORTRAN от 1 до count) в массиве requests;
•
status статус.
Если запрос на выполнение операции был сформирован неблокирующей операцией обмена, он аннулируется и ему присваивается значение MPI_REQUEST_NULL. Массив запросов может содержать пустые или неактивные запросы. Если в списке вообще нет активных запросов или он пуст, вызовы завершаются сразу со значением индекса MPI_UNDEFINED и пустым статусом. Проверка выполнения любого ранее инициализированного обмена int MPI_Testany(int count, MPI_Request requests[], int *index, int *flag, MPI_Status *status) MPI_TESTANY(COUNT, REQUESTS, INDEX, FLAG, STATUS, IERR) Смысл и назначение параметров этой подпрограммы те же, что и для подпрограммы MPI_Waitany. Дополнительный аргумент flag, который принимает значение "истина", если одна из операций завершена. Блокирующая подпрограмма MPI_Waitany и неблокирующая MPI_Testany взаимозаменяемы, впрочем, как и другие аналогичные пары. Подпрограммы MPI_Waitsome и MPI_Testsome действуют аналогично подпрограммам MPI_Waitany и MPI_Testany, кроме случая, когда завершается более одного обмена. В подпрограммах MPI_Waitany и MPI_Testany обмен из числа завершенных выбирается произвольно, именно для него и возвращается статус, а для MPI_Waitsome и
46
MPI_Testsome статус возвращается для всех завершенных обменов. Эти подпрограммы можно использовать для определения, сколько обменов завершено: int MPI_Waitsome(int incount, MPI_Request requests[], int *outcount, int indices[], MPI_Status statuses[]) MPI_WAITSOME(INCOUNT, REQUESTS, OUTCOUNT, INDICES, STATUSES, IERR) Здесь incount количество запросов. В outcount возвращается количество выполненных запросов из массива requests, а в первых outcount элементах массива indices возвращаются индексы этих операций. В первых outcount элементах массива statuses возвращается статус завершенных операций. Если выполненный запрос был сформирован неблокирующей операцией обмена, он аннулируется. Если в списке нет активных запросов, выполнение подпрограммы завершается сразу, а параметру outcount присваивается значение MPI_UNDEFINED. Неблокирующая проверка выполнения обменов int MPI_Testsome(int incount, MPI_Request requests[], int *outcount, int indices[], MPI_Status statuses[]) MPI_TESTSOME(INCOUNT, REQUESTS, OUTCOUNT, INDICES, STATUSES, IERR) Параметры такие же, как и у подпрограммы MPI_Waitsome. Эффективность подпрограммы MPI_Testsome выше, чем у MPI_Testany, поскольку первая возвращает информацию обо всех операциях, а для второй требуется новый вызов для каждой выполненной операции. Запрос для стандартной передачи создается при вызове подпрограммы MPI_Send_init: int MPI_Send_init(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request *request) MPI_SEND_INIT(BUF, COUNT, DATATYPE, DEST, TAG, COMM, REQUEST, IERR) Входные параметры: •
buf адрес буфера передачи;
•
count количество элементов;
•
datatype тип элементов;
•
dest ранг адресата;
•
tag тег сообщения;
•
comm коммуникатор.
Выходной параметр: •
request запрос на выполнение операции обмена.
Инициализация отложенного обмена int MPI_Start(MPI_Request *request) MPI_START(REQUEST, IERR) Входной параметр: •
request запрос на выполнение операции обмена.
47
Вызов MPI_Start с запросом на обмен, созданным MPI_Send_init, инициирует обмен с теми же свойствами, что и вызов подпрограммы MPI_Isend, а вызов MPI_Start с запросом, созданным MPI_Bsend_init, инициирует обмен аналогично вызову MPI_Ibsend. Сообщение, которое передано операцией, инициированной с помощью MPI_Start, может быть принято любой подпрограммой приема. Инициализация обменов, связанных с запросами на выполнение неблокирующей операции обмена в массиве requests int MPI_Startall(int count, MPI_request *requests) MPI_STARTALL(COUNT, REQUESTS, IERR) Аннулирование неблокирующих "ждущих" (ожидающих обработки) обменов int MPI_Cancel(MPI_request *request) MPI_CANCEL(REQUEST, IERR) MPI_Cancel можно использовать для аннулирования обменов, использующих как отложенный, так и обычный запрос. После вызова MPI_Cancel и следующего за ним вызова MPI_Wait или MPI_Test, запрос на выполнение операции обмена становится неактивным и может быть активизирован для нового обмена. Информация об аннулированной операции содержится в аргументе status. Проверка, произведено ли аннулирование обмена, связанного с указанным статусом int MPI_Test_cancelled(MPI_Status *status, int *flag) MPI_TEST_CANCELLED(STATUS, FLAG, IERR) Аннулирование запроса на выполнение операции (request) int MPI_Request_free(MPI_Request *request) MPI_REQUEST_FREE(REQUEST, IERR) При вызове помечает запрос на обмен для удаления и присваивает ему значение MPI_REQUEST_NULL. Операции обмена, связанной с этим запросом, дается возможность завершиться, а сам запрос удаляется только после завершения обмена.
Коллективный обмен Участниками коллективного обмена являются более двух процессов. Широковещательная рассылка int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root, MPI_Comm comm) MPI_BCAST(BUFFER, COUNT, DATATYPE, ROOT, COMM, IERR) Параметры этой процедуры одновременно являются входными и выходными: •
buffer адрес буфера;
48 •
count количество элементов данных в сообщении;
•
datatype тип данных MPI;
•
root ранг главного процесса, выполняющего широковещательную рассылку;
•
comm коммуникатор.
Схема распределения данных представлена на рис. 16.
Рис. 16. Распределение данных при широковещательной рассылке Синхронизация с помощью "барьера" (рис. 17) int MPI_Barrier(MPI_Comm comm) MPI_BARRIER(COMM, IERR)
Рис. 17. Синхронизация с «барьером»
49
При синхронизации с барьером выполнение каждого процесса из данного коммуникатора приостанавливается до тех пор, пока все процессы не выполнят вызов процедуры синхронизации MPI_Barrier. Распределение данных int MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, int root, MPI_Comm comm) MPI_SCATTER(SENDBUF, SENDCOUNT, SENDTYPE, RCVBUF, RCVCOUNT, RCVTYPE, ROOT, COMM, IERR) Входные параметры: •
sendbuf адрес буфера передачи;
•
sendcount количество элементов, пересылаемых каждому процессу (но не суммарное количество пересылаемых элементов);
•
sendtype тип передаваемых данных;
•
rcvcount количество элементов в буфере приема;
•
rcvtype тип принимаемых данных;
•
root ранг передающего процесса;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf адрес буфера приема.
Процесс с рангом root распределяет содержимое буфера передачи sendbuf среди всех процессов (рис. 18). Содержимое буфера передачи разбивается на несколько фрагментов, каждый из которых содержит sendcount элементов. Первый фрагмент передается процессу 0, второй процессу 1 и т. д. Аргументы send имеют значение только на стороне процесса root.
Рис. 18. Распределение данных при выполнении операции Scatter Сбор сообщений от остальных процессов в буфер главной задачи int MPI_Gather(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, int root, MPI_Comm comm)
50
MPI_GATHER(SENDBUF, SENDCOUNT, SENDTYPE, RCVBUF, RCVCOUNT, RCVTYPE, ROOT, COMM, IERR) Каждый процесс в коммуникаторе comm пересылает содержимое буфера передачи sendbuf процессу с рангом root. Процесс root "склеивает" полученные данные в буфере приема (рис. 19). Порядок склейки определяется рангами процессов, то есть в результирующем наборе после данных от процесса 0 следуют данные от процесса 1, затем данные от процесса 2 и т. д. Аргументы rcvbuf, rcvcount и rcvtype играют роль только на стороне главного процесса. Аргумент rcvcount указывает количество элементов данных, полученных от каждого процесса (но не суммарное их количество). При вызове подпрограмм MPI_Scatter и MPI_Gather из разных процессов следует использовать общий главный процесс.
Рис. 19. Сбор данных при выполнении операции Gather Векторная подпрограмма распределения данных int MPI_Scatterv(void *sendbuf, int *sendcounts, int *displs, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, int root, MPI_Comm comm) MPI_SCATTERV(SENDBUF, SENDCOUNTS, DISPLS, SENDTYPE, RCVBUF, RCVCOUNT, RCVTYPE, ROOT, COMM, IERR) Входные параметры: •
sendbuf адрес буфера передачи;
•
sendcounts целочисленный одномерный массив, содержащий количество элементов, передаваемых каждому процессу (индекс равен рангу адресата). Его длина равна количеству процессов в коммуникаторе;
•
displs целочисленный массив, длина которого равна количеству процессов в коммуникаторе. Элемент с индексом i задает смещение относительно начала буфера передачи. Ранг адресата равен значению индекса i;
•
sendtype тип данных в буфере передачи;
•
rcvcount количество элементов в буфере приема;
•
rcvtype тип данных в буфере приема;
•
root ранг передающего процесса;
•
comm коммуникатор.
51
Выходной параметр: •
rcvbuf адрес буфера приема.
Сбор данных от всех процессов в заданном коммуникаторе и запись их в буфер приема с указанным смещением int MPI_Gatherv(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int *recvcounts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm) MPI_GATHERV(SENDBUF, SENDCOUNT, SENDTYPE, RECVBUF, RECVCOUNTS, DISPLS, RECVTYPE, ROOT, COMM, IERR) Список параметров у этой подпрограммы похож на список параметров подпрограммы MPI_Scatterv. В обменах, выполняемых подпрограммами MPI_Allgather и MPI_Alltoall, нет главного процесса. Детали отправки и приема важны для всех процессов, участвующих в обмене. Сбор данных от всех процессов и распределение их всем процессам int MPI_Allgather(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, MPI_Comm comm) MPI_ALLGATHER(SENDBUF, SENDCOUNT, SENDTYPE, RCVBUF, RCVCOUNT, RCVTYPE, COMM, IERR) Входные параметры: •
sendbuf начальный адрес буфера передачи;
•
sendcount количество элементов в буфере передачи;
•
sendtype тип передаваемых данных;
•
rcvcount количество элементов, полученных от каждого процесса;
•
rcvtype тип данных в буфере приема;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf адрес буфера приема.
Блок данных, переданный от j-го процесса, принимается каждым процессом и размещается в j-м блоке буфера приема recvbuf (рис. 19).
Рис. 19. Сбор и распределение данных при выполнении операции Allgather
52
Пересылка данных по схеме "каждый всем" int MPI_Alltoall(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, MPI_Comm comm) MPI_ALLTOALL(SENDBUF, SENDCOUNT, SENDTYPE, RCVBUF, RCVCOUNT, RCVTYPE, COMM, IERR) Входные параметры: •
sendbuf начальный адрес буфера передачи;
•
sendcount количество элементов данных, пересылаемых каждому процессу;
•
sendtype тип данных в буфере передачи;
•
rcvcount количество элементов данных, принимаемых от каждого процесса;
•
rcvtype тип принимаемых данных;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf адрес буфера приема.
Векторными версиями MPI_Allgather и MPI_Alltoall являются подпрограммы MPI_Allgatherv и MPI_Alltoallv. Сбор данных от всех процессов и пересылка их всем процессам int MPI_Allgatherv(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int *rcvcounts, int *displs, MPI_Datatype rcvtype, MPI_Comm comm) MPI_ALLGATHERV(SENDBUF, SENDCOUNT, SENDTYPE, RCVBUF, RCVCOUNTS, DISPLS, RCVTYPE, COMM, IERR) Ее параметры совпадают с параметрами подпрограммы MPI_Allgather, за исключением дополнительного входного параметра displs. Это целочисленный одномерный массив, количество элементов в котором равно количеству процессов в коммуникаторе. Элемент массива с индексом i задает смещение относительно начала буфера приема recvbuf, в котором располагаются данные, принимаемые от процесса i. Блок данных, переданный от jго процесса, принимается каждым процессом и размещается в j-м блоке буфера приема. Пересылка данных от всех процессов всем процессам со смещением int MPI_Alltoallv(void *sendbuf, int *sendcounts, int *sdispls, MPI_Datatype sendtype, void *rcvbuf, int *rcvcounts, int *rdispls, MPI_Datatype rcvtype, MPI_Comm comm) MPI_ALLTOALLV(SENDBUF, SENDCOUNTS, SDISPLS, SENDTYPE, RCVBUF, RCVCOUNTS, RDISPLS, RCVTYPE, COMM, IERR) Ее параметры аналогичны параметрам подпрограммы MPI_Alltoall, кроме двух дополнительных параметров: •
sdispls целочисленный массив, количество элементов в котором равно количеству процессов в коммуникаторе. Элемент j задает смещение относительно начала буфера, из которого данные передаются j-му процессу.
53 •
rdispls целочисленный массив, количество элементов в котором равно количеству процессов в коммуникаторе. Элемент i задает смещение относительно начала буфера, в который принимается сообщение от i-го процесса.
Операция приведения, результат которой передается одному процессу int MPI_Reduce(void *buf, void *result, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm) MPI_REDUCE(BUF, RESULT, COUNT, DATATYPE, OP, ROOT, COMM, IERR) Входные параметры: •
buf адрес буфера передачи;
•
count количество элементов в буфере передачи;
•
datatype тип данных в буфере передачи;
•
op операция приведения;
•
root ранг главного процесса;
•
comm коммуникатор.
MPI_Reduce применяет операцию приведения к операндам из buf, а результат каждой операции помещается в буфер результата result (рис. 20). MPI_Reduce должна вызываться всеми процессами в коммуникаторе comm, а аргументы count, datatype и op в этих вызовах должны совпадать.
Рис. 20. Распределение данных при выполнении операции приведения Предопределенные операции приведения приведены в табл. 3. Определение собственных глобальных операций int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op) MPI_OP_CREATE(FUNCTION, COMMUTE, OP, IERR)
54
Входные параметры: •
function пользовательская функция;
commute флаг, которому присваивается значение "истина", если коммутативна (результат не зависит от порядка операндов). Описание типа пользовательской функции выглядит следующим образом: typedef void (MPI_User_function)(void *a, void *b, int *len, MPI_Datatype *dtype) Здесь операция определяется так: b[I] = a[I] op b[I] для I = 0, …, len – 1. •
операция
Таблица 3. Предопределенные операции приведения MPI Операция
Описание
MPI_MAX
Определение максимальных значений элементов одномерных массивов целого или вещественного типа
MPI_MIN
Определение минимальных значений элементов одномерных массивов целого или вещественного типа
MPI_SUM
Вычисление суммы элементов одномерных массивов целого, вещественного или комплексного типа
MPI_PROD
Вычисление поэлементного произведения одномерных массивов целого, вещественного или комплексного типа
MPI_LAND
Логическое "И"
MPI_BAND
Битовое "И"
MPI_LOR
Логическое "ИЛИ"
MPI_BOR
Битовое "ИЛИ"
MPI_LXOR
Логическое исключающее "ИЛИ"
MPI_BXOR
Битовое исключающее "ИЛИ"
MPI_MAXLOC
Максимальные значения элементов одномерных массивов и их индексы
MPI_MINLOC
Минимальные значения элементов одномерных массивов и их индексы
Удаление пользовательской функции int MPI_Op_free(MPI_Op *op) MPI_OP_FREE(OP, IERR) После завершения вызова op присваивается значение MPI_OP_NULL. Одновременные сбор и распределение данных int MPI_Reduce_scatter(void *sendbuf, void *rcvbuf, int *rcvcounts, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm)
55
MPI_REDUCE_SCATTER(SENDBUF, RCVBUF, RCVCOUNTS, DATATYPE, OP, COMM, IERR) Входные параметры: •
sendbuf стартовый адрес буфера приема;
•
rcvcounts целочисленный одномерный массив, который задает количество элементов в результирующем массиве, распределяемом каждому процессу. Этот массив должен быть одинаковым во всех процессах, вызывающих данную подпрограмму;
•
datatype тип данных в буфере приема;
•
op операция;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf стартовый адрес буфера приема.
Каждая задача получает не весь результирующий массив, а его часть. Сбор данных от всех процессов и сохранение результата операции приведения в результирующем буфере каждого процесса int MPI_Allreduce(void *sendbuf, void *rcvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) MPI_ALLREDUCE(SENDBUF, RCVBUF, COUNT, DATATYPE, OP, COMM, IERR) Входные параметры: •
sendbuf начальный адрес буфера передачи;
•
count количество элементов в буфере передачи;
•
datatype тип передаваемых данных;
•
op операция приведения;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf стартовый адрес буфера приема.
При аварийном завершении подпрограмма может возвращать код ошибки MPI_ERR_OP (некорректная операция). Это происходит, если применяется операция, которая не является предопределенной и которая не создана предшествующим вызовом подпрограммы MPI_Op_create. Операции сканирования (частичной редукции) int MPI_Scan(void *sendbuf, void *rcvbuf, int count, MPI_Datatype datatype, MPI_Op op, MPI_Comm comm) MPI_SCAN(SENDBUF, RCVBUF, COUNT, DATATYPE, OP, COMM, IERR) Входные параметры: •
sendbuf начальный адрес буфера передачи;
•
count количество элементов во входном буфере;
56 •
datatype тип данных во входном буфере;
•
op операция;
•
comm коммуникатор.
Выходной параметр: •
rcvbuf стартовый адрес буфера приема.
Управление коммуникаторами Стандартный коммуникатор MPI_COMM_WORLD создается автоматически при запуске параллельной программы на выполнение. Напомним, что имеются также стандартные коммуникаторы: •
MPI_COMM_SELF коммуникатор, содержащий только вызывающий процесс;
•
MPI_COMM_NULL пустой коммуникатор.
Получение доступа к группе group, связанной с коммуникатором comm int MPI_Comm_group(MPI_Comm comm, MPI_Group *group) MPI_COMM_GROUP(COMM, GROUP, IERR) Выходной параметр группа. Для выполнения операций с группой к ней сначала необходимо получить доступ. Создание новой группы newgroup из n процессов, входящих в группу oldgroup int MPI_Group_incl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup) MPI_GROUP_INCL(OLDGROUP, N, RANKS, NEWGROUP, IERR) Ранги процессов содержатся в массиве ranks. В новую группу войдут процессы с рангами ranks[0], …, ranks[n — 1], причем рангу i в новой группе соответствует ранг ranks[i] в старой группе. При n = 0 создается пустая группа MPI_GROUP_EMPTY. С помощью данной подпрограммы можно не только создать новую группу, но и изменить порядок процессов в старой группе. Создание группы newgroup исключением из исходной группы (group) процессы с рангами ranks[0], …, ranks[n — 1] int MPI_Group_excl(MPI_Group oldgroup, int n, int *ranks, MPI_Group *newgroup) MPI_GROUP_EXCL(OLDGROUP, N, RANKS, NEWGROUP, IERR) При n = 0 новая группа тождественна старой. Создание группы newgroup из группы group добавлением в нее n процессов, ранг которых указан в массиве ranks int MPI_Group_range_incl(MPI_Group oldgroup, int n, int ranks[][3], MPI_Group *newgroup)
57
MPI_GROUP_RANGE_INCL(OLDGROUP, N, RANKS, NEWGROUP, IERR) Массив ranks состоит из целочисленных триплетов вида (первый_1, последний_1, шаг_1), …, (первый_n, последний_n, шаг_n). В новую группу войдут процессы с рангами (по первой группе) первый_1, первый_1 + шаг_1, …. Создание группы newgroup из группы group исключением из нее n процессов, ранг которых указан в массиве ranks int MPI_Group_range_excl(MPI_Group group, int n, int ranks[][3], MPI_Group *newgroup) MPI_GROUP_RANGE_EXCL(GROUP, N, RANKS, NEWGROUP, IERR) Массив ranks устроен так же, как аналогичный массив MPI_Group_range_incl.
в
подпрограмме
Создание новой группы (newgroup) из разности двух групп (group1) и (group2) int MPI_Group_difference(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) MPI_GROUP_DIFFERENCE(GROUP1, GROUP2, NEWGROUP, IERR) Создание новой группы (newgroup) из пересечения групп group1 и group2 int MPI_Group_intersection(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) MPI_GROUP_INTERSECTION(GROUP1, GROUP2, NEWGROUP, IERR) Создание группы (newgroup) объединением групп group1 и group2 int MPI_Group_union(MPI_Group group1, MPI_Group group2, MPI_Group *newgroup) MPI_GROUP_UNION(GROUP1, GROUP2, NEWGROUP, IERR) Имеются и другие подпрограммы-конструкторы новых групп. Уничтожение группы group int MPI_Group_free(MPI_Group *group) MPI_GROUP_FREE(GROUP, IERR) Определение количества процессов (size) в группе (group) int MPI_Group_size(MPI_Group group, int *size) MPI_GROUP_SIZE(GROUP, SIZE, IERR)
58
Определение ранга (rank) процесса в группе group int MPI_Group_rank(MPI_Group group, int *rank) MPI_GROUP_RANK(GROUP, RANK, IERR) Если процесс не входит в указанную группу, возвращается значение MPI_UNDEFINED. Преобразование ранга процесса в одной группе в его ранг относительно другой группы int MPI_Group_translate_ranks(MPI_Group group1, int n, int *ranks1, MPI_Group group2, int *ranks2) MPI_GROUP_TRANSLATE_RANKS(GROUP1, N, RANKS1, GROUP2, RANKS2, IERR) Сравнение групп group1 и group2 int MPI_Group_compare(MPI_Group group1, MPI_Group group2, int *result) MPI_GROUP_COMPARE(GROUP1, GROUP2, RESULT, IERR) Если группы полностью совпадают, возвращается значение MPI_IDENT. Если члены обеих групп одинаковы, но их ранги отличаются, результатом будет значение MPI_SIMILAR. Если группы различны, результатом будет MPI_UNEQUAL. Создание дубликата уже существующего коммуникатора oldcomm int MPI_Comm_dup(MPI_Comm oldcomm, MPI_Comm *newcomm) MPI_COMM_DUP(OLDCOMM, NEWCOMM, IERR) В результате вызова данной подпрограммы создается новый коммуникатор (newcomm) с той же группой процессов, с теми же атрибутами, но с другим контекстом. Подпрограмма может применяться как к интра-, так и к интеркоммуникаторам. Создание нового коммуникатора (newcomm) из подмножества процессов (group) другого коммуникатора (oldcomm) int MPI_Comm_create(MPI_Comm oldcomm, MPI_Group group, MPI_Comm *newcomm) MPI_COMM_CREATE(OLDCOMM, GROUP, NEWCOMM, IERR) Вызов этой подпрограммы должны выполнить все процессы из старого коммуникатора, даже если они не входят в группу group, с одинаковыми аргументами. Если одновременно создаются несколько коммуникаторов, они должны создаваться в одной последовательности всеми процессами. Создание нескольких коммуникаторов сразу методом расщепления int MPI_Comm_split(MPI_Comm oldcomm, int split, int rank, MPI_Comm* newcomm) MPI_COMM_SPLIT(OLDCOMM, SPLIT, RANK, NEWCOMM, IERR) Группа процессов, связанных с коммуникатором oldcomm, разбивается на непересекающиеся подгруппы, по одной для каждого значения аргумента split. Процессы
59
с одинаковым значением split образуют новую группу. Ранг в новой группе определяется значением rank. Если процессы A и B вызывают MPI_Comm_split с одинаковым значением split, а аргумент rank, переданный процессом A, меньше, чем аргумент, переданный процессом B, ранг A в группе, соответствующей новому коммуникатору, будет меньше ранга процесса B. Если же в вызовах используется одинаковое значение rank, система присвоит ранги произвольно. Для каждой подгруппы создается собственный коммуникатор newcomm. MPI_Comm_split должны вызвать все процессы из старого коммуникатора, даже если они не войдут в новый коммуникатор. Для этого в качестве аргумента split в подпрограмму передается предопределенная константа MPI_UNDEFINED. Соответствующие процессы вернут в качестве нового коммуникатора значение MPI_COMM_NULL. Новые коммуникаторы, созданные подпрограммой MPI_Comm_split, не пересекаются, однако с помощью повторных вызовов подпрограммы MPI_Comm_split можно создавать и перекрывающиеся коммуникаторы. Пометить коммуникатор comm для удаления int MPI_Comm_free(MPI_Comm *comm) MPI_COMM_FREE(COMM, IERR) Обмены, связанные с этим коммуникатором, завершаются обычным образом, а сам коммуникатор удаляется только после того, как на него не будет активных ссылок. Данная операция может применяться к коммуникаторам интра- и интер-. Сравнение двух коммуникаторов (comm1) и (comm2) int MPI_Comm_compare(MPI_Comm comm1, MPI_Comm comm2, int *result) MPI_COMM_COMPARE(COMM1, COMM2, RESULT, IERR) Выходной параметр: •
result целое значение, которое равно MPI_IDENT, если контексты и группы коммуникаторов совпадают; MPI_CONGRUENT, если совпадают только группы; MPI_SIMILAR и MPI_UNEQUAL, если не совпадают ни группы, ни контексты.
В качестве аргументов нельзя использовать пустой коммуникатор MPI_COMM_NULL. Присвоение коммуникатору comm строкового имени name int MPI_Comm_set_name(MPI_Comm com, char *name) MPI_COMM_SET_NAME(COM, NAME, IERR) Определение имени коммуникатора int MPI_Comm_get_name(MPI_Comm comm, char *name, int *reslen) MPI_COMM_GET_NAME(COMM, NAME, RESLEN, IERR) Выходной параметр: •
name строковое имя коммуникатора comm.;
•
reslen длина имени.
Имя представляет собой массив символьных значений, длина которого должна быть не менее MPI_MAX_NAME_STRING.
60
Проверка, является ли коммуникатор comm (входной параметр) интеркоммуникатором int MPI_Comm_test_inter(MPI_Comm comm, int *flag) MPI_COMM_TEST_INTER(COMM, FLAG, IERR) Выходной параметр: •
flag значение флага "истина", если аргументом является интеркоммуникатор.
Создание интракоммуникатора newcomm из интеркоммуникатора oldcomm int MPI_Intercomm_merge(MPI_Comm oldcomm, int high, MPI_Comm *newcomm) MPI_INTERCOMM_MERGE(OLDCOMM, HIGH, NEWCOMM, IERR) Параметр high используется для упорядочения групп обоих интракоммуникаторов в comm при создании нового коммуникатора. Получение доступа к удаленной группе, связанной с интеркоммуникатором comm int MPI_Comm_remote_group(MPI_Comm comm, MPI_Group *group) MPI_COMM_REMOTE_GROUP(COMM, GROUP, IERR) Выходной параметр: •
group удаленная группа.
Определение размера удаленной группы, связанной с интеркоммуникатором comm int MPI_Comm_remote_size(MPI_Comm comm, int *size) MPI_COMM_REMOTE_SIZE(COMM, SIZE, IERR) Выходной параметр: •
size количество процессов в области взаимодействия, связанной с коммуникатором comm.
Создание интеркоммуникатора int MPI_Intercomm_create(MPI_Comm local_comm, int local_leader, MPI_Comm peer_comm, int remote_leader, int tag, MPI_Comm *new_intercomm) MPI_INTERCOMM_CREATE(LOCAL_COMM, LOCAL_LEADER, PEER_COMM, REMOTE_LEADER, TAG, NEW_INTERCOMM, IERR) Входные параметры: •
local_comm локальный интракоммуникатор;
•
local_leader ранг лидера в локальном коммуникаторе (обычно 0);
•
peer_comm удаленный коммуникатор;
•
remote_leader ранг лидера в удаленном коммуникаторе (обычно 0);
•
tag тег интеркоммуникатора, используемый лидерами обеих групп для обменов в контексте родительского коммуникатора.
61
Выходной параметр: •
new_intercomm интеркоммуникатор.
"Джокеры" в качестве параметров использовать нельзя. Вызов этой подпрограммы должен выполняться в обеих группах процессов, которые должны быть связаны между собой. В каждом из этих вызовов используется локальный интракоммуникатор, соответствующий данной группе процессов. При работе с MPI_Intercomm_create локальная и удаленная группы процессов не должны пересекаться, иначе возможны "тупики".
Топологии Виртуальные топологии в MPI позволяют использовать более удобные способы обращения к процессам, входящим в состав параллельной программы. Создание нового коммуникатора comm_cart посредством наделения декартовой топологией исходный коммуникатор comm_old (рис. 21) int MPI_Cart_create(MPI_Comm comm_old, int ndims, int *dims, int *periods, int reorder, MPI_Comm *comm_cart) MPI_CART_CREATE(COMM_OLD, NDIMS, DIMS, PERIODS, REORDER, COMM_CART, IERR) Входные параметры: •
comm_old исходный коммуникатор;
•
ndims размерность декартовой решетки;
•
dims целочисленный массив, состоящий из ndims элементов, задающий количество процессов в каждом измерении;
•
periods логический массив из ndims элементов, который определяет, является ли решетка периодической (значение true) вдоль каждого измерения;
•
reorder при значении этого параметра "истина", системе разрешено менять порядок нумерации процессов.
Информация о структуре декартовой топологии содержится в параметрах ndims, dims и periods. MPI_Cart_create является коллективной операцией (эту подпрограмму должны вызывать все процессы из коммуникатора, наделяемого декартовой топологией).
62
Рис. 21. Декартова топология Определение декартовых координат процесса по его рангу в группе int MPI_Cart_coords(MPI_Comm comm, int rank, int maxdims, int *coords) MPI_CART_COORDS(COMM, RANK, MAXDIMS, COORDS, IERR) Входные параметры: •
comm коммуникатор, наделенный декартовой топологией;
•
rank ранг процесса в comm;
•
maxdims длина вектора coords в вызывающей программе.
Выходной параметр: •
coords одномерный целочисленный массив (его размер равен ndims), содержащий декартовы координаты процесса.
Определение ранга процесса (rank) по его декартовым координатам в коммуникаторе comm int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank) MPI_CART_RANK(COMM, COORDS, RANK, IERR) Входной параметр: •
coords целочисленный массив размера ndims, задающий декартовы координаты процесса. Как MPI_Cart_rank, так и MPI_Cart_coords локальны.
Расщепление коммуникатора comm на подгруппы, соответствующие декартовым подрешеткам меньшей размерности int MPI_Cart_sub(MPI_Comm comm, int *remain_dims, MPI_Comm *comm_new) MPI_CART_SUB(COMM, REMAIN_DIMS, COMM_NEW, IERR)
63 I-й элемент массива remain_dims определяет, содержится ли I-е измерение в подрешетке ("истина"). Выходной параметр: •
newcomm коммуникатор, содержащий подрешетку, которой принадлежит данный процесс.
Подпрограмма MPI_Cart_sub используется только с коммуникатором, наделенным декартовой топологией. Получение информации о декартовой топологии, связанной с коммуникатором comm int MPI_Cart_get(MPI_Comm comm, int maxdims, int *dims, int *periods, int *coords) MPI_CART_GET(COMM, MAXDIMS, DIMS, PERIODS, COORDS, IERR) Входной параметр: maxdims задает длину массивов dims, periods и vectors в вызывающей программе Выходные параметры: •
•
dims целочисленный массив, задающий количество процессов для каждого измерения;
•
periods массив логических значений, задающих периодичность (true, если решетка периодическая) для каждого измерения;
•
coords целочисленный массив, задающий декартовы координаты вызывающего подпрограмму процесса.
Определение ранга процесса (newrank) в декартовой топологии после переупорядочения процессов int MPI_Cart_map(MPI_Comm comm_old, int ndims, int *dims, int *periods, int *newrank) MPI_CART_MAP(COMM_OLD, NDIMS, DIMS, PERIODS, NEWRANK, IERR) Входные параметры: •
comm коммуникатор;
•
ndims размерность декартовой решетки;
•
dims целочисленный массив, состоящий из ndims элементов, который определяет количество процессов вдоль каждого измерения;
periods логический массив размера ndims, определяющий периодичность решетки вдоль каждого измерения. Если процесс не принадлежит решетке, подпрограмма возвращает значение MPI_UNDEFINED. •
Определение рангов источника (source) сообщения, которое должно быть принято, и адресата (dest), который должен получить сообщение для заданного направления сдвига (direction) и его величины (disp) int MPI_Cart_shift(MPI_Comm comm, int direction, int displ, int *source, int *dest)
64
MPI_CART_SHIFT(COMM, DIRECTION, DISPL, SOURCE, DEST, IERR) Для n-мерной декартовой решетки значение аргумента direction должно находиться в пределах от 0 до n – 1. Определение размерности (ndims) декартовой топологии, связанной с коммуникатором comm int MPI_Cartdim_get(MPI_Comm comm, int *ndims) MPI_CARTDIM_GET(COMM, NDIMS, IERR) Создание нового коммуникатора comm_graph, наделенного топологией графа (рис. 22) int MPI_Graph_create(MPI_Comm comm, int nnodes, int *index, int *edges, int reorder, MPI_Comm *comm_graph) MPI_GRAPH_CREATE(COMM, NNODES, INDEX, EDGES, REORDER, COMM_GRAPH, IERR) Входные параметры: •
comm исходный коммуникатор, не наделенный топологией;
•
nnodes количество вершин графа;
•
index целочисленный одномерный массив, содержащий порядок каждого узла (количество связанных с ним ребер);
•
edges целочисленный одномерный массив, описывающий ребра графа;
•
reorder значение "истина" разрешает изменение порядка нумерации процессов.
Рис. 22. Виртуальная топология графа Определение вершин графа, соседних с данной int MPI_Graph_neighbors(MPI_Comm comm, int rank, int maxneighbors, int *neighbors)
65
MPI_GRAPH_NEIGHBORS(COMM, RANK, MAXNEIGHBORS, NEIGHBORS, IERR) Входные параметры: •
comm коммуникатор с топологией графа;
•
rank ранг процесса в группе коммуникатора comm;
•
maxneighbors размер массива neighbors.
Выходной параметр: •
neighbors массив, содержащий ранги процессов, соседних с данным.
Определение количества соседей (nneighbors) узла, связанного с топологией графа int MPI_Graph_neighbors_count(MPI_Comm comm, int rank, int *nneighbors) MPI_GRAPH_NEIGHBORS_COUNT(COMM, RANK, NNEIGHBORS, IERR) Входные параметры: •
comm коммуникатор;
•
rank ранг процесса-узла.
Получение информации о топологии графа, связанной с коммуникатором comm int MPI_Graph_get(MPI_Comm comm, int maxindex, int maxedges, int *index, int *edges) MPI_GRAPH_GET(COMM, MAXINDEX, MAXEDGES, INDEX, EDGES, IERR) Входные параметры: •
comm коммуникатор;
•
maxindex длина массива index в вызывающей программе;
•
maxedges длина массива edges в вызывающей программе.
Выходные параметры: •
index целочисленный массив, содержащий структуру графа (см. описание подпрограммы MPI_Graph_create);
•
edges целочисленный массив, содержащий сведения о ребрах графа.
Определение ранга процесса в топологии графа после переупорядочения (newrank) int MPI_Graph_map(MPI_Comm comm, int nnodes, int *index, int *edges, int *newrank) MPI_GRAPH_MAP(COMM, NNODES, INDEX, EDGES, NEWRANK, IERR) Входные параметры: •
comm коммуникатор;
•
nnodes количество вершин графа;
66 •
index целочисленный массив, задающий подпрограммы MPI_Graph_create);
структуру
•
edges целочисленный массив, задающий ребра графа.
графа
(см.
описание
Если процесс не принадлежит графу, подпрограмма возвращает значение MPI_UNDEFINED. Получение информации о топологии графа, связанной с коммуникатором comm int MPI_Graphdims_get(MPI_Comm comm, int *nnodes, int *nedges) MPI_GRAPHDIMS_GET(COMM, NNODES, NEDGES, IERR) Выходные параметры: •
nnodes количество вершин графа;
•
nedges количество ребер графа.
Определение типа топологии (toptype), связанной с коммуникатором comm int MPI_Topo_test(MPI_Comm comm, int *toptype) MPI_TOPO_TEST(COMM, TOPTYPE, IERR) Выходной параметр: •
toptype тип топологии (значения MPI_CART для декартовой топологии и MPI_GRAPH для топологии графа).
Производные типы Производные типы данных MPI используются для пересылки данных, элементы которых располагаются в памяти не подряд, а с «пробелами». Производный тип сначала создается с помощью вызова подпрограммы-конструктора, а потом регистрируется. Перед завершением программы производный тип должен быть удален. Конструктор векторного типа 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 идентификатор нового типа, который назначается программистом.
Исходные данные однотипные.
67
Конструктор векторного типа (рис. 23) 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 задается в байтах.
Рис. 23. Векторный производный тип Конструктор структурного типа 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 идентификатор производного типа. MPI_Aint представляет собой скалярный тип, длина которого имеет размер, одинаковый с указателем. Конструктор индексированного типа 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) Входные параметры:
68 •
count количество блоков, одновременно длина массивов indices и blocklens;
•
blocklens количество элементов в каждом блоке;
•
indices смещение каждого блока, которое задается в количестве ячеек базового типа (целочисленный массив);
•
oldtype базовый тип.
Выходной параметр: •
newtype идентификатор производного типа.
Конструктор индексированного типа 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) Смещения indices задаются в байтах: Конструктор типа данных с непрерывным расположением элементов int MPI_Type_contiguous(int count, MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_TYPE_CONTIGUOUS(COUNT, OLDTYPE, NEWTYPE, IERR) Входные параметры: •
count счетчик повторений;
•
oldtype базовый тип.
Выходной параметр: • newtype идентификатор нового типа. Конструктор индексированного типа с блоками постоянного размера int MPI_Type_create_indexed_block(int count, int blocklength, int displacements[], MPI_Datatype oldtype, MPI_Datatype *newtype) MPI_TYPE_CREATE_INDEXED_BLOCK(COUNT, BLOCKLENGTH, DISPLACEMENTS, OLDTYPE, NEWTYPE, IERR) Входные параметры: •
count количество блоков и размер массивов indices и blocklens;
•
blocklength количество элементов в каждом блоке;
•
displacements смещение каждого блока в единицах длины типа oldtype (целочисленный массив);
•
oldtype базовый тип.
Выходной параметр: •
newtype идентификатор производного типа.
69
Конструктор типа данных, соответствующего подмассиву многомерного массива 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 базовый тип.
Выходной параметр: •
newtype новый тип.
Регистрация производного типа datatype, сконструированного программистом int MPI_Type_commit(MPI_Datatype *datatype) MPI_TYPE_COMMIT(DATATYPE, IERR) Удаление производного типа datatype int MPI_Type_free(MPI_Datatype *datatype) MPI_TYPE_FREE(DATATYPE, IERR) Базовые типы данных не могут быть удалены. Определение размера типа datatype в байтах (объем памяти, занимаемый одним элементом данного типа) int MPI_Type_size(MPI_Datatype datatype, int *size) MPI_TYPE_SIZE(DATATYPE, SIZE, IERR) Выходной параметр размер size. Определение количества элементов данных в одном объекте типа datatype (его экстент) int MPI_Type_extent(MPI_Datatype datatype, MPI_Aint *extent) MPI_TYPE_EXTENT(DATATYPE, EXTENT, IERR) Выходной параметр extent. Смещения могут даваться относительно базового адреса, значение которого содержится в константе MPI_BOTTOM.
70
Определение адреса (address) по заданному положению (location) int MPI_Address(void *location, MPI_Aint *address) MPI_ADDRESS(LOCATION, ADDRESS, IERR) Может использоваться в программах на языках C и FORTRAN. В C она обычно возвращает тот же адрес, что и оператор &, хотя иногда это не так. Данная подпрограмма может понадобиться в программе на языке FORTRAN, в C есть собственные средства для определения адреса. Определение фактических параметров, использованных при создании производного типа 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.
Выходные параметры: •
integers содержит целочисленные конструировании указанного типа;
аргументы,
использованные
•
addresses содержит аргументы address, использованные при конструировании указанного типа;
•
datatypes — содержит аргументы datatype, использованные при конструировании указанного типа.
Определение нижней границы типа данных datatype int MPI_Type_lb(MPI_Datatype datatype, MPI_Aint *displacement) MPI_TYPE_LB(DATATYPE, DISPLACEMENT, IERR) Выходной параметр: •
displacement — смещение (в байтах) нижней границы относительно источника.
Определение верхней границы типа int MPI_Type_ub(MPI_Datatype datatype, MPI_Aint *displacement) MPI_TYPE_UB(DATATYPE, DISPLACEMENT, IERR) Упаковка данных int MPI_Pack(void *inbuf, int incount, MPI_Datatype datatype, void *outbuf, int outcount, int *position, MPI_Comm comm)
при
71
MPI_PACK(INBUF, INCOUNT, DATATYPE, OUTBUF, OUTCOUNT, POSITION, COMM, IERR) При вызове incount элементов указанного типа выбираются из входного буфера и упаковываются в выходном буфере, начиная с положения position. Входные параметры: •
inbuf начальный адрес входного буфера;
•
incount количество входных данных;
•
datatype тип каждого входного элемента данных;
•
outcount размер выходного буфера в байтах;
•
position текущее положение в буфере в байтах;
•
comm коммуникатор для упакованного сообщения.
Выходной параметр: •
outbuf стартовый адрес выходного буфера.
Распаковка данных 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 стартовый адрес выходного буфера.
Определение объема памяти size (в байтах), необходимого для распаковки сообщения int MPI_Pack_size(int incount, MPI_Datatype datatype, MPI_Comm comm, int *size) MPI_PACK_SIZE(INCOUNT, DATATYPE, COMM, SIZE, IERR) Входные параметры: •
incount аргумент count, использованный при упаковке;
•
datatype тип упакованных данных;
•
comm коммуникатор.
72
Атрибуты Механизм назначения атрибутов является дополнительным средством обмена информацией между процессами. Создание нового ключа атрибута keyval (выходной параметр) int MPI_Keyval_create(MPI_Copy_function *copy_fn, MPI_Delete_function *delete_fn, int *keyval, void *extra_state) MPI_KEYVAL_CREATE(COPY_FN, DELETE_FN, KEYVAL, EXTRA_STATE, IERR) Ключи уникальны для каждого процесса и не видны пользователю, хотя явным образом хранятся в виде целых значений. Будучи однажды задан, ключ может быть использован для задания атрибутов и доступа к ним в любом коммуникаторе. Функция copy_fn вызывается, когда коммуникатор дублируется подпрограммой MPI_Comm_dup, а функция delete_fn используется для удаления. Параметр extra_state задает дополнительную информацию (состояние) для функций копирования и удаления. Задание типа функции MPI_Copy_function typedef int MPI_Copy_function(MPI_Comm oldcomm, int keyval, void *extra_state, void *attribute_val_in, void *attribute_val_out, int *flag) SUBROUTINE COPY_FUNCTION(OLDCOMM, KEYVAL, EXTRA_STATE, ATTRIBUTE_VAL_IN, ATTRIBUTE_VAL_OUT, FLAG, IERR) Функция копирования вызывается для каждого значения ключа в исходном коммуникаторе в произвольном порядке. Каждое обращение к функции копирования выполняется со значением ключа и соответствующим ему атрибутом. Если она возвращает значение флага flag = 0, атрибут удаляется из продублированного коммуникатора. В противном случае (flag = 1) устанавливается новое значение атрибута, равное значению, возвращенному в параметре attribute_val_out. Функцию copy_fn в языках C или FORTRAN можно определить значениями MPI_NULL_COPY_FN или MPI_DUP_FN. Значение MPI_NULL_COPY_FN является функцией, которая не выполняет никаких действий, только возвращает значение флага flag = 0 и MPI_SUCCESS. Значение MPI_DUP_FN представляет собой простейшую функцию дублирования. Она возвращает значение флага flag = 1, значение атрибута в переменной attribute_val_out и код завершения MPI_SUCCESS. Аналогичной copy_fn является функция удаления, которая определяется следующим образом. Функция delete_fn вызывается, когда коммуникатор удаляется вызовом MPI_Comm_free или при вызове MPI_Attr_delete. Она должна иметь тип MPI_Delete_function, который определяется следующим образом: typedef int MPI_Delete_function(MPI_Comm comm, int keyval, void *attribute_val, void *extra_state); SUBROUTINE DELETE_FUNCTION(COMM, KEYVAL, ATTRIBUTE VAL, EXTRA STATE, IERR) Эта функция вызывается подпрограммами MPI_Comm_free, MPI_Attr_delete и MPI_Attr_put. Функция удаления может быть "пустой" MPI_NULL_DELETE_FN.
73
Функция MPI_NULL_DELETE_FN не выполняет никаких действий, возвращая только значение MPI_SUCCESS. Специальное значение ключа MPI_KEYVAL_INVALID никогда не возвращается подпрограммой MPI_Keyval_create. Его можно использовать для инициализации ключей. Удаление ключа keyval int MPI_Keyval_free(int *keyval) MPI_KEYVAL_FREE(KEYVAL, IERR) Эта функция присваивает параметру keyval значение MPI_KEYVAL_INVALID. Используемый атрибут можно удалить, поскольку фактическое удаление происходит только после того, как будут удалены все ссылки на атрибут. Эти ссылки должны быть явным образом удалены программой, например, посредством вызова MPI_Attr_delete каждый такой вызов удаляет один экземпляр атрибута, либо вызовом MPI_Comm_free, который удаляет все экземпляры атрибута, связанные с удаляемым коммуникатором. Задание атрибута attribute, который в дальнейшем может использоваться подпрограммой MPI_Attr_get int MPI_Attr_put(MPI_Comm comm, int keyval, void* attribute) MPI_ATTR_PUT(COMM, KEYVAL, ATTRIBUTE, IERR) С атрибутом ассоциируется значение ключа keyval. Если значение атрибута уже задано, результат будет аналогичен ситуации, когда сначала для удаления предыдущего значения вызывается MPI_Attr_delete (и выполняется функция обратного вызова delete_fn), а затем сохраняется новое значение. Вызов завершится с ошибкой, если нет ключа со значением keyval. В частности, MPI_KEYVAL_INVALID ошибочное значение ключа. Не допускается изменение системных атрибутов MPI_TAG_UB, MPI_HOST, MPI_IO и MPI_WTIME_IS_GLOBAL. Если атрибут уже назначен, вызывается функция удаления, заданная при создании соответствующего ключа. Определение значения атрибута attribute, соответствующее значению ключа keyval int MPI_Attr_get(MPI_Comm comm, int keyval, void *attribute, int *flag) MPI_ATTR_GET(COMM, KEYVAL, ATTRIBUTE, FLAG, IERR) Первый параметр задает коммуникатор, с которым связан атрибут. Если ключа со значением keyval нет, возникает ошибка. Ошибки не возникает, если значение key существует, но соответствующий атрибут не присоединен к коммуникатору comm. В этом случае возвращается значение флага flag = false. Вызов MPI_Attr_put передает в параметре attribute_val значение атрибута, а вызов подпрограммы MPI_Attr_get передает в параметре attribute_val адрес, по которому возвращается значение атрибута. Атрибуты должны извлекаться из программ, написанных на тех же языках, на которых они задавались с помощью вызова подпрограммы MPI_Attr_put.
74
Удаление атрибута с указанным значением ключа int MPI_Attr_delete(MPI_Comm comm, int keyval) MPI_ATTR_DELETE(COMM, KEYVAL, IERR) Делается это с помощью функции удаления атрибута delete_fn, заданной при создании keyval. Параметр comm задает коммуникатор, с которым связан атрибут. Все аргументы данной подпрограммы входные. При любом дублировании коммуникатора с помощью подпрограммы MPI_Comm_dup вызываются все функции копирования для атрибутов, установленных в данный момент времени. Порядок вызова произволен. Аналогичные действия выполняются при удалении коммуникатора вызовом MPI_Comm_free, но вызываются все функции удаления.
Реализации MPI Существуют различные реализации MPI. Среди них MPICH (MPI CHameleon, www.mcs.anl.gov) – свободно распространяемая реализация MPI с открытым кодом; LAM (Local Area Multicomputer) – еще одна реализация MPI (www.lammpi.org); Microsoft® MPI и Intel® MPI и т. д. Существуют реализации MPI, ориентированные на работу в среде грид. Новое в спецификации MPI-2 Спецификация MPI-2 является расширением MPI-1 и содержит следующие новые возможности: • введены средства управления процессами, в частности, процедуры запуска новых процессов MPI_COMM_SPAWN и MPI_COMM_SPAWN_MULTIPLE из выполняющейся программы и другие; • добавлены средства программирования клиент-серверных приложений, взаимодействующих через заданный порт; • появился новый класс обменов — односторонние, когда процесс-источник сообщения устанавливает параметры обмена не только для себя, но и для процесса-получателя; • расширены возможности коллективных обменов, в том числе обменов с использованием интеркоммуникаторов; • добавлены операции параллельного ввода-вывода в файл; • введена возможность добавления новой функциональности «поверх » MPI — механизм внешних интерфейсов. Изменилась также архитектура системы, для запуска параллельных программ используется другая программа-стартер.
75
IV. Фортран 90
76
Для разработки параллельных программ часто используется язык программирования Фортран. Это одно из наиболее эффективных средств программирования вычислительных задач. Далее приводится краткое описание языка.
Формат записи исходного текста Для записи исходного текста программы на Фортране могут использоваться фиксированный и свободный форматы. Первый из них характерен для стандарта Фортран 77, второй применяется в Фортране 90 и более новых версиях. Фортран 90 поддерживает также фиксированный формат, что обеспечивает совместимость со старыми стандартами записи. При записи исходного текста в фиксированном формате строка содержит 72 позиции. Первые пять позиций отведены для меток, а шестая может быть пустой или содержать любой, отличный от пробела символ. В последнем случае строка считается строкой продолжения и при обработке компилятором присоединяется к предыдущей строке программы. Оператор может занимать позиции с 7 по 72. В свободном формате записи все позиции строки равноправны, ее длина составляет 132 символа.
Структура программы Программа на Фортране состоит из главной программы и, возможно, некоторого числа подпрограмм. Подпрограммы могут быть функциями или процедурами, внешними, внутренними или модульными. Различные программные компоненты могут компилироваться раздельно. Первым оператором главной программы является её заголовок PROGRAM. За ним следует имя программы: PROGRAM ИМЯ_ПРОГРАММЫ Имя программы обязательно начинается с буквы, затем могут идти буквы, цифры и символы подчеркивания, например: PROGRAM SUMMATION PROGRAM QUADRATIC_EQUATION_SOLVER45 Максимальная длина любого имени в программах на Фортране — 31 символ. Первым оператором подпрограммы может быть только ее заголовок FUNCTION или SUBROUTINE. Последней строкой программного компонента должна быть строка с оператором END. Заключительный оператор главной программы может иметь также следующий вид: END PROGRAM ИМЯ_ПРОГРАММЫ ИМЯ_ПРОГРАММЫ является необязательной частью оператора. После заголовка следуют описания переменных, констант, меток, подпрограмм и других объектов, используемых в программе. Эта ее часть называется разделом описаний. После раздела описаний следует раздел операторов.
Базовые типы данных Перечень встроенных типов в порядке возрастания их ранга дан ниже. • LOGICAL(1) и BYTE • LOGICAL(2) • LOGICAL(4)
77 • • • • • • •
INTEGER(1) INTEGER(2) INTEGER(4) REAL(4) REAL(8) COMPLEX(8) COMPLEX(16)
У каждого встроенного типа Фортрана есть несколько разновидностей, которые отличаются друг от друга диапазоном значений и некоторыми другими характеристиками. Значение символьного типа CHARACTER представляет собой строку символов. Длина строкового значения в Фортране может быть произвольной и задается с помощью параметра LEN в предложении описания строковой переменной, например: CHARACTER(LEN = 430) :: Shakespeare_sonet
Предложение описания Предложение описания переменных в Фортране 90 имеет вид: ТИП[, АТРИБУТЫ] :: СПИСОК_ПЕРЕМЕННЫХ В списке имена переменных разделяются запятыми, а ТИП задает общий тип переменных, являясь идентификатором типа: REAL, PARAMETER :: salary = 2000 В Фортране 90 используются следующие атрибуты объектов: • PARAMETER объект является именованной константой; •
PUBLIC объект является доступным за пределами модуля;
•
PRIVATE объект недоступен за пределами модуля;
•
POINTER объект является ссылкой (указателем);
•
TARGET объект можно использовать в качестве адресата в операторах назначения ссылок;
•
ALLOCATABLE объект является динамическим массивом;
•
DIMENSION объект является массивом;
•
INTENT определяет вид связи для параметра процедуры (т. е., является входным, выходным или и входным и выходным);
•
OPTIONAL необязательный параметр процедуры;
•
SAVE сохранять значение локальной переменной подпрограммы в промежутке между ее вызовами;
•
EXTERNAL для внешней функции;
•
INTRINSIC для внутренней функции.
Буквальные константы Буквальные числовые константы записываются обычным образом. Комплексная буквальная константа записывается в круглых скобках: • (0., 1.) соответствует мнимой единице i;
78 •
(2., 1.) соответствует комплексному числу 2 + i.
Имеются две буквальные логические константы: • .TRUE. — "истина"; •
.FALSE. — "ложь".
Буквальная символьная константа обрамляется апострофами, которые не входят в значение, а являются ограничителями значения. Вместо апострофов могут быть использованы кавычки.
Арифметические и логические операции Арифметические операции Фортрана (в порядке убывания приоритета): • ** — возведение в степень; •
*, / — умножение, деление;
• –, + — вычитание, сложение. Минус (–) и плюс (+) используются также как знаки унарных операций: В Фортране поддерживаются следующие операции отношения:
Знак операции
Альтернативное обозначение
Название операции сравнения
.LT.
<
меньше
.LE.
<=
меньше или равно
.GT.
>
больше
.GE.
>=
больше или равно
.EQ.
==
Равно
.NE.
/=
не равно
Для значений комплексного типа действительны только операции "равно" (.EQ.) и "не равно" (.NE.). Логические операции: Операция
Значение операции
.NOT.
Логическое отрицание
.AND.
Логическое пересечение (умножение, логическое "И")
.OR.
Логическое объединение (сложение, логическое "ИЛИ")
.EQV. .NEQV.
и Логические эквивалентность и неэквивалентность (логические равенство и неравенство)
Массивы Массивы описываются с помощью атрибута DIMENSION: REAL, DIMENSION(1:100) :: C
79
Параметром этого атрибута должен быть список экстентов (экстент – количество элементов массива для каждого измерения) описываемых массивов в виде (экстент_1, экстент_2, …, экстент_n) Число экстентов определяет ранг массива. Каждый экстент записывается в виде: [ нижняя_граница : ] верхняя_граница Пример: REAL, DIMENSION(0:10, 2, -3:3,11) :: FGRID Если значение нижней границы опущено, ее значение полагается равным 1. В некоторых случаях, список экстентов сводится к списку разделенных запятыми двоеточий, число которых равно рангу массива. Только число экстентов задается при объявлении динамического массива: REAL, ALLOCATABLE, DIMENSION(:, :) :: BE_LATER Динамический массив — это массив, размер которого определяется при выполнении программы, тогда же происходит и выделение памяти под него. Пример: PROGRAM dyn_array IMPLICIT NONE INTEGER SIZE REAL, ALLOCATABLE, DIMENSION(:) ::array WRITE(*, *) 'SIZE?' READ(*, *) SIZE IF(SIZE > 0) ALLOCATE(array(SIZE)) … IF(ALLOCATED(array)) DEALLOCATE(array) END PROGRAM dyn_array Оператор описания массива может не содержать атрибута DIMENSION, форма массива может быть указана непосредственно после его идентификатора: REAL X(10, 20, 30), Y(100), Z(2, 300, 2, 4)
Пример программы Решение нелинейного уравнения методом Ньютона PROGRAM NEWTON IMPLICIT NONE REAL(8) :: X, DX, F, DF X = 3.3 ! НАЧАЛЬНОЕ ПРИБЛИЖЕНИЕ DO ! НЬЮТОНОВСКИЕ ИТЕРАЦИИ DX = F(X) / DF(X) ! ВЫЧИСЛЕНИЕ ШАГА X = X – DX ! ВЫЧИСЛЕНИЕ ОЧЕРЕДНОГО ПРИБЛИЖЕНИЯ IF(DX <= SPACING(X)) EXIT ! ЦИКЛ ЗАВЕРШАЕТСЯ, КОГДА ! ШАГ МЕНЬШЕ РАССТОЯНИЯ МЕЖДУ ДВУМЯ ПОСЛЕДОВАТЕЛЬНЫМИ ! ВЕЩЕСТВЕННЫМИ ЗНАЧЕНИЯМИ END DO PRINT *, X ! ВЫВОД ЗНАЧЕНИЯ КОРНЯ PRINT *, F(X) ! ВЫВОД ЗНАЧЕНИЯ ФУНКЦИИ В ТОЧКЕ X PRINT *, DF(X) ! ВЫВОД ЗНАЧЕНИЯ ПРОИЗВОДНОЙ ! ФУНКЦИИ В ТОЧКЕ X
80
END PROGRAM NEWTON REAL(8) FUNCTION F(X) IMPLICIT NONE REAL(8) :: X F = SIN(X) RETURN END
Перечень операторов языка Ниже дается перечень некоторых конструкций и операторов Фортрана. Необязательные элементы заключены в квадратные скобки []. Если символ пробела не окружен квадратными скобками, то он является обязательным. Таблица 4. Операторы программных компонент Оператор
Описание
PROGRAM имя_программы
Оператор заголовка главной программы
MODULE имя_модуля
Оператор заголовка модуля
END[ MODULE[ имя_модуля]]
Оператор завершения модуля
USE имя_модуля[, only_список]
ONLY
Оператор подключения модуля
[RECURSIVE ]SUBROUTINE имя_подпрограммы [([список_формальных_параметров] )]
Оператор процедуры
[тип ][RECURSIVE ]FUNCTION имя_функции ([список_формальных_параметров]) [ RESULT(имя_результата)]
Оператор заголовка подпрограммы-функции
INTERFACE[ родовое_описание]
Оператор заголовка интерфейса
END[ ]INTERFACE
Оператор завершения интерфейса
CONTAINS
Оператор содержания
заголовка
подпрограммы-
ENTRY BLOCK[ ]DATA[ имя_блока_данных]
Оператор заголовка блока данных
END[ ]BLOCK[ имя_блока_данных]
Оператор завершения блока данных
]DATA[
81
Таблица 5. Операторы описания и инициализации данных Оператор
Описание
MODULE PROCEDURE список_имен_модульных_процедур
Оператор декларации модульных процедур
тип[[,атрибут][,атрибут…]….::]список_объектов где тип выбирается из списка: INTEGER[(KIND=]параметр_разновидности_типа)] REAL[(KIND=]параметр_разновидности_типа)] LOGICAL[(KIND=]параметр_разновидности_типа)] COMPLEX[(KIND=]параметр_разновидности_типа)] CHARACTER[список_параметров_типа] DOUBLE[ ]PRECISION] TYPE(имя_типа) и атрибуты — совместимая комбинация из следующих значений: PARAMETER, PUBLIC, PRIVATE, POINTER, TARGET, ALLOCATABLE, DIMENSION(список_экстентов), INTENT(параметр_входа/выхода), EXTERNAL, INTRINSIC, OPTIONAL, SAVE
Оператор описания
TYPE[, атрибут_доступа ::]имя_производного_типа атрибут_доступа — PUBLIC или PRIVATE
Оператор определения производного типа, заголовок
END[ ]TYPE[ имя_типа]
Оператор определения производного типа: завершение
IMPLICIT список, где список — это тип(список-букв)[, тип(список_букв)]… или NONE
Оператор правил типизации
ALLOCATABLE [::] имя_массива[(список_экстентов)][, имя_массива[(список_экстентов)]…]
Оператор назначения атрибута ALLOCATABLE
DIMENSION имя_массива(список_экстентов)[, имя_массива(список_экстентов)…]
Оператор спецификации массивов
PARAMETER (список_определений_именованных_констант)
Оператор определения именованных констант
EXTERNAL список_внешних_имен
Оператор назначения атрибута EXTERNAL
INTRINSIC список_встроенных_имен
Оператор назначения атрибута INTRINSIC
INTENT(параметр_входа/выхода) список_формальных_параметров
Оператор назначения атрибута INTENT
OPTIONAL список_формальных_параметров
Оператор назначения атрибута OPTIONAL
определения неявной
82
Таблица 5. (Продолжение) SAVE[[::]список_сохраняемых_объектов]
Оператор назначения атрибута SAVE
COMMON /[имя_общего_блока]/список_переменных[, /имя_общего_блока/список_переменных….]
Оператор описания блока общей памяти
DATA список_объектов/список_значений/[, список_объектов/список_значений/…]
Оператор инициализации объектов
FORMAT([список_дескрипторов])∗
Оператор спецификации формата преобразования данных при операциях ввода/вывода Таблица 6. Операторы передачи управления
Оператор
Описание
END[ PROGRAM[ имя_программы]]
Оператор завершения главной программы
END[ подпрограмма[ имя_подпрограммы]] подпрограмма — SUBROUTINE или FUNCTION
где
Оператор подпрограммы
завершения
CALL имя_подпрограммы[(список_фактических_парамет ров)]
Оператор передачи управления подпрограмме-процедуре
RETURN
Оператор возврата управления вызывающему компоненту программы
STOP[ сообщение]
Оператор останова программы Таблица 7. Операторы присваивания
Оператор
Описание
переменная = выражение
Оператор скалярных объектов
ссылка => адресат
Оператор прикрепления ссылки к адресату
присваивания для и массивоподобных
83
Таблица 8. Управляющие конструкции и условные операторы Оператор
Описание
IF(скалярное_логическое_выражение) исполняемый_оператор
Условный оператор
WHERE(логическое_выражение_массив) массив_переменная = выражение_массив
Условное массивов
[if_имя:] IF(скалярное_логическое_выражение) THEN ELSE[[ ]IF(скалярное_логическое_выражение) THEN[ if_имя] END[ ]IF[ if_имя]
Конструкция IF_THEN_ELSE
WHERE(логическое_выражение_массив) ELSEWHERE END[ ]WHERE
Конструкция ветвления присваивании массивов
[select_имя:] SELECT[ ]CASE (скалярное_выражение) CASE (список_возможных_значений)[ select_имя] CASE DEFAULT[ select_имя] END[ ]SELECT[ select_имя]
Конструкция SELECT
GO[ ]TO метка
Оператор перехода
[do_имя:] DO[ метка] переменная скалярное_целое_выражение1, скалярное_целое_выражение2[, скалярное_целое_выражение3] [do_имя:] DO[ метка] WHILE(скалярное_лог_выражение)
=
[,]
присваивание
для
ветвления
в
Заголовок оператора цикла
Заголовок оператора цикла альтернативной форме
в
CYCLE[ do_имя]
Оператор перехода к оператору завершения цикла с именем do_имя
EXIT[ do_имя]
Оператор выхода из цикла с именем do_имя
CONTINUE
Оператор перехода к следующему шагу цикла
END[ ]DO[ do_имя]
Оператор завершения цикла с именем do_имя Таблица 9. Операторы управления динамической памятью
Оператор
Описание
ALLOCATE(список_выделяемых_объектов[, STAT=статус])
Оператор выделения динамической памяти для выделяемых объектов
DEALLOCATE(список_выделенных_объектов[, STAT=статус])
Оператор освобождения динамической памяти от выделенных объектов
84
Таблица 10. Операторы ввода-вывода Оператор
Описание
READ(список_управления_вводом) [список_ввода] READ формат[, список_ввода]
Оператор чтения данных
WRITE(список_управления_выводом) [список_вывода]
Оператор записи данных
PRINT формат[, список_вывода]
Оператор вывода данных на устройство стандартного вывода
OPEN(список_спецификаций)
Оператор соединения файла с логическим устройством ввода-вывода
CLOSE(список_спецификаций)
Оператор закрытия файла Таблица 11. Встроенные подпрограммы
Имя подпрограммы
Возвращаемое значение или производимое действие
ABS(A)
Абсолютная величина
ACHAR(I)
I-ый символ сортирующей последовательности ASCII
ACOS(X)
Арккосинус в радианах
AIMAG(Z)
Мнимая часть комплексного числа
AINT(A[, KIND])
Усечение до целого
ALLOCATED(ARRAY)
Проверка выделенности динамического массива
ANINT(A [, KIND])
Ближайшее целое
ASIN(X)
Арксинус в радианах
ATAN(X)
Арктангенс в радианах
ATAN2(Y, X)
Аргумент комплексного числа
CALL DATE_AND_TIME([DATE][,TIME] [,ZONE] [,VALUES])
Считывание времени и даты с часов реального времени
CALL RANDOM_NUMBER(HARVEST)
Случайное число в интервале [0,1)
CALL RANDOM_SEED([SIZE] [,PUT][,GET])
Получение/назначение массива
CALL SYSTEM_CLOCK([COUNT] [,COUNT_RATE] [,COUNT_MAX])
Целочисленный отсчет часов реального времени
CEILING(A)
Наименьшее аргумента
CHAR(I [, KIND]) и ICHAR(C)
I-ый символ в сортирующей последовательности процессора
целое
затравочного
не
меньшее
85 CMPLX(X [, Y] [, KIND])
Конструктор комплексного числа
CONJG(Z)
Комплексное сопряжение
COS(X)
Косинус
COSH(X)
Гиперболический косинус
COUNT(MASK [, DIM])
Число элементов массива, имеющих значение "истина"
CSHIFT(ARRAY, SHIFT [, DIM])
Циклический сдвиг элементов массива
DIGITS(X)
Число значащих цифр представления аргумента
DOT_PRODUCT(VECTOR_A, VECTOR_B)
Скалярное произведение векторов
DPROD(X, Y)
Умножение с двойной точностью
EOSHIFT(ARRAY, SHIFT [, BOUNDARY] [, DIM])
Вытесняющий массива
EPSILON(X)
Наименьшее число в модели представления аргумента, не пренебрежимое по сравнению с единицей
EXP(X)
Экспонента
EXPONENT(X)
Степенная часть в представления аргумента
FLOOR(A)
Наименьшее целое, не превышающее аргумент
FRACTION(X)
Дробная часть в модели представления аргумента
HUGE(X)
Наибольшее число в представления аргумента
IACHAR(С)
Индекс символа-аргумента в сортирующей последовательности ASCII
IAND(I, J)
Побитовое логическое И
IBCLR(I, POS)
Установка нуля в заданном бите
IBITS(I, POS, LEN)
Извлечение цепочки битов
IBSET(I, POS)
Установка единицы в заданном бите
ICHAR(C)
Индекс символа-аргумента в сортирующей последовательности процессора
IEOR(I, J)
Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ
INDEX(STRING, SUBSTRING [,BACK])
Индекс начала подстроки в строке
INT(A [, KIND])
Приведение к целому типу
IOR(I, J)
Побитовое логическое ИЛИ
сдвиг
в
модели
элементов
модели
модели
86
Таблица 11. (Продолжение) ISHIFT(I, SHIFT)
Логический сдвиг битов
ISHIFTC(I, SHIFT [, SIZE])
Логический циклический сдвиг части битов вправо
KIND(X)
Разновидность типа аргумента
LBOUND(ARRAY [, DIM])
Нижняя граница массива
LEN(S)
Длина текстового аргумента
LEN_TRIM(STRING)
Длина строки без учета конечных пробелов
LOG(X)
Натуральный логарифм
LOG10(X)
Десятичный логарифм
LOGICAL(L [, KIND])
Приведение к заданной разновидности логического типа
MATMUL(MATRIX_A, MATRIX_B)
Перемножение матриц
MAX(A1, A2 [, A3, …..])
Выбор максимального аргумента
MAXLOC(ARRAY [, MASK])
Индекс наибольшего элемента массива
MAXVAL(ARRAY [, DIM ] [,MASK])
Наибольший элемент массива
MIN(A1, A2 [, A3, …..])
Выбор минимального аргумента
MINLOC(ARRAY [, MASK])
Индекс наименьшего элемента массива
MINVAL(ARRAY [, DIM ] [,MASK])
Наименьший элемент массива
MOD(A, P)
Остаток от деления по модулю
MODULO(A, P)
Деление по модулю
NINT(A [, KIND])
Ближайшее целое
NOT(I)
Побитовое логическое дополнение
PRECISION(X)
Десятичная точность представления аргумента
PRESENT(A)
Проверка присутствия необязательного аргумента
PRODUCT(ARRAY [, DIM] [,MASK])
Произведение элементов массива
REAL(A [, KIND])
Приведение к вещественному типу
RESHAPE(SOURCE, ORDER])
SHAPE
[,PAD]
[,
в
модели
Изменение формы массива
SCAN(STRING,SET[,BACK])
Индекс крайнего символа STRING в наборе SET
SELECTED_INT_KIND(R)
Параметр разновидности целого типа для заданного степенного диапазона
строки
87
Таблица 11. (Продолжение) SELECTED_REAL_KIND([P][,R])
Параметр разновидности вещественного типа для заданной точности и/или заданного степенного диапазона
SHAPE(SOURCE)
Форма аргумента
SIGN(A, B)
Абсолютное значение A со знаком B
SIN(X)
Синус
SINH(X)
Гиперболический синус
SIZE(ARRAY[,DIM])
Размер массива
SQRT(X)
Квадратный корень
SUM(ARRAY[,DIM][,MASK])
Сумма элементов массива
TAN(X)
Тангенс
TANH(X)
Гиперболический тангенс
TINY(X)
Наименьшее положительное число в модели представления аргумента
TRANSPOSE(MATRIX)
Транспонирование матрицы
UBOUND(ARRAY [, DIM])
Верхняя граница массива
88
V. Литература 1. С. Немнюгин, О. Стесик, Параллельное программирование для многопроцессорных вычислительных систем. "БХВ", Санкт-Петербург, 2002 г., 396 с. 2. В.В. Воеводин, Вл.В. Воеводин, Параллельные вычисления. "БХВ", Санкт-Петербург, 2002 г., 599 с. 3. С. Немнюгин, О. Стесик, Современный Фортран. Самоучитель. "БХВ", СанктПетербург, 2004 г., 481 с.