Введение в технологии параллельного программирования (MPI). Подключение MPI в Visual Studio

Функции MPI

Производный тип, Операции, Типы данных

Bsend Buffer_attach Get_count ANY_SOURCE Sendrecv_replace ANY_TAG Probe

Allgetherv Alltoall Alltoallv Reduce Rduce_scatter Scan

Производный тип строится из предопределенных типов MPI и ранее определенных производных типов с помощью специальных функций-конструкторов

MPI_Type_contiguous, MPI_Type_vector, MPI_Type_hvector, MPI_Type_indexed, MPI_Type_hindexed, MPI_Type_struct.

Новый производный тип регистрируется вызовом функции MPI_Type_commit. Только после регистрации новый производный тип можно использовать в коммуникационных подпрограммах и при конструировании других типов. Предопределенные типы MPI считаются зарегистрированными.

Когда производный тип становится ненужным, он уничтожается функцией MPI_Type_free.

1) MPI_Init - функция инициализации. В результате выполнения этой функции создается группа процессов, в которую помещаются все процессы приложения, и создается область связи, описываемая предопределенным коммуникатором MPI_COMM_WORLD.

MPI_Type_commit - регистрация типа, MPI_Type_free – уничтожение типа

int MPI_Init(int *argc, char ***argv);

2) MPI_Finalize - Функция завершения MPI программ. Функция закрывает все MPI-процессы и ликвидирует все области связи.

int MPI_Finalize(void);

3) Функция определения числа процессов в области связи MPI_Comm_size . Функция возвращает количество процессов в области связи коммуникатора comm.

int MPI_Comm_size(MPI_Comm comm, int *size);

4) Функция определения номера процесса MPI_Comm_rank . Функция возвращает номер процесса, вызвавшего эту функцию. Номера процессов лежат в диапазоне 0..size-1.

int MPI_Comm_rank(MPI_Comm comm, int *rank);

5) Функция передачи сообщения MPI_Send . Функция выполняет посылку count элементов типа datatype сообщения с идентификатором tag процессу dest в области связи коммуникатора comm.

int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm);

6) Функция приема сообщения MPI_Recv . Функция выполняет прием count элементов типа datatype сообщения с идентификатором tag от процесса source в области связи коммуникатора comm.

int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

7) Функция отсчета времени (таймер)MPI_Wtime . Функция возвращает астрономическое время в секундах, прошедшее с некоторого момента в прошлом (точки отсчета).

double MPI_Wtime(void)

Функции передачи сообщений между процессами делятся на:

Префикс S (synchronous)

означает синхронный режим передачи данных. Операция передачи данных заканчивается только тогда, когда заканчивается прием данных. Функция нелокальная.

Префикс B (buffered)

означает буферизованный режим передачи данных. В адресном пространстве передающего процесса с помощью специальной функции создается буфер обмена, который используется в операциях обмена. Операция посылки заканчивается, когда данные помещены в этот буфер. Функция имеет локальный характер.

Префикс R (ready)

согласованный или подготовленный режим передачи данных. Операция передачи данных начинается только тогда, когда принимающий процессор выставил признак готовности приема данных, инициировав операцию приема. Функция нелокальная.

Префикс I (immediate)

относится к неблокирующим операциям.

Структура MPI_Status

После чтения сообщения некоторые параметры могут оказаться неизвестными, а именно: число считанных элементов, идентификатор сообщения и адрес отправителя. Эту информацию можно получить с помощью параметра status. Переменные status должны быть явно объявлены в MPI-программе. В языке C status - это структура типа MPI_Status с тремя полями MPI_SOURCE, MPI_TAG, MPI_ERROR.

8) Для определения числа фактически полученных элементов сообщения необходимо использовать специальную функцию MPI_Get_count .

int MPI_Get_count (MPI_Status *status, MPI_Datatype datatype, int *count);

9) Определить параметры полученного сообщения без его чтения можно с помощью функцииMPI_Probe . int MPI_Probe (int source, int tag, MPI_Comm comm, MPI_Status *status);

10) В ситуациях, когда требуется выполнить взаимный обмен данными между процессами, безопаснее использовать совмещенную операцию MPI_Sendrecv . В данной операции посылаемые данные из массива buf замещаются принимаемыми данными.

int MPI_Sendrecv(void *sendbuf, int sendcount, MPI_Datatype sendtype, int dest, int sendtag, void *recvbuf, int recvcount, MPI_Datatype recvtype, int source, MPI_Datatypeа recvtag, MPI_Comm comm, MPI_Status *status);

11) Функция проверки завершения неблокирующей операцииMPI_Test .

int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status);

Это локальная неблокирующая операция. Если связанная с запросом request операция завершена, возвращается flag = true, а status содержит информацию о завершенной операции. Если проверяемая операция не завершена, возвращается flag = false, а значение status в этом случае не определено.

12) Функция снятия запроса без ожидания завершения неблокирующей операцииMPI_Request_free .

int MPI_Request_free(MPI_Request *request);

Параметр request устанавливается в значение MPI_REQUEST_NULL.

