Е.С.Борисов
понедельник, 6 марта 2006 г.
Существуют задачи, не решаемые на серийных персональных компьютерах за приемлемое время[ 1 ], к примеру прогнозирование погоды, моделирование процессов разрушения в механике (crash-тесты).
|
Основной характеристикой при классификации параллельных вычислительных систем является способ организации памяти[ 2 ]. Все множество архитектур можно разделить на две группы: системы с общей памятью и системы с распределенной памятью.
Системы с общей памятью - все процессоры работают в едином адресном пространстве с равноправным доступом к памяти(рис. 2 ). В эту группу попадают симметричные мультипроцессорные системы(SMP). В таких системах наличие общей памяти упрощает взаимодействие процессоров между собой, однако возникает необходимость в механизме разрешения конфликтов между процессорами за доступ к памяти, что накладывает сильные ограничения на число процессоров (обычно не более 32). Таким образом, при всем удобстве использования, производительность систем с общей памятью ограничена.
Системы с распределенной памятью - каждый процессор имеет собственную локальную памятью, и прямой доступ к этой памяти других процессоров невозможен(рис. 3 ). Этой группе принадлежат системы массового параллелизма(MPP) и их менее дорогой вариант кластеры. Такая система обычно состоит из нескольких самостоятельных вычислительных узлов. Для связи узлов используется определенная сетевая технология. Системы с распределенной памятью сложнее программировать, поскольку необходимо делить обрабатываемые данные на части и рассылать их по вычислительным узлам. Общее число процессоров в таких системах теоретически не ограничено.
При написании параллельных программ можно пользоваться коммуникационными библиотеками. Такие библиотеки реализуют методы запуска и управления параллельными процессами, обычно они содержат функции обмена данными между ветвями параллельной программы, функции синхронизации процессов. Существует много библиотек и интерфейсов параллельного программирования[ 3 ]. Соответственно типу организации памяти параллельных вычислителей, выделим два основных типа библиотек:
Параллельные программы можно писать ''вручную'', непосредственно вставляя в нужные места вызовы коммуникационной библиотеки. Этот путь требует от программиста специальной подготовки. Альтернативой является использование систем автоматического и полуавтоматического распараллеливания последовательных программ[ 4 ]. Например, Adaptor - одна из реализаций спецификации High Performance Fortran (HPF) или BERT77 - средство автоматического распараллеливания Fortran-программ. Такие системы так же могут помочь пользователю выяснить, можно ли распараллелить данную задачу, оценить время ее выполнения, определить оптимальное число процессоров.
Кластерные системы приобретают все большую популярность. У этого класса параллельных вычислительных систем есть существенные преимущества перед другими архитектурами вычислителей.
Для программирования кластеров применяются библиотеки построенные по модели обмена сообщениями. Основным стандартом здесь является 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, а также реализации почти для всех суперкомпьютерных платформ и сетей рабочих станций.
В данной работе для экспериментов был использован кластер на основе сети персональных компьютеров и библиотека MPICH2 [ 6 ]. Этот пакет можно получить и использовать бесплатно. В состав MPICH2 входит библиотека программирования, загрузчик приложений, утилиты.
В данном случае MPICH2 v.1.0.3 собирался из исходных текстов для ОС FreeBSD v.5.3 на процессоре Intel Pentium III. Процесс инсталляции библиотеки описан в MPICH2 Installer's Guide .
mpicc myprog.c -o myprog mpiCC myprog.cpp -o myprog mpif77 myprog.f -o myprog
Вместо процедуры копирования программы на узлы можно использовать NFS (Network File System) :
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
При выключении одного узла выключаются все остальные.
mpiexec -n N myprog
где N - начальное количество параллельных процессов.
После этого происходит запуск N копий MPI-программы myprog .
mpiexec сам распределяет процессы по узлам, ''общаясь'' с mpd , в данном случае нет надобности указывать список узлов как в mpirun для MPICH1.
Параллельная программа описывает некоторое количество процессов (веток параллельной программы) и порядок их взаимодействия. В статической модели стандарта MPI-1 количество таких веток фиксировано и задается при запуске программы.
MPI-программа начинается с вызова функции MPI_INIT() , которая включает процесс в среду MPI, и завершается вызовом функции MPI_FINALIZE() . При запуске каждая ветка параллельной программы получает MPI-идентификатор - ранг, который можно узнать при помощи функции MPI_COMM_RANK() . Для обмена данными между процессами в рамках MPI существует много разных функций: MPI_SEND() - обмен точка-точка, посылка сообщения для одного процесса, MPI_RECV() - обмен точка-точка, прием сообщения, MPI_BCAST() - широковещательная посылка один-всем, MPI_REDUCE() - сбор и обработка данных, посылка все-одному и еще много других.
Приведем пример статической MPI-программы вычисления числа как суммы ряда на языке FORTRAN77, текст программы [ здесь ].
Результаты работы программы, на кластере из двух PC на процессоре Pentium III 700MHz, сеть FAST ETHERNET 100Mbs, размер интервала, на котором считали сумму, равен 10 8
$ mpiexec -n 1 ./pi Process 0 of 1 started on node2.home.net pi = 3.1415926
$ 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
Теперь займемся динамическим порождением процессов. Стандарт 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 ", текст программы [ здесь ].
Результат работы программы:
$ 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 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