[ Автор: Илья Евсеев ]
[ Организация: ИВВиБД ]
[ Подразделение: ЦСТ ]
Постановка задачи
Имеется программа
Crystal95.
Делает она буквально следующее:
CRYSTAL95 computes the electronic structure of periodic materials
within Hartree Fock, density functional or various hybrid approximations.
The Bloch functions of the periodic systems are expanded
as linear combinations of atom centered Gaussian functions.
Powerful screening techniques are used to exploit real space locality.
The code may be used to perform consistent studies of the physical,
electronic and magnetic structure of molecules, polymers, surfaces
and crystalline solids.
То есть, ее суть - прикладные математические расчеты.
Программа имеет параллельную архитектуру, может быть собрана поверх одного из нескольких коммуникационных интерфейсов, в т.ч. PVM. В нашем распоряжении есть исходные тексты Crystal95 на языке Фортран-77, с недвусмысленным трупным запахом Фортрана-4.
Работающий вариант программы инсталлирован на компьютере compic.ptc.spbu.ru в Петербургском Университете, где его сопровождением занимается инженер-программист Павел Егорович Борисов. Он (работающий вариант, а не Павел) служил эталоном для проверки того, что написал я.
Имеется, скажем так, суперкомпьютер фирмы "Парситек", на нем инсталлированы коммуникационные библиотеки Embedded Parix и PowerMPI (реализация MPI над Embedded Parix). В природе существует так же и PowerPVM, но он не установлен, так как его нужно покупать дополнительно.
В определенных кругах зародилась, как ни странно, мЬIcль: скрестить Парситек с Кристаллом путем портирования последнего под MPI. Работа оказалась достаточно нелинейной; ниже перечислены основные проблемы и замечания по их решению: где-то конкретные, а где-то - не очень. Поскольку подразумевалось, что работа будет носить не только практический, но и методически-назидательный характер, я излагаю весь полученный опыт предельно подробно, стараясь не пренебречь ни одной мелочью.
Все имевшиеся проблемы могут быть четко разделены на три группы по типу источника:
Программа, собранная поверх MPI, опробована на трех компьютерах:
На каждой из них она находится в каталоге ~il/cry95mpi
Прилагаемые файлы:
Примечание 2: список прочих моих рассказок про параллельное программирование находится тут.
Первое: из каталога, в котором находятся Ваши файлы, должен быть доступ к программам из ~il/cry95mpi. Либо добавьте этот путь в переменную окружения PATH, либо (лучше) сделайте ссылки:
ln -s ~il/cry95mpi/{MPIintegrals,MPIscf,runmpi} .
Второе: должен быть доступ к BIN-директории MPI: отредактируйте PATH. Вот где находится MPI на трех упомянутых машинах:
Третье: создайте файл paths.lst с названиями каталогов, в которых ветви приложения будут создавать результаты. Каталоги обязательно должны иметь разные имена, поскольку каждая ветвь дает файлам одинаковые названия. Меняя количество каталогов, Вы тем самым задаете количество ветвей. Ниже приведен пример такого файла. Он задает каталоги для пяти ветвей: одной служебной и четырех расчетных.
# paths.lst # ========= # Working directories for nodes of MPI-based Crystal'95 # /tmp/il/cry95mpi/node0 /tmp/il/cry95mpi/node1 /tmp/il/cry95mpi/node2 /tmp/il/cry95mpi/node3 /tmp/il/cry95mpi/node4 ## EOF ##
Запустите runmpi, указав в его командной строке имя файла данных, например: runmpi i29. Если он отработает успешно, в Вашем каталоге появятся файлы с именами step1, step2, ... с результатами вычислений.
Что делать, если runmpi "свалится" во время выполнения? Необходимо восстановить paths.lst из временной копии
mv __paths_.lst paths.lstКроме того, рекомедуется вручную удалить со всем содержимым каталоги, перечисленные в paths.lst.
На Парситеке runmpi "сваливается" всегда - таков Парситек! Вам потребуется запускать недовыполненые команды вручную, или переместить их в отдельный командный файл.
В PVM изначально запускается одна ветвь, ветви могут запускать другие ветви - из того же программного файла или из других. Все приложения, входящие в состав Crystal95, в PVM-варианте состоят из двух программ: ведущей (master) и ведомой. Мастер запускает по одному ведомому процессу на каждом вычислительном узле. Адреса узлов мастер читает из начала текстового конфигурационного файла.
В MPI все ветви идентичны и запускаются одновременно, различаясь только порядковым номером в коллективе. Для них выбрано следующее распределение ролей: ветвь 0 занимается функциями ведущей (читает конец config-файла, рассылает его всем остальным, впоследствии ведает сервисом "Next value"), а ветви с номерами 1..N решают задачи PVM-ветвей с номерами 0..(N-1) соответственно. Каждое приложение состоит из одного-единственного файла, который запускается в количестве N+1 экземпляров.
Проблема может возникнуть, если количество ветвей для pvm_spawn() не задается пользователем в настроечном файле или с консоли, а вычисляется на начальной стадии алгоритма, т.е. уже в ходе работы программы. Общего правила, как такое поведение программы расписать средствами MPI, не существует. К счастью, ничего подобного в Crystal95 нет: количество ветвей изначально задается извне.
В PVM не существует простого механизма для того, чтобы одна отдельно взятая ветвь могла завершить работу всего параллельного приложения (с точки зрения PVM такого понятия, как приложение, не существует вовсе), или группы (кстати, механизмом групп Crystal95 не пользуется). Места, в которых приложение может принять решение об аварийном завершении, в Crystal95 (в несколько в упрощенном виде) выглядят примерно так:
Ветвь-мастер | Ведомые ветви |
do i=0,N-1 send(task=i,buf=данные(i)) enddo do j=0,N-1 recv(task=j,buf=ответ) if(ответ.eq.ошибка) then do k=0,N-1 send(task=k,buf=ошибка) enddo pvm_exit stop endif enddo do j=0,N-1 send(task=j,buf=не_ошибка) end_do |
В MPI-варианте с использованием типового подхода на базе MPI_Barrier и MPI_Abort все сильно упрощается:
if(myRank.eq.0) then do i=1,N call MPI_Send( данные(i), ... , rank=j, ... ) enddo else call MPI_Recv( данные, ... , rank=0, ... ) сделать_что-то_умное(данные,iflag) if(iflag.eq.ошибка) then write(*,*) 'Приехали...' call MPI_Abort( MPI_COMM_WORLD, MPI_ERR_OTHER, ierror ) stop enif endif call MPI_Barrier( MPI_COMM_WORLD, ierror )
Нумерация ветвей
Задачи внутри PVM изначально имеют нумерацию, не пригодную
для распределения обязанностей внутри приложения: эта нумерация позиционирует
их не внутри отдельного приложения, а в рамках Виртуальной Машины в-целом.
Для объединения в коллектив со строгой нумерацией от 0 до (число_задач-1)
в PVM, как правило, используются т.н. "группы".
В Crystal95 над PVM используется менее привычная схема: идентификаторы запускаемых по-одной ведомых ветвей мастер накапливает в массиве ITIDS(0:NPROC-1). Затем мастер распространяет массив всем ведомым. Каждая ведомая ветвь находит в ITIDS() свой идентификатор и запоминает его позицию в переменной IAM. Как нетрудно заметить, позиция строгим порядковым номером задачи является.
MPI-ветви последовательно нумеруются непосредственно при запуске. Поскольку ветвь 0 играет роль мастера, то в MPI_Send/MPI_Recv в качестве номера ветви вместо ITIDS(n) надо писать просто n+1. IAM и NPROC получаются посредством вызовов MPI_Comm_rank() и MPI_Comm_size() с последующим уменьшением на 1:
call MPI_Init( ierror ) call MPI_Comm_size( MPI_COMM_WORLD, nproc, ierror ) call MPI_Comm_rank( MPI_COMM_WORLD, iam, ierror ) nproc = nproc-1 iam = iam-1 if ( iam.lt.0 ) then call MasterNode else call second(ft) endifПеребор по всем ведомым ветвям:
было: do i=0,NProc-1 стало: do i=1,NProc
Все-таки, процесс "выжучивания" таких мест, в которых оказалось нужным добавить единичку, или, наоборот, отнять, а где-то оставить без изменений, занял определенное время.
Привязка ветвей к узлам, работа с файлами
PVM-вариант использует следующую схему: после того, как на очередном узле посредством pvm_spawn() запущена ведомая ветвь, ей передается имя каталога, в котором она должна будет размещать свои рабочие файлы. То есть: для ветви принудительно задается место создания. Поскольку рабочие файлы всех ветвей имеют одни и те же имена, каталоги обязательно должна различаться на тот случай, если несколько ветвей реально работают с одним и тем же диском, как это имеет место быть в Париксе.
В MPI официально не существует способа заставить загрузчик ветвь с конкретным номером запустить на узле с конкретным именем. Исключение составляет только нулевая ветвь - она грузится на узле, к которому подключена консоль пользователя. Необходимо, чтобы уже после запуска ветвь определила свое местонахождение посредством функции MPI_Get_processor_name, и в зависимости от него использовала соответствующий каталог. Пример здесь не приводится в силу несколько избыточного размера.
Проблема, из-за которой упомянутый пример становится ненужным, заключается в том, что и PowerPVM, и PowerMPI возвращают такое "псевдо-имя", которое с реальными сетевыми именами узлов никак не соотносится:
я выбрал самое простое решение: создал на каждом диске все возможные каталоги: один из них будет задействован выполняющейся на данном узле ветвью, а остальные останутся пустыми. Если впоследствии это решение покажется неудовлетворительным, придется мне переписать MPI_Get_processor_name() через gethostname() (кстати, на Парситеках класса CC/nK такой подход так же может оказаться не вполне корректным), а пока и так сойдет.
Упаковка (помещение разрозненных данных в передающий буфер) и распаковка (извлечение из приемного) может быть представлена в MPI одним из следующих аналогов:
Замена pvm_mcast
Ни в коем случае не следует "в лоб" заменять pvm_mcast или pvm_bcast
на MPI_Bcast. MPI_Bcast - коллективная функция, которая должна
быть вызвана во всех ветвях: в одной - на передачу,
в остальных - на прием. Она не совместима с функциями типа
"точка-точка", такими, как MPI_Send и MPI_Recv.
Решение "в лоб" заключается в следующем: надо
call pvm_mcast( TasksCount, TaskIds, MsgId, ierror )поменять на:
do i=1,TasksCount call MPI_Send( ... , rank=TaskIds(i), tag=MsgId, MPI_COMM_WORLD, ierror ) enddo
Замена на MPI_Bcast всех взаимосвязанных вызовов pvm_mcast и pvm_recv более элегантна, и гарантирует выигрыш по скорости работы, но требует намного больше времени, а кроме того, не всегда возможна, например, следующий текст MPI_Bcast'ом не заменить:
Мастер | Ведомые |
if(все_хорошо) then call pvm_mcast( N,slaves,MsgOk,info) else call pvm_mcast( N,slaves,MsgFuck,info) endif |
COMMON/PARAL1/NPROC,IAM,MPOINT,ITASK MPOINT=1
COMMON/PARAL1/NPROC,NNPROC,IAM,MPOINT,ITASK DO WHILE(MPOINT.NE.0) ! Сразу вылетает ...
А эти неожиданные эффекты я в виде отдельного примера воспроизвести не сумел (да и не пытался, откровенно-то говоря):
CHARACTER*72 Line i=0 ! правильно : i=1 call MPI_Recv( Line(i:i), 72, MPI_CHARACTER, ... );
SUBROUTINE SUB1 IMPLICIT DOUBLE PRECISION (A-H,O-Z) COMMON /PARAL1/ NPROC, IAM ! .... END SUB SUBROUTINE SUB2 IMPLICIT DOUBLE PRECISION (A-H,O-Z) COMMON /PARAL1/ NPROC, IAM ! .... END SUB
Недоделанность Фортрана вынуждает реализовывать недостающие функции в модулях, написанных на Си; а в Фортран-программе подпрограммы на Си, как известно, лишены возможности пользоваться отладочными выводами на консоль. Это придает отладке дополнительный динамизм.
Что касается дурного стиля написания программ, то Фортран усиленно формирует его в двух основных направлениях:
Как Фортран записывает метки в объектном файле?
Увы, по-разному. Пусть у нас имеется нечто простейшее на Фортране:
C This is PIPA.F call TheSub1 call The_Sub_2 endСкомпилируем его командой fc -c pipa.f и внимательно рассмотрим результат команды nm pipa.o. Мы увидим описания двух меток:
Компилятор | Метка 1 | Метка 2 |
---|---|---|
HP Fortran на SPP-1600 | thesub1_ | the_sub_2_ |
IBM XL Fortran на Парситеке | thesub1 | the_sub_2 |
GNU Fortran | thesub1_ | the_sub_2__ |
Во-первых, такое многообразие само по себе является несколько неожиданным и требует привыкания (читай: еще день коту под хвост); а во-вторых, обращение GNU Fortran'a с метками, содержащими подчерк, оказалось несовместимым со стандартными правилами: библиотека MPI для Фортрана написана на Си и экcпортирует метки вида "mpi_init_", а объектный файл, построенный g77, импортирует "mpi_init__". Положение спас секретный ключик "-fno-second-underscore", подробнее смотрите makefile.ptc. Пользуясь случаем, выражаю благодарность Дмитрию Федорову из Новосибирского Института ядерной Физики за оперативный ответ в RU.UNIX.LINUX.
Важно, что в итоге на Парситеке готовая программа работает неправильно. Модуль MPIintegrals всегда завершается с сообщением "core dumped". Сбой происходит в инструкции STOP, то есть при попытке нормального завершения - в самой последней команде. Это заведомо не моя ошибка. Сбой может принять одну из двух форм:
Error(n2 c1 t0), preMain: PxThreadKeysDestroy failed because: Device busy Error(n2 c0 t0), epx_loader: Execute failed with -1 (/export/home/il/cry95mpi/MPIintegrals) because: Device busy Segmentation fault (core dumped)... после чего файл OUTPUT оказывается нулевой длины. Куда это может годиться?!!
Как повысить скорость на Парситеке/CC
ЭВМ Парситек/CC является набором из независимых материнских плат, засунутых в один корпус, и соединенных скоростной внутримашинной сетью. Каждая плата оснащена своим жестким диском, с которого производится загрузка операционной системы. Однако Парикс-приложение этим диском не пользуется: Парикс перехватывает функции работы с файлами и перенаправляет их на диск входного узла. В-принципе, если ветви Crystal95 будут писать временные файлы на локальные диски узлов, а не через сеть на диск entry-узла, скорость работы должна сильно повыситься. Что для этого нужно:
Как я отлаживался
На Парситеке отлаживать параллельную программу невозможно:
отладчик DeTop не запускается - и точка. IBM'овским отладчиком XLdb
можно отлаживать только последовательные программы (без MPI и Парикса).
Поэтому с какого-то момента весь проект был перетянут на SPP,
где параллельный отладчик ЕСТЬ! Называется он CXdb.
После Turbo Debugger'a выглядит бледновато, но при некотором
навыке вполне пригоден для отладки любой степени сложности.
Из всех известных мне отладчиков CXdb - единственный, умеющий
отлаживать приложения, состоящие из нескольких процессов.
То, что SPP не был немедленно выбран платформой для разработки, привело к потере действительно большого количества времени. На Парситеке единственым инструментом отладки являлись проверочные выводы, которые требовалось скурпулезно составлять и расставлять (а если отладочный вывод тоже написан с ошибкой, а?). С отладчиком же все стало намного проще - я получил возможность "взламывать" задачу, как говорится, в лоб, заставляя ее работать методом грубой силы. Там, где многократно вызываемый кусок кода сбоил предположительно на поздних итерациях, перед отладкой в CXdb я запускал программу с проверочными выводами в этом куске; но реального толку в такой простой задаче от них немного.
SPP - самая популярная наша машина, при этом далеко не самая быстрая. Причина популярности - в аппаратной и программной надежности, и в исключительно широкой номенклатуре программных средств. На практике это сводится к тому, что на SPP редко бывает меньше 5-6 считающих пользователей, и машина сильно тормозит. На Парситеке ситуация обратная - больше одного пользователя не бывает; при этом сам компьютер находится на перманентном частичном ремонте.
Вопрос: значит ли это, что при покупке новых суперкомпьютеров я рекомендую ориентироваться на машины, подобные SPP ? Ответ: нет. Нашей казенной кормушке сейчас вообще должно быть не до покупки суперкомпьютеров, ни новых, ни старых, никаких.
Чтобы выполнить перевод и последующую отладку, мне пришлось разбираться в коммуникационной части исходной программы гораздо более подробно, чем мне бы хотелось (и чем ее авторы того заслуживают). Причиной тому стала ее запутанность.
PVM и MPI - это инструменты-братья, схожие по смыслу, но, как показывает опыт, весьма различные в мелочах.
Первая же серьезная работа убедительно показала, насколько ублюдочной, нежизнеспособной конструкцией являются компьютеры Парситек со всех точек зрения; насколько несоизмеримы оказались затраты на его покупку (в валюте) и сопровождение (в валюте и в рублях) с тем мизерным эффектом, который приносит (кому-то? кому? право, не знаю) его эксплуатация.
Судя по всему, адаптирование Crystal95 с PVM на MPI и Парситек наши pykoвoдящие 0рганы рассматривают как некий пилотный проект, за которым должно последовать еще 15-20 таких же блатных работенок.
я же, со своей стороны, нахожу нелепым повторять подобный перевод с эстонского на армянский больше одного раза: правильнее один раз потратить два-три месяца на перенос PVM под Parix (и пусть Genias подавится, не надо покупать PowerPVM), чем двадцать раз по две недели на очередную отдельно взятую чушь. Вот только как это объяснить руководящим?
А если совсем коротко, то - это было бессмысленное, тягостное занятие...
Илья Евсеев,
март 1999