13) Достижение эффективного выполнения операции передачи данных от одного процесса всем процессам программы(широковещательная рассылка данных) может быть обеспечено при помощи функции MPI:

int MPI_Bcast(void *buf,int count,MPI_Datatype type,int root,MPI_Comm comm)

Функция MPI_Bcast осуществляет рассылку данных из буфера buf, содержащего count элементов типа type с процесса, имеющего номер root, всем процессам, входящим в коммуникатор comm.

14) При необходимости приема сообщения от любого процесса-отправителя для параметра source может быть указано значениеMPI_ANY_SOURCE

15) При необходимости приема сообщения с любым тегом для параметра tag может быть указано значение MPI_ANY_TAG

16) Параметр status позволяет определить ряд характеристик принятого сообщения:

- status.MPI_SOURCE – ранг процесса-отправителя принятого сообщения,

- status.MPI_TAG - тег принятого сообщения.

17) Функция

MPI_Get_coun t(MPI_Status *status, MPI_Datatype type, int *count)

возвращает в переменной count количество элементов типа type в принятом сообщении.

18) Операции передачи данных от всех процессов одному процессу. В этой операции над собираемыми

значениями осуществляется та или иная обработка данных(для подчеркивания последнего момента данная операция еще именуется операцией редукции данных)

int MPI_Reduce (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,int root,MPI_Comm comm)

19) Синхронизация процессов, т.е. одновременное достижение процессами тех или иных точек процесса вычислений, обеспечивается при помощи функцииMPI: int MPI_Barrier (MPI_Comm comm); Функция MPI_Barrier определяет коллективную операции и, тем самым, при использовании должна вызываться всеми процессами используемого коммун+икатора. При вызове функции MPI_Barrier

выполнение процесса блокируется, продолжение вычислений процесса произойдет только после вызова функции MPI_Barrier всеми процессами коммуникатора.

20) Для использования буферизованного режима передачи должен быть создан и переданMPI буфер памяти

для буферизации сообщений– используемая для этого функция имеет вид: int MPI_Buffer_attach (void *buf, int size),

- bufбуфер памяти для буферизации сообщений,

- size– размер буфера.

21) После завершения работы с буфером он должен быть отключен отMPI при помощи функции:

int MPI_Buffer_detach (void *buf, int *size).

22) Достижение эффективного и гарантированного одновременного выполнения операций передачи и приема данных может быть обеспечено при помощи функцииMPI:

int MPI_Sendrecv (void *sbuf,int scount,MPI_Datatype stype,int dest, int stag, void *rbuf,int rcount,MPI_Datatype

rtype,int source,int rtag, MPI_Comm comm, MPI_Status *status)

23) Когда сообщения имеют одинаковый тип, в MPI имеется возможность использования единого буфера: intMPI_Sendrecv_replace (void *buf, int count, MPI_Datatype type, int dest,

int stag, int source, int rtag, MPI_Comm comm, MPI_Status* status)

24) Обобщенная операция передачи данных от одного процесса всем процессам(распределение данных) отличается от широковещательной рассылки тем, что процесс передает процессам различающиеся данные (см. рис. 4.4). Выполнение данной операции может быть обеспечено при помощи функции:

int MPI_Scatter (void *sbuf,int scount,MPI_Datatype stype,

25) Операция обобщенной передачи данных от всех процессоров одному процессу(сбор данных) является обратной к процедуре распределения данных(см. рис. 4.5). Для выполнения этой операции вMPI предназначена функция:

int MPI_Gather (void *sbuf,int scount,MPI_Datatype stype,

void *rbuf,int rcount,MPI_Datatype rtype, int root, MPI_Comm comm)

26) Следует отметить, что при использовании функции MPI_Gather сборка данных осуществляется только

на одном процессе. Для получения всех собираемых данных на каждом из процессов коммуникатора

необходимо использовать функцию сбора и рассылки:

int MPI_Allgather (void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, MPI_Comm comm)

27) Передача данных от всех процессов всем процессам является наиболее общей операцией передачи данных(см. рис. 4.6). Выполнение данной операции может быть обеспечено при помощи функции:

int MPI_Alltoall (void *sbuf,int scount,MPI_Datatype stype, void *rbuf,int rcount,MPI_Datatype rtype,MPI_Comm comm)

28) Функция MPI_Reduce обеспечивает получение результатов редукции данных

только на одном процессе. Для получения результатов редукции данных на каждом из процессов коммуникатора необходимо использовать функцию редукции и рассылки:

int MPI_Allreduce (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,MPI_Comm comm).

29) И еще один вариант операции сбора и обработки данных, при котором обеспечивается получение и всех частичных результатов редуцирования, может быть получен при помощи функции:

int MPI_Scan (void *sendbuf, void *recvbuf,int count,MPI_Datatype type, MPI_Op op,MPI_Comm comm).

Общая схема выполнения функции MPI_Scan показана на рис. 4.7. Элементы получаемых сообщений представляют собой результаты обработки соответствующих элементов передаваемых процессами сообщений, при этом для получения результатов на процессе с рангом i, 0≤i

