Технологией программирования для параллельных компьютеров с распределенной памятью. MPI-программа
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Основные понятия
Наиболее распространенной технологией программирования для параллельных компьютеров с
распределенной памятью в настоящее время является MPI. Основным способом взаимодействия параллельных
процессов в таких системах является передача сообщений друг другу. Это и отражено в названии данной
технологии - Message Passing Interface (интерфейс передачи сообщений). Стандарт MPI фиксирует интерфейс,
который должен соблюдаться как системой программирования на каждой вычислительной платформе, так и
пользователем при создании своих программ. Современные реализации, чаще всего, соответствуют стандарту
MPI версии 1.1. В 1997- 1998 годах появился стандарт MPI-2.0, значительно расширивший функциональность
предыдущей версии. Однако до сих пор этот вариант MPI не получил широкого распространения и в полном
объеме не реализован ни на одной системе. Везде далее, если иного не оговорено, мы будем иметь дело со
стандартом MPI-1.1.
MPI поддерживает работу с языками Фортран и Си. В данном пособии примеры и описания всех
процедур будут даны с использованием языка Фортран. Однако это совершенно не является принципиальным,
поскольку основные идеи MPI и правила оформления отдельных конструкций для этих языков во многом
схожи. Полная версия интерфейса содержит описание более 125 процедур и функций. Наша задача - объяснить
идею технологии и помочь освоить необходимые на практике компоненты. Дополнительную информацию об
интерфейсе MPI можно найти на тематической странице Информационно-аналитического центра по
параллельным вычислениям в сети Интернет http://parallel.ru/tech/tech_dev/mpi.html.
Интерфейс MPI поддерживает создание параллельных программ в стиле MIMD (Multiple Instruction
Multiple Data), что подразумевает объединение процессов с различными исходными текстами. Однако писать и
отлаживать такие программы очень сложно, поэтому на практике программисты гораздо чаще используют
SPMD-моделъ (Single Program Multiple Data) параллельного программирования, в рамках которой для всех
параллельных процессов используется один и тот же код. В настоящее время все больше и больше реализаций
MPI поддерживают работу с нитями.
Поскольку MPI является библиотекой, то при компиляции программы необходимо прилинковать
соответствующие библиотечные модули. Это можно сделать в командной строке или воспользоваться
предусмотренными в большинстве систем командами или скриптами mpicc (для программ на языке Си), mpicc
(для программ на языке Си++), и mpif 77/mpif 90 (для программ на языках Фортран 77/90). Опция компилятора
"-о name" позволяет задать имя name для получаемого выполнимого файла, по умолчанию
выполнимый файл yказывается на out, например:
mpif77 -o program program.f .
После получения выполнимого файла необходимо запустить его на требуемом количестве процессоров.
Для этого обычно предоставляется команда запуска MPI-приложений mpirun, например:
mpirun -np N <программа с аргументами>,
где N - число процессов, которое должно быть не более разрешенного в данной системе числа процессов
для одной задачи. После запуска одна и та же программа будет выполняться всеми запущенными процессами,
результат выполнения в зависимости от системы будет выдаваться на терминал или записываться в файл с
предопределенным именем.
Все дополнительные объекты: имена процедур, константы, предопределенные типы данных и т.п.,
используемые в MPI, имеют префикс MPI_. Если пользователь не будет использовать в программе имен с таким
префиксом, то конфликтов с объектами MPI заведомо не будет. В языке Си, кроме того, является существенным
регистр символов в названиях функций. Обычно в названиях функций MPI первая буква после префикса MPI_
пишется в верхнем регистре, последующие буквы - в нижнем регистре, а названия констант MPI записываются
целиком в верхнем регистре. Все описания интерфейса MPI собраны в файле mpif.h (mpi.h) , поэтому в начале
MPI-программы должна стоять директива include 'mpif.h' (ttinclude "mpi.h" для программ на языке
Си).
MPI-программа - это множество параллельных взаимодействующих процессов. Все процессы
порождаются один раз, образуя параллельную часть программы. В ходе выполнения MPI-программы
порождение дополнительных процессов или уничтожение существующих не допускается (в MPI-2.0 такая
возможность появилась). Каждый процесс работает в своем адресном пространстве, никаких общих
переменных или данных в MPI нет. Основным способом взаимодействия между процессами является явная
посылка сообщений.
Для локализации взаимодействия параллельных процессов программы можно создавать группы
процессов , предоставляя им отдельную среду для общения- коммуникатор . Состав образуемых групп
произволен. Группы могут полностью совпадать, входить одна в другую, не пересекаться или пересекаться
частично. Процессы могут взаимодействовать только внутри некоторого коммуникатора, сообщения,
отправленные в разных коммуникаторах, не пересекаются и не мешают друг другу. Коммуникаторы имеют в
языке Фортран тип INTEGER (В языке Си - предопределенный тип MPI_Comm) .
При старте программы всегда считается, что все порожденные процессы работают в рамках
всеобъемлющего коммуникатора, имеющего предопределенное имя MPI_COMM_WORLD. Этот коммуникатор
существует всегда и служит для взаимодействия всех запущенных процессов MPI-программы. Кроме него при
старте программы имеется коммуникатор MPI_COMM_SELF, содержащий только один текущий процесс, а
также коммуникатор MPI_COMM_NULL, не содержащий ни одного процесса. Все взаимодействия процессов
протекают в рамках определенного коммуникатора, сообщения, переданные в разных коммуникаторах, никак
не мешают друг другу.
Каждый процесс MPI-программы имеет в каждой группе, в которую он входит, уникальный атрибут
номер процесса , который является целым неотрицательным числом. С помощью этого атрибута происходит
значительная часть взаимодействия процессов между собой. Ясно, что в одном и том же коммуникаторе все
процессы имеют различные номера. Но поскольку процесс может одновременно входить в разные
коммуникаторы, то его номер в одном коммуникаторе может отличаться от его номера в другом. Отсюда
становятся понятными два основных атрибута процесса: коммуникатор и номер в коммуникаторе . Если группа
содержит п процессов, то номер любого процесса в данной группе лежит в пределах от 0 до п - 1.
Основным способом общения процессов между собой является явная посылка сообщений. Сообщение это набор данных некоторого типа. Каждое сообщение имеет несколько атрибутов , в частности, номер
процесса-отправителя, номер процесса-получателя, идентификатор сообщения и другие. Одним из важных
атрибутов сообщения является его идентификатор или тэг. По идентификатору процесс, принимающий
сообщение, например, может различить два сообщения, пришедшие к нему от одного и того же процесса. Сам
идентификатор сообщения является целым неотрицательным числом, лежащим в диапазоне от 0 до
MPI_TAG_UP, причем гарантируется, что MPI_TAG_UP не меньше 327 67. Для работы с атрибутами сообщений
введен массив (в языке Си - структура), элементы которого дают доступ к их значениям.
В последнем аргументе (в языке Си - в возвращаемом значении функции) большинство процедур MPI
возвращают информацию об успешности завершения. В случае успешного выполнения возвращается значение
MPI_SUCCESS, иначе - код ошибки. Вид ошибки, которая произошла при выполнении процедуры, можно будет
определить из ее описания. Предопределенные значения, соответствующие различным ошибочным
ситуациям, перечислены в файле mpif .h. .
Общие процедуры MPI
В данном разделе мы остановимся на общих процедурах MPI, не связанных с пересылкой данных.
Большинство процедур этого раздела необходимы практически в каждой содержательной параллельной
программе.
MPI_INIT(IERR) INTEGER IERR
Инициализация параллельной части программы. Все другие процедуры MPI могут быть вызваны только
после вызова MPI_INIT. Инициализация параллельной части для каждого приложения должна выполняться
только один раз. В языке Си функции MPi_init передаются указатели на аргументы командной строки
программы argc и argv, из которых системой могут извлекаться и передаваться в параллельные процессы
некоторые параметры запуска программы.
MPI_FINALIZE(IERR) INTEGER IERR
Завершение параллельной части приложения. Все последующие обращения к любым процедурам MPI, в
том числе к MPI_INIT, запрещены. К моменту вызова MPI_FINALIZE каждым процессом программы все действия,
требующие его участия в обмене сообщениями, должны быть завершены.
Пример простейшей MPI-программы на языке Фортран выглядит следующим образом:
program example1
include 'mpif.h'
integer ierr
print *, 'Before MPI_INIT'
call MPI_INIT(ierr)
print *, 'Parallel section'
call MPI_FINALIZE(ierr)
print *, 'After MPI_FINALIZE'
end
В зависимости от реализации MPI строчки 'Before MPI_INIT' И 'After MPI_FINALIZE' может печатать либо
один выделенный процесс, либо все запущенные процессы приложения. Строчку 'Parallel section' должны
напечатать все процессы. Порядок вывода строк с разных процессов может быть произвольным.
Общая схема MPI-программы на языке Си выглядит примерно следующим образом:
#include "mpi.h"
main(int argc, char **argv)
MPI_Init(&argc, &argv); MPI_Finalize() ;
Другие параллельные программы на языке Си с использованием технологии MPI можно найти,
например, в Вычислительном полигоне: http://polygon.parallel.ru.
MPI_INITIALIZED(FLAG, IERR) LOGICAL FLAG INTEGER IERR
Процедура возвращает в аргументе FLAG значение .TRUE. , если вызвана из параллельной части
приложения, и значение .FALSE. - в противном случае. Это единственная процедура MPI, которую можно
вызвать до вызова
MPI_INIT.
MPI_COMM_SIZE(COMM, SIZE, IERR) INTEGER COMM, SIZE, IERR
В аргументе SIZE процедура возвращает число параллельных процессов в коммуникаторе сомм.
MPI_COMM_RANK(COMM, RANK, IERR) INTEGER COMM, RANK, IERR
В аргументе RANK процедура возвращает номер процесса в коммуникаторе сомм. Если процедура
MPI_COMM_SIZE ДЛЯ ТОГО же коммуникатора сомм вернула значение SIZE, то значение, возвращаемое
процедурой MPI_COMM_RANK через переменную RANK, лежит в диапазоне от о до SIZE-I.
В следующем примере каждый запущенный процесс печатает свой уникальный номер в коммуникаторе
MPI_COMM_WORLD И ЧИСЛО процессов в данном коммуникаторе.
program example2
include 'mpif.h'
integer ierr, size, rank
call MPI_INIT(ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
print *, 'process ', rank, ', size ', size
call MPI_FINALIZE(ierr)
end
Строка, соответствующая вызову процедуры print, будет выведена столько раз, сколько процессов было
порождено при запуске программы. Порядок появления строк заранее не определен и может быть, вообще
говоря, любым. Гарантируется только то, что содержимое отдельных строк не будет перемешано друг с другом.
DOUBLE PRECISION MPI_WTIME(IERR) INTEGER IERR
Эта функция возвращает на вызвавшем процессе астрономическое время в секундах (вещественное
число двойной точности), прошедшее с некоторого момента в прошлом. Если некоторый участок программы
окружить вызовами данной функции, то разность возвращаемых значений покажет время работы данного
участка. Гарантируется, что момент времени, используемый в качестве точки отсчета, не будет изменен за
время существования процесса. Заметим, что эта функция возвращает результат своей работы не через
параметры, а явным образом. Таймеры разных процессоров могут быть не синхронизированы и выдавать
различные значения, это можно определить по значению параметра MPI_WTIME_IS_GLOBAL (l синхронизированы, о - нет).
DOUBLE PRECISION MPI_WTICK(IERR) INTEGER IERR
Функция возвращает разрешение таймера на вызвавшем процессе в секундах. Эта функция также
возвращает результат своей работы не через параметры, а явным образом.
MPI_GET_PROCESSOR_NAME(NAME, LEN, IERR) CHARACTER*(*) NAME INTEGER LEN, IERR
Процедура возвращает в строке NAME имя узла, на котором запущен вызвавший процесс. В переменной
LEN возвращается количество символов в имени, не превышающее значения константы
MPI_MAX_PROCESSOR_NAME. С помощью этой процедуры можно определить, на какие именно физические
процессоры были спланированы процессы МР1-приложения.
В следующей программе на каждом процессе определяются две характеристики системного таймера: его
разрешение и время, требуемое на замер времени (для усреднения получаемого значения выполняется
NTIMES замеров). Также в данном примере показано использование процедуры
MPI GET PROCESSOR NAME.
program example3
include 'mpif.h'
integer ierr, rank, len, i, NTIMES
parameter (NTIMES = 100)
character*(MPI_MAX_PROCESSOR_NAME) name
double precision time_start, time_finish, tick
call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
call MPI_GET_PROCESSOR_NAME(name, len, ierr)
tick = MPI_WTICK(ierr)
time_start = MPI_WTIME(ierr)
do i = 1, NTIMES
time_finish = MPI_WTIME(ierr)
end do
call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierr)
print *, 'processor ', name(1: len), &', process ', rank, ': tick = ', tick, &', time = ', (time_finish-time_start)/NTIMES
call MPI_FINALIZE(ierr)
end
Справочные функции для языка программирования C++, необходимые для выполнения лабораторных и
контрольной работ имеются в приложениях файла лабораторных работ. Выполнять лабораторные работы
можно на любом языке программирования, поддерживающим интерфейс MPI.