М. П. Крутиков
При ближайшем рассмотрении, выясняется, что каждый узел представляет собой ``нормальную'' машину, со своим жестким диском, своей оперативной памятью. Более того, на каждом узле запущена своя копия операционной системы. Что же позволяет считать эту машину параллельным суперкомпьютером? А вот что: все узлы оборудованы специальным коммуникационным устройством, которое позволяет осуществлять быстрый обмен информацией между узлами. Таким образом, появляется возможность синхронизации процессов, запущенных на разных узлах, и информационного обмена.
Итак, Parsytec CC - это 16 компьютеров, связанных быстрой сетью. Возможности параллельного исполнения задания опираются на работу с ``быстрыми связями''. Интерфейсом к параллельной системе является операционная система PARIX (точнее Embedded PARIX), позволяющая скрыть от пользователя детали синхронного запуска задач.
Каждый узел имеет свой SCSI жесткий диск, с которого он загружается. Размер оперативной памяти в нашей конфигурации - 32Мбайт на узел.
Для решения параллельных задач используется PARIX (Parallel extensions to UNIX), которая реализована в виде надстройки над ОС AIX. При обращении к ОС PARIX, последняя захватывает ресурсы всех процессоров в указанном разделе и запускает на них вашу прикладную задачу (о разделах смотри ниже).
Важно, что PARIX - это однозадачная система: если один пользователь запустил PARIX-приложение на разделе, то никакой другой пользователь уже не сможет запустить своего PARIX-приложения на данном разделе, покуда первое приложение не закончит свою работу и не освободит раздел.
С одной стороны, такой подход несомненно ограничивает пользователей, заставляя их строго координировать свои расчетные задачи. С другой же стороны, такая схема обеспечивает наиболее благоприятный режим для запущенного приложения.
В трехмерном ящике с узлами не может быть дыр: если существует процессор с координатами (0,0,0) и существует (0,0,2), значит обязан существовать и (0,0,1). Кстати, узел (0,0,0) существует всегда (если в системе ненулевое количество процессоров).
Сие означает, что логическое пространство, занимаемое процессорами можно характеризовать положением угловых узлов. В нашей системе логическая конфигурация линейна: (0,0,0)-(15,0,0). Логическая конфигурация может быть (относительно легко) изменена. В принципе в системе с 16 процессорами возможны также конфигурации вида квадрата (0,0,0)-(4,4,0), (0,0,0)-(4,2,2) и др. Удобство той или иной конфигурации определяется в конечном счете характером решаемых задач (так, для расчетов матриц, вероятно, наиболее удобна конфигурация (0,0,0)-(4,4,0)).
Конечная трехмерная решетка, включающая в себя все процессоры данной машины называется основным разделом. Обычно основной раздел делится на несколько более мелких (и, возможно, перекрывающихся) разделов. Самым мелким, понятно, является раздел, состоящий из одного узла. Например, (0,0,0)-(15,0,0) у нас поделен на четыре четырехпроцессорных раздела (0,0,0)-(3,0,0), (4,0,0)-(7,0,0), (8,0,0)-(11,0,0) и (12,0,0)-(13,0,0) и шестнадцать однопроцессорных раздела. В общем случае, разделы могут перекрываться. Важнейшей характеристикой раздела является его размер, т.е. размер трехмерного ящика с узлами, входящими в данный раздел. Размер раздела (0,0,0)-(15,0,0) есть (4,1,1).
ОС PARIX может запускать приложения не только на основном разделе, но и на любом из логических разделов. Так, можно запустить задачу на четырехпроцессорном разделе. При этом останутся свободными три четырехпроцессорных раздела. На них также можно что-нибудь запустить. В вырожденном случае можно запустить одновременно 16 PARIX-приложений, но каждое, увы, будет иметь лишь один процессор. Как ведет себя PARIX, если разделы перекрываются? Так: если из двух перекрывающихся разделов один занят, то на другом не может быть запущено PARIX-приложение (блокируется).
И последнее (перед последующим). Ваша прикладная задача может узнать о параметрах раздела, на котором она запущена. Однако, точка отсчета раздела всегда транслируется в ноль, так что, приложение не может отличить, скажем, запущено оно на (0,0,0)-(3,0,0) или на (8,0,0)-(11,0,0). Оба случая для прикладной задачи будут выглядеть как раздел (0,0,0)-(3,0,0) с размером (4,1,1).
Еще раз подчеркнем отличие логических связей от физических:
Задача должна строиться так, чтобы объем требуемой оперативной памяти не превышал 32Mb (имеется в виду та часть задачи, которая будет выполняться на отдельном узле). То, что в целом, в системе 32x16=512Мb оперативной памяти вовсе не означает, что вы можете работать с массивом размером 512Mb!
Следует ответственно подходить к разработке схемы коммуникации между узлами, поскольку от этого может сильно зависеть производительность.
Очень важно понимать конфигурацию системы. Так, если в задаче очень много дисковых операций, следует запускать ее на том разделе, где есть IO-node с теми дисками, с которыми система работает, иначе данные будут передаваться через внутреннюю сеть машины (не через HSL), что довольно медленно. Наоборот, если задача чисто расчетная следует избегать использования разделов с IO-узлами, поскольку эти узлы могут быть загружены обменом данными. Особенно неприятен в этом случае Entry-node, на котором запущенное PARIX-приложение будет конкурировать за процессорное время с задачами других пользователей: компиляция и отладка программ.
Прикладная программа должна быть написана так, чтобы ее можно было запустить на произвольном количестве процессоров (масштабируема), поскольку на каком именно разделе она будет выполняться определяется пользователем при ее запуске.
Наконец, должно быть ясно, что адаптация вашей программы под PARIX есть не очень простая задача, и здесь нельзя отделаться опцией компилятору (как в ConvexOS). Вопрос: а что будет, если кто-нибудь просто откомпилирует свою задачу, не внося в нее изменений? Ответ: она будет успешно откомпилирована, однако если ее запустить, то задача будет считаться параллельно (и независимо) от начала до конца на каждом из процессоров в разделе. Так что для 16-процессорного раздела вы получите 16 идентичных результатов. Скорость счета будет равна скорости счета на одном процессоре. Из этого должен последовать неизбежный вывод о том, что, увы, придется трудиться, если есть желание использовать все мощности системы Parsytec CC/16.
Следующие, характерные для многопроцессорной системы, задачи вам предстоит решать:
С точки зрения пользователя, обращение к возможностям ОС PARIX происходит с помощью команды px
px command
Собственно команда px модифицирует пользовательское окружение, так что command понимается уже как команда ОС PARIX. Например,
px man run
выдаст manual pages из ОС PARIX, в то время как просто команда man ... выдает справочную информацию по AIX.
Приложения AIX не будут работать, если их запустить на разделе PARIX, и наоборот, AIX не ``поймет'' запросов запущенного приложения PARIX. Так что PARIXу - PARIXово.
Parsytec CC/16 оснащен стандартными для AIX компиляторами языков C и FORTRAN. С помощью этих компиляторов вы можете создать как приложение для AIX, так и приложение для PARIX. Для приложений AIX вы используете команду
ancc -o test test.c
Тогда как для того, чтобы сделать исполняемый файл для PARIX, надо дать команду
px ancc -o test.px test.c
Как обычно, команда-префикс px модифицирует следующую за ней команду так, что она превращается в команду ОС PARIX.
Отметим, что по соглашению исполняемые файлы PARIX имеют суффикс .px. Это позволяет отличать их от приложений, скомпилированных для AIX.
Компилятор языка FORTRAN вызывается командой f77. Если вы компилируете PARIX приложение, следует использовать команду px f77, например
px f77 -o test.px test.f
Разделы имеют атрибуты. Основным атрибутом является количество процессоров.
Каждый раздел может использовать Entry-узел и IO-узлы машины для операций ввода-вывода.
Управление разделами осуществляется командой OC PARIX nrm (Network Resource Manager). Для просмотра конфигурации разделов, используется команда
px nrm -pc
которая выводит список определенных в системе разделов и их атрибуты:
ultra10% px nrm -pc Partition | Attributes | Servers ----------+------------+-------- p1 | | earth | p16 | east | p4 | north | p4 | south | p4 | west | p4 |
Более подробную информацию даст команда
px nrm -pp
где будет видна привязка разделов к физической конфигурации системы:
ultra10% px nrm -pp CC Configuration: Wed Jul 24 13:12:23 1996 edited by root@ultra10 CC ParsytecCC: - no init info - Topologie = using file /epx/epx/nrm/system.rt, Dimensions (16,1,1), Command Port 12544 Node ultra10 at position (0,0,0) with attributes ( master-epxd IO-node ) Node cc001736 at position (1,0,0) with attributes ( P-node ) Node cc001737 at position (2,0,0) with attributes ( P-node ) Node cc002245 at position (3,0,0) with attributes ( IO-node ) Node cc002243 at position (4,0,0) with attributes ( IO-node ) Node cc001738 at position (5,0,0) with attributes ( P-node ) Node cc001739 at position (6,0,0) with attributes ( P-node ) Node cc002246 at position (7,0,0) with attributes ( P-node ) Node cc001732 at position (8,0,0) with attributes ( P-node ) Node cc001733 at position (9,0,0) with attributes ( P-node ) Node cc001734 at position (10,0,0) with attributes ( P-node ) Node cc001735 at position (11,0,0) with attributes ( P-node ) Node cc001740 at position (12,0,0) with attributes ( P-node ) Node cc001744 at position (13,0,0) with attributes ( P-node ) Node cc001743 at position (14,0,0) with attributes ( P-node ) Node cc001742 at position (15,0,0) with attributes ( P-node ) Server filesrv with command line (/epx/bin/dserver -h) Partition p1: corner atoms = (0,0,0) (0,0,0) servers = ( ) Partition earth: attributes = ( p16 ), corner atoms = (0,0,0) (15,0,0) servers = ( ) Partition east: attributes = ( p4 ), corner atoms = (0,0,0) (3,0,0) servers = ( ) Partition north: attributes = ( p4 ), corner atoms = (4,0,0) (7,0,0) servers = ( ) Partition south: attributes = ( p4 ), corner atoms = (8,0,0) (11,0,0) servers = ( ) Partition west: attributes = ( p4 ), corner atoms = (12,0,0) (15,0,0) servers = ( )
Следующая команда
px nrm -pa
показывает статус разделов (занят/свободен) и имя пользователя, занявшего раздел. Разделы, помеченные звездочкой являются свободными:
ultra10% px nrm -pa CC ParsytecCC: Partition | Used by | Since ----------+--------------+------------------------- p1 | east | earth | east | east | mike@ultra10 | Sat Nov 16 22:52:39 1996 north | * | Mon Nov 11 14:31:58 1996 south | * | Mon Nov 11 14:31:58 1996 west | * | Mon Nov 11 14:31:58 1996Из данной таблицы видно, что раздел east занят пользователем mike. При этом, блокируется раздел earth, т.к. он перекрывается с east (раздел earth является основным разделом и, естественно, он блокируется при запуске приложения на любом из подразделов), а также блокирован раздел p1, т.к. этот однопроцессорный раздел содержит узел из раздела east. Свободными остались три четырехпроцессорных раздела north, south и west.
Другой способ представления той же информации дает команда
px nrm -pm
которая представляет систему наглядно в виде матрицы, где элементу соответствует один процессор:
ultra10% px nrm -pm CC ParsytecCC: Actual users of CC ParsytecCC: A : mike@ultra10 Allocation of CC ParsytecCC: AAAA************
Раздел можно именовать либо явно (тем именем, которое присвоено разделу администратором - см. px nrm -pc), либо неявно - указанием атрибутов раздела. Последнее более удобно, так как вы можете просто сказать, что ``требуется раздел с 4 процессорами'', и система выберет вам первый свободный 4-процессорный раздел.
Например
px run -a earth hello.px
Запускает PARIX-приложение hello.px на разделе с именем earth,
px run -a p4 hello.px
Запускает ту же программу на 4-х процессорном разделе.
Отметим, что команда run занимает указанный раздел, так что он (вместе с перекрывающимися разделами) становится недоступен для запуска других приложений PARIX.
Однако в ряде случаев (при аварийном завершении) раздел может остаться занятым. То есть, несмотря на то, что никакое приложение не исполняется на разделе, он остается недоступен для других пользователей. Поэтому будьте очень внимательны, и убедитесь, что уходя из системы, вы не оставили блокированных за вами разделов. Используйте для этого команду px nrm -pa. Разблокировать раздел можете либо вы сами, либо системный администратор.
Для явного освобождения раздела дайте команду PARIX nrm -f, например
px nrm -f earth
Отметим, что при освобождении раздела требуется указать имя раздела (а не его атрибуты).
PARIX позволяет организовывать виртуальную конфигурацию раздела. Так, можно потребовать, чтобы раздел, который имеет размеры (2,1,1) выглядел для приложения как раздел (2,2,1). Такая процедура называется организацией виртуального раздела.
Существует ограничение: каждый физический процессор может эмулировать не более 16 виртуальных. Так что на Parsytec CC/16 нельзя организовать виртуальный раздел с числом процессоров более 256.
Когда полезны виртуальные разделы?
Для создания виртуального раздела используются аргументы к команде run:
px run -a earth 4 2 2 test.px
запускает приложение на физическом разделе earth, но приложение ``видит'' виртуальный раздел с конфигурацией (4,2,2).
На каждом узле имеется набор глобальных данных (доступных программе), которые определяют положение узла в разделе. Именно
Обмен данных основан на концепции виртуальных связей между процессорами, которые образуют каналы ``точка-точка'' между любыми процессорами. Набор созданных виртуальных связей задает виртуальную топологию (решетка, конвейер, дерево, гиперкуб).
В PARIX связи могут быть одного из следующих трех видов:
#include < stdio.h> int main() { printf("Hello, world\n"); return 0; }Теперь надо построить PARIX-приложение. Для этого даем команду
px ancc -o hello.px hello.c
Теперь мы получили исполняемый файл hello.px, который можно запустить. Действительно, команда
px run -a p4 hello.px
запустит PARIX-приложение на разделе из 4-х процессоров и вы получите четыре строчки (ответит каждый процессор) Hello, world
Теперь немного модифицируем программу.
#include < stdio.h> #include < epx/root.h> int main() { int ID; ID=GET_ROOT()-> ProcRoot-> MyProcID; printf("Hello, world! This is processor %d.\n",ID); return 0; }
Скомпилируем программу и запустим. Каждый процессор сообщит свой номер. На этом простом примере демонстрируется важное свойство PARIX: на каждом узле изначально запущены идентичные приложения. Но поведение процесса изменяется в соответствии с позицией процессора в решетке раздела. Функция GET_ROOT осуществляет доступ к (глобальным) данным о конфигурации раздела и положении текущего узла в решетке. Эта функция возвращает указатель на данные следующей структуры:
typedef struct { int MyProcID; /* my processor number */ int MyX; /* my X coordinate */ int MyY; /* my Y coordinate */ int MyZ; /* my Z coordinate */ int nProcs; /* Number of processors */ int DimX; /* array dimension in X direction */ int DimY; /* array dimension in Y direction */ int DimZ; /* array dimension in Z direction */ } RootProc_t;Эти данные не являются независимыми, т.к. справедливо следующее
MyProcID = (MyZ * DimX * DimY) + (MyY * DimX) + MyX nProcs = Dimx * DimY * DimZ
Эта информация позволяет также вычислить идентификаторы соседних процессоров.
Более содержательные примеры см. в каталоге /epx/epx/examples.
C C Hello, world! C PROGRAM HELLO WRITE (*,*) 'Hello, world!' STOP ENDТеперь надо построить PARIX-приложение. Для этого даем команду
px f77 -o hello.px hello.f
Теперь мы получили исполняемый файл hello.px, который можно запустить. Действительно, команда
px run -a p4 hello.px
запустит PARIX-приложение на разделе из 4-х процессоров и вы получите четыре строчки (ответит каждый процессор) Hello, world!
Теперь немного модифицируем программу.
C C Hello, world! (Variant 2) C PROGRAM HELLO INTEGER MYPROCID ID=MYPROCID() WRITE (*,*) 'Hello, world! This is processor ',ID,'.' STOP END
Скомпилируем программу и запустим. Каждый процессор сообщит свой номер.
Более содержательные примеры ищите в каталоге /epx/epx/examples/f77.
В рамках одного узла можно организовать несколько ветвей процесса (threads). Каждая ветвь выполняется параллельно. Все созданные ветви одного процесса имеют доступ к глобальным данным процесса. Говорят, что все ветви исполняются в рамках одного контекста. Каждая ветвь имеет свой собственный стек.
Понятие ветви является основным для модели программирования в симметричных мультипроцессорных системах (например Convex) и должно быть уже известно тем пользователям, кто создавал параллельные приложения для Convex.
Функция CreateThread создает новую ветвь процесса. Как обычно, стандартным методом синхронизации ветвей одного процесса является механизм семафоров.
Другим элементом параллельного программирования является создание нового контекста. В любой момент некоторая ветвь процесса может загрузить и запустить новый процесс (т.е. контекст), со своим собственным кодом, глобальными данными, и начальной ветвью. Породившая ветвь будет ожидать окончания выполнения запущенного контекста. Новый контекст создается функцией Execute.
Типичным приемом в параллельном программировании является следующий: на каждом процессоре запускается начальное приложение, которое определяет свое положение, и в зависимости от результата запускает тот или иной контекст.
Приведем список функций:
AExit AINIT AInfo AInit ARecv ASend ASync AbortServer AddHead AddTail AddTop AddTop_Data BreakLink ChangePriority CondSelect CondSelectList ConnectLink CreateSem CreateThread DestroySem Execute Free2DGrid Free2DTorus Free3DGrid Free3DTorus FreeClique FreeDeb FreeHCube FreePipe FreeRing FreeStar FreeTop FreeTree GETGLOBAL GET_ROOT Get2DGrid_Data Get2DTorus_Data Get3DGrid_Data Get3DTorus_Data GetClique_Data GetDeb_Data GetGlobId GetHCube_Data GetLink GetLinkCB GetLocal GetPipe_Data GetPriority GetRing_Data GetStar_Data GetTop_Data GetTree_Data InitList InitSem LIST LogError Make2DGrid Make2DTorus Make3DGrid Make3DTorus MakeClique MakeDeb MakeHCube MakeLink MakePipe MakeRTree MakeRing MakeStar MakeTree NewTop PostInsert PreInsert ReceiveOption ReceiveOption_B Recv RecvLink RecvNode RemHead RemTail Remove SELECT SEMAPHORES SEND SENDNODE SearchList Select SelectList Send SendLink SendNode SetLocal Signal StartThread TIME TIME TOPOLOGY TOPOLOGY Terminate TestWait TimeAfter TimeAfterOption TimeAfterOption_B TimeNow TimeWait VIRT_TOP Virt_Top Wait WaitThread WalkList