30) Начальное значение переменной bufpos должно быть сформировано до начала упаковки и далее устанавливается функцией MPI_Pack . Вызов функции MPI_Pack осуществляется последовательно для упаковки всех необходимых данных.

int MPI_Pack_size (int count, MPI_Datatype type, MPI_Comm comm, int *size)

31) После упаковки всех необходимых данных подготовленный буфер может быть использован в функциях передачи данных с указанием типа MPI_PACKED.

После получения сообщения с типом MPI_PACKED данные могут быть распакованы при помощи функции:

int MPI_Unpack (void *buf, int bufsize, int *bufpos, void *data, int count, MPI_Datatype type, MPI_Comm comm)

Complex Instruction Set Computer

CISC (англ. Complex instruction set computing, или англ. complex instruction set computer -

компьютер с полным набором команд) - концепция проектирования процессоров, которая характеризуется следующим набором свойств:

сравнительно небольшое число регистров общего назначения;

· большое количество машинных команд, некоторые из которых нагружены семантически аналогично операторам высокоуровневых языков программирования и выполняются за много тактов;

· большое количество методов адресации;

· большое количество форматов команд различной разрядности;

· преобладание двухадресного формата команд;

· наличие команд обработки типа регистр-память.

Недостатки :

высокая стоимость аппаратной части; сложности с распараллеливанием вычислений.

Методика построения системы команд CISC противоположна другой методике - RISC. Различие этих концепций состоит в методах программирования, а не в реальной архитектуре процессора. Практически все современные процессоры эмулируют наборы команд как RISC-, так и CISC-типа.

Reduced Instruction Set Computer

В ее основе лежат принципы RISC-архитектуры: фиксированный формат команд, регистровые операции, однотактовое выполнение команд, простые способы адресации, большой регистровый файл. В то же время имеется несколько существенных особенностей, отличающих данную архитектуру от архитектур других RISC-процессоров. К ним относятся: независимый набор регистров для каждого из исполнительных устройств; включение в систему отдельных CISC-подобных инструкций; отсутствие механизма “задержанных переходов”; оригинальный способ реализации условных переходов. Основной областью применения микропроцессоров с архитектурой являются высокопроизводительные серверы и суперкомпьютеры.

Такие компьютеры основаны на архитектуре, отделяющей команды обработки от команд работы с памятью, и делали упор на эффективную конвейерную обработку. Система команд разрабатывалась таким образом, чтобы выполнение любой команды занимало небольшое количество машинных тактов (предпочтительно один машинный такт). Сама логика выполнения команд с целью повышения производительности ориентировалась на аппаратную, а не на микропрограммную реализацию. Чтобы упростить логику декодирования команд использовались команды фиксированной длины

и фиксированного формата.

В чем смысл технологии буфера целевых адресов переходов

В процессоре предусмотрен механизм динамического прогнозирования направления переходов. С этой

целью на кристалле размещена небольшая кэш-память, которая называется буфером целевых адресов переходов (BTB), и две независимые пары буферов предварительной выборки команд (по два 32битовых буфера на каждый конвейер). Буфер целевых адресов переходов хранит адреса команд, которые находятся в буферах предварительной выборки. Работа буферов предварительной выборки организована таким образом, что в каждый момент времени осуществляется выборка команд только в один из буферов соответствующей пары. При обнаружении в потоке команд операции перехода вычисленный адрес перехода сравнивается с адресами, хранящимися в буфере BTB. В случае совпадения предсказывается, что переход будет выполнен, и разрешается работа другого буфера предварительной выборки, который начинает выдавать команды для выполнения в соответствующий конвейер. При несовпадении считается, что переход выполняться не будет и буфер предварительной выборки не переключается, продолжая обычный порядок выдачи команд. Это позволяет избежать простоев конвейеров

Структурные конфликты и способы их минимизации

Совмещенный режим выполнения команд в общем случае требует конвейеризации функциональных устройств и дублирования ресурсов для разрешения всех возможных комбинаций команд в конвейере. Если какая-нибудь комбинация команд не может

быть принята из-за конфликта по ресурсам, то говорят, что в машине имеется структурный конфликт. Наиболее типичным примером машин, в которых возможно появление структурных конфликтов, являются машины с не полностью конвейерными функциональными устройствами.

Минимизация: конвейер приостанавливает выполнение одной из команд до тех пор, пока не станет доступным требуемое устройство.

Конфликты по данным, остановы конвейера и реализация механизма обходов

Одним из факторов, который оказывает существенное влияние на производительность конвейерных систем, являются межкомандные логические зависимости. Конфликты по данным возникают в том случае, когда применение конвейерной обработки может изменить порядок обращений за операндами так, что этот порядок будет отличаться от порядка, который наблюдается при последовательном выполнении команд на неконвейерной машине. Проблема, поставленная в этом примере, может быть разрешена с помощью достаточно простой аппаратной техники, которая называется пересылкой или продвижением данных (data forwarding), обходом (data bypassing), иногда закороткой (short-circuiting).

