Кластерные системы
· 2 Кластерные системы и стандарт параллельного программирования MPI
Кластерные системы приобретают все большую популярность. У этого класса параллельных вычислительных систем есть существенные преимущества перед другими архитектурами вычислителей.
- В первую очередь это теоретическая возможность неограниченного наращивания производительности путем добавления новых узлов.
- Гибкость технологии. Системы этого класса могут строится на разнородной аппаратной базе, иметь оптимальную для данной задачи топологию соединительной сети и допускают различные формы организации работы параллельных процессов.
- Хороший показатель цена/производительность. Вычислительные системы сверхвысокой производительности стоят дорого. За счет гибкости устройства часто можно построить кластерную систему по доступной цене, которая обеспечит требуемую производительность. Можно использовать уже существующую сеть рабочих станций (системы такого типа иногда называют COW - Cluster Of Workstations). При этом узлы могут иметь различную архитектуру, производительность, работать под управлением разных OC (MS Windows, Linux, FreeBSD). Если узлы планируется использовать только в составе кластера, то их можно существенно облегчить (отказаться от жёстких дисков, видеокарт, мониторов и т.п.). В облегчённом варианте узлы будут загружаться и управляться через сеть с головной машины. Количество узлов и требуемая пропускная способность сети определяется задачами, которые планируется запускать на кластере.
· 2.1 Стандарт MPI
Для программирования кластеров применяются библиотеки построенные по модели обмена сообщениями. Основным стандартом здесь является MPI : Message Passing Interface [5]. На настоящий момент существуют две основные версии этого стандарта: MPI-1 и MPI-2, причем MPI-1 является частным случаем MPI-2. Стандарт MPI-1 описывает статическое распараллеливание, т.е. количество параллельных процессов фиксировано. Он позволяет описывать обмены типа точка-точка, широковещательные(коллективные) обмены, групповые обмены а также позволяет организовывать топологии взаимодействия процессов. Стандарт MPI-2 помимо функциональности MPI-1 содержит возможность динамического порождения процессов и управления ими.
Разными коллективами разработчиков написано несколько программных пакетов, удовлетворяющих спецификациям MPI (MPICH, MPICH2, LAM, OpenMPI, HPVM, etc.). Существуют стандартные ''привязки'' MPI к языкам С, С++, Fortran 77/90, а также реализации почти для всех суперкомпьютерных платформ и сетей рабочих станций.
· 2.2 Запуск MPI-2 на кластере
В данной работе для экспериментов был использован кластер на основе сети персональных компьютеров и библиотека MPICH2 [6]. Этот пакет можно получить и использовать бесплатно. В состав MPICH2 входит библиотека программирования, загрузчик приложений, утилиты.
1. Прежде всего необходимо скачать и установить на все узлы кластера пакет MPICH2. Исходные тексты или уже скомпилированные пакеты под разные платформы можно получить на сайте [6].
В данном случае MPICH2 v.1.0.3 собирался из исходных текстов для ОС FreeBSD v.5.3 на процессоре Intel Pentium III. Процесс инсталляции библиотеки описан в MPICH2 Installer's Guide.
Рекомендуемые материалы
2. MPICH2 использует rsh (remote shell). Поэтому необходимо запустить на каждом узле rshd (remote shell server) и согласовать права доступа, т.е. по команде rsh mynode система должна сразу ''пускать'' вас не спрашивая пароль. Для обеспечения более высокого уровня сетевой безопасности можно использовать ssh - OpenSSH remote login client.
3. Компиляция MPI-программы на языке С выполняется утилитой mpicc, представляющей собой надстройку над C-компилятором, установленным в данной ОС. Так же есть аналогичные утилиты для языков С++ - mpiCC и FORTRAN77 - mpif77.
- mpicc myprog.c -o myprog
- mpiCC myprog.cpp -o myprog
- mpif77 myprog.f -o myprog
- Перед запуском ''бинарника'' myprog необходимо разослать его на все узлы кластера, причем локальный путь до myprog должен быть одинаковый на всех машинах, например - /usr/mpibin/myprog.
Вместо процедуры копирования программы на узлы можно использовать NFS (Network File System) :
· на головной машине запускаем NFS-сервер и открываем каталог с myprog
· на каждом рабочем узле кластера, монтируем NFS головной машины, используя единый для всех узлов локальный путь.
8. В отличии от MPICH1, который использовал только rsh, в случае MPICH2 процессы на узлах управляются специальным демоном mpd. Перед началом работы надо запустить его с головной машины на всех узлах при помощи mpdboot.
- mpdboot --totalnum=2 --file=hosts.mpd --user=mechanoid --verbose
где hosts.mpd - текстовый файл со списком используемых узлов кластера.
Проверка состояния mpd выполняется при помощи mpdtrace:
$ mpdtrace -l
node2.home.net_51160 (192.168.0.2)
node1.home.net_53057 (192.168.0.1)
Завершение работы mpd выполняется при помощи mpdexit
$ mpdexit node2.home.net_51160
При выключении одного узла выключаются все остальные.
10. Запуск MPI-программы производится командой :
- mpiexec -n N myprog
где N - начальное количество параллельных процессов.
После этого происходит запуск N копий MPI-программы myprog.
mpiexec сам распределяет процессы по узлам, ''общаясь'' с mpd, в данном случае нет надобности указывать список узлов как в mpirun для MPICH1.
· 2.3 Статическая параллельная программа с использованием MPI
Параллельная программа описывает некоторое количество процессов (веток параллельной программы) и порядок их взаимодействия. В статической модели стандарта MPI-1 количество таких веток фиксировано и задается при запуске программы.
MPI-программа начинается с вызова функции MPI_INIT(), которая включает процесс в среду MPI, и завершается вызовом функции MPI_FINALIZE(). При запуске каждая ветка параллельной программы получает MPI-идентификатор - ранг, который можно узнать при помощи функции MPI_COMM_RANK(). Для обмена данными между процессами в рамках MPI существует много разных функций: MPI_SEND() - обмен точка-точка, посылка сообщения для одного процесса, MPI_RECV() - обмен точка-точка, прием сообщения, MPI_BCAST() - широковещательная посылка один-всем, MPI_REDUCE() - сбор и обработка данных, посылка все-одному и еще много других.
Приведем пример статической MPI-программы вычисления числа как суммы ряда на языке FORTRAN77, текст программы [http://mechanoid.narod.ru/parallel/mpi2/pi.f].
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* $Id: pi.f,v 1.4 2006/03/06 12:31:32 mechanoid Exp mechanoid $
*
* pi parallel calculate with MPI
*
* OS : FreeBSD 5.3
* COMPILER : mpif77 for MPICH2 v.1.0.3
* gcc version 3.4.2 [FreeBSD]
* GNU ld version 2.15 [FreeBSD]
*
* AUTHOR : Evgeny S. Borisov
*
* Glushkov Institute of Cybernetics
* National Academy of Sciences of Ukraine
*
* http://www.mechanoid.kiev.ua
* e-mail : par@mechanoid.kiev.ua
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
program pi_calculate
implicit none
include 'mpif.h'
* размер интервала, на котором будем считать сумму
integer N
parameter(N=10000000)
* номер процесса
integer iam
* количество ветвей параллельной программы
integer nprocs
* имя узла, на котором выполняется процесс
character*18 pname
integer ierr, i,nlen
real*8 pi, h, x, res
* инициализация MPI
call MPI_INIT(ierr)
* получаем количество ветвей параллельной программы
call MPI_COMM_SIZE(MPI_COMM_WORLD, nprocs, ierr)
* получаем номер процесса
call MPI_COMM_RANK(MPI_COMM_WORLD, iam, ierr)
* получаем имя узла, на котором выполняется процесс
call MPI_GET_PROCESSOR_NAME(pname,nlen,ierr);
* процесс 0 печатает размер интервала, на котором будем считать сумму
if(iam .eq. 0) then
write(*,*) 'N = ', N
endif
write(*,*)'Process ',iam,' of ',nprocs,' started on ',pname
* считаем частичную сумму
pi = 0.0
h = 1.0/N
do i= 1+iam , N, nprocs
x = h*(i-0.5)
pi = pi+(4.0/(1.0+x*x))
enddo
pi = h * pi
* процесс 0 получает результаты от ветвей и суммирует их
call MPI_REDUCE(pi,res,1,MPI_REAL8,MPI_SUM,0,MPI_COMM_WORLD,ierr)
* процесс 0 печатает конечный результат
if(iam .eq. 0) then
write(*,*) 'pi = ', res
endif
* завершение работы
call MPI_FINALIZE(ierr)
stop
end
*
* результаты запуска на кластере из двух Pentium III 700MHz, FAST ETHERNET 100Mbs
*
* два узла
*
* mechanoid@node2 /usr/home/mechanoid/MPI/pi/f
* $ time mpiexec -n 2 ./pi
* N = 1000000000
* Process 0 of 2 started on node2.home.net
* Process 1 of 2 started on node1.home.net
* pi = 3.1415926
*
* real 0m24.265s
* user 0m0.396s
* sys 0m0.045s
*
* один узел
*
* mechanoid@node2 /usr/home/mechanoid/MPI/pi/f
* $ time mpiexec -n 1 ./pi
* N = 1000000000
* Process 0 of 1 started on node2.home.net
* pi = 3.1415926
*
* real 0m46.724s
* user 0m0.355s
* sys 0m0.090s
*
Результаты работы программы, на кластере из двух PC на процессоре Pentium III 700MHz, сеть FAST ETHERNET 100Mbs, размер интервала, на котором считали сумму, равен 108
- один узел, время работы - 47 секунд
- $ mpiexec -n 1 ./pi
- Process 0 of 1 started on node2.home.net
- pi = 3.1415926
- два узла, время работы - 24 секунды
- $ time mpiexec -n 2 ./pi
- Process 0 of 2 started on node2.home.net
- Process 1 of 2 started on node1.home.net
- pi = 3.1415926
· 2.4 Динамическая параллельная программа с использованием MPI
Теперь займемся динамическим порождением процессов. Стандарт MPI-2 предусматривает механизмы порождения новых ветвей из уже запущенных в процессе выполнения параллельные программы. В MPI-2 это происходит путем запуска файлов программ (аналогично функциям exec() стандарта POSIX.1) с помощью функции MPI_COMM_SPAWN() или MPI_COMM_SPAWN_MULTIPLE(), первая запускает заданное количество копий одной программы, вторая может запускать несколько разных программ.
Запущенные с помощью MPI_COMM_SPAWN процессы не принадлежат группе родителя (MPI_COMM_WORLD) и выполняются в отдельной среде. Порожденные процессы имеют свои ранги которые могут совпадать с рангами группы, в которой выполнялся родительский процесс. Обмен сообщениями между процессами родительской и дочерней групп происходит с использованием так называемого интеркоммуникатора, который возвращается процессу-родителю функцией MPI_COMM_SPAWN(). Дочерние процессы могут получить интеркоммуникатор группы родителя с помощью функции MPI_COMM_GET_PARENT(). Значение интеркоммуникатора используется функциями обмена сообщениями MPI_SEND(), MPI_RECV() и другими.
Приведем пример динамической MPI-программы на языке FORTRAN77. Здесь один родительский процесс порождает три дочерних, затем один дочерний процесс посылает остальным дочерним процессам широковещательное сообщение(MPI_BCAST()), после чего один дочерний процесс посылает сообщение родителю (MPI_SEND()) и принимает от него ответ (MPI_RECV()). Дочерние процессы запускаются родительским с параметром командной строки "--slave", текст программы [http://mechanoid.narod.ru/parallel/mpi2/spawn.f].
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* $Id: spawn.f,v 1.7 2006/03/04 16:56:04 mechanoid Exp mechanoid $
*
*
* dynamical parallel procceses demo with MPICH2
*
*
* OS : FreeBSD 5.3
* COMPILER : mpif77 for MPICH2 v.1.0.3
* gcc version 3.4.2 [FreeBSD]
* GNU ld version 2.15 [FreeBSD]
*
* AUTHOR : Evgeny S. Borisov
*
* Glushkov Institute of Cybernetics
* National Academy of Sciences of Ukraine
*
* http://www.mechanoid.kiev.ua
* e-mail : par@mechanoid.kiev.ua
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PROGRAM spawn
implicit none
include 'mpif.h'
* количество дочерних процессов
integer PROCESS
parameter(PROCESS=3)
logical cmd_arg_parse,master
integer iam,nprocs,ierr,namelen
integer icomm,errcodes(PROCESS),i
integer slave_rank, master_rank
integer stat(MPI_STATUS_SIZE)
character*50 proc_name
character*20 argv(2)
character*30 msg
* проверка аргументов командной строки
if(cmd_arg_parse(master)) then
stop
endif
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* инициализация MPI, сбор технической информации
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call MPI_INIT(ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, iam, ierr)
call MPI_GET_PROCESSOR_NAME(proc_name,namelen,ierr);
proc_name(namelen+1:namelen+1)=' '
if(master) then
write(*,3000),'master',iam,proc_name
else
write(*,3000),'slave',iam,proc_name
endif
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* запуск дочерних процессов
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
IF(master) THEN
* главный процесс запускает дочерние
argv(1) = '--slave'
argv(2) = ' '
call MPI_COMM_SPAWN('./spawn ',
+ argv,
+ PROCESS,
+ MPI_INFO_NULL,
+ 0,
+ MPI_COMM_SELF,
+ icomm,
+ errcodes,
+ ierr)
if((ierr.ne.MPI_SUCCESS).or.(icomm.eq.MPI_COMM_NULL))then
write(*,*)'MPI_COMM_SPAWN error:',ierr
goto 100
endif
* проверка состояния дочерних процессов
do i=1,PROCESS
if(errcodes(i).ne.MPI_SUCCESS) then
write(*,*)iam,': spawn error =',ierr,' for process ',i
endif
enddo
ELSE
* дочерний процесс получает коммуникатор родителя
call MPI_COMM_GET_PARENT(icomm,ierr)
if((ierr.ne.MPI_SUCCESS).or.(icomm.eq.MPI_COMM_NULL))then
write(*,*)'MPI_COMM_GET_PARENT error:',ierr
goto 100
endif
ENDIF
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* обмен сообщениями между родительским дочерним процессами
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ранг родительского процесса в своей группе, участвующего в коммуникации
master_rank=0
* ранг дочернего процесса в своей группе, участвующего в коммуникации
slave_rank=2
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* обмен между дочерними процессами
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* формируем сообщение
if( (.not.master).and.(iam.eq.slave_rank)) then
write (msg,4000),'slave', iam
else
msg='--- '
endif
* процесс slave_rank посылает сообщение всей своей группе
if(.not. master) then
call MPI_BCAST(msg,len(msg),MPI_CHARACTER,
+ slave_rank,MPI_COMM_WORLD,ierr)
write(*,1000),'slave',iam,proc_name, msg
endif
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* посылка от родительского дочернему процессу
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* формируем сообщение
if( master .and. (iam.eq.master_rank)) then
write (msg,2000),'master', iam
else
msg='--- '
endif
* родительский процесс отсылает сообщение
if( master.and.(master_rank.eq.iam) ) then
call MPI_SEND(msg,len(msg),MPI_CHARACTER,
+ slave_rank,0,icomm,ierr)
endif
* дочерний процесс принимает сообщение
if((.not.master).and.(iam.eq.slave_rank)) then
call MPI_RECV(msg,len(msg),MPI_CHARACTER,
+ master_rank,0,icomm,stat,ierr)
write(*,1000),'slave',iam,proc_name, msg
endif
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* посылка от дочернего родительскому процессу
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* формируем сообщение
if( (.not.master) .and. (iam.eq.slave_rank)) then
write (msg,2000),'slave', iam
else
msg='---'
endif
* дочерний процесс отсылает сообщение
if((.not.master).and.(iam.eq.slave_rank)) then
call MPI_SEND(msg,len(msg),MPI_CHARACTER,
+ master_rank,0,icomm,ierr)
endif
* родительский процесс принимает сообщение
if( master.and.(master_rank.eq.iam) ) then
call MPI_RECV(msg,len(msg),MPI_CHARACTER,
+ slave_rank,0,icomm,stat,ierr)
write(*,1000),'master',iam,proc_name, msg
endif
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* завершение работы
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
call MPI_COMM_DISCONNECT(icomm, ierr)
100 continue
call MPI_FINALIZE(ierr)
stop
1000 format(a7,i2,' on ',a15,' : ',a30)
3000 format(a7,i2,' on ',a15,' : start')
2000 format('message from', a7, i2)
4000 format('broadcast from', a7, i2)
END
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* проверка аргументов командной строки
* если командная строка допустима -- функция возвращает .false.
* если командная строка содержит аргумент "--slave"
* то переменная master устанавливается в .false. иначе в .true.
function cmd_arg_parse(master)
logical cmd_arg_parse
logical master
integer npar
character*100 arg
cmd_arg_parse=.true.
master=.true.
* количество аргументов
npar=iargc()
if(npar.eq.0) then
* без параметров -- процесс master
cmd_arg_parse=.false.
return
endif
if(npar .ne. 1) then
* неправильное количество параметров
write (*,*) 'usage: getarg [--slave]'
return
endif
* получение значения аргумента номер 1
call getarg(1,arg)
if(arg.ne.'--slave')then
write (*,*) 'unknow option:',arg
write (*,*) 'usage: getarg [--slave]'
return
endif
* процесс slave
master=.false.
cmd_arg_parse=.false.
return
end
Результат работы программы:
$ mpiexec -n 1 ./spawn
master 0 on node2.home.net : start
slave 1 on node2.home.net : start
slave 0 on node1.home.net : start
Вместе с этой лекцией читают "16 - Применение горных и буровых работ".
slave 2 on node1.home.net : start
slave 1 on node2.home.net : broadcast from slave 2
slave 0 on node1.home.net : broadcast from slave 2
slave 2 on node1.home.net : broadcast from slave 2
master 0 on node2.home.net : message from slave 2
slave 2 on node1.home.net : message from master 0