[ЗАСТАВКА]
[ЗАСТАВКА] Здравствуйте,
уважаемые слушатели.
Сегодня мы с вами продолжим знакомство с функциями библиотеки MPI.
Нужно отметить, что основным назначением библиотеки MPI
является организация взаимодействия между процессами.
И это взаимодействие происходит посредством приема и передачи сообщений.
Под сообщением MPI мы будем подразумевать набор данных некоторого типа,
передаваемый от одного процесса к другому.
Сообщение в MPI состоит из двух частей: сами данные (или передаваемая информация)
и служебные данные, необходимые для доставки данного сообщения адресату.
Поговорим сначала о передаваемых данных.
При работе с функциями библиотеки MPI вы будете часто сталкиваться с такими
понятиями, как буфер обмена, буфер приема или буфер передачи.
Буфер в MPI — это некоторая область памяти, в которое сообщение должно
быть принято или из которой сообщение должно быть отправлено.
Буфер однозначно определяется тремя параметрами: адрес начала буфера,
количество передаваемых элементов и тип передаваемых элементов.
На практике вы часто будете сталкиваться с необходимостью передачи элементов массива
определенного типа.
Давайте рассмотрим,
как осуществляется передача трех элементов целочисленного массива a.
Буфер при этом имеет следующий вид.
Это означает, что будет передано 3 элемента типа MPI_INT,
начиная с элемента a[0].
Данные при передаче выбираются из памяти последовательно,
поэтому будут переданы элементы a[0], a[1] и a[2].
Важно, что в качестве типа данных указывается базовый тип MPI.
Это делается из соображений переносимости кода,
чтобы исключить возможность появления ошибок,
связанных с различным представлением о типах данных на различных компьютерах.
Есть таблица соответствия между базовыми типами MPI и типами в языках
программирования Fortran и C.
С этими таблицами вы можете познакомиться в дополнительных материалах.
Мы чаще всего будем работать с типами MPI_INT и MPI_DOUBLE,
что соответствует типам int и double из языка C.
Вернемся к функциям передачи сообщений.
Для отправки сообщений используется функция MPI_Send.
Параметры вызова этой функции вы видите сейчас у себя на экране.
Первые 3 параметра описывают буфер отправки сообщений.
То есть buf — это адрес начала буфера отправки, count — это количество
передаваемых элементов, а type — это тип передаваемых элементов.
Следующие три параметра являются служебными.
Они определяют кому сообщение должно быть отправлено,
с каким идентификатором и в рамках какого коммуникатора.
Функция MPI_Send является блокирующей.
Это означает, что вызвавший ее процесс останавливается, пока функция выполнена.
Это гарантирует, что после выхода из функции,
мы можем повторно использовать буфер обмена, так как сообщение уже отправлено.
При этом блокировка не гарантирует, что сообщение уже доставлено получателю.
Сообщение может находиться в некотором промежуточном системном буфере.
Для приема сообщений используется функция MPI_Recv.
Параметры вызова этой функции вы видите сейчас на экране.
Первые три параметра здесь также определяют буфер приема сообщений.
Следующие три параметра являются служебными и определяют,
от процесса с каким номером, сообщение с каким идентификатором и в рамках какого
коммуникатора должно быть получено.
И последний параметр — это указатель на структуру Status,
в которую помещается служебная информация о принятом сообщении.
Функция MPI_Recv также является блокирующей,
а возврат из процедуры гарантирует,
что данные приняты и размещены в буфере приема, и мы их можем использовать.
При использовании функции MPI_Recv мы не обязательно должны указывать номер
процесса-отправителя.
В качестве этого номера можно указать константу MPI_ANY_SOURCE,
и тогда наша функция MPI_Recv будет принимать сообщения от любого процессора.
То же самое с идентификатором сообщения.
Мы можем принять сообщение с любым идентификатором,
использовав константу MPI_ANY_TAG.
Нужно отметить некоторую ассиметрию между функциями MPI_Send и MPI_Recv.
При отправке сообщения мы обязательно указываем, кому сообщение отправляется,
а при приеме сообщения мы можем принимать сообщения от любого процессора, с любым
идентификатором, но всегда в рамках какого-то конкретного коммуникатора.
Рассмотрим на примере, как работает передача данных.
Для этого составим следующую программу, которая с нулевого процесса передает часть
элементов массива a на первый процесс и полученные элементы размещает в массиве b.
Программу, выполняющую эту задачу, вы видите у себя на слайде.
Итак, что здесь происходит?
Сначала осуществляется инициализация MPI, потом с помощью функции MPI_Comm_rank
идет самоопределение процессов, то есть каждый процессор узнает свой номер.
Потом программа разбита на две секции: секция процессора с номером 0 и секция
процесса с номером 1.
Нулевой процессор передает часть элементов массива a первому процессу,
а первый процесс принимает несколько элементов от нулевого процесса
и размещает их в буфере приема, где в качестве буфера приема указан массив b.
Предлагаю вам самостоятельно доработать эту программу так,
чтобы перед отправкой массива a содержимое массива a как-то изменялось,
а при приеме сообщения первым процессом это сообщение выдавалось на экран.
Также нам нужно рассмотреть еще одну функцию — MPI_Wtime.
Эта функция не относится к базовым, но она нам очень необходима для
определения времени работы нашей программы.
Эта функция возвращает время в секундах с некоторого момента в прошлом.
Как использовать эту функцию для замера времени выполнения вашей программы,
вы видите сейчас на экране.
То есть мы перед вычислительной частью замеряем время,
потом замеряем время после вычислительной части,
и разница двух этих времен — это и будет время работы нашей программы.
Полученное время мы будем использовать для оценки эффективности и
ускорения работы нашей параллельной программы.
Ускорение работы параллельной программы — это отношение T1 к Tp,
где под T1 мы будем подразумевать время выполнения программы на одном процессоре,
а под Tp — время выполнения программы на p процессорах.
Таким образом, если ваша программа на 10-ти процессорах работает в 8 раз
быстрее, соответственно, ускорение вашей программы будет 8.
Еще один важный термин, который нужно отметить, это «идеальное ускорение».
Это когда при использовании p процессоров ваша программа работает в p раз быстрее.
Это ускорение еще называют линейным.
На практике вы будете часто сталкиваться с такой ситуацией, что при увеличении
количества используемых процессоров у вас ускорение просто перестанет расти.
Как правило, это будет обусловлено увеличением количества накладных расходов
на обмен и синхронизацию.
К этому надо просто быть готовыми.
Таким образом, мы познакомились с двумя функциями для обмена сообщениями —
MPI_Send и MPI_Recv и узнали,
как определить ускорение работы вашей параллельной программы.
А в рамках следующей лекции мы с вами поговорим о том,
как построить собственный параллельный алгоритм.
Спасибо за внимание и до новых встреч!