Конфликты по данным, приводящие к приостановке конвейера

Вместо этого нам нужна дополнительная аппаратура, называемая аппаратурой внутренних блокировок конвейера (pipeline interlook), чтобы обеспечить корректное выполнение примера. Вообще такого рода аппаратура обнаруживает конфликты и приостанавливает конвейер до тех пор, пока существует конфликт. В этом случае эта аппаратура приостанавливает конвейер начиная с команды, которая хочет использовать данные в то время, когда предыдущая команда, результат которой является операндом для нашей, вырабатывает этот результат. Эта аппаратура вызывает приостановку конвейера или появление "пузыря" точно также, как и в случае структурных конфликтов.

Буфера прогнозирования условных переходов

Буфер прогнозирования условных переходов представляет собой небольшую память, адресуемую с помощью младших разрядов адреса команды перехода. Каждая ячейка этой памяти содержит один бит, который говорит о том, был ли предыдущий переход выполняемым или нет. Это простейший вид такого рода буфера. В нем отсутствуют теги, и он оказывается полезным только для сокращения задержки перехода в случае, если эта задержка больше, чем время, необходимое для вычисления значения целевого адреса перехода. Буфер прогнозирования переходов может быть реализован в виде небольшой специальной кэш-памяти, доступ к которой осуществляется с помощью адреса команды во время стадии выборки команды в конвейере (IF), или как пара битов, связанных с каждым блоком кэшпамяти команд и выбираемых с каждой командой.

Запуск MPI-приложения на вычислительном кластере возможен только через систему пакетной обработки заданий. Для упрощения запуска и постановки в очередь параллельной программы предусмотрен специальный скрипт mpirun. Например, mpirun -np 20 ./first.exe запустит параллельную программу first.exe на 20 процессорах, т.е. на 5 узлах. (Каждый узел имеет 2 двуядерных процессора). Стоит обратить внимание, что для запуска исполняемого модуля находящего в текущей директории ($pwd) необходимо явно указать путь «./» Ряд реализаций MPI-1 предоставляет команду запуска для программ MPI, которая имеет форму mpirun <аргументы mpirun><программа><аргументы программы>

Отделение команды запуска программы от самой программы обеспечивает гибкость, особенно для сетевых и гетерогенных реализаций. Наличие стандартного механизма запуска также расширяет мобильность MPI программ на один шаг вперед, к командным строкам и сценариям, которые управляют ими. Например, сценарий набора программ проверки правильности, который выполняет сотни программ, может быть переносимым сценарием, если он написан с использованием такого стандартного механизма запуска. Чтобы не перепутать ``стандартную"" команду с существующей на практике, которая не является стандартной и не переносимой среди реализаций, вместо mpirun MPI определил mpiexec.

В то время как стандартизированный механизм запуска улучшает применимость MPI, диапазон сред настолько разнообразен (например, не может даже быть интерфейса командной строки), что MPI не может принять под мандат такой механизм. Вместо этого, MPI определяет команду запуска mpiexec и рекомендует, но не требует, как совет разработчикам. Однако, если реализация обеспечивает команду называемую mpiexec, она должна иметь форму, описанную ниже: mpiexec -n <программа>

будет по крайней мере один способ запустить <программу> с начальным MPI_COMM_WORLD, чья группа содержит процессов. Другие аргументы mpiexec могут зависеть от реализации.

Пример 4.1 Запуск 16 экземпляров myprog на текущей или заданной по умолчанию машине:

mpiexec -n 16 myprog

3. Напишите программу параллельного вычисления определенного интеграла от функции 2*(x+2*x*x/1200.0) в интервале .

Метод левых прямоугольников

double f(double x)

{return 2*(x+2*x*x/1200);} // iskomyi integral

int main(int argc,char **argv)

MPI_Status status;

MPI_Init(&argc,&argv);

MPI_Comm_rank(MPI_COMM_WORLD,&rank);

MPI_Comm_size(MPI_COMM_WORLD,&size);

int n=1000,i,d; // 1000 - uzly

float a=0, b=1, h=(b-a)/n,s=0,r=0; //a i b -nachalo i konec otrezka

if (rank!=size-1) // schitaut vse processy, krome poslednego

{ for (i=rank*d; i<(rank+1)*d; i++) { s=s+h*f(a+i*h); }

MPI_Send(&s,1,MPI_FLOAT,size-1,1,MPI_COMM_WORLD);}

{ for (i=0; i

{ MPI_Recv(&s,1,MPI_FLOAT,i,1,MPI_COMM_WORLD, &status); r+=s; } }

MPI_Finalize();}

Сурак

1. Shared & distributed memory архитектуралары.

Распределенная общая память (DSM - Distributed Shared Memory)

Традиционно распределенные вычисления базируются на модели передачи сообщений, в которой данные передаются от процессора к процессору в виде сообщений. Удаленный вызов процедур фактически является той же самой моделью (или очень близкой). DSM - виртуальное адресное пространство, разделяемое всеми узлами (процессорами) распределенной системы. Программы получают доступ к данным в DSM примерно так же, как они работают с данными в виртуальной памяти традиционных ЭВМ. В системах с DSM данные перемещаются между локальными памятями разных компьютеров аналогично тому, как они перемещаются между оперативной и внешней памятью одного компьютера. Конфигурация - с распределенной разделяемой памятью, представляет собой вариант распределенной памяти. Здесь все узлы, состоящие из одного или нескольких процессоров, подключенных по схеме SMP, используют общее адресное пространство. Отличие этой конфигурации от машины с распределенной памятью в том, что здесь любой процессор может обратиться к любому участку памяти. Однако, время обращения к разным участкам памяти для каждого процессора различно в зависимости от того, где участок физически расположен в кластере. По этой причине такие конфигурации еще называют машинами с неоднородным доступом к памяти NUMA (non-uniform memory access).

Отличия MPI и PVM.

Система PVM (Parallel Virtual Machine) была создана для объединения нескольких связанных сетью рабочих станций в единую виртуальную параллельную вычислительную машину. Система представляет собой надстройку над операционной системой UNIX и используется на различных аппаратных платформах, включая и системы с массовым параллелизмом. Наиболее распространены сейчас системы параллельного программирования на основе MPI (Message Parsing Interface). Идея MPI исходно проста и очевидна. Она предполагает представление параллельной программы в виде множества параллельно исполняющихся процессов, взаимодействующих друг с другом в ходе исполнения передачи данных с помощью коммуникационных процедур. Они и составляют библиотеку MPI. Однако надлежащая реализация MPI для обеспечения межпроцессорных коммуникаций оказалась довольно сложной. Такая сложность связана с необходимостью достижения высокой производительности программ, необходимостью использовать многочисленные ресурсы мультикомпьютера, и, как следствие большим разнообразием в реализации коммуникационных процедур в зависимости от режима обработки данных.

  • Tutorial

В этом посте мы расскажем об организации обмена данными с помощью MPI на примере библиотеки Intel MPI Library. Думаем, что эта информация будет интересна любому, кто хочет познакомиться с областью параллельных высокопроизводительных вычислений на практике.

Мы приведем краткое описание того, как организован обмен данными в параллельных приложениях на основе MPI, а также ссылки на внешние источники с более подробным описанием. В практической части вы найдете описание всех этапов разработки демонстрационного MPI-приложения «Hello World», начиная с настройки необходимого окружения и заканчивая запуском самой программы.

MPI (Message Passing Interface)

MPI - интерфейс передачи сообщений между процессами, выполняющими одну задачу. Он предназначен, в первую очередь, для систем с распределенной памятью (MPP) в отличие от, например, OpenMP . Распределенная (кластерная) система, как правило, представляет собой набор вычислительных узлов, соединенных высокопроизводительными каналами связи (например, InfiniBand).

MPI является наиболее распространенным стандартом интерфейса передачи данных в параллельном программировании. Стандартизацией MPI занимается MPI Forum . Существуют реализации MPI под большинство современных платформ, операционных систем и языков. MPI широко применяется при решении различных задач вычислительной физики, фармацевтики, материаловедения, генетики и других областей знаний.

Параллельная программа с точки зрения MPI - это набор процессов, запущенных на разных вычислительных узлах. Каждый процесс порождается на основе одного и того же программного кода.

Основная операция в MPI - это передача сообщений. В MPI реализованы практически все основные коммуникационные шаблоны: двухточечные (point-to-point), коллективные (collective) и односторонние (one-sided).

Работа с MPI

Рассмотрим на живом примере, как устроена типичная MPI-программа. В качестве демонстрационного приложения возьмем исходный код примера, поставляемого с библиотекой Intel MPI Library. Прежде чем запустить нашу первую MPI-программу, необходимо подготовить и настроить рабочую среду для экспериментов.

Настройка кластерного окружения

Для экспериментов нам понадобится пара вычислительный узлов (желательно со схожими характеристиками). Если под руками нет двух серверов, всегда можно воспользоваться cloud-сервисами.

Для демонстрации я выбрал сервис Amazon Elastic Compute Cloud (Amazon EC2). Новым пользователям Amazon предоставляет пробный год бесплатного использования серверами начального уровня.

Работа с Amazon EC2 интуитивно понятна. В случае возникновения вопросов, можно обратиться к подробной документации (на англ.). При желании можно использовать любой другой аналогичный сервис.

Создаем два рабочих виртуальных сервера. В консоли управления выбираем EC2 Virtual Servers in the Cloud , затем Launch Instance (под «Instance» подразумевается экземпляр виртуального сервера).

Следующим шагом выбираем операционную систему. Intel MPI Library поддерживает как Linux, так и Windows. Для первого знакомства с MPI выберем OC Linux. Выбираем Red Hat Enterprise Linux 6.6 64-bit или SLES11.3/12.0 .
Выбираем Instance Type (тип сервера). Для экспериментов нам подойдет t2.micro (1 vCPUs, 2.5 GHz, Intel Xeon processor family, 1 GiB оперативной памяти). Как недавно зарегистрировавшемуся пользователю, мне такой тип можно было использовать бесплатно - пометка «Free tier eligible». Задаем Number of instances : 2 (количество виртуальных серверов).

После того, как сервис предложит нам запустить Launch Instances (настроенные виртуальные сервера), сохраняем SSH-ключи, которые понадобятся для связи с виртуальными серверами извне. Состояние виртуальных серверов и IP адреса для связи с серверами локального компьютера можно отслеживать в консоли управления.

Важный момент: в настройках Network & Security / Security Groups необходимо создать правило, которым мы откроем порты для TCP соединений, - это нужно для менеджера MPI-процессов. Правило может выглядеть так:

Type: Custom TCP Rule
Protocol: TCP
Port Range: 1024-65535
Source: 0.0.0.0/0

В целях безопасности можно задать и более строгое правило, но для нашего демонстрационного примера достаточно этого.

И, напоследок, небольшой опрос по поводу возможных тем для будущих публикаций, посвященных высокопроизводительным вычислениям.

Только зарегистрированные пользователи могут участвовать в опросе. , пожалуйста.

Основные функции MPI

Наиболее распространенной технологией программирования для параллельных систем с распределенной памятью в настоящее время является MPI (Message Passing Interface). Основным способом взаимодействия параллельных процессов друг с другом в таких системах является передача сообщений (Message Passing). По сути MPI – это библиотека и среда исполнения для параллельных программ на языках C или Fortran. В данном пособии будут описаны примеры программ на языке С.

Изначально MPI позволяет использовать модель программирования MIMD (Multiple Instruction Multiple Data) – много потоков инструкций и данных, т.е. объединение различных программ с различными данными. Но программирование для такой модели на практике оказывается слишком сложным, поэтому обычно используется модель SIMD (Single Program Multiple Data) –одна программа и много потоков данных. Здесь параллельная программа пишется так, чтобы разные ее части могли одновременно выполнять свою часть задачи, таким образом, достигается параллелизм. Поскольку все функции MPI содержаться в библиотеке, то при компиляции параллельной программы необходимо будет прилинковать соответствующие модули.

Под параллельной программой в рамках MPI понимается множество одновременно выполняемых процессов. Процессы могут выполняться на разных процессорах, но на одном процессоре могут располагаться и несколько процессов (в этом случае их исполнение осуществляется в режиме разделения времени). При запуске MPI – программы на кластере на каждом из его узлов будет выполняться своя копия программы, выполняющая свою часть задачи, из этого следует, что параллельная программа – это множество взаимодействующих процессов, каждый из которых работает в своем адресном пространстве. В предельном случае для выполнения параллельной программы может использоваться один процессор - как правило, такой способ применяется для начальной проверки правильности параллельной программы.

Количество процессов и число используемых процессоров определяется в момент запуска параллельной программы средствами среды исполнения MPI - программ и в ходе вычислений меняться не может. Все процессы программы последовательно перенумерованы от 0 до np-1, где np есть общее количество процессов. Номер процесса называется рангом процесса.

Взаимодействуют параллельные процессы между собой при помощи посылки сообщений. Методы посылки (их называют коммуникации) бывают двух видов – коллективные(collective) и “точка-точка” (point-to-point). При коллективных коммуникациях процесс посылает нужную информацию одновременно целой группе процессов, еще есть более общий случай, когда внутри группы процессов передача информации идет от каждого процесса к каждому. Более простыми коммуникациями являются коммуникации типа ”точка-точка”, когда один процесс посылает информацию второму или они оба обмениваются информацией. Функции коммуникаций – основные функции библиотеки MPI. Кроме этого, обязательными функциями являются функции инициализации и завершения MPI – MPI_Init и MPI_Finalize. MPI_Init должна вызываться в самом начале программ, а MPI_Finalize – в самом конце. Все остальные функции MPI должны вызываться между этими двумя функциями.

Как процесс узнает о том, какую часть вычислений он должен выполнять? Каждый процесс, исполняющийся на кластере, имеет свой уникальный номер – ранг. Когда процесс узнает свой ранг и общее количество процессов, он может определить свою часть работы. Для этого в MPI существуют специальные функции – MPI_Comm_rank и MPI_Comm_size. MPI_Comm_rank возвращает целое число - ранг процесса, вызвавшего ее, а MPI_Comm_size возвращает общее число работающих процессов.

Процессы отлаживаемой параллельной программы пользователя объединяются в группы. Под коммуникатором в MPI понимается специально создаваемый служебный объект, объединяющий в своем составе группу процессов и ряд дополнительных параметров (контекст), используемых при выполнении операций передачи данных. Коммуникатор, автоматически создаваемый при запуске программы и включающий в себя все процессы на кластере, называется MPI_COMM_WORLD. В ходе вычислений могут создаваться новые и удаляться существующие группы процессов и коммуникаторы. Один и тот же процесс может принадлежать разным группам и коммуникаторам. Коллективные операции применяются одновременно для всех процессов коммуникатора, поэтому для них одним из параметров всегда будет выступать коммуникатор.

При выполнении операций передачи сообщений в функциях MPI необходимо указывать тип пересылаемых данных. MPI содержит большой набор базовых типов данных, основанных на стандартных типах данных языка С. Кроме того, программист может конструировать свои типы данных при помощи специальных функций MPI. Ниже приведена таблица соответствия для базовых типов данных.

Константы MPI ТИП данных языка С
MPI_INT signed int
MPI_UNSIGNED unsigned int
MPI_SHORT signed int
MPI_LONG signed long int
MPI_UNSIGNED_SHORT unsigned int
MPI_UNSIGNED_LONG unsigned long int
MPI_FLOAT float
MPI_DOUBLE double
MPI_LONG_DOUBLE long double
MPI_UNSIGNED_CHAR unsigned char
MPI_CHAR signed char

Пример запуска библиотеки MPI: логин student, пароль s304.

#include

#include

int main (int argc, char *argv)

/* Инициализация MPI */

MPI_Init (&argc, &argv);

/* получение ранга процесса */

MPI_Comm_rank (MPI_COMM_WORLD, &rank);

/* получение общего числа процессов */

MPI_Comm_size (MPI_COMM_WORLD, &size);

printf("Hello world from process %d of %d\n", rank, size);

/* завершение MPI */

Для компиляции используется компилятор и линковщик. Командная строка mpicc. (см. mpicc….- help)

Каждый из запущенных процессов должен вывести на экран свой ранг и общее число процессов. Попробуем откомпилировать и запустить эту программу.

$ mpicc hello.c –o hello.o

$ mpicc hello.o –o hello

Файл hello и будет исполняемым файлом примера. Можно запустить его на одной машине и посмотреть, что число процессоров будет равно 1, а ранг процесса 0:

$ ./hello

Hello world from process 0 of 1

При работе на сервере для запуска используется команда mpirun . У нее есть два основных аргумента – имя файла, содержащего адреса узлов и число узлов, на котором будет запущена программа.

$ mpirun n0-6 –v hosts hello

Hello world from process 0 of 7

Hello world from process 3 of 7

Hello world from process 5 of 7

Hello world from process 4 of 7

Hello world from process 2 of 7

Hello world from process 6 of 7

Hello world from process 1 of 7

Программа будет запущена на 7 узлах (включая сервер), а адреса этих узлов находятся в файле hosts. Печать на экран осуществлялась процессами не по порядку их рангов. Это связано с тем, что запуск процессов не синхронизирован, однако в MPI существуют специальные функции для синхронизации процессов.

Простейшая программа не содержит функций передачи сообщений. В реальных же задачах процессам требуется взаимодействовать друг с другом. Естественно, на передачу сообщений тратится время, что снижает коэффициент распараллеливания задачи. Чем выше скорость интерфейса передачи сообщений (например Ethernet 10Mb/sec и Gigabit Ethernet), тем меньше будут затраты на передачу данных. Т.к. время обмена данными между процессами намного (на порядки) больше времени доступа к собственной памяти, распределение работы между процессами должно быть ‘крупнозернистым’, нужно избегать ненужных пересылок данных.

Среди задач численного анализа встречается немало задач, распараллеливание которых очевидно. Например, численное интегрирование сводится фактически к (многочисленному) вычислению подинтегральной функции (что естественно доверить отдельным процессам), при этом главный процесс управляет процессом вычислений (определяет стратегию распределения точек интегрирования по процессам и собирает частичные суммы). Подобным же распараллеливанием обладают задачи поиска и сортировки в линейном списке, численного нахождения корней функций, поиск экстремумов функции многих переменных, вычисление рядов и другие. В этой лабораторной работе мы рассмотрим два параллельных алгоритма вычисления числа π.

Вычисление числа π методом численного интегрирования

Известно, что

Заменяя вычисление интеграла конечным суммированием, имеем , где , n-число участков суммирования при численном интегрировании. Площадь каждого участка вычисляется как произведение ширины ‘полоски’ на значение функции в центре ‘полоски’, далее площади суммируются главным процессом (используется равномерная сетка).

Очевидно, что распараллеливание этой задачи легко сделать, если каждый процесс будет считать свою частичную сумму, а затем передаст результат вычислений главному процессу. Как избежать здесь повторяющихся вычислений? Процесс должен знать свой ранг, общее число процессов и число интервалов, на которое будет разбит отрезок (чем больше интервалов, тем выше будет точность). Тогда в цикле от 1 до числа интервалов процесс будет вычислять площадь полоски на i-ом интервале, а затем переходить не на следующий i+1интевал, а на интервал i+m, где m-число процессов. Как мы уже знаем, для получения ранга и общего числа процессов существуют функции MPI_Comm_rank и MPI_Comm_size . Перед началом вычислений главный процесс должен передать всем остальным число интервалов, а после вычислений собрать у них полученные частичные суммы и просуммировать их, в MPI это реализуется передачей сообщений. Для передачи сообщений здесь удобно использовать функцию коллективного взаимодействия MPI_Bcast , которая рассылает одинаковые данные от одного процесса всем остальным. Для сбора частичных сумм есть 2 варианта - можно использовать MPI_Gather , которая собирает данные со всех процессов и отдает их одному (получается массив из m элеменов, где m-число процессов) или MPI_Reduce . MPI_Reduce действует аналогично MPI_Gather – собирает данные от всех процессов и отдает одному, но не в виде массива, а предварительно производит определенную операцию между элементами массива, например, суммирование и после этого отдает один элемент. Для этой задачи более удобным выглядит использование MPI_Reduce . Текст программы приведен ниже

#include "mpi.h"

#include

#include

double f(double a)

return (4.0 / (1.0 + a*a));

int main(int argc, char *argv)

int n, myid, numprocs, i;

double PI25DT = 3.141592653589793238462643;

double mypi, pi, h, sum, x;

double startwtime, endwtime;

MPI_Init(&argc,&argv);

MPI_Comm_size(MPI_COMM_WORLD,&numprocs);

MPI_Comm_rank(MPI_COMM_WORLD,&myid);

startwtime = MPI_Wtime();

MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);

h = 1.0 / (double) n;

for (i = myid + 1; i <= n; i += numprocs)

x = h * ((double)i - 0.5);

MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);

printf("pi is approximately %.16f, Error is %.16f\n",

pi, fabs(pi - PI25DT));

endwtime = MPI_Wtime();

printf("wall clock time = %f\n",

endwtime-startwtime);

Рассмотрим подробнее вызовы функий MPI_Bcast и MPI_Reduce :

MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD) – содержимое переменной n и одного элемента типа MPI_INT из процесса с рангом 0 посылается всем остальным процессам (MPI_COMM_WORLD – все процессы в коммуникаторе) в ту же переменную n. После этого вызова каждый процесс будет знать общее число интервалов. По окончании вычислений MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD) суммирует (параметр MPI_SUM) значения из переменных mypi типа MPI_DOUBLE каждого процесса и записывает результат в переменную pi процесса с рангом 0. Для замера времени вычислений главный процесс использует функцию MPI_Wtime.

double MPI_Wtime();

MPI_Wtime возвращает число секунд в формате числа с плавающей точкой, представляющее время, прошедшее с момента старта программы.

Вычисление числа π методом Монте-Карло

Для вычисления значения πможно использовать метод ‘стрельбы’. В применении к данному случаю метод заключается в генерации равномерно распределенныхна двумерной области точек и определении .

Вычисленное таким образом значение π является приближенным, в общем случае точность вычисления искомого значения повышается с увеличением числа ‘выстрелов’ и качества генетатора случайных чисел; подобные методы используются в случае трудностей точной числовой оценки.

Параллельный алгоритм вычисления числа π данным методом во многом похож на предыдущий рассмотренный нами алгоритм. Для генерации случайных чисел нужно использовать функцию srand, которой в качестве агрумента (семени последовательности) задавать ранг процесса, таким образов, у каждого процесса будет своя последовательность.

#include

void srand(unsigned seed);

Функция srand() устанавливает исходное число для последовательности, генерируемой функцией rand().

#include

int rand(void);

Функция rand() генерирует последовательность псевдослучайных чисел. При каждом обращении к функции возвращается целое в интервале между нулем и значением RAND_MAX.

Для сбора результата здесь также удобно использовать MPI_Reduce с заданием операции суммирования (MPI_SUM), затем разделить полученную сумму на число процессоров, получив среднее арифметическое.

int MPI_Reduce(void* sendbuf, void* recvbuf, int count,

MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm);

Параметры:

sendbuf адрес посылающего буфера

recvbuf адрес принимающего буфера

op операция редукции

comm коммуникатор

int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root,

MPI_Comm comm);

Параметры:

buffer адрес посылающего/принимающего буфера

count количество элементов в посылающем буфере (целое)

datatype тип данных элементов посылающего буфера

root номер главного процесса (целое)

comm коммуникатор

Задание : в соответствии с номером варианта откомпилировать и запустить параллельную программы, вычисляющую число π по заданному алгоритму.

Запустить задачу на одном узле и с кластера, на заданном количестве узлов. Оценить время выполнения вычислений, точность и коэффициент распараллеливания Амдала с учетом сетевой задержки теоретически и по результатам выполнения работы.

Варианты заданий

№ Варианта Алгоритм Число процессоров Число итераций на каждом процессоре
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло
Численное интегрирование
Монте-Карло

· Постановка задачи, вариант.

· Текст параллельной программы на языке С согласно заданию.

· Результаты запуска программы на одном узле, время выполнения t i , результат вычислений, ошибка.

· Результаты запуска программы на сервере, время выполнения, результат вычислений, ошибка.

· Описать параллельный алгоритм, информационные потоки при выполнении программы и загрузку КЭШ-памяти узлов. Вычислить по результатам работы программы коэффициент Амдала - К j .

· Учитывая результаты работы группы студентов, построить гистограмму зависимости К j , t i от количества процессоров участвующих в вычислениях.



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: