Книга
Книга
Краткая история параллельных вычислений
Параллельная обработка - не новое понятие. Операционные системы по крайней мере уже лет двадцать имеют дело с моделированием и реальным воплощением параллельного действия компьютеров. Разработчики аппаратных средств занимались проблемами параллелизма еще со времен фон Неймана. Действительно, в ранних проектах машин фон-неймановского типа рассматривались различные свойства параллельной работы. Однако эти проекты отвергались главным образом из-за низкой надежности компонентов, доступных для конструирования машин. Способствовал реализации возможно более простых проектов и общий недостаток опыта.
Ныне производители машин стали предоставлять разработчикам и программистам средства управления при использовании множества процессоров непосредственно для совместного решения одной задачи.
Некоторый беспорядок в области параллельного программирования существует в настоящее время благодаря большому разнообразию компьютерных культур, которые стали источниками терминологии, используемой энтузиастами параллельного программирования. Часто это приводит к ситуациям, когда одно базовое понятие может быть описано тремя или четырьмя словами или фразами, которые имеют почти, но не полностью идентичный смысл.
Еще большее расхождение в терминологии возникает из-за различия между машинами с общей памятью и с передачей сообщений. Каждый тип машины может моделировать машину другого типа, однако хорошо видна ли-
ния раздела, что, конечно, отражается в двух перекрывающихся, но не идентичных множествах терминов для родственных понятий.
Терминология в области параллельной обработки
Когда на машине выполняется некоторая программа, вся работа, необходимая для ее выполнения называется задачей или процессом. При выполнении на многопроцессорной машине задача может разделяться на несколько (возможно, большое) число различных и независимых ветвей выполнения, которые в отсутствии других ограничений могут выполняться одновременно разными процессорами.
Каждую независимую ветвь выполнения называют процессом. Часто нескольким процессам необходимо совместно использовать некоторую информацию. Например, один процесс может вычислять некоторые значения, а другой - использовать их. Если эти значения хранятся в памяти, доступной обоим процессам, их называют разделяемыми данными. Доступ к разделяемым данным должен всегда производиться с осторожностью, чтобы была уверенность в корректном выполнений программы. Нельзя допускать, чтобы один процесс записывал данные в то время, как другой процесс делает попытку воспользоваться ими (прочитать их).
Критической областью называют секцию программы, которая должна выполняться с исключительным правом доступа к разделяемым данным, на которые имеются ссылки в этой программе. Процесс, готовящийся войти в критическую область, может быть задержан, если любой другой процесс в это время выполняется в подобной критической области. Одним из средств, позволяющих программистам или компиляторам надежно организовывать критические области, являются семафоры или замки (блокировки).
В машинах с распределенной памятью (например, Intel Hypercube) для синхронизации вычислений образом, сходным с замками и семафорами в машинах с общей памятью, используются сообщения. Конечно, замки и сообщения не исключают друг друга, так как возможно представить себе гибридные машины, в которых оба средства синхронизации могут быть использованы одновременно.
Один способ периодически координировать выполнение нескольких ветвей вычисления состоит в образовании барьера. В программах решения научных задач оказались полезными барьеры нескольких типов. При использовании барьера одного типа предполагается, что все процессы некоторой группы должны достигнуть определенной точки своей программы (барьера), прежде чем любому из них будет разрешено двинуться дальше. Вариантом организации барьера может быть однониточная следующая за барьером критическая секция из одной ветви, которую выполняет любой (но только один) процесс из группы процессов. Модернизация этого типа барьера заключается в разрешении только последнему из прибывших к барьеру процессу выполнять критическую секцию из одной ветви.
Когда некоторый процесс пытается овладеть замком, то это действие должно быть неделимым, т. е. только один из множества процессов может получить доступ к замку. Процессы, которым не удалось захватить замок, могут быть либо задержаны (когда имеется другой задержанный процесс, ожидающий освобождения центрального процессора), либо переведены в состояние занятого ожидания (иногда называемого прокруткой), в котором процесс периодически пытается получить замок. (Последняя стратегия, очевидно, не привлекательна, поскольку возможно, что замок не будет когда-либо освобожден!)
Взаимной блокировкой (тупиком) называют ситуацию, в которой каждый процесс из группы процессов ожидает, когда другой процесс из этой же группы произведет некоторое действие (обычно откроет или освободит замок). “Бесконечный обмен сообщениями" (livelock) - более активная, но не более продуктивная ситуация. Так называют ситуацию, в которой каждый процесс из группы процессов занят передачей сообщений (сигнализацией) другим процессам группы, однако не делает ничего, чтобы продолжить выполнение вычислений.
В реальной жизни иногда встречаются очень запутанные случаи. Возможно, что некоторые части параллельной программы окажутся заблокированными, некоторые - зацикленными на передаче сообщений, в то время как другое выполняют процесс, ведущий к получению желаемого ответа. Даже возможные для последовательной программы ошибки маскируются как параллельные ошибки [2].
13. Вопросы создания программного обеспечения
Параллельное программирование может быть еще более разочаровывающим, чем традиционное программирование. К обычным для всех видов разработки программ проблемам здесь добавляются новые, и для успешного параллельного программирования необходимо считаться с дополнительными критериями. Разработка программного обеспечения для параллельных ЭВМ непосредственно связана со следующими проблемами:
исключение взаимных блокировок и бесполезных обменов сообщениями;
предохранение от нежелательных условий состязаний;
избежание образования слишком большого числа параллельных процессов;
обнаружение завершения программы простейшим способом.
К новым критериям оценки параллельных программ относятся:
ускорение программы в зависимости от числа процессоров;
величина затрат времени на синхронизацию;
влияние размера задачи на ускорение;
максимальное число занятых процессоров при решении задачи;
детерминизм выполнения программы.
Кроме того, при проектировании программ возникают вопросы нового типа:
какой размер должны иметь “порции" параллельной программы?
сколько параллельных процессов должно быть образовано?
какой вцд синхронизации процессов должен быть принят?
как должен управляться доступ к разделяемым данным?
как гарантировать детерминированное выполнение программы?
как должны подразделяться задачи обработки для достижения наибольшей эффективности использования доступного параллельного оборудования?
Отладка параллельных программ очень трудна. Условия состязаний могут маскироваться под логические ошибки программы. Например, когда на машине НЕР возникает блокировка, адреса, на которых зависли различные параллельные процессы, ожидая друг друга, можно определить не без некоторых усилий. Однако значительно труднее представить себе последовательность событий, которые привели программу в тупиковую ситуацию. Попытки проведения традиционной отладочной трассировки могут оказать сильнейшее влия-
2 1Ь5
ние на поведение параллельной программы. Недетерминированные программы при добавлении средств трассировки часто “фиксируют сами себя", так как трассировка преобразует часть выполнения программы в последовательную форму.
Задача, используемая в книге в качестве примера
Авторы этой книги хотели выбрать для реализации на каждой параллельной ЭВМ одну очень небольщую задачу. Остановились на задаче приближенного вычисления числа Пи (программа Pi) с использванием правила прямоугольников для вычисления определенного интеграла
1
f(x)dx
О
4
где f(x) -
(1+х2)
Согласно правилу прямоугольников, п
Rn(f) - h ^ f(xj)i=l
где h=l/n, a X|«(i-l/2)h.
Так как для вычисления такого интеграла методом прямоугольников требуется небольшая параллельная программа, в ней не встретятся некоторые из упомянутых выше “ловушек" параллельного программирования. Однако даже эта очень крошечная тестовая задача будет полезна для выявления изюминок различных сред параллельного программирования, а кроме того, она позволяет провести некоторые интересные эксперименты на различных параллельных машинах. Следует отметить, что программа Pi - "процессорная" программа. С ее помощью нельзя исследовать другие важные вопросы параллельных вычислений, например критическое влияние процессов ввода- вывода.
План типичной главы книги
Цель описаний машины в гл. 3 - 10 - охарактеризовать аппаратные и программные средства.
Аппаратные средства описываются лишь с той степенью подробности, которой достаточно для знакомства программиста с различными свойствами параллельной машины, а также для пояснения рассматриваемого примера. Более подробную информацию о каждой вычислительной системе можно найти в приложениях.
Вводится описание среды параллельного программирования, обеспечиваемой машиной и операционной системой, отмечается, какие особенности аппаратных средств параллельных машин проявляются в программном обеспечении. Дано также описание обычной последовательности разработки программ, включающее краткое описание шагов разработки, отладки и измерения времени выполнения параллельной программы.
Затем в каждой главе рассматривается пример параллельной программы. Обсуждается стратегия параллельного программирования для вычисления числа Пи (иногда дается несколько вариантов). Приводятся распечатки программы, снабженные подробными комментариями их параллельных свойств. Цель этого описания - дать достаточные пояснения к небольшим, но существенным для понимания устройства всей программы фрагментам параллельных программ. Читатели, желающие получить более полную информацию о механизмах распараллеливания, могут найти подробности в приложениях.
Далее идет речь о производительности рассматриваемых машин, включая те аспекты синхронизации, которые не были затронуты в пояснениях программного примера.
Часть каждой главы посвящена “ловушкам" и проблемам, с которыми приходится сталкиваться как при последовательном, так и при параллельном программировании. Описывается некоторый положительный опыт.
Каждая глава завершается выводами, благодарностями и ссылками на литературные источники.
Приложения содержат распечатки программ для рассмотренного примера, извлечения из документации по параллельному программированию и другую информацию, имеющую отношение к основным главам книги.
В гл. 2, написанной в 1984 г., рассматриваются результаты некоторых ранних экспериментов с параллельными вычислениями на машине НЕР и на суперЭВМ CRAY Х-МР, проведенных двумя исследователями в Ливерморской национальной лаборатории им. Лоуренса. В этой главе рассматриваются вопросы, касающиеся переноса прикладного программного обеспечения на многопроцессорную вычислительную систему, и дан набросок возможной стратегии разрешения возникающих при этом проблем. Препринт этой главы был одним из основных стимулов создания всей книги.
Библиография
W.Danny Hillis, “The Connection Machine", Cambridge, MA: MIT Press, 1985.
Robert E. Hiromoto, Los Alamos National Laboratory, private communication.
Использование
многопроцессорных систем: вопросы и возможности
Джеймс Р. Мак-Гроу, Тимоти С. Акселрод
Поведение даже очень коротких параллельных программ может оказаться удивительно сложным. Тот факт, что программа с некоторым конкретным набором данных выполнилась правильно один или даже сто раз, не гарантирует того, что завтра эта программа с тем же самым набором исходных данных не завершится с ошибкой.
Введение
Близится время, когда появится новое поколение суперЭВМ. Создатели вычислительных машин, неспособные обеспечить достаточную производительность только за счет быстрых аппаратных компонентов^ предлагают ныне многопроцессорные системы как новое направление в области разработки ЭВМ. Как и при внедрении первого векторного процессора, теперь появилась необходимость в освоении других форм высокоскоростных вычислений. Цель данной работы состоит в рассмотрении вопросов и ситуаций, с которыми «можно встретиться при подготовке к такому переходу.
Сделаем два основных предположения относительно условий использования многопроцессорных систем. Первое состоит в том, что все ресурсы вычислительной системы должны одновременно использоваться для выполнения одной программы. Второе предположение заключается в том, что в программах должны использоваться более общие формы параллелизма, чем доступные в случае применения только векторизации.
Сначала о практичности. Конечно, можно остановиться на использовании многопроцессорной системы путем распределения доступных машинных ресурсов (например, памяти) по процессорам и выполнении отдельных потоков заданий на каждом из них. В близком к такому способу виде подобная возможность, конечно, была использована. Однако, чтобы многопроцессорные системы использовать эффективно, нужно решать задачи как можно большего размера в течение короткого времени. Следовательно, необходимо сосредото-
чить внимание на вопросе распределения одной задачи по всем доступным процессорам с целью параллельного ее решения.
Второй вопрос касается слабости, присущей векторизации. Хотя векторизация и крайне полезна, она не включает многих форм параллелизма, которые нам потребуется использовать в будущем. Конечно, нельзя отказываться от векторизации, но необходимо рассматривать и другие виды параллелизма.
Идея параллельной обработки непосредственно поднимает много трудных вопросов. Рассмотрим, например, следующие:
Какие средства можно использовать в программе для выражения различных форм параллелизма?
Как необходимо описывать различные виды синхронизации?
Какой эффект будет давать параллельная обработка на тех типах алгоритмов, которые наилучшим образом используют многопроцессорную систему?
Будут ли различные виды параллелизма приводить к неповторяемости результатов выполнения программ?
Как отлаживать параллельные программы?
Каким образом определить, когда параллельную программу можно считать корректной?
Большая часть этих вопросов будет рассматриваться здесь в двух направлениях. В следующем разделе описаны четыре свежих и характерных примера, связанные с попыткой программирования многопроцессорных систем. В каждом случае описывается основная задача, которую предстояло решить на конкретной машине. Затем описываются результаты наблюдения за поведением программы и стратегии отладки при попытках устранения ошибок. Каждый пример задачи завершается анализом основных причин возникших проблем и извлечением уроков соответствующего эксперимента. Вслед за этим разделом обсуждаются четыре базовые стратегии программирования многопроцессорных систем, которым было уделено большое внимание в литературе по вычислительным наукам. Для каждой из стратегий сделана попытка оценить перспективы ее применения к решению крупномасштабных научных задач, решаемых в Ливерморской национальной лаборатории им. Лоуренса и в Лос- Аламосской национальной лаборатории.
Опыт расширений языка Фортран
В этом разделе описываются некоторые вопросы параллельной обработки, связанные с использованием различных видов расширения языка Фортран. Цель состоит в рассмотрении на относительно высоком уровне вопросов и задач, с которыми могут встретиться программисты, желающие использовать эту стратегию при применении параллельных машин. Использование языка Фортран - хорошо испытанный подход к проектированию алгоритмов и программированию. Однако параллельная обработка имеет свой собственный набор ограничений, которые также необходимо учитывать. В этом разделе мы попытаемся показать, что согласование между ними в лучшем случае затруднительно. К наиболее существенным относятся следующие особенности параллельного программирования:
часто встречаются неповторяемые ошибки;
корректный результат не позволяет делать вывод о корректности программы;
обычные методы отладки могут дать искаженную информацию;
. перенос на параллельные системы существующих программ требует почти полного и всестороннего их пересмотра.
Некоторые из рассматриваемых здесь работ были выполнены на вычислительной системе Denelcor HEP, остальные - проведены на суперЭВМ CRAY Х-МР. Расширения Фортрана в этих двух системах несколько отличаются друг от друга, но для целей нашего обсуждения их можно считать весьма близкими. В обоих вариантах предусмотрен базовый механизм для инициации параллельных процессов. Например, в ЭВМ НЕР подпрограмма может быть вызвана либо с помощью оператора CALL, либо с помощью оператора CREATE. В последнем случае вызвавшая программа (подпрограмма) продолжает свое выполнение, а вызванная подпрограмма становится отдельно выполняемым процессом. Принципиально то, что программист полностью ответственен за то, какие операции будут выполняться параллельно. Связь между процессами в обеих системах осуществляется с использованием разделяемых данных. Обе системы предоставляют программисту средства для организации критических секций. Главное здесь, что на программиста ложится вся забота о том, чтобы при каждом использовании этих средств действительно был обеспечен надежный доступ к разделяемым данным.
После столь беглого обзора представим несколько примеров параллельных задач, с которыми нам приходилось встречаться.
Пример 1. Назначение работ
Эта программа была написана для вычисления определенного интеграла функции одной переменной в пределах от А до В. Простейший параллельный алгоритм для решения этой задачи состоит в разбиении интервала интегрирования на N подынтервалов и приписывании каждому подынтервалу отдельного процесса. Каждый такой процесс может выполняться независимо, реализуя некоторый алгоритм, например правило трапеций, при вычислении интеграла для своего подынтервала. При своем завершении каждый процесс входит в критическую секцию и прибавляет свою часть результата к значению разделяемой глобальной переменной. При образовании всех процессов программа дает каждому процессу уникальный идентифицирующий его номер, который можно использовать для определения подынтервала, соответствущего данному процессу. Для этого используется следующий фрагмент программы на Фортране:
00 100 I - 1 , N
CREATE CALC (I) 100 CONTINUE
В каждом процессе CALC используется формула
(В - А) / N * (I - 1) * А
для нахождения левой границы подынтервала. Подробное изложение остальных частей программы в предлагаемом здесь обсуждении необязательно.
Полученные результаты
На первом этапе тестирования выполнялась программа с одним процессом (в предыдущем цикле вместо оператора CREATE использовался оператор CALL). После завершения этого этапа тестирования было сделано предположение о том, что тело функции дейстует корректно (при условии, что не выполнялись другие операторы CREATE и не производилось обращение к разделяемым переменным где-либо помимо финальной критической секции). Тестирование параллельного варианта программы выявило сумбурность ее поведения. Никакие два тестовых прогона не давали одинакового результата и никакой результат не совпадал с известным правильным ответом. Даже повторные прогоны при одном и том же значении м давали разные результаты. Сначала была сделана проверка в критической секции, чтобы убедиться, что каждый раз к общей сумме прибавляется только одно полученное значение. Эта программа действительно оказалась правильной.
На следующем шаге отладки критическая секция не использовалась и каждый процесс отдельно записывал свои частичные результаты в массив (значение I использовалось для указания места записи результата). Следующее множество прогонов показало, что несколько позиций в массиве не получило никаких значений (или если значения и были, то они равнялись нулю). Каждый прогон показывал, что безрезультатными оказывались вычисления в различных подынтервалах, однако наибольшая их концентрация наблюдалась у левой границы интервала (А,В). Кроме того, сумма записанных в массив значений была значительно меньше, чем общий результат, полученный путем суммирования в разделяемой переменной всех значений, вычисленных процессами.
Дальнейшее тестирование показало, что каждый процесс выполнял некоторые вычисления для своего подынтервала и записывал результаты в массив. Было выявлено, что в то время, как некоторые подынтервалы игнорировались, другие - обрабатывались более чем один раз. Кроме того, распределение повторно используемых интервалов изменялось от прогона к прогону, и никакой корреляции обнаружить не удалось. Наконец, было замечено, что не каждый процесс получал различные значения I. Некоторые процессы получали одно и то же число, в то время как другие значения I не использовались ни одним из процессов.
Причины некорректного поведения программы
Наконец, выяснилось, что причиной некорректного поведения программы было непреднамеренное (и нежелательное) распределение данных, которое возникало в упомянутом операторе CREATE. В Фортране все параметры передаются “по ссылке “, а это значит, что адрес переменной I передавался при каждом вызове подпрограммы CALC и, таким образом, становился общим для всех N процессов и исходного процесса, образованного каким-то другим процессом. При создании каждого процесса происходит обращение к переменной I, используемой для вычисления интервала, соответствующего этому процессу. Однако в то же самое время исходный процесс производит изменение переменной I как часть цикла DO. В зависимости от точности синхронизации и планирования процессов любой процесс мог получить либо предназначенное для него значение I, либо некоторое произвольное более позднее значение I.
Еще одно замечание. Для устранения этой ошибки часто предлагается простой прием, навеянный многолетним использованием языка Фортран. Опыт подсказывает, что при передаче в качестве параметра для подпрограммы CALC некоторого выражения (например, что-нибудь типа 1*1) действия
подпрограммы не будут изменять значение I в основной программе. Это решение, казалось бы, устраняет нежелаемую общую переменную. Но это не так. Фортран просто образует временную переменную для хранения значения выражения 1*1 и адрес этой временной переменной передает затем в подпрограмму CALC. Но теперь вместо совместного использования адреса переменной I процессы совместно используют адрес временной переменной! Простейшее корректное решение состоит в образовании массива, содержащего аргументы для каждой задачи. В этом случае программа будет выглядеть следующим образом:
DO 100 I - 1 , N IARG(I) - I CREATE CALC (IARG(I))
100 CONTINUE
Выводы
Этот простой пример показывает некоторые элементарные проблемы, обычно встречающиеся при параллельных вычислениях. При каждом прогоне программы с одинаковыми исходными данными могут получиться различные результаты. Природа этих ошибок не помогает выявить ту часть программы, которая обусловливает различное ее поведение. Отметим полезность использования массива для сохранения результата каждого процесса, однако причина для путаницы еще сохраняется, поскольку предполагалось, что при использовании индекса I результат вычисления в каждом процессе будет присваиваться различным элементам массива. На самом деле, так как несколько процессов имели одно и то же значение I, они обрабатывали один и тот же интервал и записывали в массив один и тот же ответ. Это выглядело просто, как будто несколько процессов не выполняли никакой работы, и естественным приемом отладки стало использование еще одной общей переменной (несколько процессов имели доступ к одним и тем же элементам массива). По общему признанию ошибка в программе была предельно простой. Нас расстроил тот факт, что обнаружение подобной ошибки потребовало так много энергии и времени.
Пример 2. Двойственные алгоритмы
Следующая проблема программирования относится к более сложной форме параллельной обработки. Целью была реализация интегрирования методом “разделяй и властвуй44 с использованием алгоритма, называемого адаптивной квадратурой [1 ]. В алгоритме предполагается, что затраты на вызов функции крайне высоки. Поэтому желательно во время выполнения программы решать, в каких точках интервала лучше выполнять вычисление функции. Согласно стратегии “разделяй и властвуй44, делается попытка разбить задачу по независимым интервалам, и, оценивая с помощью некоторого теста качество аппроксимации на каждом интервале, принимать независимые решения о необходимости его дальнейшего разбиения. В результате некоторые подынтервалы потребуют незначительной обработки, а другие будут разбиваться на все более мелкие подынтервалы.
Одна из целей нашего анализа заключалась в том, чтобы убедиться, что в программе не порождается слишком много ветвей параллельных вычислений. В неуправляемой ситуации такая программа легко порождает тупиковую
ситуацию (различные части программы требуют для своего продолжения продвижения других частей программы, однако взаимность таких потребностей не позволяет процессу сдвинуться с места). Например, можно пожелать сохранения списка всех интервалов, нуждающихся в дальнейшей обработке. Что произойдет, если список заполнен, а все активные процессы требуют дальнейшего помещения интервалов в список? Хочется иметь абсолютную гарантию защиты программы от любых возможных блокировок.
Эта цель была достигнута благодаря разработке двух версий программы, реализующей алгоритм адаптивной квадратуры. Первой (и основной) была параллельная программа. В ней порождались дополнительные процессы по мере появления дополнительной работы. Но при исчерпании любых машинных ресурсов (как, например, памяти, выделенной для списка) происходил переход ко второй версии алгоритма. Эта версия обеспечивала эффективное выполнение алгоритма, если не возникала новая работа, однако алгоритм выполнялся последовательно. Периодически происходила проверка возможности порождения новой работы, и если таковая обнаруживалась, то происходил возврат к параллельной версии.
Результаты наблюдения
В связи с этой задачей возникло несколько проблем разных уровней. Сначала программа довольно часто блокировалась несмотря на усилия по предотвращению таких исходов. К счастью, удалось определить, на каком операторе “застревает" каждый процесс и потребовалось только несколько часов для обнаружения и устранения наших ошибок. Эти проблемы были вызваны некорректной записью критической секции для обработки списка интервалов, требующих дополнительной работы. В этом случае никакой процесс не мог обратиться к списку.
Как только эта проблема была решена, программа стала выполняться до конца и давать некоторые результаты. Однако в связи со спецификой реализации данной задачи интегрирования мы не смогли проверить всю программу, так как один из двух алгоритмов сильно зависел от условий параллельной обработки и, таким образом, был проверен только последовательный алгоритм.
При параллельном выполнении вновь на каждом прогоне программы получались разные результаты. На этот раз они обычно были близки к правильному результату, но все же оставались явно ошибочными. Первое предположение состояло в том, что эти два алгоритма как-то по-разному принимают решения о разделении работы, когда в этом наступает необходимость. Так как различные выполнения могут позволять различным процессам выполняться с различными скоростями, такие вычисления могут завершаться разными ответами. Сначала была сделана попытка с помощью операторов печати трассировать проверки условий принятия решений о разделении работы и определить, какая версия алгоритма принимала каждое решение. Полученный объем данных оказался слишком большим для анализа.
Следующим предложением было подобрать небольшую задачу интегрирования, которая демонстрировала бы те же характеристики выполнения. Но при этом можно было справиться с анализом выходных данных. Очень маленькие задачи давали корректные результаты. К счастью, мы случайно натолкнулись на одну задачу, которая была достаточно большой, чтобы вызвать эти проблемы, и достаточно маленькой для нас, чтобы можно было охватить промежуточные данные результатов трассировки. Действительно, такая проблема встретилась в двух наших программах деления. Оказалось, что программа деления в последовательной версии (считалось, что она проверена)
имела ошибку. Программа работала прекрасно при одном прогоне, но в случае разбиения области на подынтервалы всегда оказывалась ошибочной.
К сожалению, устранение этой ошибки не изменило поведение программы; она все еще давала неповторяемые результаты. Эти ответы были близки, но все же ошибочны. В конце концов, мы “открыли способ" - выбрали небольшую задачу и использовали каждый из двух алгоритмов в отдельности (они были достаточно малы, чтобы при их размерах как раз не возникали блокировки). Прогоны с использованием каждого из алгоритмов не давали корректного результата!
Причины проблемы
Ошибка в этой программе была связана с мало используемой частью программы, которая обеспечивала переключение этих двух версий алгоритмов. При решении любой вычислительной задачи имела место следующая последовательность событий:
Очередь интервалов по этому списку становилась полной, так что использовалась последовательная версия алгоритма интегрирования.
По крайней мере в двух текущих интервалах, работа завершалась без дальнейшего дробления, так что некоторое пространство в очереди должно было освобождаться.
По крайней мере один из интервалов, обрабатываемых в последовательной версии алгоритма интегрирования, обнаруживал свободное пространство и делалась попытка выполнять дополнительную работу.
После такой последовательности событий новые интервалы, не имеющие точно того же конечного значения, которое было вычислено первоначально, включались в список. Вероятность осуществления последовательности этих событий оказалась значительно меньше, чем можно было предположить. Более того, было почти невозможно вызвать эту последовательность событий и приходилось систематически предварительно проверять эту программу.
Выводы
Полученный в этом эксперименте урок труден для объяснения. Стратегия “разделяй и властвуй" - очень мощное средство для организации параллельной обработки и обеспечения высокой загрузки процессоров. В наших экспериментах все доступные вычислительные средства были активны более чем 95% времени. Но с точки зрения пользователя предотвращение блокировок также существенно. Нельзя представить себе такое объяснение, что программа пользователя “пригнана" к машине, но иногда возникает так много “параллельной" работы, что машина “захлебывается". (Возможно, пользователь предложит такой выход: “Хорошо, можно использовать меньше "параллелизма".)
По нашему мнению, эта проблема неотделима от природы языка и свойств примитивов, используемых для описания параллелизма и критических секций. Элементарный алгоритм “разделяй и властвуй" имеет очевидное определение, но применяемые для его описания средства позволяют рутинно выражать неопределенные вычисления. Любые ошибки при программировании, в критической секции или вне ее, ведут к неповторяемости результатов. В таком случае при переходе от версии к версии одного и того же алгоритма есть риск рассогласования в обработке - как бы незначительное изменение точных границ интервала.
Представляется естественным позволить программисту определять те части программы, которые могут выполняться одновременно. Однако если только программист отвечает за корректность этой информации, то должна быть уверенность в том, что он не ошибается. Фортран как базовый язык, на котором должен проводиться такой анализ, препятствует автоматическому обнаружению параллелизма компилятором. Неизвестно, какие средства могли быть добавлены к языку Фортран, чтобы гарантировать параллелизм и детерминированность поведения всех программ.
Заслуживает внимания еще одно замечание к этой задаче. Недавно эта программа транслировалась для выполнения на четырехпроцессорной вычислительной системе VAX (после прогона более 50 тестов на ЭВМ НЕР). После того, как программа выполнилась корректно, были проведены некоторые простые измерения временных характеристик. Во время этого тестирования примерно в каждом из десяти прогонов результат оказывался с незначительными ошибками. Причина - еще одна ошибка в той же самой секции программы. Эта ошибка находилась в “последней44 версии программы для ЭВМ НЕР, но оставалась необнаруженной. Мораль этой истории такова - пока программисты отвечают за организацию в программе всей синхронизации, будет крайне трудно (если вообще возможно) гарантировать, что все возможные ошибки синхронизации будут устранены.
Пример 3. Непреднамеренное совместное использование ресурсов
Эта проблема возникла в процессе преобразования программы для исследования излучательного переноса, реализованной на суперЭВМ CRAY-1, в многозадачную программу для ЭВМ НЕР. Решаемая задача является задачей с временной зависимостью, а вычисление для каждого временного шага имеет три последовательные стадии. В программе рассматривается только случай сферической симметрии, и на второй стадии (ядро алгоритма) - пошаговая обработка серии пучков (каждый пучок касателен к сферической оболочке). Интегрирование по каждому пучку может производиться независимо от интегрирования по другим пучкам, что позволяет выполнить естественное распараллеливание задачи. Таким образом, в программе для ЭВМ НЕР образуется группа параллельных процессов, каждый из которых запускается при обнаружении пучка, требующего вычислений, выполняет это вычисление и вновь ищет новый пучок. Первая и третья стадии этого вычисления также имеют простые структуры, состоящие в основном из двойных вложенных циклов D0 с независимыми внешними итерациями.
Нетрудно было установить несколько мест, где требовалась синхронизация паралельных процессов:
Необходимо было разделить каждую из трех последовательных стадий синхронизационными барьерами с тем, чтобы ни один из процессов не мог выполняться на следующей стадии до тех пор, пока не завершатся все процессы на текущей стадии.
Следовало позаботиться о том, чтобы никакие два процесса не выбрали для работы один и тот же пучок и чтобы никакой пучок не остался необработанным.
В процессе пошаговой обработки пучка необходимо обновлять некоторые общие массивы и ясно, что эта модификация должна производиться в критической секции.
Результаты, наблюдений
Сначала казалось, что преобразование проходит гладко. Уже после одного дня работы параллельная версия программы была написана и успешно компилировалась. Первая ошибка, связанная с программированием синхронизирующего барьера, была быстро обнаружена и исправлена. Полученная программа испытывалась с одним и с десятью активными процессами и в обоих случаях был получен результат, совпадающий с результатами программы на суперЭВМ CRAY. Отладка закончена! Осталось только прогнать программу для различного числа процессов, чтобы получить информацию о достигаемом ускорении. Такая информация была быстро получена, но при грубой оценке результатов было обнаружено, что прогон с пятью активными процессами дает ответ, отличающийся от полученного в других случаях. Отличие было небольшим, обычно в четвертом или пятом десятичном знаке, и проявлялось только после прохождения нескольких временных шагов. Кроме того, успешные прогоны с пятью активными процессами для одинаковых входных данных давали разные результаты (отличие снова состояло в четвертом или пятом десятичном знаке).
Началась интенсивная отладка. Было очень трудно продвигаться вперед, главным образом из-за отсутствия средств отладки, которые бы не влияли на выполнение программы. Было замечено, что для целей отладки бесполезно вставлять в программу оператор WRITE. Проблема просто исчезала. Ясно, что присутствие в программе операторов WRITE приводило к нарушению синхронизации некоторых критических событий и маскированию искомой ошибки. В надежде локализовать ошибку на одной из трех последовательных стадий была сделана версия программы, которая позволяла любой набор стадий выполнять с одним-единственным активным процессом, а остальную часть программы, как и было задумано в исходной программе, всеми активными процессами. Были сделаны прогоны программы для всех восьми возможных комбинаций. Результаты вызвали недоумение, но указали на точку “просачивания64 через синхронизирующие барьеры. Однако интенсивная проверка этой части программы ни к чему не привела. После двух дней и ночей работы начало казаться, что наши затруднения вызваны только неисправностями в оборудовании машины. По истечении отведенного времени мы, озадаченные, вернулись домой.
Причины, проблемы,
Действительная причина ошибки была найдена спустя несколько недель. В нашей программе интегрирования пучка использовалось несколько небольших массивов для хранения промежуточных результатов. В исходной программе для суперЭВМ CRAY эти массивы содержались в блоке COMMON, и в программе для ЭВМ НЕР они также остались в блоке COMMON. Однако теперь, когда интегрирование производилось одновременно несколькими процессами, каждый процесс необходимо было снабдить своими собственными копиями этих массивов. Вместо этого единственная копия каждого массива непреднамеренно использовалась совместно всеми процессами. Каждый процесс использовал эти массивы в течение очень коротких промежутков вычислений, так что вероятность того, что никакие два процесса не использовали их одновременно, была достаточно мала. Следовательно программа чаще всего выполнялась правильно. Как только эта ошибка была устранена, программа стала выполняться правильно во всех случаях ее испытания.
Выводы
Из проведенных экспериментов были получены следующие уроки:
Чрезвычайно рискованно преобразовывать существующую программу в программу, предназначенную для выполнения в параллельной среде. Так как обычно приходится делать небольшие изменения, по крайней мере если измерять их числом строк программы, то основательный анализ, который подразумевается при написании программы заново, выполняется редко. В результате слишком легко сохранить структуру, которая окажется некорректной в новой среде.
Вообще структуры данных должны совместно использоваться процессами только после тщательного рассмотрения. Непреднамеренное совместное использование должно быть одной из первых возможностей, рассматриваемых во время отладки.
Перспективы нахождения ошибок синхронизации с помощью обычных средств отладки являются сомнительными. При отсутствии средств отладки наиболее полезным является тщательный анализ программы с карандашом и распечаткой в руках.
Пример 4. Гонки и блокировки
Почти во всех написанных нами параллельных программах необходимо было использовать синхронизирующие барьеры - конструкции, с помощью которых каждый процесс, достигший барьера, ждет, пока барьера не достигнут все другие процессы. Необходимость в барьерах возникает наиболее часто тогда, когда алгоритм состоит из нескольких последовательно выполняемых этапов, каждый из которых обладает внутренним параллелизмом, но выполнение которых не должно перекрываться.
Для суперЭВМ CRAY Х-МР нами была написана подпрограмма SYNCAL, предназначенная для реализации барьеров и используемая в различных программах. Функции SYNCAL очень просты. При вызове процессом подпрограммы SYNCAL происходит увеличение значения счетчика на единицу и новое значение сравнивается с числом процессов, достигших барьера (который должен быть известен заранее). Если не все процессы достигли барьера, то вызваю- щий процесс ждет установки события EVENT. Последний достигший барьера процесс устанавливает событие EVENT, позволяя всем ожидающим процессам выполняться, присваивает счетчику значение 0 и продолжает свое выполнение. Однако существует некоторая очевидная опасность. Если увеличение значения разделяемого счетчика не защищено критической областью, то два или большее число процессов могут пытаться выполнить увеличение счетчика одновременно, что приведет к некорректному результату. Чтобы избежать этой опасности для защиты счетчика используется блокировка (LOCK).
Результаты наблюдений
Подпрограмма SYNCAL заработала очень быстро. В конечном счете она содержала менее 20 строк текста на языке Фортран. После успешных предварительных проверок подпрограмма SYNCAL стала использоваться в нескольких программах. Проблемы возникли немедленно. Некоторую значительную часть времени программа завершалась блокировкой, при которой один или большее число процессов навсегда оказывались в состоянии ожидания события EVENT. Результаты были снова невоспроизводимыми, а обычные средства отладки оказались, как и прежде, бесполезными.
Причины некорректного выполнения функции SYNCAL
Возникла мысль о двух причинах ошибочного поведения функции SYNCAL, которое проявлялось лишь при наличии более одного барьера. В обоих случаях ошибка возникала потому, что программист при написании программы имел ввиду определенный порядок следования событий, а на самом деле возможностей было куда больше. Для иллюстрации рассмотрим следующий текст подпрограммы SYNCAL.
SUBROUTINE SYNCAL COMMON /SYNCCOM/ NPR, NSYNCD, SYNCLOK, SYNCEV
CALL LOCKON (SYNCLOK)
NSYNCD=NSYNCD+1 LNSYNCD=NSYNCD CALL LOCKOFF (SYNCLOK)
IF (LSYNCD .LT. NPR) THEN CALL EVWAIT(SYNCEV) CALL EVCLEAR(SYNCEV)
ELSE
CALL EVPOST(SYNCEV) CALL LOCKON (SYNCLOK) NSYNCD=O
CALL LOCKOFF (SYNCLOK) ENDIF
RETURN
END
Молчаливое предположение программиста состояло в том, что вся часть программы, следующая после слова ELSE, должна выполняться немедленно процессом, который достиг барьера последним. Однако гарантии тому нет. Что, например, случится, если этот процесс “заснет“ на время после выполнения вызова “CALL EVPOST ...“, но перед сбросом счетчика? В этом случае некоторый другой процесс может достичь следующего барьера, в то время как последнее значение счетчика соответствует предыдущему барьеру. Ошибочно будет решено тогда, что прибыл последний процесс, и обработка пойдет по его пути. Была обнаружена еще одна аналогичная проблема в представленной программе. После устранения ошибки подпрограмма SYNCAL заработала надежно.
Затем некоторые наши программы вновь стали выполняться на значительно менее загруженной суперЭВМ CRAY Х-МР, где почти всегда нам предоставлялись оба процессора. Программы снова стали завершаться блокировками! Причина была почти той же, что и прежде. Снова оказалась возможной последовательность событий, которая не принималась во внимание и приводила к неверному функционированию. В результате потребовалось время для изменения подпрограммы SYNCAL, которая выросла более чем в два раза. После этого подпрограмма SYNCAL работала без ошибок в течение про-
должительного периода времени, и мы будем удивлены (по крайней мере слегка), если в дальнейшем появятся ошибки.
Выводы
Вновь эксперименты дали некоторые полезные уроки:
В параллельных программах очень небольшие части программы, обеспечивающие взаимодействие процессов, способны привести к неожиданно большим изменениям поведения программ.
Поэтому такие части программы целесообразно инкапсулировать настолько полно, насколько это возможно, и после того как они начинают функционировать корректно, следует их использовать как можно шире.
23. Возможные перспективы
Реально мы видим четыре различных способа создания прикладного программного обеспечения для многопроцессорных систем в течение следующих пяти - десяти лет. Каждый из них в настоящее время имеет ряд серьезных недостатков, ограничивающих возможность их применения. Эти способы могут быть кратко описаны следующим образом:
Расширение существующих языков, например Фортрана, новыми операторами, что даст возможность пользователю описывать параллельные вычисления и их синхронизацию.
Расширение существующих компиляторов средствами выявления параллельно выполняемых операций, где это возможно, и добавление необходимых средств синхронизации для обеспечения корректности программы.
Добавление нового языкового уровня над существующим языком для описания желаемой параллельности и необходимой синхронизации, с тем чтобы позволить базовым прикладным программам оставаться относительно неизменными.
Определение абсолютно нового языка и системы компиляции, в которых интегрируются понятия параллельности и синхронизации со всеми существующими взглядами на описание вычислительных алгоритмов.
В этом разделе каждая из этих возможностей рассматривается несколько подробнее. Для каждой из них приводятся основные аргументы и критические нерешенные вопросы, ограничивающие их приемлемость в настоящее время. Следует отметить, что эти варианты не являются полностью ортогональными и нельзя проводить между ними жестких границ. Каждый из них предлагает некоторые изменения языка компилятора. Для создания других возможностей с улучшенными свойствами допустимо их объединение, однако это ведет и к обострению трудностей.
Языковые расширения
Наибольший опыт работы на многопроцессорных системах приобретен при использовании существующих языков программирования (обычно Фортрана) с минимально необходимыми расширениями. Этот подход обычно требует изменений в решении некоторых (или всех) вопросов:
поддержка повторной входимости;
образование и завершение параллельных процессов;
синхронизация параллельных процессов;
подразделение данных на локальные и на разделяемые данные процессов.
Существует ряд удачных расширений языков, демонстрирующие различные подходы к обеспечению необходимых требований [2, 3, 4]. Каждое из этих расширений было успешно применено для написания и отладки прикладных программ для многопроцессорных систем. Известны другие расширения языков, находящиеся на ранних стадиях разработки [5].
Кроме очевидной выгоды, подход на основе языкового расширения при надлежащей реализации имеет некоторые другие преимущества:
Во многих случаях расширения могут быть сделаны только за счет добавления новых библиотечных подпрограмм (при этом исходный язык и соответствующий компилятор остаются неизменными). Это относительно легко и недорого для экспериментов с альтернативными расширениями. Те из них, которые окажутся удачными, в конечном счете могут быть включены в новые языки.
Некоторые существующие прикладные программы могут быть преобразованы в многозадачные варианты с минимальными изменениями, по крайней мере если степень изменений оценивается по числу измененных строк программы. Наибольшим изменениям подвергается остовная часть программы, которая управляет параллельными процессами и их данными, а вычислительные “внутренности44 программы остаются неизменными.
Подход, основанный на расширении языков, связан также и с трудностями. Большинство из них возникает в связи с отсутствием у компилятора знания того, как распараллеливать программу. Это делает невозможным анализ распределения данных между процессами, что, например, влечет за собой исключение потенциально значимых источников информации для оптимимза- ции и отладки. Компилятор может выполнить оптимизации, которые на самом деле некорректны для параллельных программ. Например, компилятор, которому ничего неизвестно о синхронизирующих примитивах, может выполнить недопустимые перемещения частей программы, не обращая внимания на критическую секцию, приводящие к нарушениям, что фактически встречается при работе компилятора CFT фирмы Cray Research Inc.
Связанные трудности (или, быть может, свойства) состоят в том, что программист лишь слабо ограничен в способе использования конструкций синхронизации и общих данных. Следовательно, ему очень легко написать некорректную программу, что достаточно хорошо демонстрируют примеры в разд. 2.2. Язык, специально спроектированный для поддержки параллелизма, может уменьшить возможность получения некорректной программы путем введения более строгих правил. Только эксперимент покажет, где приемлемый баланс между свободой и ограничениями. Ясно, что основной опыт будет получен в результате экспериментирования с расширениями языков.
Анализ компиляторов
Бесспорно, существует надежда, что в перспективе разработчикам прикладных программ не потребуется изменять свои программы, чтобы использовать их на многопроцессорной системе. Национальные лаборатории сильно полагаются на огромное количество существующих программ. Любая стратегия, которая позволит выполнять эти программы на многопроцессорной системе без внесения в них изменений, должна рассматриваться как более предпочтительная по сравнению с другими вариантами, поскольку не требует за-
трат времени на переделку программ. Кроме того, так как существующие программы имеют воспроизводимое поведение, программист не должен отвечать ни за какие проблемы синхронизации.
Такой подход, естественно, требует предельно сложного компилятора. В этом разделе рассматриваются нынешние возможности и ограничения построения такого компилятора. Неудивительно, что эффективность вычислений частично зависит от структуры используемой многопроцессорной ЭВМ. Однако многие базовые вопросы компиляции связаны с достижением независимости от применяемой машины. К счастью, многие исследования по созданию векторизующих компиляторов непосредственно применимы к решению более общих вопросов компиляции для многопроцессорных систем. Вероятно, наиболее продвинутая работа в этой области выполнена Куком и др. [6]. В настоящее время почти все работы были направлены на анализ циклов с целью выявления возможности максимального их распараллеливания. Многопроцессорные машины имеют преимущество перед векторными машинами, состоящее в том, что условные переходы и вызовы подпрограмм внутри циклов не являются препятствием для распараллеливания. Кроме того, любые векторные циклы могут без особых усилий отображаться на более универсальные многопроцессорные ЭВМ. Видимо, есть основания надеяться, что этот подход может быть расширен для идентификации и других форм распараллеливания циклов, которые могут систематически транслироваться для многих разнотипных многопроцессорных ЭВМ [7].
Два основных вопроса, связанных с этим подходом, остаются неразрешенными:
Если усилия компилятора концентрируются главным образом на циклах как источниках распараллеливания, то какая степень распараллеливания может быть достигнута?
Если компиляторы должны обращаться к более динамическим свойствам программ, например, таким, как вызовы подпрограмм, то насколько эффективно они могут определять и использовать возможности распараллеливания и ее реализации?
В решении этих вопросов “возможность распараллеливания" сопряжена с проблемами. Для многих многопроцессорных ЭВМ, таких, как суперЭВМ CRAY Х-МР, чтобы гарантировать, что дорогостоящие процессоры не будут тратить много времени на реализацию системных вызовов и ожидание в связи с синхронизацией, параллельные фрагменты программы должны быть достаточно “крупными".
Первый вопрос зависит от техники компиляции и от природы анализируемых программ. В отличие от векторных машин, где основное внимание уделяется анализу внутренних циклов, компиляторы многопроцессорных систем для обнаружения параллелизма должны анализировать внешние циклы, что сокращает взаимодействие между процессорами. Интересный аспект этого вопроса - в чем проявятся эти усилия. Программисты, использующие векторные машины, организуют вычисления для распараллеливания внутреннего, а не внешнего цикла. Выигрыш невозможно определить до тех пор, пока созданию такого компилятора не будет уделено должного внимания.
Если для анализа программ вне циклов необходимы интеллектуальные компиляторы, то перспективы успеха остаются туманными. Успешность анализа цикла тесно связана с тем фактом, что статическое определение цикла строго соответствует динамической структуре его выполнения. Однако динамические свойства, например вызовы подпрограмм, нарушают такое соответствие. Распараллеливание связано с устранением зависимости данных. Но в подпрограммах зависимости данных трудны для определения, поскольку при
статическом анализе программы не всегда удается установить, какие переменные будут передаваться в различные подпрограммы.
В этом направлении были предприняты некоторые усилия. К. Кеннеди из Университета Райса исследовал вопросы анализа глобальных данных и смог найти способы усовершенствования идентификации параллелизма при использовании подпрограмм. Один аспект этой проблемы остается нерешенным. Циклы обеспечивают прямолинейный способ приближенного балансирования нагрузки в многопроцессорной ЭВМ (гарантию, что каждый процессор будет занят). С появлением на сцене подпрограмм обеспечение такого баланса становится значительно более трудым. Справедливо, что общая задача равномерной нагрузки является предельно сложной, и нам не известны какие-либо хорошие эвристики для ее приближенного решения.
Возвращаясь к цели этой общей стратегии (спасения старых программ), необходимо сделать еще один комментарий. Все средства компиляции, разработанные Куком и др., в сильной степени полагаются на верность программ стандарту языка Фортран. Программы, которые используют подпрограммы на языке ассемблера и, например, обращаются к элементам массива за определенными границами массива или полагаются на конкретную схему загрузки блоков COMMON в память, всегда будут препятствовать корректной работе средств компилятора. Чтобы такие программы удовлетворяли стандартному определению языка, они должны быть переработаны.
Уровни языков для параллельных вычислений
Большинство ошибок, встречающихся в параллельных программах, в основном не зависит от того, какие вычисления были выполнены. Например, ошибки могут быть при использовании средств синхронизации или при разбиении данных. Эти ошибки скорее связаны с параллельной структурой или каркасом программы, а не с внутренними деталями вычислений. Вероятно, для описания каркаса программы надо использовать новые языковые формы, а при спецификации внутренних частей программы полагаться на существующие языки программирования (например, Фортран или Си). Существует надежда, что возможность появления ошибок может быть существенно снижена при упрощении преобразования существующих программ в параллельные программы и многократном использовании существующих модулей. Начало таким работам уже положено [8,9].
Один привлекательный подход состоит в использовании графического языка для представления каркаса программы при сохранении существующего текстового языкы для программирования “внутренностей44 программы. Этот подход мотивируется тем, что параллелизм в нем естественно выражается графическими средствами в двумерной среде - обычно одно измерение используется для времени, а ортогональное ему измерение - для представления степени параллелизма. Действительно, при проектировании параллельной программы обычно много листов бумаги покрывается такими диаграммами. При этом часто возникают ошибки, а при трансляции диаграмм в текстовое представление теряется наглядность. С другой стороны, графические представления не могут идти в сравнение с текстовыми в отношении эффективности и ясности представления того вида алгебраических преобразований, которые обильно используются в наших программах. Информационная плотность в расчете на единицу площади экрана просто слишком мала.
Двухуровневый подход может быть реализован с использованием препроцессора к компилятору для некоторого объектного языка, поддерживающего параллельное программирование. Специфицированный каркас программы дол-
жен быть автоматически транслирован в примитивы для параллельного программирования, которые входят в объектный язык программирования и интегрироваться с "наполнением" программы, уже специфицированным на этом языке. Несомненно существуют нерешенные задачи, связанные с этим двухуровневым подходом:
Было бы полезно, чтобы язык описания каркаса программы позволял специфицировать не только синхронизацию взаимодействия процессов, но и распределение данных. Не ясно, как сделать это наилучшим образом.
Как всегда в случае препроцессорных реализаций отладка затрудняется. Как выполнять отладку в терминах объектов, используемых во время программирования?
Надеемся в ближайшем будущем провести некоторые эксперименты с использованием этого подхода.
Новый язык/Проект компилятора
Наиболее радикальный подход к использованию многопроцессорных систем состоит в перестройке всего программного обеспечения, начиная с основ. Очевидно, что эта стратегия связана с предельно большими расходами и потенциально требует интенсивного труда. С учетом наших огромных вкладов в существующие прикладные программы этот вариант должен выглядеть либо как последнее средство, либо как средство развития идей эволюции существующих систем в направлении структур, пригодных для параллельной обработки.
Вероятно, наиболее плодотворный подход при проектировании языков и компиляторов, специально предназначенных для многопроцессорных систем, в настоящее время связан с созданием функциональных языков (например, SISAL [10], FP [11] и KRC [12]). Эти языки созданы с целью увеличения наглядности распараллеливания без использования программистом средств управления синхронизацией. Они также снимают зависимость от любой конкретной модели параллельной архитектуры. Следовательно, при наличии интеллектуальных компиляторов можно использовать различные параллельные машины, прилагая значительно меньше усилий для подгонки исходных программ.
Необходимо ответить на следующий очевидный вопрос: "Почему теперь спокойно воспринимаются такие радикальные решения?" Можно указать не сколько важных причин:
Несмотря на благие намерения, оказалось, что большинство прикладных программ должны быть перепрограммированы, так почему не выбрать хороший исходный язык? Возникшая ситуация весьма сходна с нашим опытом последних лет по переходу на векторные ЭВМ. Сколько было затрачено сил при переносе программ на ЭВМ CDC Star и обеспечении их эффективного выполнения?
Если ответственность за управление синхронизацией возлагается на программистов, то требуются более совершенные методы, облегчающие идентификацию и/или предотвращающие зависимость результатов от времени. К сожалению, эта проблема не нова - разработчики операционных систем сталкивались с ней давно и был получен ряд хороших решений. В настоящее время отсутствуют отладчики и анализаторы программ, которые могли бы помочь в отыскании ошибок в программах такого рода. Все мы теперь вынуждены гадать на кофейной гуще.
Мы приступаем к использованию новых архитектур, которые, конечно, будут господствовать десятилетия. Начиная с исходного состояния, можно
увеличить наши шансы на значительную жизнь разрабатываемых программ и хорошее использование систем.
Если признается возможность перехода на новую систему “язык-компиля- тор“, то функциональные языки дают некоторые уникальные преимущества. Следующие три из них наиболее важны:
Параллельность - основное правило выполнения; последовательные операции возникают только в связи с зависимостями данных в программе.
Дополнительные свойства языка, такие как рекурсия, увеличивают возможности для выражения алгоритмов, в которых заключено больше параллелизма.
Вся параллельность программы достаточно управляема определением языка, чтобы гарантировать детерминированное поведение программы (или если допускается некоторая недетерминированость, то она ограничивается вполне определенными модулями).
Функциональные языки представляют одно из средств программирования задач, реализуемых на многопроцессорных ЭВМ. Параллельность извлекается из одного элементарного источника - одновременного вычисления всех входных аргументов функции. В функциональном языке каждое исполняемое действие - это математическая функция, и входные аргументы многих функций могут вычисляться одновременно. Действительно, любые две функции могут вычисляться одновременно до тех пор, пока их входные аргументы не зависят друг от друга. Кроме того, рекурсия увеличивает возможности распараллеливания, поскольку она способствует использованию высоко параллельных алгоритмов типа “разделяй и властвуй “.
Хотя такой способ определения параллельности может выглядеть хаотическим, математическая природа языка гарантирует упорядоченное вычисление функции, которое дает детерминированные результаты. Следовательно, программисты могут сосредоточиться на математической структуре решения и параллельности, связанной с этой структурой. При этом нет необходимости рассматривать подробности разбиения задач на подзадачи или назначения их на процессоры и не требуется определять детали механизма синхронизации, необходимой для обеспечения правильной работы программы. Заботу об этом берет на себя компилятор и/или архитектура. Известно, что в некоторых частных случаях такое разделение ответственности может быть предельно эффективным. Например, программа SIMPLE была написана на функциональном языке и выполнена на Манчестерской потоковой экспериментальной машине. Задача размера “4 на 4“ реализована на десятипроцессорной системе с коэффициентом использования 97,5%. Программа не требует от программиста информации о разбиении и выдает устойчивые результаты.
Воспроизводимость результатов - главная выгода от применения функциональных языков, что особенно важно при отладке программ. Почти все функциональные языки исповедуют философию записи детерминированных программ. Наш опыт отладки параллельных программ подтверждает очевидность преимуществ этого. Если программы воспроизводимы, то можно применить современные методы отладки (в существующем их виде) для анализа поведения ошибочных программ. С точки зрения программистов, свободу от преследования перемежающихся ошибок переоценить невозможно.
К сожалению, распространение функциональных языков ограничено несколькими нерешенными задачами. Вообще в этом случае требуются весьма интеллектуальные компиляторы, так как они ответственны за разбиение, отображение и синхронизацию. Поскольку архитектуры используемых ЭВМ могут быть разнообразны, каждая из них будет требовать особого внимания к достижению высокой производительности при вычислениях. Ожидается, что
этот тип языка позволит решать упомянутые задачи эффективнее, чем при полном анализе программ, написанных на Фортране. Различные организации прилагают усилия для реализации языка SISAL на нескольких ЭВМ с существенно различными архитектурами. В частности, он реализуется на машинах HEP, CRAY-1S, VAX 11/780 и на Манчестерской потоковой машине. Вероятно, р будущем он будет реализован для четырехпроцессорного VAX- кластера и для суперЭВМ CRAY Х-МР.
Однако самый тяжелый из нерешенных для функциональных языков вопросов возникает в связи с указанными машинами. Это вопрос динамического распределения памяти. Простейшие реализации функциональных языков требуют огромного количества дополнительной памяти помимо той, что необходима для организации параллельных вычислений. Это требование может быть снижено с помощью методов анализа при компиляции, но до сих пор они все еще находятся в стадии разработки. В настоящее время нельзя определить, насколько эффективно эти методы будут сокращать объем памяти, требуемой для выполнения программы.
2.4. Заключение
В этой главе была сделана попытка сжато обобщить опыт нескольких лет использования реальных многопроцессорных ЭВМ в надежде, что он окажется полезен тем, кто пишет программы для этих машин. Основное внимание уделено трудностям разработки правильно работающих параллельных программ и возможности их преодоления в будущем. Ограниченный объем публикации не позволил помимо логической корректности программ для многопроцессорных ЭВМ рассмотреть широкий круг вопросов, связанных с производительностью таких систем. Надеемся, что этому будет посвящена очередная публикация.
Суммируя наш опыт, хотим прежде всего отметить, что мы оказались в состоянии эффективно использовать многопроцессорные ЭВМ для решения задач из широкого диапазона. Это успокаивает, так как мы полагаем, что возрастание уровней параллелизма дает большую надежду на значительное ускорение вычислений в будущих супрЭВМ. Тем не менее мы столкнулись со значительными трудностями при создании корректно функционирующих программ и не хотим преуменьшать их. Кратко их можно изложить следующим образом:
Поведение даже очень коротких параллельных программ может оказаться на редкость сложным. Тот факт, что программа с некоторым конкретным набором исходных данных выполнилась правильно один раз или даже сто раз, не гарантирует, что завтра эта программа с тем же набором исходных данных не завершится с ошибкой.
Поиск ошибок в параллельных программах может быть чрезвычайно трудным, что обусловлено логической сложностью, невоспроизводимым поведением и отсутствием подходящих средств.
Полностью удовлетворительного решения этих проблем не предвидится - можно ожидать лишь частичные решения. Как мы пытались объяснить, каждая из этих возможностей имеет свои недостатки. Тем, кто должен работать с доступными современными средствами, сообщим следующее:
Процесс преобразования последовательной программы в программу, предназначенную для многопроцессорной системы, весьма вероятно, превратится в проблему. По возможности разработайте заново хотя бы каркас новой программы.
Ввиду больших трудностей отыскания ошибок гораздо большее внимание должно быть уделено написанию изначально корректных программ. В этом отношении разработка программы в терминах чисто математических функций (с явно идентифицируемыми входными аргументами и непересекаю- щимся множеством выходов) увеличивает возможности получения корректной программы и ее автоматического контроля в процессе компиляции.
Другой метод, помогающий уменьшить число ошибок, состоит в использовании, где это возможно, стандартных средств синхронизации. Кроме локализации ошибок и ограничения сложности программ этот подход облегчает использование программы другими программистами.
Если, несмотря на представленные выше методы, возникает настоящая, зависящая от синхронизации ошибка, то часто лучший подход к ее отысканию - это сесть за стол с распечаткой, карандашом и бумагой. Обычно полезно привлечь в помощники кого-нибудь, кто не знаком с этой программой.
В части более масштабных стратегий для облегчения этих проблем давать советы более трудно. Число альтернатив велико, а наш опыт еще очень ограничен. Тем не менее мы полагаем, что:
Должны проводиться энергичные исследования всего разнообразия альтернативных языков програмирования, покрывающих изложенные нами в общих чертах подходы.
Созданию новых средств поддержки процесса отладки программ должен быть дан высокий приоритет. Для функционирования некоторых из этих средств может потребоваться аппаратная поддержка. С этим могут быть связаны важные требования к новым суперЭВМ.
Решение проблем, связанных с построением логически корректных параллельных программ, имеет решающее значение для успеха использования будущих суперЭВМ.
Наконец, мы убеждены, что приобретение многопроцессорных ЭВМ и переход на них должны планироваться более тщательно. Следует сбалансировать необходимость продолжающейся разработки лучших физических алгоритмов в наших программах с необходимостью разработки эффективной стратегии использования многопроцессорных систем.
Благодарности
Эта работа частично поддерживалась научной программой по прикладной математике Управления энергетических исследований (США, Министерство энергетики) по контракту #W-7405-ENG-48. с Национальной лабораторией им. Лоуренса в Ливерморе.
Библиография
] D.H. Grit and J.R. McGraw, “Programming Divide and Conquer on a Multiprocessor", UCRL-88710, Lawrence Livermore National Laboratory, May 1983.
E.D. Brooks III, “A Multitasking Kernel for the C and Fortran Programming Language", UCID-20167, Lawrence Livermore National Laboratory, Sept. 1984.
Cray Research, Inc., "Multitasking User Guide", SN-0222, Feb. 1984.
Denelcor, Inc., "HEP FORTRAN User's Guide", Publication Number
9000006, Feb. 1982.
Myrias Research Corporation, “Myrias 4000 System Description", Dec. 1983.
DJ. Kuck et al., “Dependence Graph and Compiler Optimization", Proceedings of the 8th ACM Symposium on the Principles of Programming Languages, Jan. 1981.
J.R. McGraw, D.H. Kuck, and M. Wolfe, “A debate: Retire Fortran?", Physics Today, May 1984.
C. Fraboul and N. Hifdi, “LESTAP: A Language for Expression and Synchronization of Tasks on a Multi-Array Processor Architecture", Experiences in Applying Parallel Processors to Scientific Computation, March 1984.
P.F. Reynolds, “Parallel Processing Structures: Languages, Shedules, and Performance Results", Ph.D. Thesis, University of Texas at Austin, Dec. 1979
J.R. McGraw et al., “SISAL: Streams and Iteration in a Single Assignment Language", Language Reference Manual, M-126, Version 1.1, Lawrence Livermore National Laboratory, July 1983.
J. Backus, “Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs", Communications of the ACM, vol. 21, no. 8, Aug. 1978.
D.A. Turner, “The Semantic Elegance of Applicative Languages", Proceedings of the 1981 ACM Conference on Functional Programming Languages and Computer Architectures, Oct. 1981.
3
Alliant FX/8
Дэвид Ц. ДиНуччи
Машина / Модель:
Alliant FX/8
Местонахождение:
Аргоннская национальная лаборатория, Аргонн, Иллинойс
Процессоры:
8 вычислительных элементов (ВЭ)
6 интерактивных процессоров (ИП) Общая память 32 Мбайт
Общая для ВЭ кэш-память 128 Кбайт
Кэш-память 64 Кбайт для ИП (по 32 Кбайт для совместного использования тремя ИП)
Операционная система:
Concentrix, версия 2.0
Язык:
Фортран 77, расширенный средствами обработки массивов языка Фортран 8Х
Компилятор:
FX/ Fortran, версия 2.0.18
В ЭВМ Alliant FX/8 реализована возможность автоматического использования параллелизма, присущего научным программам. Цель заключалась в осуществлении параллельной обработки при использовании существующих программ на языке Фортран с минимальным изменением (или без изменения) их исходного текста. Хотя Alliant FX/8 сам по себе является быстрым параллельным процессором для научных вычислений, на его основе в Илли- нойсском университете создается в рамках проекта Cedar более мощная суперЭВМ [1].
Аппаратные средства
ЭВМ Alliant FX/8 состоит из восьми процессоров, названных вычислительными элементыми (ВЭ), и двенадцати интерактивных процессоров (ИП). ВЭ, ИП и общая глобальная память емкостью до 256 Мбайт связываются, как показано на рис. 3.1, с помощью общей шины памяти. Доступ ВЭ и ИП к этой шине осуществляется через кэш-память (ВЭ совместно используют кэш-память емкостью до 512 Кбайт и до 128 Кбайт этой памяти, по 32 Кбайт для каждых трех ИП, используется интерактивными процессорами). ВЭ, кроме того, соединены непосредственно друг с другом с помощью шины параллельного управления.
Шина управления параллельной работой
Рис. 3.1. Архитектура ЭВМ Alliant FX/8 (конфигурация ЭВМ, устанавленная в Аргоннской национальной лаборатории).
Каждый ИП реализован на основе микропроцессора Motorola 68000. В центральных процессорах ВЭ применяются заказные схемы, которые поддерживают набор команд микропроцессора М68020, расширенный командами для вычислений с плавающей запятой, операции над векторами (длина - 32 элемента, операнды - целочисленные или с плавающей запятой) и параллельную работу. Для выполнения операции с плавающей запятой в ВЭ используется процессор плавающей арифметики Weitek. Вообще ВЭ используются для выполнения вычислительных процессов, которые целесообразно, как будет описано в этой главе, векторизовать или распараллелить на уровне циклов, тогда как ИП используются для выполнения интерактивных процессов и операций ввода/вывода между памятью и периферийными устройствами.
Программные средства
Операционная система Concentrix, используемая в ЭВМ Alliant FX/8, является версией операционной системы Berkeley UNIX 4.2. Параллельное программирование для этой ЭВМ выполняется с помощью языка Фортран, для которого был спроектирован компилятор, ориентированный на поддержку параллельных вычислений, или на языке Си посредством явного вызова подпрограмм для организации параллельной обработки. В этой главе обсуждается использование только языка Фортран.
Разработка программ
В то время как многие параллельные процессоры требуют от пользователя явного указания того, какие вычисления в программе должны производиться параллельно, компилятор Alliant FX/ Fortran активно обнаруживает параллелизм в обычной последовательной программе. Даже если эти программы написаны без ориентации на параллельную обработку, компилятор автоматически вырабатывает объектную программу, которая эффективно выполняется на многопроцессорной ЭВМ. Принципы компиляции основаны на работе Кука и его студентов из Иллинойсского университета (Urbana-Champaign) [2]. Этот подход должен доказать свою привлекательность многим научным работникам, имеющим готовые программы и не желающим переписывать их, чтобы достичь ускорения выполнения программ, которое дает параллельная обработка.
В аппаратуре имеются два основных средства для поддержки параллельной обработки. Восемь ВЭ позволяют целым секциям программы выполняться параллельно (по одной секции на процессор). Кроме того, конвейерные векторные устройства, имеющиеся в каждом ВЭ, позволяют обрабатывать пары операндов с перекрытием во времени функционирования устройств сложения и умножения с плавающей запятой. Такая конвейеризация может привести к ; очень высокой скорости вычислений, однако для этого требуется в качестве операндов применять достаточно длинные векторы. Чтобы использовать обе эти формы параллелизма, компилятор пытается выделить программные сегменты для параллельного выполнения программы на восьми процессорах и векторные операнды, для обработки которых требуется применять одну и ту же комбинацию арифметических операций. Поскольку обе формы параллелизма связаны с циклами DO языка Фортран, эти циклы и являются основным объектом анализа, выполняемого компилятором.
Когда компилятор обнаруживает некоторое препятствие для распараллеливания, он генерирует параллельную программу, пока гарантируется корректность результатов выполнения программы. В большинстве случаев компилятор в этом отношении очень консервативен. Его решения основываются на анализе типов операторов тела цикла и способа использования переменных, так как последний часто влияет на то, в какой степени итерации цикла могут быть перекрыты при параллельном выполнении.
Режим параллельного выполнения циклов DO осуществляется посредством реализации различных итераций цикла разными ВЭ. Так как имеется восемь процессоров, одновременно может выполняться до восьми итераций. Если необходимо, компилятор включает в объектную программу средства синхронизации, чтобы обеспечить правильный порядок изменения и использования переменных в цикле и гарантировать, что операторы программы, следующие за циклом, не будут выполняться до тех пор, пока не завершатся все итерации цикла.
Если компилятор сталкивается с определенной трудностью в распараллеливании цикла известными ему способами или просто обнаруживает свою неспособность проследить глобальные связи элементов программы, то он выдает информационное сообщение, говорящее пользователю о том, что компилятор пытался сделать и почему не справился с работой. Если пользователь определяет, что в конкретном случае на самом деле может быть осуществлено распараллеливание, то он может поместить в программу, обычно перед рассматриваемым циклом DO, специальную директиву и повторить компиляцию. По этой директиве компилятор будет выполнять желаемую оптимизацию. Директивы компилятора и информационные сообщения могут рассматриваться как средства диалога между компилятором и пользователем.
Отметим, что если пользователь по ошибке поместит в программу директиву, говорящую компилятору “продолжай распараллеливать44, когда это делать недопустимо, программа, вероятно, будет вести себя непредсказуемо (например, недетерминированно) как ошибочная параллельная программа. Если причина действительно в этом, то очень важно, чтобы пользователь мог убедиться, что программа ведет себя странно, посредством отказа от всех оптимизирующих действий и возвращения к последовательной программе. Если программа продолжает вести себя странно, в ней есть обычная программная ошибка. Если такой ошибки нет, то следует с помощью директив компилятора осуществлять выборочную оптимизацию параллельных частей программы до тех пор, пока ошибка не будет обнаружена. Такой метод отладки эффективен только потому, что правильная исходная последовательная программа и исходная параллельная программа - это одно и то же.
Другое преимущество этого метода для параллельного программирования по сравнению с применением некоторой библиотеки явно вызываемых подпрограмм состоит в том, что компилятору известно, где можно обнаружить параллелизм, что позволяет ему воздерживаться от приемов оптимизации, которые не могут корректно работать в паралельной программе. Другими словами, компилятор является оптимизирующим ради параллельного выполнения, а не вопреки ему.
Механика оптимизации
В фирме Alliant распараллеливание рассматривается как некоторая форма оптимизации и в отсутствии параметра -О в команде вызова компилятора не делается никаких попыток оптимизации. Оптимизация может быть ограничена (например, только векторизацией) путем добавления определенных букв после параметра -О. Если параметр -О опущен, то все директивы компилятора игнорируются.
Директивы компилятора в программе начинаются с первой колонки ком- бинацией символов CVD$. Это означает, что другими компиляторами для языка Фортран они будут рассматриваться как комментарии, что повышает мобильность программ. Буква, непосредственно следующая за знаком $, говорит о том, применима ли эта директива только к следующему за ней циклу DO (в этом случае используется буква L) или она относится ко всей оставшейся части исходного файла (в этом случае используется буква G). Ключевое слово, записанное начиная с седьмой колонки, определяет разрешение или запрещение оптимизации.
По умолчанию вызов подпрограммы внутри цикла препятствует распараллеливанию тела цикла, начиная с точки вызова. Директива CVD$L CNCALL отвергает это правило, позволяя такие циклы выполнять в параллельном режиме. Директива оказывает действие только на следующий за ней цикл, так как после знака $ записана буква L. Этой директивой программист утверждает, что данная подпрограмма может запускаться параллельно с другими выполнениями этой же подпрограммы или других подпрограмм, вызванных в том же цикле.
Параллельные задачи могуть создаваться с помощью вызова подпрограммы в цикле DO, которому непосредственно предшествует упомянутая выше директива CNCALL. Подпрограмме может быть передан индекс цикла, с помощью которого в ней может определяться номер соответствующей итерации, что позволяет каждой итерации выполняться независимо и, если необходимо, с использованием оператора IF и вычисляемого оператора GOTO. Такая под-
программа может даже вызвать другие подпрограммы, которые будут продолжать параллельное выполнение.
С помощью директивы CDV$G NOCONCUR можно запретить параллельное выполнение всех циклов вплоть до конца исходного файла.
Для описания общих данных для основной программы и подпрограмм, как и в обычном последовательном Фортране, используется оператор COMMON. В компиляторе FX/Fortran не составляет труда передать индекс цикла в качестве параметра параллельной подпрограммы (см. гл. 2), так как индекс цикла действительно не изменяется после вызова подпрограммы. Каждая итерация параллельного цикла, в котором была вызвана подпрограмма, имеет свою собственную копию индекса цикла. Однако, как и в других параллельных машинах с общей памятью, необходимо проявлять осторожность при передаче других параметров, модифицируемых параллельными подпрограммами.
Если даже подпрограмма вызывается параллельно, существует только одна копия подпрограммы и локальных переменных. Это приводит к затруднениям, так как различные вызовы подпрограммы должны иметь возможность параллельно работать с различными значениями переменных. Этот недостаток устраняется, если подпрограмму сделать рекурсивной. Для этого надо либо выполнить ее компиляцию с параметром -recursive, либо использовать ключевое слово RECURSIVE перед именем подпрограммы в исходном тексте подпрограммы. В любом случае при каждом вызове рекурсивной программы в стеке будет выделяться пространство для размещения ее локальных переменных.
Более подробно средства параллельной обработки, реализованные в языке FX/Fortran, рассмотрены в приложении АЗ.
Примеры параллельных программ
Программа Pi с автоматическим распараллеливанием
Если возложить на компилятор всю работу, то реализация (с точки зрения пользователя) программы Pi окажется тривиальной. Сначала программу нужно написать как обычную последовательную программу, затем следует выполнить ее компиляцию, обращая внимание на любое информационное сообщение компилятора.
1
С
2
С — Программа Pi выполняет в цикле вычисление площадей
3
С - - элементарных прямоугольников и их суммирование
4
С
5
real ttl(2), Ш(2)
6
integer*4 intrvls, cut
7
double precision sumall, width, f, x
8
C
9
f(x) - 4d0 / (IdO + x * x)
10
C
И
read(*,*) intrvls
12
t2 ■ etime(ttl)
13
C
14
C — Вычисление ширины элементарного прямоугольника
15 С
16
width ■ l.dO / intrvls
17
sumall ■ O.OdO
Эта строках f(x).
программа является простым “переводом" 1-17 определяются исходные данные и
поставленной задачи. В интегрируемая функция
18
С
19
С - - Цикл по числу прямоугольников и суммирование площадей
20
С
21
do 100 cut = 1, intrvls
22
sumall • sumall + width * f((cut - .5D0) * width)
23
100 continue
24
C
25
C — Вычисление времени выполнения и печать результатов
26
С
27
tl ■ etime(tt2)
28
write(6, *) 'Time in main =', tl - t2, ', sum »', sumall
29
write(6, *) 'Ошибка = ', sumall - 3.14159265358979323846d0
30
stop
31
end
Зная принципы действия компилятора, можно предположить, что он сосредоточит свои усилия на цикле DO (строки 21-23). Вызов компилятора с помощью команды
fortran -0 pi.f
завершится выдачей следующих сообщений:
Начало неименованной программной единицы
Строка 22 Информационное сообщение # 1219
Распараллеливание и векторизация затруднены из-за наличия в цикле переменной SUMALL, использованной слева и справа в одном операторе присваивания.
Строка 22 Информационное сообщение # 1308
Распараллеливание и векторизация затруднены, так как не разрешены ассоциативные преобразования (т.е. SUM или DOTPRODUCT) — SUMALL
Первое сообщение говорит о том, что цикл D0 не будет выполняться параллельно вследствие способа использования переменной sumall. Компилятор классифицирует используемые таким образом скалярные переменные как скаляры “кругового переноса44 (скалярные переменные, которые используются как в левой, так и в правой части оператора присваивания, содержащегося в
некотором цикле). Появление таких переменных обусловливает зависимость по данным каждой итерации цикла и следующей за ней итерации, что приводит к необходимости последовательного выполнения итераций.
Второе сообщение тесно связано с первым, но оно относится к операции, последовательно выполняемой с переменной sumall. Теоретически не должно иметь значения, в каком порядке производится прибавление результатов выполнения на каждой итерации (т.е. площадей элементарных прямоугольников) • к сумме, поскольку итог будет тем же самым после завершения всех итераций цикла в любом порядке. То же самое справедливо, если переменную sumall умножать в каждой итерации на различные числа и снова присваивать результат вновь переменной sumall. Компилятору известно об ассоциативных свойствах сложения и умножения, и он сообщает, что смог бы распараллелить этот цикл (наличие скаляра “кругового переноса" не является препятствием), если это будет разрешено, либо с помощью параметра -AS в командной строке fortran, либо с помощью директивы CDV$L ASSOC, помещенной непосредственно перед этим циклом.
Может вызывать недоумение, что компилятор должен испрашивать разрешение, хотя мог бы действовать на основе своих знаний. Это - пример осторожности, упоминаемой раньше. Хотя параллельное выполнение цикла (теоретически)" не должно вызывать никаких проблем, суммирование площадей элементарных прямоугольников в различном порядке может привести к изменениям в результате вследствие ошибок округления при выполнении операций с плавающей запятой.
Когда эта программа выполнялась в своем исходном виде, она работала хорошо, но при этом не использовались преимущества параллельной обработки. Поэтому программа повторно компилировалась с использованием параметра компиляции -AS:
fortran -О -AS pi.f
Компиляция завершилась без информационных сообщений, и была получена параллельная программа. Параллельная программа выполнялась более чем в 14 раз быстрее последовательной и была достигнута скорость вычислений примерно в 17 Mflops (более подробное обсуждение времени выполнения будет дано ниже в разд. "Производительность"). Результаты вычислений, полученные по этим двум программам, отличались незначительно (примерно на 3.3*10-14), а это значит, что ассоциативные преобразования (использование параметра -AS) могут привести к несущественным ошибкам из-за округления. Как показывает опыт, быстрая (параллельная) программа дает ответ с меньшей вычислительной погрешностью.
Программа Pi с явным параллелизмом
Хотя рассмотренный пример показывает легкость преобразования программы в параллельную форму, он не демонстрирует некоторых сложностей, которые могут при этом возникнуть. Поэтому будет описана другая реализация той же самой программы, в которой параллелизм будет представлен более зримо. Эта версия позволит также управлять степенью распараллеливания, чтобы сделать более осмысленной синхронизацию. Отметим, что, хотя этот стиль программирования для ЭВМ Alliant FX/8 не поощряется, пользователь должен осознавать проблемы и методы, рассматриваемые здесь в общих чертах, когда он использует директиву компилятора CNCALL.
Новая основная программа выполняет первоначальный ввод, вызывает подпрограмму суммирования (в цикле DO) и печатает результаты. Каждый
вызов программы суммирования вычисляет сумму площадей для своего множества прямоугольников, а затем прибавляет эту локальную сумму к глобальной сумме. Путем организации последовательного выполнения цикла суммирования в пределах подпрограммы суммирования и параллельного выполнения цикла, в котором вызывается эта подпрограмма, можно в целях эксперимента изменять число параллельно выполняемых подпрограмм. Этот алгоритм работает одинаково хорошо независимо от того, вызываются ли параллельные подпрограммы суммирования или вся программа выполняется последовательно. Единственное отличие состоит в том, что в параллельном варианте должна осуществляться синхронизация.
С
С - - Основная программа запускает подпрограммы work и
С — печатает ответ и время выполнения каждой подпрограммы
С
double precision sumall, time(50)
real ttl(2), tt2(2)
integer*4 prcnum, nprocs, intrvls
common /comm/ sumall, time
Именованный блок COMMON /comm/ (строка 8) содержит полную сумму sumall и вектор time, используемый для записи времени выполнения каждого процесса.
9
С
10
read(*,*) nprocs, intrvls
11
t2 * etime(ttl)
12
C
13
C - - Вызов параллельно выполняемых подпрограмм
14
С
15
CVD$L CNCALL
16
do 100 prenum = 1, nprocs
17
call work(prcnum, intrvls, nprocs)
18
100 continue
Хотя этот цикл по умолчанию не предназначен для параллельного выполнения, так как в его теле есть оператор CALL, директива компилятора в строке 15 отменяет это правило. Вследствие этого все пргос копий подпрограммы work выполняются параллельно. Действительно, так как в ЭВМ есть восемь процессоров, по крайней мере восемь копий подпрограммы work могут выполняться сразу. Отметим, что одним из параметров подпрограммы является prenum (индекс цикла). Важно, что здесь использован параметр подпрограммы, а не переменная в блоке COMMON, так как для каждого вызова подпрограммы должно использоваться свое значение prenum, когда различные “примеры44 подпрограммы исполняются одновременно.
19
20
21
22
23
24200
tl ■ etime(tt2)
write(6, *) ’Time in main *’, tl - t2, ', sum ■ ', sumall write(6, *) 'Ошибка - ', sumall - 3.14159265358979323846d0 do 200 i ■ 1, nprocs
write(6, *) 'Process ', i, ' Time = ', time(i) continue
25
26
stop end
В последней части основной программы производится печать результатов и программа завершается. Эта часть программы не выполняется до тех пор, пока не завершатся итерации предыдущего цикла (строки 16 - 18), даже если цикл выполняется параллельно.
27 subroutine work(idproc, intrvls, nprocs)
28 CDV$R NOCONCUR
29 C
30 C - - Вычисление интеграла методой прямоугольников для
31 С - - каждого из nprocs элементарных прямоугольников.
32 С — Число прямоугольников - параметр типа integer*4.
33 С
Директива NOCONCUR в подпрограмме work (строка 28) препятствует параллельному выполнению любого цикла в подпрограмме. Это позволяет осуществлять в программе строгий контроль над параллельностью. Так как компилятор не может разобраться в том, может или нет конкретная подпрограмма выполняться параллельно, он не способен обнаружить и учесть возможные проблемы синхронизации, которые могут возникнуть при одновременных ссылках к разделяемым переменным. Эта задача "передается" программисту, который должен ее решить во многом тем же способом, как и для других машин, посредством использования замков-переменных. Замок должен быть установлен перед обращением к разделяемой переменной и разблокирован после него.
34
Integer * 4 cut, intrvls, idproc, nprocs
35
Real dummy(2)
36
Double precision sum, sumall, width, f, x, time(50)
37
common /comm/ sumall, time
38
common /synch/ lock
В строке 38 описана переменная lock - блокировка или замок. Она является общей для всех копий подпрограммы, поскольку все они имеют блок common /synch/. Назначение этой блокировки - предотвращение одновременного изменения переменной sumall. В основной программе использование блокировки не требуется, поскольку невозможно, чтобы одновременно произошло обращение к этой переменной в основной программе и в какой-либо подпрог-
рамме.
В ЭВМ Allant FX/8 замком может быть любая переменная типа integer, и для него не существует специальных описаний. Так как установка и снятие блокировки производится путем изменения значения определенной переменной, она не может быть использована для других целей. Установка и снятие блокировки выполняется специальными подпрограммами lockon и lockoff соответственно. Эти подпрограммы были доступны в Аргоннской национальной лаборатории, но фирмой Aliant они не поставляются. Каждая из этих подпрограмм состоит из нескольких операторов языка Ассемблера, которые выполняют для замка команды “проверить и установить".
Замок и разделяемая переменная должны размещаться в блоке COMMON. Попытка обратиться к уже установленному некоторым другим процессом замку приводит программу к “прокрутке" (т.е. к состоянию ожидания в холостом цикле) до тех пор, пока другой процесс (обычно процесс, установивший замок) не снимет блокировку.
Сам компилятор использует в распараллеливаемой программе неявную синхронизацию, напоминающую блокировки. Хотя это может показаться несущественным, важно отметить, что блокировки компилятора подавляются при отключении оптимизации, но явное использование подпрограмм lockon и lockoff не подавляется. Следовательно, замки компилятора могут обусловить различное выполнение одной и той же программы в случаях, когда компилятору разрешено или запрещено проведение распараллеливания программы.
f(x) ■ 4d0 / (IdO + х * х)
С
tl ■ etime(dummy)
С
С - - Вычисление количества элементарных прямоугольников
С - - и их ширины.
width - l.dO / intrvls
С
С - - Вычисление площадей элементарных прямоугольников
С - - и их суммирование.
С
sum * O.OdO
do 100 cut ■ idproc, intrvls, nprocs
52sum ■ sum + width * f((cut - .500) * width)
53100 continue
54tiMe(idproc) ■ etiMe(duMMy) - tl
Строки 39 - 54 составляют тело программы work. Цикл DO (строки 51-53) не распараллеливается, так как выше в строке 28 была использована директива NOCONCUR. Нет необходимости в запрете одновременного доступа к массиву tine (см. строку 54), так как каждый процесс обращается к различным его элементам. Кроме того, основная программа не пытается обратиться к массиву tine до тех пор, пока не завершится выполнение всех экземпляров подпрограмм.
55 С
56 С — Возврат результата в базовый узел
57 С
58call lockon(lock)
59sumall ■ sumall + sum
60call lockoff(lock)
61return
62end
Как упоминалось ранее, два экземпляра программы work не должны одновременно изменять значение общей переменной sumall (при такой попытке возникает конфликт по записи). Чтобы этого не случилось, перед обращением к переменной sumall устанавливается (см. строку 58) блокировка (в рассматриваемой программе используется переменная lock), а после обращения к этой переменной производится снятие блокировки (строка 60). Так как эта переменная совместно используется всеми экземплярами программы work и так как аппаратные средства гарантируют, что одновременно только один процесс может захватить эту ячейку памяти, доступ к переменной sumall осуществляется корректно.
При компиляции без параметра -AS выдаются следующие информационные сообщения: •
Начало неименованного программного модуля
Строка 23 Информационное сообщение I 1297
Распараллеливание и векторизация запрещены из-за наличия в цикле ограничивающего оператора
Начало программного модуля WORK
Строка 52 Информационное сообщение # 1219
Распараллеливание и векторизация запрещены из-за наличия в цикле переменной, использованной слева и справа в одном операторе присваивания -- SUM
Строка 52 Информационное сообщение # 1308
Распараллеливание и векторизация запрещены, из-за недопустимых ассоциативных преобразований (т.е. SUM или DOTPRODUCT) — SUM
Первое сообщение связано с использованием в цикле основной программы оператора write, что не позволяет компилятору представить этот цикл в виде параллельных подциклов. Это не так важно, поскольку параллельное выполнение этого цикла бесполезно.
Второе и третье сообщения такие же, как и в предыдущей реализации этой программы. Чтобы строго управлять параллельным выполнением, используя только цикл в основной программе, рассматриваемый цикл временно сделан не параллельным. (Позже, в целях эксперимента, была выполнена повторная компиляция программы с параметром -AS, которая, как обнаружилось, выполнялась почти так же быстро, как и первая программа с тем же самым параметром, поскольку число процессоров было кратно 8.)
Программа с первого раза дала правильный результат. Она выполнялась в последовательном и параллельном режимах с одинаковыми результатами.
Производительность
Время выполнения первой и второй версий программы сильно различалось. Ниже их временные характеристики сначала рассматриваются отдельно, а затем сравниваются. Измерение времени производилось с использованием подпрограммы etime и функции time системы UNIX.
Измерение времени, по сути дела, производилось с помощью подпрограммы etime, которая поставляется фирмой Alliant. Эта подпрограмма дает время центрального процессора в секундах в виде числа с плавающей запятой и с разрешением в 0.01 с. Во второй версии программы функция etime использовалась как в основной программе, так и в каждой из подпрограмм.
По команде time системы UNIX выдается время пользователя (utime), системное время (stime) и полное время прохождения задания (etime). Хотя последнее время сравнительно неточно (оно включает время ввода данных для программы и зависит от степени загрузки системы), тестирование производилось преимущественно на незагруженной системе, а время ввода составляло от 2 до 3 секунд.
Первая программа
Временные характеристики первой неоптимизированной программы, как можно было бы предположить, соответствовали времени выполнения ее на любой последовательной машине. Системное время оставалось равным нулю, utime и etime были равны друг другу, а общее время выполнения было всегда на 2 - 3 секунды больше. Скорость вычисления, соответствующая значению etime или etime, равному 3.0 с, оставалась постоянной и была равна примерно 0.59 Mflops для всех исходных данных (количество прямоугольников - от 1О0О00 до 4000000).
Затем программа оптимизировалась (использовался параметр -о, а параметр -AS не использовался). При числе прямоугольников от 1000000 до 2870000 системное время не было равно нулю, а время etime все еще было приблизительно равно сумме utime и stime, полное время выполнения было примерно на 2 - 3 секунды больше времени etime. Скорость вычислений соответствующая времени etime (или etime), равному 3.0 с, оставалась постоянной и равнялась 1.16 Mflops.
Однако при числе прямоугольников от 2871000 до 3120000 общее время выполнения резко возросло, тогда как тенденция изменения других временных характеристик преимущественно сохранилась. (Можно было предположить, что кто-то параллельно запустил большое задание, но это было не так, поскольку обнаруженный эффект наблюдался и при повторном выполнении программы.) Хотя скорость выполнения программы, рассчитанная по времени etime, лишь незначительно снизилась примерно до 1.06 Mflops, скорость, рассчитанная на основании полного времени выполнения (3 мин) упала до 0.45 Mflops (см. рис. 3.2, иллюстрирующий этот эффект).
При исходном числе прямоугольников, превышающем 3120000, выполнение программы немедленно прекращалось с выдачей сообщения “ошибка шины44, иногда сопровождаемого сообщениями “нарушение сегмента- ции“ и “сброс оперативной памяти“. Это было весьма загадочно, поскольку эта программа, очевидно, не пыталась при обращении нарушить границы массива или выполнить другое недопустимое обращение к памяти, которым
обычно объясняются такого рода сообщения об ошибках. Этот эффект будет обсуждаться в разд. 3.5.
55555050Когда при компиляции использовался как параметр -о, так и параметр -AS, временные характеристики стали такими же, как и в случае неоптими- зированной программы, за исключением, конечно, того, что программа стала выполняться значительно быстрее. Ошибки шины в этом случае не появлялись, даже тогда, когда исходное значение числа прямоугольников увеличивалось до 200000000. Скорость вычислений, рассчитанная либо по времени etiBe, либо по полному времени выполнения программы, уменьшенному на 3 с, устойчиво составляла 17.5 Mflops.
55
55
50
50
О — Полное времяО
Время, вычисленное с использованием функции etime
△ ~ Время пользователя
2020101055JII1L510152025JII1L5101520253030
20
20
10
10
5
5
JII1L
510152025
JII1L
510152025
30
30
□ - Системное времяО
Число интервалов
Рис. 3.2. Время выполнения программы 1 (компиляция с параметром -AS).
Вторая программа
Целью написания второй версии программы было добиться некоторого управления степенью распараллеливания. Однако результаты измерения времени выполнения показали, что реально влияние не столь велико, как можно было ожидать.
Так как машина имеет восемь ВЭ, ожидалось возрастание скорости выполнения программы за счет распределения общего числа итераций цикла по процессам (до восьми процессов). Предполагалось, что при большем числе процессов время выполнения стабилизируется, так как дополнительные процессы не смогут выполняться, пока выполняются первые восемь процессов, а возможно, уменьшится, поскольку затраты на загрузку процесса в процессор, вероятно, станут меньше при той же степени распараллеливания.
Временные характеристики в этом случае особенно трудны для интерпретации. Когда в программе использовалось более восьми процессов, величина eti* для первых восьми завершенных процессов обычно была примерно в четыре-пять раз выше, чем для всех последующих завершенных процессов. Это может быть объяснено эффектами, связанными с использованием буфера команд.
Кроме того, значение времени etime, полученное в основной программе, видимо, равно сумме значений etime для процессов 1, 9, 17 и т.д. Системное время и время пользователя также увеличилось для процессов с этими номерами. Поэтому выглядит вероятным, что etime и time системы UNIX вычислялись как сумма времен выполнения всех процессов, которые выполнялись на одном й том же процессоре в качестве основной программы, а не как сумма времен выполнения всех процессов на всех процессорах.
Вернемся к проблеме, связанной с “ошибкой шины". Если любой из процессов требовал для вычисления более чем 50000 итераций цикла, то выполнение программы немедленно прекращалось. При этом общее время выполнения почти во всех прогонах оставалось ниже 10 с. Поскольку это время выражалось в секундах и сильно зависило от времени ввода с клавиатуры, оно было относительно бесполезно для расчета скорости вычисления в Mflops. Однако для 5000000 прямоугольников обнаружилась некоторая зависимость как полного времени выполнения программы, так и системного времени от числа процессов, которое изменялось, как показано на рис. 3.3. Представленная таблица показывает, что и системное время и общее время выполнения делают поразительный скачок, когда число процессов становится меньше 13.
procs
etime
utime
stime
etime
Mflops*
10
37.94
14.3
24.1
3:04
0.19
И
25.78
14.0
12.3
1:18
0.47
12
16.31
10.2
12.0
0:56
0.66
13
14.45
7.3
7.6
0:18
2.3
14
13.33
7.0
6.7
0:17
2.5
15
12.76
6.2
6.9
0:16
2.7
16
12.57
6.7
6.4
0:17
2.5
40
7.2
5.1
2.6
0:11
4.4
* Mflops рассчитано по времени etime, равному 3.0.
Рис. 33. Временные характеристики явной параллельной версии программы для 5000000 прямоугольников.
Сравнение временных характеристик первой и второй программ
Первая программа, будучи значительно проще в реализации, оказалась и значительно быстрее. По второй программе для заданного числа прямоугольников вычисления производятся быстрее, когда данные распределяются по большему числу процессов, однако скорость вычислений при выполнении второй программы никогда не достигает скорости первой. Обсуждение вопросов измерения производительности ЭВМ Alliant FX/8 содержится в [3].
Ловушки и проблемы
Компиляция с определенными режимами оптимизации (или вообще без оптимизации) вызывала аварийное прерывание в связи с “ошибкой шины“, если делалась попытка выполнить слишком много итераций цикла. Это не приводило к осложнениям, когда программа оптимизировалась целиком, но если условия применения незначительно отличались и параметр -AS не мог свободно использоваться, то возникали существенные трудности.
Фирма Alliant дает этому такое объяснение. Когда программа векторизуется, вычисления (в данном случае сложения) произволятся одновременно на одном наборе из 32 элементов. Если используется параметр -AS, то как только частичная сумма вычислена, она может быть тут же прибавлена к текущей сумме без использования дополнительной памяти.
Если же параметр -AS не используется, частичные суммы не могут быть прибавлены непосредственно к текущей сумме, так как векторные вычисления не смогут завершиться в правильном порядке. Следовательно, по мере вычисления частичных сумм они должны записываться во вспомогательный массив, который размещается в динамическом стеке вместе с другими локальными переменными программы. Когда все эти частичные суммы вычислены, элементы массива могут быть просуммированы один за другим.
В данном случае этот рабочий массив просто слишком велик для размещения в стеке. Здесь могут быть использованы следующие очевидные решения:
увеличение размера стека, который является позиционным параметром;
сохранение векторизованных циклов в пределах приемлемого диапазона в тех случаях, когда невозможно использовать параметр -as;
разбиение этих циклов на меньшие;
подавление векторизации;
Последняя возможность должна быть использована только в экстремальных случаях, что подтверждает различие производительности в представленных экспериментах. Некоторые результаты измерения времени могут быть также объяснены использованием длинного рабочего вектора, так как обратный проход по вектору для суммирования всех его элементов может вызвать большое число “промахов** при обращении к кэш-памяти и прерываний из-за отсутствия страниц.
Используемая на ЭВМ Alliant операционная система отличается от других версий системы UNIX. Например, когда выполнялась вторая программа с большим числом вызовов подпрограммы, формировалось достаточно большое количество выходных данных. Казалось, невозможно остановить вывод после того, как он начался. По-видимому, выполнение программы завершилось и выходной поток просто был записан в буфер терминала, так что ввод команды control-C (сигнал прерывания) не оказал никакого влияния. В других случаях, когда программа находилась в “интенсивном вычислительном** цикле, распознавание запроса на прерывание требовало несколько секунд.
Существуют некоторые недостатки, происхождение которых непосредственно объясняется подходом фирмы Alliant к параллельному программированию. Автоматическое обнаружение компилятором мест в программах на Фортране, которые могут быть распараллелены, - единственное в настоящее время средство поддержки выполнения параллельных программ машиной Alliant. Это, несомненно, полезно, когда пользователь имеет последовательный алгоритм, который должен быть преобразован для работы в параллельной среде. Хотя на первый взгляд кажется, что пользователь может просто бездельничать, позволив компилятору преобразовывать последовательную программу в параллельную, ясно, что иногда он должен дать компилятору указания, как выполнить это преобразование. Различие между этим и более традиционным подходом, когда пользователь явно добавляет требуемые конструкции для обеспечения параллельного выполнения программы, менее понятно, чем может показаться с первого взгляда.
Один из доводов в пользу машины Alliant состоит в том, что этот подход дает пользователю прямые подсказки возможностей распараллеливания данной программы. С другой стороны, компилятор позволяет процессу выполняться параллельно только тогда, когда он может выявить параллелизм на основе анализа цикла DO. Если программист имеет свое мнение о распараллеливании или желает реализовать хорошо известный параллельный алгоритм, то единственный способ осуществить распараллеливание состоит в реконструировании программы так, чтобы ее параллельная часть входила в цикл DO. Это иногда очень неудобно, как было обнаружено во время реализации параллельной программы быстрой сортировки. Конечно, можно создать препроцессоры для переноса других моделей параллелизма в модель системы Alliant, и эта возможность исследуется, в том числе и в фирме Alliant.
Выводы
Проектировщики ЭВМ Alliant FX/8 имели ввиду очень ясную цель и они в значительной степени приблизились к ней. Их подход к параллельному программированию гарантирует, что большинство программ, написанных для ЭВМ Alliant, будут и быстрыми и переносимыми. Кроме того, он позволяет пользователю, решающему научные задачи, практически сразу без какой-либо подготовки эффективно использовать вычислительную машину. Пользователи могут экспериментировать, изменяя степень распараллеливания своих программ от “чисто последовательной программы" до программы с существенно развитым параллелизмом.
Однако программы, уже написанные для какой-либо параллельной системы не легко использовать на ЭВМ Alliant. Это может быть аргументировано тем, что такой переход труден во всех случаях из-за отсутствия какой-либо стандартизации для конструкций параллельных программ и что в язык FX/Fortran включены многие конструкции языка Fortran 8Х и почти все параллельные механизмы, описанные Куком и др. [1,2]. Однако в настоящее время фирма Alliant не дает других средств, кроме упомянутых, для преобразования явно параллельных программ или алгоритмов для этой машины. Эти возможности, по-видимому, могут быть легко добавлены; аппаратные средства, несомненно, могут их поддержать, а остальное, вероятно, должно быть реализовано с помощью библиотеки поддержки выполнения, макросов и/или препроцессоров.
Возможно, эта точка зрения подобна точке зрения программиста, переходящего от языка ассемблера к языку высокого уровня: "Это, несомненно, мощный язык, но где все те возможности управления программой, к которым я привык?" Позже выясняется, что управление в большинстве случаев
и не требуется, и дополнительная мощность (без головной боли) принимается с благодарностью.
Благодарности
Наша работа на ЭВМ Alliant была бы невозможна без помощи Джека Донгарры из Аргоннской национальной лаборатории. Мы также очень признательны за помощь Дэнни Соренсену и Рику Стивенсу из Аргонна и Ричарду Свифту и Мэри Бет Шульц из фирмы Alliant.
Библиография
David J. Kuck, Edward S. Davidson, Duncan H. Lawrie, and
Ahmed H. Sameh, “Parallel supercomputing today and
the Cedar approach",
Science, vol. 231, no. 4741, 28 Feb. 1986, pp. 967-978.
David A. Padua, David J. Kuck, and Duncan H. Lawrie, “High-speed multiprocessor and compilation techniques", IEEE Trans. Computers, vol. C-29, no.9, Sept. 1980, pp. 763-776.
Walid Abu-Sufah and Allen D. Malony, “Vector Processing on the Alliant FX/8 multiprocessor", in Proceedings of the 1986 International Conference on Parallel Processing, Aug. 1986, pp. 559-566
Параллельный процессор
BBN Butterfly
Аллан Р. Ларраби, Кит Э. Пенник, Стюарт М. Стерн
Машина / Модель:
Параллельный процессор BBN Butterfly
Местонахождение:
Вычислительный центр компании “Боинг" Центр передовой технологии, Белльвью, Вашингтон
Процессоры:
32
4 Мбайт памяти на процессорный узел
Операционная система:
Chrysalis, версия 2.3.1
Язык:
Си
Компилятор:
Green Hills С, версия 1.8.0
Параллельный процессор BBN Butterfly был создан фирмой BBN (Bolt Beranek and Newman Inc.). Проект финансировался Управлением перспективных исследований и разработок Министерства обороны США (DARPA) и является частью стратегической инициативы США в области вычислительной техники. Фирма BBN Advanced Computers ответственна за дальнейшую разработку и сбыт вычислительной системы Butterfly. Разработанная сначала как высокоскоростная переключательная сеть (коммутатор), система Butterfly была использована в различных приложениях (например, сложное моделирование, задачи динамики жидкости, распознавание образов, передача данных). Во всех сетевых приложениях использовались небольшие конфигурации системы (до 16 узлов). С того времени система Butterfly развилась в универсальную параллельную ЭВМ.
Аппаратные средства
Система Butterfly представляет собой сильносвязанную ЭВМ типа MIMD с общей памятью. Она состоит из следующих трех основных компонент:
. Процессорные узлы.
. Коммутатор Butterfly (коммутатор типа “бабочка").
. Аппаратные средства ввода-вывода.
Система Butterfly может состоять из различного числа независимых друг от друга одинаковых процессорных узлов, связанных высокоскоростной коммутационной сетью. Как показано на рис. 4.1, каждый процессорный узел, состоит из следующих элементов:
Центральное процессорное устройство MC68020 и сопроцессор MC68881, предназначенный для вычислений над операндами, представленными в формате с плавающей запятой.
От 1 до 4 Мбайт локальной памяти (1 Мбайт на основной плате, 3 Мбайт наг дополнительной плате).
Аппаратные средства управления памятью.
Контроллер процессорного узла (КПУ).
Шина ввода-вывода.
Интерфейс коммутатора Butterfly.
Система Butterfly может содержать до 256 узлов, хотя в используемых в настоящее время системах число узлов не превышает 128. По мере добавления процессорных узлов к некоторой конфигурации системы Butterfly возрастает ее производительность, увеличиваются память, пропускная способность средств ввода-вывода, размерность коммутатора, причем увеличение пропорционально росту числа процессорных узлов.
Рис. 4.1. Блок-схема процессорного узла параллельного процессора Butterfly.
Вся память является локальной (каждый процессорный узел имеет 1 или 4 Мбайт памяти). Однако посредством коммутатора Butterfly любой процессор в системе может непосредственно обращаться к памяти любого другого про-
цессорного узла. Управление памятью в каждом процессорном узле осуществляется контроллером процессорного узла. КПУ определяет, относится ли некоторая ссылка к локальной или удаленной памяти и передает или принимает пакеты по коммутационной сети. С точки зрения программиста, память является одной большой общей памятью, удаленные ссылки к которой реализуются несколько дальше, чем ссылки локальные (примерно 6 мкс для удаленной и 2 мкс для локальной ссылки).
Процессорные узлы
Прикладные программы пользователя выполняются комбинацией процессоров 68020/68881. Вся программа, выполняемая процессором, размещается в локальной памяти. Процессоры 68020/68881 имеют тактовую частоту 16 МГц, остальное оборудование процессорного узла работает на частоте 8 МГц.
На КПУ возлагаются различные задачи. Он передает и принимает сообщения из коммутатора Butterfly и использует аппаратные средства управления памятью для отображения виртуальных адресов в физические. КПУ воспринимает все обращения к памяти, выдаваемые процессором, таким образом, что для прикладных программ все ссылки к памяти кажутся одинаковыми независимо от того является ссылка локальной или удаленной. Кроме того, КПУ реализует посредством микропрограмм операции, необходимые для параллельной обработки. К этим операциям относятся семафорные операции, операции с очередями, а также операции, реализующие механизм обработки событий и управление процессами. Эти операции гарантируют, что одновременно только один процессор может иметь доступ к данным, они также обеспечивают эффективную реализацию примитивов управления передачей данных и синхронизации. КПУ гарантирует, что эти операции происходят “неделимо", поскольку он управляет всеми локальными ссылками к памяти независимо от того, производятся ли они локальными или удаленными процессорами.
Колииутатор Butterfly
В коммутаторе Butterfly для достижения высокой производительности используется метод пакетной коммутации, который обеспечивает надежную и эффективную по затратам межпроцессорную связь. Этот коммутатор представляет собой набор последовательных переключающих узлов с топологией, подобной топологии схемы "бабочка", используемой при реализации алгоритма быстрого преобразования Фурье. Передача данных от каждого процессора к любому другому процессору производится со скоростью 32 Мбит в секунду. Пропускная способность коммутатора в зависимости от числа процессорных узлов растет линейно. Например, 32-процессорная система Butterfly имеет агрегированный коммутатор Butterfly, обеспечивающий передачу данных со скоростью 1 Гбит в секунду.
На рис. 4.2 показана схема соединения для 16-узловой системы Butterfly. Каждый узел имеет 4 входных и 4 выходных канала для передачи данных и реализуется как заказная СБИС. Коммутатор на 16 входов и 16 выходов реализуется с помощью восьми заказных СБИС, размещаемых на одной печатной плате.
Существует по крайней мере один путь через коммутатор от одного процессорного узла к другому. Работа коммутатора подобна работе сети с пакетной коммутацией. Пакет формируется путем присоединения к передаваемым данным адреса узла назначения. Адресные биты используются для выбора маршрута при прохождении коммутатора. Каждый коммутационный
узел выделяет два младших бита пакета и использует их для выбора одного из четырех выходных каналов. Остальная часть пакета передается по выбранному каналу. Не существует постоянно выделенного пути между каждой парой процессорных узлов. Следовательно, возможно, что в некоторый коммутационный узел одновременно поступят два сообщения, для которых требуется использовать один и тот же выходной порт. В этом случае одно сообщение немедленно передается его адресату, а другое будет передано после короткой задержки.
Рис. 4.2. Коммутатор для 16-узлового процессора Butterfly.
Сложность коммутатора Butterfly растет с увеличением числа процессорных узлов. Коммутатор, для 64-процессорной системы Butterfly может быть сконструирован путем добавления к коммутатору 16-процессорной системы колонки коммутационных узлов и количества рядов, достаточного для подключения еще 48 процессорных узлов (см. рис. 4.3). Число коммутационных элементов для «-процессорной системы составляет nlog4n. Сложность такого коммутатора значительно меньше, чем сложность матричного коммутатора, определяемая как я2.
При добавлении дополнительных коммутационных узлов, образуются дополнительные пути между каждой парой процессорных узлов. С увеличением
размера коммутатора возрастает его живучесть в условиях отказа отдельных коммутационных узлов. Кроме того, наличие альтернативных путей уменьшает вероятность возникновения конфликтов при коммутации. Когда некоторое сообщение должно быть повторно передано вследствие конфликта при использовании выходного порта коммутационого элемента, сообщение немедленно передается по альтернативному пути. Конфигурация системы должна выбираться так, чтобы вероятность конфликтов в коммутаторе была сравнительно мала. Хотя количество конфликтов зависит от решаемой задачи, дополнительное время, обусловленное конфликтами, обычно составляет от 1 до 5 процентов общего времени выполнения программы. Время передачи сообщения в основном определяется временем, которое требуется для последовательной бесконфликтной передачи сообщения через коммутатор.
Рис. 4.3. Коммутатор для 64-узлового процессора Butterfly.
Для иллюстрации использования коммутатора Butterfly процессорными узлами рассмотрим следующий пример считывания данных из удаленной памяти. Когда центральный процессор выдает адресную ссылку на считывание, КПУ воспринимает ее и с помощью своих аппаратных средств управления памятью преобразует виртуальный адрес в физический. Чтобы осуществить считывание содержимого из памяти, КПУ передает с помощью коммутатора пакет к удаленному процессорному узлу, запрашивая тем самым содержимое физической ячейки памяти. КПУ удаленного процессорного узла производит считывание из памяти и использует коммутатор для передачи пакета, содержащего считанное значение, локальному КПУ. Затем локальный КПУ передает значение центральному процессору, удовлетворяя тем самым запрос на считывание из памяти. Общее время передачи пакета через коммутатор для обеспечения доступа к удаленной памяти равно примерно 4 мкс. КПУ с использованием коммутатора может также передавать между любыми процессорными узлами блоки данных, состоящие из нескольких слов, с максимальной скоростью 32 Мбит/с.
Архитектура ввода-вывода
Каждый процессорный узел снабжен интерфейсом ввода-вывода. В настоящее время имеются два типа интерфейсных плат: плата адаптера Butterfly MULTIBUS и плата последовательного ввода-вывода системы Butterfly. Плата адаптера Butterfly MULTIBUS поддерживает эффективный обмен между процессорным узлом и шиной MULTIBUS. С ее помощью можно подключить любое совместимое с шиной MULTIBUS устройство, память или процессор. Плата последовательного ввода-вывода системы Butterfly поддерживает работу четырех интерфейсов RS-232 и четырех соединений типа RS-422. Применяемая для экспериментов машина была оснащена платой адаптера Butterfly MULTIBUS, обеспечивающего работу по последовательным каналам, и платой Ethernet. В настоящее время дисковые устройства не могут подключаться к плате адаптера Butterfly MULTIBUS, и все устройства массовой памяти связываются с оконечными машинами.
Программное обеспечение
Поскольку в настоящее время ЭВМ Butterfly не допускает подключения к ней устройств памяти на жестких дисках, такие устройства могут использоваться только на главной оконечной машине. Процессор Butterfly подключается к главной машине с помощью последовательного порта и канала Ethernet TCP/IP. Для начальной загрузки процессора Butterfly из главной машины используется последовательный канал. После загрузки программных средств Ethernet для дальнейшей загрузки программ в процессор Butterfly может быть использована высокоскоростная плата Ethernet. Средства разработки программ (компиляторы, библиотеки и др.) находятся в главной машине. Программы для процессора Butterfly разрабатываются с использованием главной ЭВМ, в качестве которой могут выступать либо ЭВМ DEC VAX, либо рабочая станция SUN, работающие под управлением операционной системы UNIX 4.2BSD (см. рис. 4.4). Разрабатываемые программы компилируются с помощью кросс-компиляторов и по каналу Ethernet загружаются в процессор Butterfly.
Операционная cucre.ua Chrysalis
Операционная система Chrysalis подобна системе UNIX, но рассчитана на одного пользователя. Она обеспечивает несколько уровней поддержки функционирования ЭВМ:
Ободочка Оболочка обеспечивает загрузку программ и их выполнение.
Однако опытному пользователю системы UNIX существующая оболочка системы Chrysalis может показаться недостаточно развитой. Например в ней не поддерживается переадресация при выполнении ввода-вывода. Средства оболочки Chrysalis подобны средствам оболочки системы UNIX Bourne.
ПрограммаПрограмма управления окнами поддерживает работу средств
управленияввода-вывода на экран консоли для нескольких процессов.
• окнамиС помощью этих средств можно образовывать и удалять окна,
выполнять переходы из одного окна в другое и изменять их размер. Помимо операций экранного редактирования поддер- 5живаются возможности уничтожения, замораживания и размо-
iраживания процессов.
ЯдроЯдро системы Chrysalis состоит из нескольких микропрограм
мных сегментов, размещаемых на каждом процессоре. Планировщик на каждом процессоре обеспечивает быстрое переключение контекстов (обычно за 120 мкс).
ПамятьСистема виртуальной памяти системы Butterfly 68000 управля
ется регистрами отображения памяти, которые называют реги- .ограми определения сегментов. Каждый регистр рассчитан на
определение сегмента размером до 64 Кбайт. В прикладных программах можно непосредственно управлять отображением памяти с помощью системных примитивов.
Ввоа-вывод Каждый процессор имеет возможность управлять своими собственными средствами ввода-вывода. Однако обычно для управления вводом-выводом используется только один из процессоров, который непосредственно подключается к шине MULTIBUS. По мере выдачи разными процессорами запросов на ввод-вывод они передаются из запрашивающего процессора к соответствующему исполнителю по системе межпроцессорной связи.
ЯзыкиДля программирования процессора Butterfly используется ком
пилятор Green Hills С. Этот кросс-компилятор вырабатывает 'программы для процессора 68000, работая на оконечной ма
шине. Основой для компилятора языка Butterfly С послужил компилятор UNIX portable С Compiler. Для процессора Butterfly также реализован кросс-компилятор языка Green Hills Fortran. В этой реализации языка Фортран содержатся все средства языка Fortran 77 стандарта ANSI и некоторые средства языка Berkeley UNIX 4.2BSD f77. Кроме того, были добавлены параллельные конструкции, разработанные в фирме BBN.
Средства отладки В настоящее время для использования отладочных средств необходимо знать язык ассемблера 68000. Однако процедуры диалогового отладчика DDT позволяют программисту, не владеющему языком ассемблера, глубоко проникать в существующие проблемы отладки.
Параллельный процессор Butterfly
Рис. 4.4. Средства разработки программ для процессора Butterfly.
Система Uniform System (Однородная системная библиотека)
Система Uniform System, поставляемая фирмой BBN, представляет собой набор подпрограмм, который назван однородной системной библиотекой. Эта библиотека обеспечивает программиста средствами высокого уровня для управления параллельным процессором на основе развитых принципов управления памятью и процессорами системы Butterfly. Обеспечиваемые функции включают многие примитивы операционной системы Chrysalis, что приводит к значительному сокращению дополнительных расходов специфически системного программирования. Эти функции могут использоваться совместно с системными программами и примитивами операционной системы Chrysalis.
Чтобы поставить все процессоры в одинаковые условия, средства управления памятью организуют все адресное пространство распределенной памяти в один большой блок общей памяти, совместно используемой всеми процессорами. Тем самым обеспечивается среда программирования, подобная среде программирования для однопроцессорной ЭВМ, в которой нет необходимости иметь дело непосредственно с регистрами отображения памяти системы Butterfly. При таком подходе все параллельно работающие процессоры имеют равные условия использования данных.
Коммутатор Butterfly организован так, что при попытке одновременного обращения к памяти одного и того же процессорного узла возникает конфликт при доступе к памяти. В системе Uniform System для предотвращения таких ситуаций предусмотрены функции, распределяющие память равномерно на общем пространстве памяти. Хотя доступ к удаленной памяти требует б мкс, а к локальной - 2 мкс, простота доступа к памяти, обеспечиваемая системой Uniform System, удобна для многих приложений.
Средства управления процессами обеспечивают программиста возможностью поддержки динамического баланса загрузки процессоров при использовании любого числа процессоров. При таком подходе все процессоры рассматриваются как фонд доступных 11 исполнителей “, каждый из которых имеет доступ к общему сегменту памяти. Программист должен определить параллельные элементы программы и сегментировать ее на подпрограммы (задачи). С помощью генератора осуществляется логическое управление задачами и распределение их по процессорам, по мере того как они становятся доступными.
На главной ЭВМ размещается эмулятор системы Uniform System, который позволяет программисту производить первоначальную частичную отладку программ с помощью главной рабочей станции.
Перспективные разработки
Согласно публикациям, фирма BBN в систему Butterfly предполагает внести несколько усовершенствований, которые приведут к значительному улучшению системы. Операционная система Chrysalis 3.0 будет рассчитана на нескольких пользователей. Каждый пользователь в начале сеанса будет иметь возможность предварительного распределения полного набора или подмножества (кластера) узлов процессора Butterfly. Кроме того, интерфейс “пользователь - главная ЭВМ" будет предоставлять более логичный подход к организации доступа к процессору Butterfly и управлению им. Пользователь будет регистрироваться в рабочей станции главной ЭВМ, разрабатывать кросс-программу и обращаться к процессору Butterfly посредством “оболочки" главной машины. Загрузка и инициализация системы будет существенно улучшена благодаря реализации платы MULTIBUS RAM, которая содержит копию системы. Это должно привести к большому увеличению скорости по сравнению с последовательным каналом и каналом Ethernet.
Драйвер диска Butterfly будет реализовываться для диска типа SMD (Storage Module Drive disk). Первая версия программного обеспечения будет поддерживать “плоскую" файловую систему. Кроме того, проходит тестирование шинный адаптер VME, который должен обеспечить широкую полосу пропускания для ввода-вывода. Реализуется новая библиотека подпрограмм, имеющая название RamFile. В ее функции входит обеспечение программиста файловой системой, аналогичной файловой системе UNIX, использующей не- распределяемую память системы Butterfly, которая может быть очень обширной.
32-разрядный процессор, выполняющий операции над числами с плавающей запятой, сможет использовать память размером 4 Мбайт на узел (общая память системы составит в этом случае 128 Мбайт). Библиотека RamFile, будет иметь средства для использования структур данных, которые в настоящее время слишком велики для существующего адресного пространства процесса. Поддерживается параллельный доступ различных процессоров к резидентной библиотеке RamFile. В дополнение к существующим компиляторам для языков Си и Фортран предполагается полностью реализовать язык Common Lisp, который будет поддерживать использование общей памяти и содержать конструкцию future, разработанную в MIT.
В [1] содержится обзор параллельной системы Butterfly. Подробное изложение стратегии эффективного использования библиотеки (системы) Uniform System для программирования системы Butterfly дано в [2]. Часть материала этой работы воспроизведена в приложении В2.
Измерение времени выполнения программы
Время выполнения программы может быть оценено с помощью последовательных вызовов системной программы GetRtc. Время между двумя вызовами может быть напечатано с помощью программы printf, как показано на примере программы Pi в разд. 4.3.
. Пример параллельной программы
В процессе вычисления интеграла по программе Pi каждый процессор вычисляет частичные суммы для равных интервалов. После завершения вычисления частичной суммы каждый процессор прибавляет частичную сумму к значению глобальной переменной с именем tpi. Для обеспечения “неделимого" выполнения операции сложения используется общая переменная lock.
1
#include <us.h>/* предложения include должны записы-
*/
2
finclude <stdio.h> /* ваться в указанном порядке
*/
3
BEGIN_SHARED_DECL /* в этой программе демонстрируются
*/
4
double tpi;/* три способа совместного использо-
*/
5
END_SHAREDJ)ECL/* вания данных
*/
6
int nrecs, nprocs, timel, time2;
7
short *lodk, *nodecount;
В строках 3 - 5 с помощью макросредств определяются глобальные общие переменные, значения которых мотуг изменяться процессорами. Эти макросредства описания общих переменных могут использоваться в программе для Uniform System только один раз. Доступ к переменным, которые необходимо использовать как общие и которые не будут переопределяться, осуществляется с помощью макрокоманды Share (см. строку 23 программы), которая просто предоставляет соответсвующий адрес переменной каждому процессору.
Userinput ()
{
printf(w\n4MC*o прямоугольников ...");
scanf(w\n%dw, &nrecs);
printf("\n4ncAO процессоров ...");
scanf ("\n%dM, &nprocs);
}
В строках 8-14 содержатся операторы, используемые для ввода данных nrecs (число прямоугольников) и nprocs (число процессов), задаваемых пользователем для каждого прогона программы.
ShareData()
{
MakeSharedVariables;
nodecount = (short *) Allocate(sizeof(short));
*nodecount ■ nprocs;
lock ■ (short *) Allocate(sizeof(short));
*lock - 0.
SHARED tpi « 0;
23Share(&nrecs);Share(&nprocs);Share(&nodecount); Share (Mock);
24 }
После инициализации системы Uniform System (строка 70) вызывается, если только уже был использован макрос SHARED_DECL, макрокоманда MakeSharedVariables (строка 17), которая используется для выделения памяти и передачи каждому процессору необходимых адресов. Каждая переменная, описанная с помощью макроса SHARED J)ECL, может быть использована в следующей программе, если имени переменной предшествует слово SHARED (см., например, строку 22). Иначе обстоит деле с переменными nodecount и lock, которые размещаются явно с помощью функций (см. строки 18 и 20). Системная программа Allocate(size) размещает блок памяти размера size байтов и возвращает значение типа char, которое переводится затем в нужный тип. В строке 23 с помощью макрокоманды share передаются всем процессорам указатели на четыре переменные. Переменные, указатели на которые определяются с помощью макрокоманда Shared, должны иметь четырехбайтовую длину. Другие макросы, используемые для работы с разделяемыми данными, описаны в приложении В2.
25 PrintAnswerO
26 {
27int elapsed_time;
28double pi;
29if (time2 > timel)
30{
elapsed_time ■ time2 - timel;
printf(w\Time is XdO, elapsed_time);
}
pi * 3.1415926535897932384626433;
pi - pi - SHARED tpi;
printf(M\nBH4HCAeHHoe pi ... %27.20e\nw, SHARED tpi);
printf("\nPi минус вычисленное pi ... %27.20e\nw, pi); 38 }
Функция, описанная в строках 25 - 38, вызывается после выполнения основного тела программы. Она просто выводит вычисленное значение числа Пи и сравнивают это значение с известным значением. Известное значение показано в строке 34, оно записано без использования формата представления чисел с двойной точностью, реализованной в этой машине.
В основном теле следующей программы системная программа GetRtc используется для получения значения системного времени. После выполнения вычислений снова считывается значение системного времени, и если системные часы были в рабочем состоянии, то оператором printf (строка 32) будет напечатано значение времени выполнения (одна единица системного времени равна 62.5 мкс).
Dummy2() {};
PartialJM (Dummy2, index)
int index;
{
int me, nl, n2, j, section;
double h, x, sum;
{
me■index;
h■(double) 1.0/nrecs;
section ■ nrecs / nprocs;
nl=(ме * section)* 1*;
50n2=nl + section-1;
51 if (me ■• (nprocs - 1)) n2 « nrecs;
52 sum • 0.0;
Строки 39 - 64 программы определяют вычисления, выполняемые каждым процессором. Основное тело программы состоит из функции Partial_P1(Dummy2, index) (строка 40). Dummy2 - аргумент, передаваемый программе, которая вызывается на каждом процессоре перед выполнением функции Partial_Pi. Аналогично тот же самый аргумент может быть передан программе, которая выполняется после выполнения основной программы (в данном случае это Partial_P1). В данной версии возможность такого автоматического вызова программ не используется, однако она описана ниже при обсуждении основной программы (строки 65 - 75), которая генерирует задачи для каждого процессора.
Использование переменной index (см. строки 40 - 46) гарантирует, что каждый процессор, выполняющий программу Partial_Pi получит уникальный
номер. Значения переменной index образуют компактное множество целых чисел в диапазоне от 0 до числа, на единицу меньшего ранее заданного числа процессоров. Переменная h - ширина прямоугольников (строки 44 - 47). Переменная х (см. строки 44 - 55) - это середина подынтервала (основания прямоугольника). Каждый процессор использует свое значение переменной index и переменную selection для определения своих пределов интегрирования (строки 49 - 51).
53 for (j - nl; j <« n2; -н-j)
54{
55x « (j - 0.5) * h;
56sum « sum + (4.0/(1.0 + x * x));
57}
58LOCK(lock, 0);
59SHARED tpi = SHARED tpi + (h * sum);
60UNLOCK(lock);
61Atomic_add(nodecount,-1);
62while (*nodecount> 0)UsWait (0);
63}
64 }
Собственно вычисление интеграла для каждого интервала выполняется оператором цикла for (см. строки 53 - 57). Макросы LOCK и UNLOCK (для их записи требуется использовать прописные буквы) реализуют блокировку в режиме занятого ожидания. Второй аргумент в макросе LOCK задает число десятков микросекунд, определяющее интервал времени между попытками овладеть замком. Нулевому значению этого аргумента соответствует значение, принимаемое по умолчанию (оно примерно равно 1 мс). После неделимого изменения переменной tpi счетчик, первоначальное значение которого равно числу процессоров, неделимо уменьшается и вызывается еще одна макрокоманда с именем UsWait (п) (строка 62) для организации цикла занятого ожидания. Такое ожидание организуется для того, чтобы гарантировать, что никакая программа после строки 62 не будет выполняться до тех пор, пока все процессоры не завершат свои задачи. Как и прежде, аргумент п макрокоманды UsWait равен числу десятков микросекунд, и, по умолчанию, его нулевое значение задает интервал, приблизительно равный 1 мс.
65 Dummyl() {}; Dummy3() {};
66 main ()
67{
68Userlnput();
69 timel = GetRtc(); /* считывание значения времени */
70InitializeUsO;
71ShareData();
72GenOnIFu11(Dummy1, PartiaIP1, Dummy3, 0, nprocs, 0, 0);
73time2 = GetRtc();
74PrintAnswerO;
75 }
Наконец, обратимся к основной функции, в которой вызываются другие рассмотренные выше функции. В строке 68 вызывается функция userinput, которая требует ввести число прямоугольников (интервалов) и число процессоров. В строке 69 функция GetRtc обеспечивает считывание значения системного времени. В строке 70 вызывается программа установки и инициализации системы Uniform System, затем следует вызов функции ShareData, которая обеспечивает распределение требуемых разделяемых переменных.
В строке 72 производится вызов генератора, используемого для образования задач для каждого процессора. Здесь использован один из многих генераторов, доступных пользователю в системе Butterfly. Генератор GenSOnIFull вызывает параллельное выполнение программы Partial_Pi на nprocs процессорах. Тот процессор, на котором выполнялась программа GenOnIFull, также получает свою задачу, ему соответствует нулевое значение переменной index. Программа Dummyl вызывается на каждом процессоре перед вызовом программы Partial_Pi, а программа ОивяуЗ - после завершения программы Partla1_Pl. Четвертый аргумент программы GenOnIFull (в представленном случае равен 0) является артументом, который может быть передан программе Dueeyl и/или программе Du»y2, если она используется. Шестой аргумент (также равен 0) ограничивает число процессоров. Если это значение равно 0 или -1, то число процессоров ограничено значением nprocs, положительное число само определяет предельное число процессоров. Если седьмой аргумент имеет значение 1, то любой процессор при соблюдении некоторых условий может прервать выполнение программы GenOnIFull. Если это происходит, то все процессоры передают управление процессору, инициализировавшему вызов программы GenOnIFull, и ни одна задача больше не начинается. Однако выполняемые задачи будут завершены. Программа AbortGen после ее прерывания возвращает переданное ей значение.
Оператор в строке 73 выполнится лишь тогда, когда завершатся все задачи. В это время будет считано второе значение системного времени и будет осуществлена печать результатов счета.
Производительность
В настоящее время система Butterfly рассчитана на одного пользователя. Требуется целая минута для загрузки скомпилированной программы Р1 из оконечной ЭВМ в процессор Butterfly с использованием последовательного порта. (Канал Ethernet должен работать быстрее.) Время установки системы Uniform System (см. строку 70) несколько меньше двух секунд. По описанной здесь программе каждый процессор выполняет nrecs/ nprocs итераций. На время выполнения последней итерации оказывает незначительное влияние число используемых процессоров. В любом случае загрузка хорошо сбалансирована. Единственное изменение разделяемой переменной tpi выполняется после того, как каждый процесс завершит все свои итерации. Полное время решения задачи для 10000000 итераций, что соответствует 60000000 операций с плавающей запятой, при использовании 29 вычислительных узлов составляет в среднем 18.0 с (производительность - 3.3 Mflops).
На рис. 4.5 показана зависимость ускорения от числа процессоров в системе при разбиении интервала интегрирования на 10000000 подынтервалов.
При числе процессоров, не превышающем 29, разница между вычисленным значением Пи и значением, представленным в строке 34, не превышала 10"15, что близко к пределу точности представления чисел с двойной точностью. Для рассмотренного примера программы использование в качестве параметров компиляции -0 и -02 не приводит к ускорению выполнения программы.
Ловушки и проблемы
Операционная система Chrysalis находится на ранней стадии разработки. Работавшие с системой UNIX пользователи найдут в системе Crysalis небольшое количество команд и утилит, которые окажутся им знакомыми. Однако существует еще много системных программ, которые предназначены для того, чтобы обеспечить пользователя значительной гибкостью в реализации программ. Система Uniform System поддерживает простоту использования машины, а в примере, рассмотренном в этой главе, используются исключительно эти средства. Использование большого числа явных команд позволяет работать более изощренно, однако для этого требуется лучшее понимание операционной системы.
Ряс. 4.5. Зависимость ускорения вычислений от числа процессоров (4 - 29) для 10000000
интервалов.
Существует много примитивов низкого уровня, позволяющих управлять локальной и глобальной памятью и осуществлять связь между процессорами. Вызовы генераторов задач могут быть вложенными, однако это не приводит к тупикам. Как и в большинстве параллельных процессоров, здесь можно написать программу, которая с некоторыми данными будет выполняться корректно, а с другими - нет. Иными словами, компилятор не защищает пользователя от написания некорректных программ. Документация, как правило, вносит ясность в эти вопросы.
Подход к реализации программ, особенно схема распределения памяти, может оказывать сильное влияние на время ее выполнения. Например, частые ссылки многих процессоров к глобальным переменным могут стать узким
местом при выполнении программы, так как все глобальные переменные размещаются в одной и той же физической памяти.
Заключение
Система Butterfly представляет собой удобное средство для изучения параллельных архитектур. Наличие локальной памяти у каждого процессора и возможность быстрого доступа к общей памяти поддерживают среду, пригодную для отображения многих стратегий построения параллельных программ. Определенные модели могут привести к проблемам, связанным с возможным возникновением конфликтов. Как признано, доступные средства имеют ограничения, а диалоговый отладчик DDT может показаться некоторым пользователям утомительным, но мобильность и разнообразие средств управления процессами и памятью дают возможность гибкого программирования. Однако это вовсе не означает, что существующие программы и алгоритмы можно легко перенести на эту машину и получить максимум производительности при умеренных усилиях.
Благодарности
Благодарим Минди Гарбера из фирмы BBN Advanced Computers за помощь в получении документации по однородной системной библиотеке Uniform System Liblary, описанной в приложении В2, и за помощь пр составлению технического обзора системы Butterfly, представленного в этой главе.
Библиография
BBN Laboratories Inc, “Butterfly™ Parallel Processor Overview", BBN Report No. 6148, Version 1, March 6, 1986.
BBN Laboratories Inc, “The Uniform System Approach to Programming the Butterfly™ Parallel Processor", BBN Report No. 6149, Version 2,
June 16, 1986.
CRAY X-MP
Курт Б. Модал
Машина / Модель:
CRAY Х-МР Модель 22*
Местонахождение:
Национальный вычислительный центр MFE1), Калифорнийский университет, Национальная лаборатория им. Лоуренса
Процессоры:
в Ливерморе
2 центральных процессора, использующих общую память емкости 2 миллиона 64-разрядных слов
Операционная система:
CRAY Time Sharing System (CTSS) - система разделения времени
Язык:
Расширенный Фортран 77
Компилятор:
CRAY Fortran Compiler (CFT версия 1.14)
Аппаратные средства
СуперЭВМ CRAY Х-МР 22, разработанная фирмой Cray Research Inc. - многопроцессорная версия вычислительной машины CRAY-1. Она состоит из двух процессоров, аналогичных процессору суперЭВМ Сгау-1, которые совместно используют общую память и подсистемы ввода-вывода. Расслоенная центральная память может иметь объем до 2 миллионов 64-разрядных слов. Во время каждого машинного такта параллельно и независимо друг от друга могут производиться обращения ко всем банкам расслоенной памяти. Время основного машинного такта составляет 9.5 нс. Каждый центральный процессор (ЦП) имеет 12 функциональных устройств, организованных в четыре группы: адресные, скалярные, векторные устройства и устройства для выполнения вычислений с плавающей запятой. Все функциональные устройства - конвейерного типа и могут работать параллельно друг с другом. Между памятью и функциональными устройствами находятся группы регистров: восемь адресных (А), 64 буферных адресных (В), восемь скалярных (S), 64 буферных скалярных (Т) и восемь 64-элементных векторных (V) регистров. На рис. 5.1 представлено схематическое изображение архитектуры двухпроцессорной суперЭВМ CRAY Х-МР.
Все арифметические команды являются командами типа “регистр-регистр": функциональные устройства считывают операнды и записывают результаты выполнения операций в регистры A, S и V. Межпроцессорное взаимодействие обеспечивается в языке ассемблера CAL для суперЭВМ CRAY X- МР (CRAY Assembly Language) посредством использования наборов общих
9 MFE (Magnetic Fusion Energy) - . метод генерации энергии путем термоядерного синтеза с магнитным удержанием. - Прим. ред.
Векторные регистрыВекторные регистрыПодсчет числа единиц и четностиПодсчет числа единиц и четности
Векторные регистры
Векторные регистры
Подсчет числа единиц и четности
Подсчет числа единиц и четности
четчип рсальи. времени
2М
Память
Векторные регистры
+ (Ак))
((АО)
Управление векторными операциями
Регистр векторной маски
V7
V6
V5
((АО) + (Ак)) «АОЖАк))
Состояние
Счетчик программно-временных прерываний
Подсчет числаединиц и четности
Логические Jоперации I Сдвиг
Скалярные регистры
АО
ОО
РТС
РИС
37
Управление обменом пакета । состояния
S7
S6
((АЬ) + jkm)
(Ah) + jkm)
О
Буферы команд
Счетчик |'сальн. времени
1
SM
4137
1
Адресные регистры
24
Сложение
Векторныефункциональныеустройства
| УмножениеСложение
ФУПЗ
J Ак
Вычислениеобратной
Подсчет числа единици ведущих нулей
Логические | °Т"”Гс^7
Сложение
Скалярныефупкциональи)устройства
| Умножение
Сложение
Адресныефункциональныеустройства
Ак
^7
ST
Рис. 5.1(6). Архитектура двухпроцессорной суперЭВМ CRAY Х-МР (продолжение).
регистров. Каждый набор общих регистров состоит из восьми общих адресных регистров (SB), восьми общих скалярных регистров (ST) и 32 одноразрядных семафорных регистров (SM). Оба процессора совместно используют регистр RTC (Real-Time Clock) - “часы реального времени “. Операционная система, обеспечивающая режим разделения времени, CTSS (CRAY Time Sharing System), использует один из аппаратных семафорных регистров для синхронизации межпроцессорного взаимодействия. Аппаратные семафорные регистры доступны и программисту, использующему язык ассемблера, однако в описанных в данной главе вычислительных экспериментах возможность непосредственного использования семафорных регистров не рассматривается.
Программное обеспечение
В суперЭВМ CRAY Х-МР, как и в суперЭВМ CRAY-1, нижний уровень параллелизма достигается посредством векторизации вычислений. Компилятор для языка CRAY FORTRAN (CFT) производит анализ внутренних циклов оо с целью обнаружения векторизуемых последовательностей операторов и затем генерирует код, используя преимущества организации этого процессора. Векторизация выполняется компилятором автоматически, она приводит к увеличению производительности (к уменьшению времени выполнения программы) без изменения структуры программы или ручного программирования. Кроме того, в суперЭВМ CRAY Х-МР в многозадачном режиме достигается параллелизм высокого уровня. В этом случае все процессоры участвуют в решении одной задачи посредством параллельного выполнения отдельных подзадач.
При использовании операционной системы CTSS необходимая синхронизация задач должна определяться программистом с помощью примитивов (подпрограмм) библиотеки подержки многозадачного режима MULTILIB (MULTItasking LIBrary). Использование многозадачного режима требует внимательного рассмотрения реализуемого алгоритма и анализа возможных в нем зависимостей данных. Система CTSS предоставляет разнообразие средств поддержки многозадачного режима, в том числе такие необходимые, как:
средства компоновки программ,
утилиты,
средства управления памятью,
примитивы синхронизации в многозадачном режиме. Указанные средства поддержки многозадачной среды описаны в приложении С2.
Задача определяется как программная единица, которая может независимо от других программных единиц назначаться для выполнения на процессор. Все задачи программы совместно используют одну и ту же область памяти (область COMMON, определенную в языке Фортран), но каждой задаче выделяется и собственная память для размещения локальных переменных. Первоначально любая программа состоит из одной задачи. Любая задача может породить некоторое число других задач. Все порождаемые задачи являются потомками исходной задачи и логически выполняются параллельно, однако фактически ход параллельной обработки на двух процессорах зависит от текущей машинной загрузки и доступных ресурсов. Следовательно, не так лер ко определить в конкретном прогоне действительно ли отдельные задачи выполняются параллельно.
Более подробно о средствах параллельного программирования для суперЭВМ CRAY Х-МР, предлагаемых фирмой Crey Research, можно прочитать в [1, 2, 3].
Разработка программы
Подготовка параллельных программ для суперЭВМ CRAY Х-МР, работающей под управлением операционной системы CTSS, обычно состоит из следующих шагов:
Подготовка исходного текста программы на языке Фортран с использованием строкового редактора trixgl или какого-либо другого доступного редактора.
Компиляция исходного текста компилятором CFT производится по директиве:
eft i-source,b=binary,1-11sting,link-2
где параметр link предназначен для размещения локальных переменных в стеке.
Редактирование и загрузка двоичной версии программы в память ЭВМ:
Idr b=binary,lib=(multilib,fortlib),x-execute
где Multi lib - библиотека программ поддержки многозадачного режима, а fort lib - библиотека с обычными Фортран-программами. '
Выполнение исполнимой объектной программы производится по директиве:
execute
Пример параллельной программы
Последовательная и параллельная версии программы Pi используют один и тот же исходный код. Основная задача генерирует п подзадач и ждет, когда завершатся вычисления для всех подзадач в соответствующей части интервалов (число интервалов совпадает с числом выполняемых циклов). Часть работы, выполняемая в каждой задаче циклом DO, определяется общим количеством задач и номерами задач (от 1 до 4). Полученные суммы передаются основной задаче с помощью одной переменной, находящейся в области COMMON. Основная программа ожидает завершения выполнения всех подзадач, а после их завершения-печатается окончательный результат.
Исходный текст основной программы и пояснения к нему
1
REAL
SUM
2
INTEGER
TL(4), INTRVLS, NTASKS, TLOCK
3
COMMON
SUM, TL, INTRVLS, NTASKS, TLOCK
4
INTEGER
LSTAT
5
REAL
PI
6 С
7
INTEGER
ITC(3,4)
8
EXTERNAL
TASK
9 С
В строках 1-2 описаны переменные из блока COMMON, к которым будут обращаться задачи (в строке 8 задачи дано имя TASK и атрибут EXTERNAL):
SUM - окончательный результат вычислений; переменная, к значению которой каждая задача прибавляет свой вклад в общую сумму;
TL - массив, используемый для хранения информации о времени выполнения каждой задачи;
INTRVLS и NTASKS - задаваемые пользователем входные значения, определяющие количество интервалов и число задач соответственно;
TLOCK - блокировка (переменная для организации блокировки в многозадачном режиме) - используется каждой задачей для координации доступа к общей переменной sum;
LSTAT - используется для возврата статуса вызова, предназначенного для инициализации и присваивания значения блокировке TLOCK (строка 12);
PI - контрольное значение числа Пи, представленное 23 десятичными знаками;
ПС - массив управления задачами, используемый для инициализации до 4 подзадач.
Предложение EXTERNAL TASK (строка 8) необходимо для обеспечения вызова программы TSKSTART в строке 37.
10
CALL LINK('UNIT59-TTY//’)
и
PI - 3.14159265358979323846433Е0
12
CALL L0CKAS6N (TLOCK,LSTAT)
13 С
14 С ***
УСТАНОВКА МАССИВА УПРАВЛЕНИЯ ЗАДАЧАМИ И
15 С
ЗАПУСК ЗАДАЧ
16
00 15 I - 1, 4
17
ITC(l.I) - 3
18 С
*** ЗАПИСЬ НОМЕРА ЗАДАЧИ В МАССИВ УПРАВЛЕНИЯ
19
1ТС(3,1) - I
20 15
CONTINUE
21 С
22 100
VRITE(59,*) 'ВВОД ИНТЕРВАЛОВ И ЗАДАЧ (ДО 4) 0=>END'
23
READ (59,*,END - 10 , ERR - 10 ) INTRVLS,NTASKS
24
WRITE(59,*) ' ИНТЕРВАЛЫ - ', INTRVLS
25
WRITE(59,*) ' ЧИСЛО ЗАДАЧ - ', NTASKS
26
SUM - 0.0E0
27 С ***
ВЫПОЛНЯТЬ ПОКА INTRVLS HE РАВНО НУЛЮ ***
28
IF ( INTRVLS .EQ. 0 ) GO TO 999
29 С
30 С
*** ПОЛУЧЕНИЕ ВРЕМЕНИ ЗАПУСКА ЗАДАЧ ***
31 С
32
ISTART - IRTC()
33 С
Вызов подпрограммы LINK (строка 10) используется для присвоения терминалу пользователя логического номера устройства. Во всех операторах ввода-вывода в программе используется ниже устройство с номером 59 (термв-
нал пользователя). Исходные данные - число интервалов и число задач (1, 2 или 4) - вводятся пользователем (оператор READ - строка 23). Подпрограмма LOCKASGN (строка 12) используется для инициализации переменной (блокировки) TLOCK. Если блокировка ЛОСК уже была установлена (в данном месте программы она не должна быть установлена), то переменная LSTAT принимает значение 1, в противном случае (если лоск была инициализирована правильно) ее значение будет равно 0. В этом месте программы должен проверяться статус возврата. Массив управления задачами используется для инициирования подзадач, инициализированных в цикле D0 (строки 16 - 20). Первый элемент массива определяет число слов в каждом массиве управления задачей. Второму элементу в результате вызова подпрограммы TSKSTART присваивается уникальный идентификатор задачи. Третий элемент содержит значение задачи, которое в этой программе идентифицируется как номер задачи (1 - 4).
Время начала некоторой работы в тактах “часов реального времени" rtc может быть получено посредством вызова встроенной функции irtc (строка 32). Разность между временем завершения вычисления и временем его начала (в машинных тактах) дает точное время решения задачи для различного количества интервалов и задач. Вызов IRTC реализуется как инлайновая подстановка и поэтому его влияние на оценку времени несущественно.
34
*** ЗАПУСК ВСЕХ ЗАДАЧ
35 С
36
DO 50 I - 1, NTASKS
37
CALL TSKSTART ( ITC(l.I) , TASK )
38 50
CONTINUE
39 С
*** ОЖИДАНИЕ ЗАВЕРШЕНИЯ ВСЕХ ПОДЗАДАЧ ***
40
DO 60 I - 1, NTASKS
41
CALL TSKWAIT ( ITC(l.I) )
42 60
CONTINUE
43 С
Подпрограммы библиотеки MULTLIB используются для образования новых задач исходной основной задачей. Вызовы подпрограммы TSKSTART (строка 37) используются для образования, инициализации и запуска новых задач для определенной точки входа подпрограммы (в данном случае TASK). Массив ПС должен формироваться вызывающей программой. В зависимости от того, какое значение задал пользователь для числа задач NTASKS, будет образовано 1, 2 или 4 задачи (вызов подпрограммы TSKSTART в цикле D0, строки 36 - 38). Для каждой образованной задачи в цикле D0 производится вызов подпрограммы TSKWAIT (строки 40 - 42), что приводит к задержке выполнения основной программы до тех пор, пока не завершатся подзадачи. Вызовы подпрограммы TSKWAIT выполняются последовательно. Если некоторая подзадача никак не может завершиться, то программа, вызвавшая подпрограмму TSKWAIT, остается заблокированной.
44 С *** ПОЛУЧЕНИЕ ВРЕМЕНИ ЗАВЕРШЕНИЯ ЗАДАЧ ***
45 IEND - IRTC()
46 10WRITE(59,*) ' ЗАДАЧИ ЗАВЕРШЕНЫ '
47WRITE(59,*) ' ВЫЧИСЛЕННОЕ ЗНАЧЕНИЕ PI - ' , SUM
48
WRITE(59,*)
' ОШИБКА ВЫЧИСЛЕНИЯ -', PI - SUM
49
WRITE(59,*)
' ПРОЦЕССОРНОЕ ВРЕМЯ ■', TL
50
51
С
WRITEC59,*)
' ВРЕМЯ ПРОГОНА ПО ТАЙМЕРУ - ', IEND - ISTART
52
53
54
55
с с
с
*** ВВОД СЛЕДУЮЩИХ ИНТЕРВАЛОВ И ЧИСЛА ЗАДАЧ ***
GO ТО 100
56
57
58
999
CONTINUE CALL EXIT END
Время завершения задачи получается при втором обращении к функции IRTC (строка 45). Окончательный результат (SUM), время выполнения каждой задачи и общее время решения выводятся на печать. Цикл повторяется до тех пор, пока пользователь не введет для переменной INTRVLS значение 0.
Исходный текст подпрограммы TASK и пояснения к нему
59
С
60
SUBROUTINE
TASK
61
С
*** Задачи
1-4 ***
62
REAL
SUM
63
INTEGER
TL(4), INTRVLS , NTASKS , TLOCK
64
COMMON SUM
, TL ,INTRVLS , NTASKS , TLOCK
65
С
66
REAL
LSUM
67
INTEGER
MEI
68
INTEGER
TL1(3)
69
С
70
INTEGER
I
71
REAL
H , F , X
72
F(X) - 4.0E0 / ( 1.0E0 + X**2 )
Подпрограмма TASK вызывается оператором CALL TSKSTART в основной программе (строка 37). Имя подпрограммы должно быть описано в основной программе как внешнее (атрибут EXTERNAL). В строках 62 - 63 повторяются (как в основной программе) описания переменных, размещаемых в блоке COMMON. В локальной переменной LSUM накапливаются "вклады" каждой задачи в общую сумму. Переменная МЕ1 определяет начальный индекс для интервалов, отводимых каждой задаче. Время выполнения каждой подзадачи записывается в массив TL1. В строке 72 определяется функция-оператор, используемая для вычисления значений функции по значениям аргумента X. Для этой функции применяется инлайновая подстановка.
73
C
74
C*** Т0ЧКА ВХОдд здддЧИ ***
75
С
76
CALL TSKVALUE (MEI)
77
H - 1.0E0 / INTRVLS
78
LSUM - 0.0E0
79
C
80
C
81
DO 10 I « MEI, INTRVLS, NTASKS
82
LSUM - LSUM + F((I-0.50E0)*H)
83
10 CONTINUE
84
C
85
LSUM « LSUM * H
Вызов подпрограммы TSKVLUE (строка 76) используется для выборки целочисленного значения, записанного в массив управления задачей основной программой (строка 19). В результате переменной МЕ1 присваивается значение (1 - 4), используемое для разбиения (распределения) интервалов в цикле 00. Например, для четырех задач задача 1 вычисляет суммы для интервалов 1, 5, 9 и т. д.
Для развязки задач при обращении к общей памяти или при выполнении параллельных операций ввода-вывода используются блокировки. Прежде чем переменная-блокировка будет использована другими программами, применяемыми для синхронизации, она должна быть инициализирована с помощью подпрограммы LOCKASGN. Переменная-блокировка должна размещаться в блоке COMMON с тем, чтобы она была доступна программам, в которых используется этот блок. Подпрограмма LOCKON устанавливает блокировку и возвращает управление вызвавшей ее задаче. Если блокировка уже установлена, то задача задерживается до тех пор, пока блокировка не будет снята. Снятие блокировки может позволить другой задаче продолжить выполнение. Если задача установила блокировку, то она становится “владельцем" этой блокировки, и только она может с помощью подпрограммы LOCKOFF снять эту блокировку. Если задача пытается снять блокировку, установленную другой задачей или повторно установить блокировку, которая уже была установлена, то фиксируется катастрофическая ошибка.
86 С *** ПРИБАВЛЕНИЕ ЧАСТИЧНОГО РЕЗУЛЬТАТА К ОБЩЕЙ СУММЕ ***
87 CALL LOCKON (TLOCK)
88SUM - SUM + LSUM
89CALL LOCKOFF (TLOCK)
90 C
91CALL TSKTIME (TL1 , 3)
92TL(MEl) - TL1(1)
93RETURN
94 C
95END
После вычисления в каждой задаче частичной суммы (LSUM) эта задача должна прибавить значение LSUM к значению переменной SUM (окончательному результату), которая для всех подзадач является глобальной переменной. Доступ к переменной SUM должен управляться так, чтобы при изменении значения этой переменной не возникало конфликтов между любыми двумя задачами. Вызов подпрограммы LOCKON(TLOCK) в строке 87 - это попытка установить переменную-блокировку, которая была инициализирована в основной программе. Если переменная TLOCK уже была установлена, то задача задерживается до тех пор, пока эта блокировка не будет снята (путем вызова подпрограммы LOCKOFF) задачей, которая первоначально ее установила. В противном случае блокировка TLOCK устанавливается и продолжается выполнение вызвавшей задачи.
Обращение к подпрограмме TSKTIME (строка 91) используется для получения суммарного процессорного времени в числе тактов, затрат времени на ввод-вывод и системного времени. В строке 92 суммарное процессорное время выполнения задачи пересылается в соответствующую данной задаче часть глобального массива TL. Отметим, что все задачи могут вводить свои значения в массив TL параллельно, поскольку каждая задача присваивает переменной МЕ1 различные значения.
Производительность
Для достижения приемлемого уровня точности вычисления интеграла предполагалось, что будет необходимо выполнить разбиение на 10000000 интервалов. (Эксперименты, однако, показали, что при 50000 - 100000 интервалов результаты были наиболее точными.) К сожалению, при разбиении на 10000000 интервалов был получен неожиданный результат: значение интеграла оказалось равным 0.0, хотя программа выполнилась очень быстро. Объясняется это тем, что в компиляторе CFT для языка Фортран индексная переменная в цикле D0 не может превышать 223-1 (8388607). Такое ограничение возникает из-за размещения индексной переменной цикла D0 в 24-разрядном A-регистре. Так как для представления обычных целых языка Фортран в CRAY доступна 64-разрядная точность, такое ограничение показалось неожиданным. Чтобы обойти его для индекса цикла, верхняя граница индекса цикла была поделена пополам. Теперь исходный цикл D0 (строки 79 - 83) будет выглядеть следующим образом:
79 INTEND * INTRVLS / 2
80 DO 10 I » MEI, INTEND, NTASKS
81LSUM « LSUM + F((I-0.50E0)*H)
82LSUM « LSUM + F((I+INTEND-0.50E0)*H)
83 10 CONTINUE
Значение переменной INTEND определяет верхнюю границу индекса DO цикла. Эта версия программы (VI) прекрасно работала при 10000000 интервалов, но, конечно, ошибка произойдет вновь, когда значение переменной I (индекса цикла) станет больше 223-! (т.е. при 16777214 интервалах). Эта версия была впоследствие модифицирована путем объединения двух операторов присваивания в теле цикла D0 в один. Так была получена следующая версия (V2) рассматриваемой программы:
79
INTEND « INTRVLS / 2
80 С
81
DO 10 I - ИЕ1, INTEND, NTASKS
82
LSUM - LSUM + F((I-0.50E0)*H) + F((I+INTEND-0.50E0)*H)
83 10
CONTINUE
Программа V2 выполнялась быстрее, чем программа VI.
Число задач
1
2
4
Общее число тактов
705539734.8
715635474.2
715919421.6
Полное время в секундах
6.70263
6.79854
6.80123
Ошибка
4.016Е-08
+2.008Е-08
+1.004Е-08
Время в тактах/Число задач
705539734.8
357817737.1
178979855.4
Время в секундах/Число задач
6.70263
3.39927
1.70031
Параллельная производительность Mflops
9.698
19.1218
19.114
Последовательная производительность Mflops
9.698
9.561
9.557
Рис. 5.2. Результаты вычислений по программе VI (два оператора присваивания в цикле DO).
На рис. 5.2 представлены результаты вычислений по программе VI для 10000000 интервалов и для 1, 2 и 4 задач. Небольшие отклонения полученного значения числа Пи от эталонного значения объясняются ошибками округления, связанными с различным порядком суммирования при различном числе задач. Кроме того, приведено для каждой задачи суммарное процессорное время (в тактах суперЭВМ CRAY Х-МР) и время решения в секундах для четырех прогонов. Временные характеристики определялись посредством использования подпрограммы TSKTIME, которая в качестве результата дает приближенное число тактов работы центрального процессора (длительность такта равна 9.5 нс) за время выполнения работы. Время в секундах было вычислено из общего числа тактов работы, усредненного по числу задач. Производительность в миллионах операций с плавающей запятой в секунду (Mflops) определялась посредством умножения числа операций с плавающей запятой, выполняемых в одной итерации цикла (13), на число итераций (5000000) в предположении, что центральный процессор был занят выполнением только одной этой задачи. и Параллельнаяи скорость вычисления в Mflops представляет собой гипотетическую максимальную параллельную скорость, вычисленную в предположении, что оба процессора были выделены для решения только этой задачи. “Последовательная44 скорость вычислений в Mflops представляет собой гипотетическую скорость, вычисленную в предположении, что параллельные вычисления реально не выполнялись.
На рис. 5.3 показаны результаты вычислений по программе V2, в которой тело цикла DO содержит один оператор присваивания. Ускорение, полученное при переходе от программы VI к программе V2 представляет боль- ший интерес, чем ускорение при выполнении 1, 2 и 4 задач. Программа V2 выполняется примерно в шесть раз быстрее программы VI независимо от числа задач. Возможное объяснение такого ускорения состоит в том, что вторая версия программы выполняется быстрее, поскольку в ней в два раза меньше ссылок на переменную LSUM, чем в первой версии. Более правдоподобное объяснение дают распечатки, выданные компилятором CFT, которые показывают, что только для второй версии программы была выполнена векторизация. Ускорение, вычисленное на основании затрат времени центрального процессора для каждой версии программы показывает, что оно очень близко к линейному от числа задач: при двух задачах вычисление выполняется в два раза быстрее, чем при одной и т. д.
Число задач
1
2
4
Общее число тактов
118170665.5
115774946.8
117087091.2
Полное время в секундах
1.12262
1.09986
1.11233
Ошибка
6.279Е-10
+3.139Е-10
+1.570Е-10
Время в тактах/ЧисАО задач
118170665.5
57887473.4
29271772.8
Время в секундах/ЧисАО задач
1.12262
0.54993
0.27808
Параллельная производительность Mflops
57.900
118.197
116.872
Последовательная производительность Mflops
57.900
59.098
58.436
Ускорение (V2/V1)
5.97
6.18
6.11
Рис. 5.3. Результаты вычислений по программе V2 (один оператор присваивания в цикле DO).
Довольно интересно различие в величине ошибки вычислений для рассматриваемых версий программы. Программа версии VI дает ошибки, в 50 раз превышающие ошибки, соответствующие результатам программы V2. Вновь объяснения сводятся к различию в числе обращений к памяти и выполнению векторизации только во второй версии программы.
Более реалистичную меру производительности многопроцессорной системы в режиме разделения времени дает измерение общего времени решения задачи для последовательного и параллельного вариантов программы. Если параллельная программа пользователя в таком режиме не выполняется значительно быстрее (или даже, в худшем случае, выполняется значительно медленнее), то, вероятно, найдется мало пользователей, желающих выполнять параллельные программы на такой системе.
На рис. 5.4. показаны результаты измерения времени выполнения программы V2 с одним оператором присваивания в теле DO цикла (время изме-
рялось посредством считывания значения датчика времени IRTC). Здесь приведены - число тактов, среднее время в секундах и стандартное отклонение в секундах для 20 последовательных прогонов для 10000000 интервалов и 1, 2 и 4 задач. Измерения проводились в обычный день недели после обеда на суперЭВМ CRAY Х-МР в Национальном вычислительном центре MFE, который всегда весьма сильно загружен, обычно обслуживая в режиме разделения времени более тысячи пользователей.
Одни и те же вычисления при использовании одной задачи выполнялись медленнее всего, при двух задачах появлялось незначительное ускорение, но при четырех - некоторое замедление по сравнению с двухзадачным решением. Так как использовалась двухпроцессорная машина, то замедление при выполнении программы с четырьмя задачами связано с дополнительными расходами на создание и завершение третьей и четвертой задач.
Стандартное отклонение во всех случаях довольно велико, но особенно оно велико при одной задаче. Разница в 2.5 с в среднем времени прохождения программы для случая одной и двух задач определенно не заслуживает усилий программиста по распараллеливанию небольшой программы для вычисления числа Пи, однако для программ, выполнение которых на суперЭВМ может длиться несколько часов или даже дней, параллельное программирование, вероятно, даст заметное ускорение. По словам сотрудников вычислительного центра MFE, должны быть определенные преимущества по прохождению задач при параллельной обработке (такие преимущества могут искусственно обеспечиваться операционной системой CTSS для программ с высшим приоритетом и большими запросами памяти). Отсутствие какой-либо подавляющей тенденции в этих экспериментах может быть следствием того, что для решения задач требовались и незначительная память, и небольшое время выполнения.
Число задач
1
2
4
Такты/Число прогонов (*16**6)
1031.879
768.335
837.8
Секунды/Число прогонов
9.803
7.299
7.959
Среднее квадратичное отклонение
(такт*10**6)
620.6
165.6
191.3
Производительность в
Mf lops
6.631
8.905
8.167
Рис. 5.4. Временные характеристики выполнения программы V2 (один оператор в цикле DO) в мультипрограммной среде.
Ловушки и проблемы
Операционная система CTSS и строковый редактор trixgl, вероятно, должны создать для неопытного пользователя большие препятствия в выполнении параллельных программ. Вероятно, программисты, не привыкшие к экранному редактору, будут испытывать меньше трудностей в работе с опера-
ционной системой CTSS. Хотя почти вся документация была оперативно доступна, она оказалась громоздкой, трудной для использования и не всегда современной. При возникновении проблемы появлялись затруднения в отыскании в ней нужной информации.
Строковый редактор trixql, предлагаемый пользователям этой системы, имеет некоторые достаточно изощренные команды, которые могут быть оценены по достоинству серьезными пользователями суперЭВМ CRAY Х-МР, однако, это, вероятно, не касается большей части пользователей, имевших дело с экранными редакторами. Многие из ошибок, обнаруженных компилятором во время отладки пробных параллельных программ, были прямо или косвенно связаны с отсутствием опыта применения для редактирования исходных текстов программ редактора trixql.
Образованные или модифицированные с помощью редактора файлы должны с помощью утилиты filem переноситься в более долговременную память. Если пользователь забывает применить утилиту filem для некоторого важного файла, то спустя 32 часа он его "потеряет". Необходимость вручную выполнять некоторую утилиту после образования и модификации файлов для сохранения их в постоянной памяти, по-видимому, необоснованно обременительна. Вероятно, при использовании суперЭВМ CRAY Х-МР в режиме разделения времени оказывается недостаточно дисковой памяти для хранения всех файлов каждого пользователя.
В справочном руководстве MPDOC описаны средства многозадачного режима, реализсзанного для суперЭВМ CRAY Х-МР. Оно содержит подробное описание доступных программисту библиотечных подпрограмм, используемых для разработки параллельных программ. Приложение В2 содержит извлечения из этого руководства. Подпрограммы TSKSTART, TSKTIME, TSKVALUE и TSKWAIT были использованы выше для реализации примера программы. Описания подпрограмм в руководстве MPDOC сравнительно кратки, что затрудняет их прямое использование начинающими "параллельными" программистами. Руководство MPDOC содержит главным образом примеры, показывающие когда, где и как использовать различные библиотечные подпрограммы для организации параллельных программ. Например, предупреждающее сообщение WARNING, вырабатываемое компилятором CFT, говорит об ошибке в задании параметра в операторе
CALL TSKSTART(ITC1, TASK, 1)
в более ранней версии примера рассматриваемой программы, в которой параметр ITC1 был массивом управления первой задачи. Это сообщение было вызвано некорректным использованием литералов в качестве аргументов при вызове подпрограммы TSKSTART.
После компиляции и загрузки программы был выполнен первый ее прогон для 1000000 интервалов и 4 задач, однако полученный для числа Пи результат 5.234... оказался значительно больше ожидаемого. Ошибка не была связана с параллелизмом, а была порождена пропуском оператора Н*1 .OEO/INTRVLS в строке 101 программы. Этот пример демонстрирует неприятную особенность параллельной обработки, при которой “последовательные" ошибки могут приниматься за “параллельные".
Еще одна проблема была обусловлена переносом программы с 32-раз- рядной ЭВМ на суперЭВМ CRAY Х-МР. При этом, хотя и был получен правильный результат, программа выполнялась примерно в 25 раз медленнее, чем ожидалось (была достигнута производительность в 270 Kflops - эта величина примерно равна производительности ЭВМ серии VAX). Объяснение простое: в исходной программе все вещественные переменные были описаны
как переменные с двойной точностью (DOUBLE PRECISION). Арифметические операции для 128-разрядных операндов в формате с плавающей запятой эмулируются в суперЭВМ CRAY Х-МР программно, что приводит к значительным дополнительным затратам времени и, следовательно, к замедлению выполнения программы.
Выводы
Возможность использования одной из наиболее быстродействующих современных суперЭВМ позволяет приобрести интересный опыт. Кажется, однако, что некоторые средства разработки программ не под стать аппаратным средствам. С другой стороны, доступная в системе CTSS, эксплуатируемой в центре MFE, библиотека подпрограмм для поддержки разработки параллельных программ, достаточно хорошо документирована и совместима с несколькими суперЭВМ фирмы Cray Research Inc. (CRAY-1, CRAY Х-МР и CRAY- 2). Полезно иметь листинги исходных программ с таблицами перекрестных ссылок и сообщениями, говорящими, где компилятор CFT смог векторизовать программу, что облегчило бы понимание изменений времени выполнения разных версий программ.
Благодарности
Мы хотели бы поблагодарить Дэнни Соренсена из Аргоннской национальной лаборатории, который предоставил для наших экспериментов часть своего машинного времени на суперЭВМ CRAY Х-МР в Национальном вычислительном центре MFE. Юджин Сомдал и Джордж Спике из фирмы Cray Research оказали нам помощь в использовании средств поддержки многозадачного режима, реализованных в этой фирме. Консультации пользователей в центре MFE были очень полезны при использовании системы CTSS и средств многозадачного режима, входящих в эту систему. Мы хотели бы также поблагодарить сотрудников центра MFE Кирби Фонга и Ларри Бердала за их помощь в рецензировании этой главы и приложения С2, содержащего материалы из руководства MPDOC.
Библиография
John L Larson, “Multitasking on the CRAY X-MP-2 multiprocessor", Computer, vol. 17, no. 7, July 1984, pp. 62-69.
Steve S. Chen, Chris C. Hsiung, John L Larson, and Eugene R. Somdahl, “CRAY X-MP: A Multiprocessor Supercomputer" in Vector and Parallel
Processors: Architecture, Applications, and Performance Evaluation, Ginsberg (ed.) Amsterdam: North Holland, (to appear in 1987).
Cray Research, Inc., “Multitasking User Guide", Cray Computer System Technical Note SN-0222, January 1985.
б
Параллельный процессор FPS (Floating Point Systems) серии Т
Филлип Мидлер, Чарльз Е.Ст. Джон, Стьюарт В. Ханкинсон
Машина / модель: Местонахождение: Процессоры: Операционные системы:Машина / модель: Местонахождение: Процессоры: Операционные системы:Языки программирования: Компилятор:Языки программирования: Компилятор:Т/20
Машина / модель: Местонахождение: Процессоры: Операционные системы:
Машина / модель: Местонахождение: Процессоры: Операционные системы:
Языки программирования: Компилятор:
Языки программирования: Компилятор:
Floating Point Systems, Бивертон, Орегон 16 узлов, каждый с ОЗУ 1 Мбайт VMS на внешней машине MicroVAX TOPSYS В01 на системной плате серии Т VB Main Process В01 на векторной плате серии Т
Оккам 1, VAX/VMS Фортран v. 4.2 Transputer Development System 2.2A
Серия T представляет собой разработку фирмы FPS в области архитектур с массовым параллелизмом. Конфигурация системы представляет собой n-мерный гиперкуб - структуру, в которой каждый узел связан с п ближайшими соседями. Архитектура серии Т создана благодаря использованию трех важных разработок:
СБИС Inmos Transputer [1, 2].
Язык программирования Оккам.
Обновленная схема многопортовой памяти.
Даже в быстроменяющемся мире параллельных архитектур эти идеи выделяются среди подходов, используемых в разработках других параллельных систем.
В этой главе обсуждается архитектура серии Т и два примера программ, которые выполнялись на небольшой системе серии Т. Первая программа демонстрирует межузловые связи, вторая - использование аппаратуры и утилит векторной обработки.
Аппаратура
Конфигурация системы серии Т может изменяться от 8 (23) узлов (минимальная система) до 16 384 (214) узлов (максимальная система). Пиковая производительность системы в этих экстремальных случаях возрастает с 96 Mflops до 196 Gflops. Примеры программ, описанных в этой главе, выполнялись на 16-узловой системе.
Предельной топологией системы в ее максимальной конфигурации является 14-мерный гиперкуб. Другие топологии, определяемые пользователем, такие как сетка, тор, кольцо, цилиндр, относительно легко создаются из гиперкубической схемы связи. Базовая конфигурация серии Т содержит модуль, состоящий из:
платы системного управления;
80-Мбайтного системного диска;
8 векторных плат.
Удвоение числа векторных плат добавляет новое измерение в структуре гиперкуба. Прикладные программы можно разрабатывать независимо от размера системы, предусматривая простые средства для работы на системах серии Т различной конфигурации.
Плата системного управления
Плата системного управления (ПСУ) связывает модуль серии Т с восемью присоединенными векторными платами и с дисковой подсистемой, а также при необходимости с внешней машиной. Программно ПСУ не доступна пользователю, однако она содержит операционную систему, выполняющую запросы пользовательских программ, на организацию взаимодействия и обмен данными с внешней машиной, векторными платами, дисковой подсистемой и, в многомодульных системах, с другими ПСУ.
Векторные платы
Каждую векторную плату (ВП) можно рассматривать как одноплатный матричный процессор с управляющей машиной (host coaputer). Уже упоминалось, что существует восемь векторных плат, подсоединенных к плате системного управления. Каждая векторная плата содержит:
транспьютер;
адаптер каналов/MUX;
многопортовое ОЗУ емкостью в 1 Мбайт;
секцию АЛУ, состоящую из устройств сложения и умножения чисел с плавающей запятой (32- или 64-разрядный форматы IEEE);
статическое ОЗУ емкостью в 4К 64-разрядных слов, используемое для микропрограммной реализации векторных операций.
АЛУ и память микропрограмм образуют векторное процессорное устройство (ВПУ). Программирование ВПУ осуществляется использованием набора процедур языка программирования Оккам, поддерживаемого FPS, которые выполняются на транспьютере ВП. Процедуры Оккама обеспечивают интерфейс высокого уровня с микропрограммными операциями ВПУ.
Для программирования ВПУ в Оккаме имеется шесть групп процедур:
Многоузловые подпрограммы (Multi-node Subroutines) выполняют операции над данными, распределенными по множеству узлов. Эти операции выполняются параллельно и поддерживаются некоторыми операциями межузлового обмена.
Одноузловые подпрограммы (Single-node Subroutines) выполняют операции над данными, находящимися в памяти одного узла системы серии Т. Эти операции включают в себя распространенные операции с матрицами и векторами. Операции этой группы не выполняют обменов между узлами.
Подпрограммы утилит математической библиотеки (Math Library Utility Subroutines) обеспечивают механизм пересылки данных, управления состоянием и установки операционных режимов в ВПУ.
Родовые подпрограммы (Generic Subroutines) обеспечивают более низкий уровень взаимодействия с ВПУ. Эти подпрограммы поддерживают такие действия, как векторно-скалярные операции умножения и сложения с полным вектором на ВПУ.
Подпрограммы блока параметров (Parameter Block Subroutines) обеспечивают еще более низкий уровень взаимодействия с ВПУ. Эти подрограм- мы поддерживают такие действия, как инициация, выполнение и завершение векторных операций.
Подпрограммы формирования векторов (Vector Form Subroutines) поддерживают самый нижний уровень взаимодействия с ВПУ. Эти подпрограммы инициируют микропрограммные операции ВПУ, определяющие поток данных через ВПУ, устанавливающие операционные режимы и модифицирующие регистры ВПУ.
ВПУ действует асинхронно по отношению к управляющему им транспьютеру, т.е. векторные операции могут выполняться параллельно с Оккам-программой пользователя. Обычно транспьютер ВП осуществляет обмен данными с ОЗУ и дисковой памятью, устанавливает границы массивов, а также выполняет другие операции с исходными и результирующими данными. ВПУ автоматически производит обмен данными между ОЗУ и арифметическим устройством при выполнении векторных операций.
Средства связи векторной платы
Имеются четыре канала связи с микросхемой транспьютера. Последовательные каналы имеют измеренную скорость передачи данных 0.5 мегабайта в секунду.
Две процедуры byte.slice, input и byte, si ice. output управляют вводом и выводом по каналам. Векторная плата позволяет использовать любые из 16 каналов для каналов транспьютера. 16 каналов мультиплексируются таким образом, что пользователь в любой момент может выбрать 4 из них. Эти четыре канала выбираются при помощи системного вызова set.links. Выбор должен быть сделан до использования byte.si ice-процедур. Отказ от каналов производится при помощи системного вызова relese.links. -
Два из 16 каналов резервируются системой. Остальные 14 каналов доступны для использования программистом в качестве коммуникационных каналов гиперкуба или для соединения с внешними устройствами ввода-вывода. Один канал на каждое измерение гиперкуба используется каждой ВП для связи с соседним узлом; оставшиеся каналы могут быть использованы для соединения с внешними устройствами ввода-вывода. В максимальной конфигурации системы серии Т (16384 узла) все 14 каналов используются для связи с соседними узлами, хотя существует возможность для выделения одного или нескольких измерений гиперкуба для подключения дополнительной дисковой памяти.
Рис. 6.1 иллюстрирует, как векторные платы могут быть объединены в восьмиузловую структуру гиперкуба. Один из двух резервируемых системой
каналов подсоединен ко всем восьми векторным платам и к одному из двух каналов ПСУ. Векторная плата использует канал ПСУ для загрузки прикладных программ и входных данных или для отсылки результирующих данных в системную плату и вывода на системный диск модуля. Другой системный канал резервируется для последующего развития системы.
Память ВП
Для правильного применения ВПУ следует хорошо разобраться в структуре основной памяти ВП. Доступ к памяти узла имеют только транспьютер ВП и ВПУ. ВП не могут напрямую обращаться к памяти других узлов; пересылка данных производится с использованием коммуникационных примитивов и процедур. Основная память ВП состоит из многопортового ОЗУ емкостью в 1 мегабайт, в котором хранятся исходные данные и результаты векторных операций. Порт произвольного доступа основной памяти используется транспьютером для обмена данными с системным диском. Порты последовательного доступа используются ВПУ для обращения к строкам данных для векторных операций. Полная строка данных может быть считана или записана в любое место основной памяти.
В каждый такт каждый из двух последовательных портов А или В может пересылать элемент вектора из основной памяти ВП в ВПУ. Одновременно может записываться результат выполнения предыдущей операции ВПУ. ВПУ наиболее эффективно выполняет операции над векторами, длина которых кратна 256 32-разрядным словам или 128 64-разрядным словам. Рисунки 6.2 и 6.3 показывают структуру системы памяти.
Дополнительную информацию об архитектуре серии Т можно найти в приложении D2 и в [3].
Программное обеспечение
Отличительной особенностью программного обеспечения серии Т является язык программирования Оккам. Оккам с самого начала предназначался для транспьютеров и тесно связан с их архитектурой. Оккам имеет простую и естественную запись для выражения параллелизма. Зарезервированное слово PAR используется для описания операторов или процессов, которые могут выполняться параллельно. Противоположным к нему является ключевое слово SEQ, предписывающее компилятору вырабатывать операторы, которые будут выполняться последовательно. PRI PAR позволяет задавать приоритеты параллельных процессов. ALT определяет выполнение первого готового процесса из всех перечисленных.
Взаимодействие процессов осуществляется в Оккаме использованием операций ? (ввод) и ! (вывод). Для пересылки данных между процессорами в FPS имеются две процедуры byte.slice, input и byte.si ice.output, определенные в разд. 6.1.
Синхронизирующая информация обеспечивается в Оккаме конструкциями TIME, WAIT, NOW и AFTER. Эти конструкции дают возможность измерять время, задерживать процесс, сравнивать значения времени, устанавливать ограничения по времени на процессы. Дополнительные сведения об использовании некоторых языковых конструкций и примитивов можно получить из примеров и соответствующих комментариев.
Каналы Ввод/выеод
системного диска
Рис. 6.1. Модуль ЭВМ серии Т.
Подбанк О 256 Кбайт
Подбанк 1
256 Кбайт
Подбанк 2 256 Кбайт
Подбанк 3256 Кбайт
1 Мбайт/Векторная плата
Рис. 6.2. Подбанки основной памяти.
Отводы для векторов различной длины
Рис. 6.3. Основная память векторной платы.
Отличным справочником по языку Оккам является [4]. Автор дает много примеров, показывающих свойства Оккама. Он также описывает некоторые подходы к разделению задачи на параллельные компоненты и их размещению в транспьютерной системе. Более строгий курс Оккама можно найти в [5].
Построение программ
Выполнением программ в системе серии Т можно управлять из внешней машины при помощи резидентной программы, написанной на Фортране или Си. С помощью этой программы пользователь может:
назначать и освобождать машины серии Т;
загружать прикладную программу;
обмениваться файлами данных с прикладными дисками системы серии Т;
удалять файлы с этих дисков;
узнавать о состоянии выполнения этих операций.
Существуют также утилиты, позволяющие пользователю преобразовывать данные из форматов представления чисел с плавающей запятой IEEE, принятых в серии Т, в форматы представления таких чисел, принятые на внешней машине, и наоборот.
Программное обеспечение магнитных дисков для пользователей
Каждая векторная плата имеет доступ к файловой системе на системном диске данного модуля. Системой поддерживаются обычные операции над файлами: создать файл, открыть файл, удалить файл и т.п. Так как по умолчанию все создаваемые файлы получают статус "временный", добавлены операции, которые могут изменять статус файла на "постоянный".
Система программирования Оккам
Система программирования Оккам (СПО), разработанная в Inmos Corporation, позволяет пользователю разрабатывать программу на языке Оккам по частям и моделировать ее выполнение на внешней машине, которой является Digital Equipment Corporation MicroVAX. Полное описание СПО можно найти в [6].
Ядром этой интегрированной программной поддержки является специализированный редактор, ориентированный на язык Оккам. Из этого редактора пользователь может вызывать следующие утилиты:
Утилита проверки синтаксиса выполняет синтаксический анализ части программы на Оккаме. Таким образом пользователь может избавиться от неожиданностей на этапе компиляции всей программы.
Утилита компилятора с языка Оккам создает объектный модуль, который может быть собран и выполнен на внешней машине.
Утилита оценки собирает некоторую полезную статистику о программе пользователя. Наиболее существенными являются сведения о размере программы и оценка времени ее выполнения (в тактах).
В случае успешной компиляции программы пользователь может закончить сеанс работы с СПО и собрать, а затем и выполнить программу на
внешней машине. Если программа завершится аварийно, можно снова начать работу с СПО и вызвать утилиту локализации ошибки. Редактор выделит строки программы, содержащие ошибки.
Транспьютерная система разработки программ
Транспьютерная система разработки программ (Transputer Development System - TDS) используется для создания программ, которые можно выполнять на системах, построенных из транспьютеров. Редактор и его утилиты в основном те же, что и в системе программирования на языке Оккам. TDS используется для создания программ для систем серии Т. FPS поставляет вспомогательный стандартный файл, используемый при разработке прикладных программ для систем серии Т. Этот стандартный файл содержит библиотеки программного обеспечения для использования в пользовательских программах. Дополнительную информацию о программировании систем серии Т можно найти в [8].
Примеры параллельных программ
Этот раздел содержит два примера:
Простую программу на языке Оккам, которая выполняется на нескольких процессорах и реализует простое взаимодействие процессов.
Реализацию программы вычисления числа Пи, которая демонстрирует использование аппаратуры векторной обработки.
Пример L Межузловые связи в системе серии Т
Нижеследующий текст представляет собой пользовательскую часть программы на языке Оккам. В него не включены определения системных переменных, информация о конфигурации системы, процедуры из библиотеки поддержки. Все это входит в стандартный файл, предоставляемый FPS. Рассматриваемая программа на языке Оккам определяет процессы, каждый из которых выполняется в своем узле и передает сообщения своим ближайшим соседям в гиперкубе. Эти процессы завершаются выдачей на печать сообщения, в котором указаны их размещение в гиперкубе и процессы, от которых ими были получены сообщения. Для 8-узловой системы это сообщение выглядит так:
Vector Board t 0receivedmessagefrom VB's124
Vector Board ♦ 1receivedmessagefrom VB's035
Vector Board I 2receivedmessagefrom VB's036
Vector Board ♦ 3 received message from VB's 127
Vector Board I 4receivedmessagefrom VB's056
Vector Board # 5receivedmessagefrom VB's147
Vector Board I 6receivedmessagefrom VB's247
Vector Board I 7receivedmessagefrom VB's356
Заметим, что для каждого узла его ближайшим соседом по гиперкубу является узел, чей номер в двоичной записи отличается на один бит от номера данного узла. Например, процессор номер 7 (в двоичном виде - 111) имеет ближайших соседей - процессор номер 3 (011), номер 5 (101) и но-
мер 6 (110). Номер канала, который соединяет два соседних узла, равен увеличенному на единицу номеру битовой позиции несовпадающего бита. Отсюда видно, что процессор номер 7 связан с процессором номер 3 по линии номер 3, процессор номер 5 связан с процессором номер 4 по каналу номер 1 и т.д.
1 DEF dimension ■ 3
2 VAR ne1ghbor[dimension]
3 DEF recvaddr « #80000:
4 PUTBYTE(processor ,#40000)
Строка 1 первого примера описывает константу, представляющую число измерений гиперкуба. Значение три означает, что программа использует 8 (23) процессоров. Строка 2 описывает массив (целых чисел), который содержит номера всех узлов, посылающих сообщения в данный узел. Строка 3 описывает константу, определяющую адрес в памяти ВП, по которому будет размещаться принимаемое от соседних узлов сообщение. В строке 4 номер процессора данного узла помещается в память ВП по байтовому адресу 40000 (шестнадцатеричный).
SEQ
— internode communicationexample
— пример мехуэловой коммуникации
set.links (4,1,2,3)
SEQ 1 * [1 FOR dimension]
PAR
byte.slice.output (1, #40000, 1)
byte.si ice.input (1, recvaddr + (1 - 1),1)
release links
13
В строке 5 находится ключевое слово SEQ - начало управляющей структуры языка Оккам. Эта структура определяет последовательное выполнение всех составляющих ее предложений. Она охватывает все предложения, текст которых размещен с отступом на два (или более) пробелов от начала ключевого слова (строки 6-26). Так представляется иерархия управляющих структур в языке Оккам. В строке 6 находится комментарий: “Пример межузловых связей". В строке 7 каналы коммутируются для конкретного случая межузловой связи: активируются каналы с первого по четвертый. В строке 8 находится оператор цикла D0 языка Оккам, тело которого будет выполняться последовательно. Тело цикла находится в строках 9-11. Ключевое слово SEQ можно было бы заменить на PAR: в этом случае сообщения принимались бы и передавались по трем каналам параллельно.
В строке 9 - начало управляющей структуры Оккама, определяющей параллельное выполнение предложений в строках 10 и 11. Процесс может принимать и передавать данные параллельно. Параметры обращений к подпрограммам в строках 10 и 11 определяют, что один байт данных (номер процессора), находящийся в ОЗУ ВП по адресу 40000, будет передаваться по каналу i и один байт данных (номер процессора соседнего узла) будет приниматься из канала i и помещаться в ОЗУ ВП по адресу, различному для разных каналов. Отмена коммутации каналов производится в строке 12.
hkeep(status)
-- Send the terminating messages to the monitor screens
— Вывод сообщений об окончании на экран монитора
hwrlte.string(нVector Board f, 0, processor, status)
hwrlte.1nt(processor, 0, processor, status)
hwrlte. stri ng (** received messages from VB's ”,
0, processor, status)
SEQ 1 ■ [0 FOR dimension]
SEQ
GETBYTE(ne1ghbor[1],recvaddr + 1)
hwrlte.1nt( ne1ghbor[1], 0, processor, status)
hwrlte.string(w ”, 0, processor, status)
hwr1teln( 0, processor, status)
hrelease(status)
В строках 14-26 производится выдача на печать строк, в которых помещается номер узла (или адрес в гиперкубе) и номера соседних узлов.
Пример 2. Программа вычисления числа Пи
По методу прямоугольников интеграл можно аппроксимировать суммой площадей прямоугольников, которыми покрывается интегрируемое пространство. При увеличении числа прямоугольников возрастает точность апроксима- ции. Эта задача хорошо подходит к параллельным вычислениям, так как вычисление интеграла можно распределить по большему числу процессорных элементов. В следующей программе, которая выполнялась на системе Т/20, каждый процессор выполняет вычисления над своей частью интервала [а,Ь]. Каждая часть разбивается на 16384 прямоугольника, вся подынтегральная область разбивается на 262144 прямоугольника.
— source code
— исходный текст программы
2DEFone «TABLE[#00000000,f3FF00000]:
3DEFhalf-TABLE[#00000000,#3FE00000]:
4DEFfour«TABLE[#OOOOOOOO,*40100000]:
5DEFp1 -TABLE[*54442D18,*400921FB]:
В строках 2-5 при помощи оккамовской структуры TABLE определяются константы (числа с плавающей запятой в формате IEEE). Каждая константа определяется двумя 32-разрядными таблицами: первая содержит младшие значащие разряды слова, вторая - старшие. На константу можно ссылаться, используя индексированное значение таблицы. Например, р1[0] эквивалентно младшим значащим разрядам 64-разрядного значения р1. В строке 1 находится комментарий.
VARftemp[2],temp,ntemp,xn[2]:
VARh[2],a[2],b[2],sum[2],new[2]:
VARnt,np,npp,ns,lnsp,vlen,xoffs[2]:
VARxa,x2a,xb.x2b:
VARbegin,m1dl,m1d2,m1d3,end:
ИVARelapl,elap2,elap3,elap4:
12VARh.adrs,xoffs.adrs,xn.adrs:
13VARsum.adrs,new.adrs,one.adrs:
Предложения VAR в строках 6-13 объявляют переменные языка Оккам, используемые как параметры программы, промежуточные результаты вектор- дых операций, временные целочисленные переменные и окончательное значение вычисленного интеграла. Также объявляются переменные, используемые для синхронизации четырех секций программы, и переменные для хранения адресов векторов.
SEQ
— define addresses
-- определение адресов
addr.v(h,h.adrs)
addr.v(xoffs,xoffs.adrs)
addr.v(xn,xn.adrs)
addr.v(sum,sum.adrs)
addr.v(new,new.adrs)
addr.v(one,one.adrs)
Строки 14-21 определяют адреса памяти ВП и переменные, хранящиеся по этим адресам. Подпрограммы обработки векторов работают с адресами переменных, а операции транспьютера ссылаются на значения этих переменных. Ключевое слово SEQ в строке 14 определяет, что предложения в следующих 7 строках будут выполняться последовательно.
22
— report value of pi
— вывод значения числа пи
23
24
25
26
27
28
29
30
31
32
33
34
35
36
IF
(processor=0)
SEQ
rel.control
hkeep(status)
hwrite.string(" True value for PI is w,0, processor,status)
ct.hwrite.rea164(pi,2,20,0,processor,status) hwt i te1n(0,processor,status
hwt i te1n(0,processor,status
hrealese(status)
get.control
TRUE
SKIP
Строки 22-36 предписывают процессору 0 выдавать вычисленное значение Пи на экран терминала. Строки 26 и 34 перехватывают у системного узла и возвращают ему обратно управление прерываниями. В предложении IF языка Оккам должна быть определена и ветвь ELSE, в данном случае для этого используется глобальная константа TRUE, которая в сочетании со SKIP в строке 36, показывает, что все процессоры, номера которых отличны от 0, не вы-
полняют никаких операций. Значение Пи, вычисленное методом прямоугольников, выдается в конце выполнения программы.
37-- define х at BankA,Bl boundaries
— определение х в границах банка А и В1
38хаVP.BankA
39xb:■ VP.BankBl
30—sync all processors for timing run
— синхронизация всех процессов для отсчета времени
41SEQ j«[0 FOR 5]
42 SEQ i«[l FOR dimension]
43set.links(i,-l,-1,-1)
44release.links
45TIME ? begin
В строках 37-39 определяется расположение вектора х (х - из интеграла от 1/(1+х**2) на интервале [а,Ь]) в начале банка А ОЗУ ВП. Второй экземпляр вектора х размещается в начале банка В1 ОЗУ ВП. Каждый процессор, как будет показано ниже, содержит в ОЗУ своего ВП свою часть общего вектора х и обрабатывает только ее.
Строки 41-44 определяют синхронизацию всех процессоров путем захвата и освобождения каналов межузловых связей. Для этого необходимы точные измерения времени. В строке 45 начальное время определяется предложением TIME.
46
— partition domain
— область частичных вычислений
47
пр :■ l«d intension
——
number of processors ■ 2*dim число процессоров
48
vlen :■ 128
—
original vector length ■ slice начальная длина вектора
49
Insp :■ 7
—
Insp ■ log2(ns)
50
ns :« l«lnsp
—
number of slice ns ■ 2*lnsp число вырезок
51
nt := np*ns*128
—
total quad points (2*14 per processor) общее число точек разбиения
52
npp := nt/np
—
quad points per processor число точек разбиения для процессора
Строки 46-52, как видно из комментариев к ним, задают значения параметров, определяющих каждому процессору свою долю вычислений. Число процессоров получается в результате сдвига влево («) целого, равного размерности системы. Интервал интегрирования разбивается 262144 точками, так что каждый из 16 процессоров выполняет вычисления над 16384 точками. Эти точки можно рассматривать как середины оснований 262144 прямоугольников. Каждое основание равно h«l/262144.
53— specify interval [a,b]
— определение интервала
54а[0] О
55а[1]О
56Ь[0]:■опе[0]
57Ь[1]опе[1]
58
59— compute h :■ (b-a)/nt
-- вычисление
60--ftemp :■ float(nt)
61IntegerToDReal(ftemp,nt)
62
63--h:■b-a
64DRealOp(h,b,Sub,a)
65
66—h:■(b-a)/nt
67DRealOp(h,h,Div,ftemp)
В строках 53-57 определяются начальная и конечная точки интервала интегрирования - 0 и 1. В строках 61-67 величина h (основание прямоугольника) определяется делением интервала интегрирования на число точек разбиения в формате чисел с плавающей запятой.
68— х[1] :« xoffs + h*i
69
70—xoffs = а + (p*npp+0.5)*h
71
72ntemp :■ processor*npp
73IntegerToDRea1(ftemp,ntemp)
74DRea1Op(ftemp,ftemp,Mu1,h)
75DRealOp(xoffs,ftemp,Mui,h)
В строках 72-75 каждый процессор находит свою часть отрезка интегрирования и помещает ее начало в переменную xoffs. Часть для нулевого процессора начинается с середины основания первого прямоугольника, часть для первого процессора - с середины основания 16364-го прямоугольника, часть для п-го процессора - с середины основания (16364*п)-го прямоугольника.
76xwrd :« (ха»2)
77SEQ 1 - [О FOR vlen]
78 SEQ
79PUTVORD(i,xwrd)
80PUTWORD(O,xwrd+l)
81xwrd :« xwrd+2
8282В строках 76-81 создается вектор длины 128, который будет использоваться процессором для вычисления своей части общего вектора х. В строке 76 байтовый адрес ОЗУ ВП ха преобразуется в словный адрес, который требуется для операций PUTWORD в строках 79 и 80. Цикл в строках 77-81 размещает целые числа от 1 до 128 в последовательные ячейки ОЗУ, начиная с адреса ха.
82
82
float sequence of integers & add offsetsпоследовательность целых как число с плавающей
запятой и добавление смещений
83
84
85
86
87
88
89
90
GN.VO(xa,AF.Xfloat,xa,VP.null,vlen)--float 1
GN.SVO(h.adrs,xa,MF.XYmul,xa,VP.null,vlen)--h*i
GN.SVO(xoffs.adrs,xa,AF.XYadd,xa,xb,vlen)—h*i+xoffy
xn«float(vlen)*h
IntegerToDRea1(ftemp,vlen)
DRealOp(xn,ftemp,Mui,h)
TIME ? midi
В строке 83 выполняется одноузловая родовая векторная операция, которая преобразует вектор длины 128 в формат чисел с плавающей запятой и размещает его в ОЗУ ВП, начиная с адреса ха. Параметрами этой векторной операции являются:
адрес исходного вектора,
индекс арифметической операции,
начальный адрес для размещения первого вектора результата,
начальный адрес для размещения второго вектора результата (в данном случае HULL),
число элементов вектора, над которыми выполняется операция.
В строке 84 выполняется скалярно-векторная операция умножения вектора на шаг h. Результат помещается на то же место памяти. В строке 85 завершается формирование основной вырезки для каждого процессора путем прибавления к каждому элементу вектора начала его части. Эта основная вырезка помещается в ОЗУ ВП в банк А (начиная с адреса ха) и в банк В (начиная с адреса xb).
В результате образуется вектор, элементами которого являются середины оснований первых 128 прямоугольников для каждого процессора. Копия этого вектора будет использована для обработки всех 16364 прямоугольников. Строки 88-89 определяют переменную хп равной шагу обработки. В строке 90 определяются затраты времени на выполнение программы в микросекундах.
91
vlen.xn and xi doubled Insp times дублирование vlen,xn и xi Insp раз
92
93
94
95
96
97
98
99
100
SEQ Hl FOR Insp]
SEQ
x2a :■ xa+(vlen*8)
x2b :■ xb+(vlen*8)
GN.VSO(xa,xn.adrs,AF.XYadd,x2a,x2b,v1en)
DRea10p(xn,xn,Add,xn) vlen :■ vlen+vlen store x in both A and В banks запись x в оба банка А и В
101
TIME ? mid2
Строки 93-99 содержат цикл размножения вектора, в результате выполнения которого формируется полный вектор х. Во время первой итерации цикла векторно-скалярная операция строки 97 выполняет сложение всех 128 элементов начального вектора с приращением хп, результат дописывается в
память в конец исходной основной вырезки. Длина общего вектора х становится равной 256. Величины приращения (хп) и длины (vlen) удваиваются, и на следующей итерации новые 256 элементов дописываются в конец полного вектора х. После семи итераций цикла формируется вектор длины 16364 (на каждом процессоре), один экземпляр которого находится в банке А, второй - в банке В ОЗУ ВП. В строке 101 подытоживаются затраты времени на формирование вектора.
102-- compute sum over ns slices
— вычисление суммы ns вырезок
103-l+x(i)*x(i)
104GN.VVOSO(xa,xb,MF.XYmul,one.adrs,AF.XYadd,xb,VP.null,vlen)
105SN.VRECIP(xb,l,xb,lvlen)
106GN.VRO(xb,AF.XYadd,sum.adrs,vlen)
107TIME ? mid3
В строках 104-106 заканчивается вычисление интеграла. Вектор х возводится поэлементно в квадрат и к каждому элементу прибавляется 1. Это производится операцией вектор-вектор-скаляр в строке 104. Умножение векторов (в данном случае возведение в квадрат) выполняется более эффективно, когда сомножители выбираются из разных банков ОЗУ ВП. По этой причине были использованы две копии вектора х. К этому моменту завершается вычисление 1+Х**2. В строке 105 выполняется векторная операция поэлементного вычисления обратной величины над промежуточным результатом, а в следующей строке элементы вектора суммируются. В строке 107 вновь вычисляется затраченное время.
108 — communicate and collapse sums over processors — взаимодействия и сбор сумм от всех процессоров
109
110rel.control
111set.I1nks(l,2,3,4)
112
ИЗSEQ 1 ■ [1 FOR dimension]
114 SEQ
115—exchange sums
обмен суммами
116PAR
117byte.slice.output(1,sum.adrs,8)
118byte.slice.input(i,new.adrs,8)
119
120—accumulate sums
сбор сумм
121DRealOp(sum,sum,Add,new)
122
123 release.links
В строках 110-123 суммы, полученные каждым процессором в соответствии с его частью вычисления интеграла, пересылаются по межузловым связям гиперкуба к ближайшим соседним узлам. В параметрах процедур byte.si ice в строках 117 и 118 указываются номер канала связи, байтовые
адреса ОЗУ ВП для размещения получаемых или выбора передаваемых данных и число пересылаемых байтов. В результате выполнения всех операций пересылки и накопления каждый процессор получит сумму для всей области интегрирования.
124-- compute sum :■ sum4*h
— вычисление
125DRealOp(sum,sum,Mui,four)
126DRealOp(sum,sum,Mul,h)
127TIME ? end
128
129— output results
— вывод результатов
130elapl:«midl-begin
131elap2:■mid2-begin
132elap3:■mid3-begin
133elap4:■end-begin
В строках 125-133 каждый процессор завершает вычисления приближенного значения числа Пи и определяет, сколько микросекунд потребовалось для выполнения вычислений во всех четырех частях программы.
134hkeep(status)
135 hwrite.stringf” Est. value for PI is ”,0,processor,status)
136ct.hwrite.real64(sum,2,20,0,processor,status)
137hwrite.string(”on processor ”,0,processor,status)
138hwrite.int(processor,0,processor,status)
139hwri te1n(0,processor,status)
140 hwrite.stringf” Elapsed times:float;gen.pts;sum pts;comm ”,0,
141processor,status)
142hwr i te1n(0,processor,status)
143hwrite.1nt(elapl,0,processor,status)
144hwrite.stringf” ”,0,processor,status)
145hwrite.int(elap2,0,processor,status)
146hwrite.stringf” ”,0,processor,status)
147hwrite.int(elap3,0,processor,status)
148hwrite.stringf” ”,0,processor,status)
149hwrite.1nt(elap4,0,processor,status)
150hwri te.str1ng(” ”,0,processor,status)
151hwr1te1n(0,processor,status)
152 hwrite.stringf” total t pts, nt* ”,0,processor,status)
153hwrite.int(nt,0,processor,status)
154hre lease (status)
155get, control
Предложения в строках 134-135 предписывают каждому процессору выдавать на экран терминала:
приближенное значение числа Пи;
информацию о затратах времени;
Производительность
Для измерения затрат времени программа была разбита на четыре секции. Промежуточное значение времени измерялось в конце каждой секции. Секции были следующие:
Секция MIDI: построение начального вектора х.
Секция MID2: формирование полного вектора х.
Секция MID3: вычисление 1/(1+Х**2) для каждого элемента вектора х.
Секция END: суммирование результатов по всем вершинам гиперкуба
Программа выполнялась с разбиением интервала интегрирования на 32768 и на 262144 части. Результаты выполнения программы с этими начальными условиями следующие:
32768 evaluation points:
Section MIDI elapsed time ■
2.3
msec
Section MID2 elapsed time ■
1.3
msec
Section MID3 elapsed time ■
6.9
msec
Section MID4 elapsed time ■
2.1
msec
262144 evaluation points:
Section MIDI elapsed time ■
2.3
msec
Section MID2 elapsed time ■
5.2
msec
Section MID3 elapsed time ■
53.2
msec
Section MID4 elapsed time ■
2.1
msec
Если считать, что в программе выполняются 8 операций над числами с плавающей запятой для каждого элемента вектора (2 векторных умножения, 3 векторных сложения и 3 операции вычисления обратной величины), можно прикинуть достигнутую производительность машины. В случае 262К точек разбиения производительность всей системы оказывается равной 37 Mflops, в случае 32К точек - около 21 Mflops. Представляется, что разница в полученной производительности объясняется тем, что для обоих случаев расходы на предварительные вычисления - загрузку процессоров, вычисления областей разбиения для каждого процессора, выполнения преобразования форматов и т.п. - примерно одинаковы. При возрастании числа точек разбиения отношение времени на предварительные вычисления ко всему времени векторной обработки уменьшается.
Дополнительную информацию о производительности системы серии Т можно найти в [9 ].
Ловушки и проблемы
При написании и выполнении программ на языке Оккам был получен богатый опыт. К удивлению, накладные расходы на изучение языка Оккам оказались минимальны. Предварительное знакомство со структурированным языком типа Паскаль может значительно облегчить изучение языка Оккам. Полезно предсталять себе язык Оккам как некую оболочку, которая окружает векторные операции и операции пересылки данных.
Программа вычисления числа Пи была написана дважды: в первый раз использовались подпрограммы векторной формы, во второй - родовые подпрограммы (позднейшая версия включена в текст этой главы). Хотя векторная форма подпрограмм позволяет полностью управлять ВПУ, использование родовых подпрограммам уменьшает накладные расходы на начальную загрузку, а сцепление подпрограмм укорачивает код.
Наиболее затруднительным аспектом генерации параллельного кода было точное определение того, как задача распределяется по вершинам гиперкуба, и что именно - переменная, адрес переменной или просто значение - используется в векторной или транспьютерной операции.
Большинство ошибок в программе было следствием неправильного применения векторных операций или операций пересылки данных. Очень легко забыть, что когда byte, si ice и векторные операции получают байтовый адрес, то в первом случае идет счет байтов, а во втором - счет слов. Также необходимо соблюдать осторожность при использовании byte.slice ввода и вывода. У каждого посылающего узла должен быть принимающий узел, иначе возникнет тупиковая ситуация.
После первых попыток программирования для системы серии Т было обнаружено, что отлаживать программы проще, выполняя программу небольшими порциями, после чего можно выполнить анализ содержимого памяти. После создания большего количества вспомогательных отладочных средств распределение задачи по узлам гиперкуба и отладка программ, несомненно, облегчаются.
Заключение
Оккам - интересный язык, и, пожалуй, он характерен для стиля языков, предназначенных для программирования на архитектурах с массовым параллелизмом, в частности архитектурах без общей памяти. Встроенное управление параллелизмом и связью, вероятно, является наиболее существенной чертой языка. Transputer Development System также примечательная система, хотя иногда вызывает сожаление отсутствие некоторых средств создания программ, таких как библиотеки и редактор связей с возможностью размещения модулей по предопределенным адресам. Траспыотер станет более распространен, когда для создания программ окажутся доступны такие привычные языки, как Фортран и Си1). Полученный опыт показал важность тщательного анализа природы всякой задачи при попытке решения на параллельной машине. Степень, с которой естественный параллелизм задачи будет распознан и реализован, имеет тесную взаимосвязь с уровнем достижимой производительности машины.
Благодарности
Авторы приносят свою искренную благодарность за помощь многим людям, просмотревшим эту рукопись, включая д-ра Джона Густавсона (John Gustavson), Джуд Грошонг (Jud Groshong), Стива Ослона (Steve Oslon), за тщательную работу д-ру Герману Дж.Майджлиора (Herman J. Migliore), который сделал программу вычисления числа Пи работающей на системе серии Т, но больше всего нашим женам за постоянную поддержку.
^В настоящее время уже доступны - Прим, перев.
Литература
Paul Walker, “The Transputer, a building block for parallel processing", Byte Magazine, May, 1985.
Colin Whitby-Strevens, “The Transputer", in Proceedings of the 12th Annual International Symposium on Computer Architecture, IEEE Computer Society Press, 1985, p. 292.
John L Gustavson, Stuart W. Hawkinson, and Ken Scott, “The Architecture of a homogeneous vector supercomputer", Journal of Parallel and Distributed Computing, vol.3,no.3, Sept. 1986,pp. 297-304.
Geraint Jones, “Programming in okkam. A tourist guide to parallel programming", Programming Research Group, Technical Monograph PRG- 13, Oxford University Computing Laboratory, 1985.
Inmos Corporation, “Okkam Programming Manual", 1985.
Inmos Corporation, “Okkam Programming System", 1985.
Inmos Corporation, “Transputer Development System", 1985.
Donna Bergmark, “Programming the FPS T Series", to appear in Proceedings of the 1987 ARRAY Conference, Montreal, Canada, 1987.
Brenda K. Helminen and David A. Poplawski, “A performance characterization of the FPS T Series hypercube",to appear in Proceedings of the 1987 ARRAY Conference, Montreal, Canada, 1987.
IBM 3090
Майкл С. Бекерман
Машина / модель:
IBM 3090
Местонахождение:
IBM Scientific Center, Пало-Альто, Калифорния
Процессоры:
2, каждый с векторным устройством, общее ОЗУ 64 Мбайт и дополнительное ОЗУ 128 Мбайт
Операционная система:
VM/SP версия 4 с CMS
Язык программирования:
Фортран 77
Поддержка
распараллеливания:
VM/EPEX
Компилятор:
VS FORTRAN Level 2.1.0 (Jan.1986)
ЭВМ IBM 3090, описываемая в этой главе, используется на протяжении всего рабочего дня в научном центре IBM. Средняя нагрузка, зарегистрированная во время измерений, - около 200 пользователей. Время отклика - превосходное в течение всего рабочего дня. Редактирование программ выполнялось с помощью Xedit [1 ] - блочного редактора, который позволяет производить редактирование локального экрана и обмен блоками текста экрана с основной машиной. Приемом в центре руководил д-р Алан Карп, который обеспечил техническую экспертизу и службы печати, а также снабжал “подпольной" информацией и фольклором фирмы IBM.
Аппаратура
Каждый из двух центральных процессоров IBM 3090 имеет кэш-память емкостью 64 Кбайт, обеспечивающую доступ к командам и данным (см. рис. 7.1). Этот буфер динамически управляется аппаратурой и прозрачен для программ. Память в модели 3090 разбивается на две области: основную память и расширение (дополнительную память). Время доступа процессора к памяти имеет порядок микросекунды и динамически зависит от реальной памяти и системной программы управления ресурсами.
Машина, на которой производились замеры, имеет основную память емкостью 64 Мбайт и расширение емкостью 128 Мбайт. Проверочные разряды кода для обнаружения и исправления ошибок хранятся вместе с данными в основной памяти. Одиночные ошибки, обнаруженные при пересылке данных, исправляются. Кратные ошибки обнаруживаются (выставляется флаг для дальнейшей обработки).
Расширение памяти - высокоскоростное объемное хранилище данных, разбитое на страницы по 4 Кбайт, обеспечивающее синхронный постраничный обмен с основной памятью за время порядка 75 мкс, из которых поло-
вина времени идет на работу аппаратуры, а половина на выполнение служебных программ. Работа расширения памяти “прозрачна" для пользовательских программ и подсистем. Проверочные разряды кода для обнаружения и исправления ошибок, хранящиеся в расширении памяти, используются для исправления как одиночных, так и двойных ошибок. С их помощью также обнаруживаются все тройные и некоторые ошибки большей кратности. Исправление обнаруженных ошибок производится аппаратурой без вмешательства программы. О неисправимых ошибках программа извещается при помощи флагов.
Модель 3090, используя конструкцию канала IBM ХА, поддерживает образ единой системы даже в случае двух процессоров. “Интеллектуальность" распространяется от центральных процессоров до канальной подсистемы, контроллера 3880 и дисковой подсистемы 3380 под управлением системных средств MVS/ХА или VM/XA. Время обмена с устройствами, подсоединенными к каналу, - порядка микросекунд.
Программное обеспечение
В описываемых экспериментах использовалось в основном следующее программное обеспечение:
REXX - командный язык системы CMS;
ЕРЕХ - экспериментальная система распараллеливания, доступная во время проведения работ только в IBM;
компилятор VS FORTRAN [2].
VM/EPEX была разработана в рамках проекта исследования параллельной обработки (Research Parallel Processing Project - RP3). Это - высокорас- параллеленная система с общей памятью, состоящая в полной конфигурации из 512 процессоров. Работа над ней ведется в исследовательском центре IBM в Йорктауне. Цель системы ЕРЕХ - обеспечить построение прикладных параллельных систем еще до того, как станет доступна аппаратура RP3, но эта система может использоваться для параллельного выполнения и на существующих многопроцессорных системах IBM, таких как 308Х и 309Х, включающих до 4 процессоров. VM/EPEX отображает параллельные процессоры (соответствующие процессам или заданиям) на отдельные виртуальные машины системы VM. Взаимодействие процессов и синхронизация выполняются над общей памятью, которая моделируется существующими средствами операционной системы VM. Синхронизация процессов выполняется при помощи директив, которые встроены в программы. Операционная система для выполнения синхронизации не привлекается.
В разработанные модели вычислений, поддерживаемые существующей средой VM/OS, входят:
Модель ЕРЕХ-FORTRAN, которая поддерживает вычислительную модель SIMD, - все процессоры выполняют одну и ту же программу, но в каждый момент они мшут выполнять разные команды, обрабатывая разные данные в соответствии с синхронизационными директивами и значениями данных [6].
Модель ЕРЕХ-С, основанная на использовании примитивов fork и join.
Модель среды обмена сообщениями, которая использует общую памяп для пересылки сообщений.
Рис. 7.1. Функциональные элементы ЭВМ IBM 3090 Модели 200VF.
В описываемой работе эксперименты проводились только с моделью EPEX-FORTRAN, поэтому описание ограничивается именно этой моделью.
В среде ЕРЕХ параллелизм может быть специфицирован при помощи препроцессорных конструкций EPEX-FORTRAN, которые транслируются в вызовы подпрограмм и строки кода Фортрана. ЕРЕХ обеспечивает пользователей:
поддержкой на высоком уровне работы с общими данными;
множеством форм параллельных циклов;
разделами последовательных кодов;
синхронизационными барьерами;
зарезервированными параметрами ЕРЕХ, известными как выделенные переменные.
Хотя проведенные эксперименты опирались на использование этого препроцессора (который во время выполнения работ не был доступен вне IBM, за исключением специальных кооперативных исследовательских соглашений), выход после препроцессирования использовал только существующие аппаратуру и программное обеспечение. Более подробное описание среды EPEX- FORTRAN можно найти в IBM Research Report, воспроизведенном в приложении Е4.
Разработка параллельных программ
Разработка и отладка параллельных программ исключительно проста, если пользоваться помощью препроцессора EPEX-FORTRAN, Xedit и REXX. Д-р Карп предварительно написал несколько командных файлов REXX для помощи в компилировании исходных текстов EPEX-FORTRAN, и они были использованы в тестах. В случае их отсутствия пришлось бы инициировать препроцессирование, компиляцию и генерацию загрузочных модулей вручную, что прибавило бы много работы.
Вывод результатов компилятора VS FORTRAN хорошо вербализован, детально демонстрирует все специфицированные опции компиляции и результирующую организацию памяти объекта. Когда исходный текст компилируется с включением опции векторизации, те программные циклы, которые векторизуются компилятором, легко обнаруживаются в листинге программы. Каждый такой цикл дополняется графическим представлением цикла, помещенным на левом поле листинга. Это представление показывает точную протяженность цикла в исходных предложениях. Листинг показывает также результат анализа компилятором успешности векторизации. Если цикл векторизуется, то слева от графического представления помещается VECT. Если же цикл подходит для векторизации, но компилятор считает, что в некоторых частных случаях векторизация неэффективна, вместо VECT помещается ELIG. Таким образом, легко определить, как компилятор обработал программу при заданных опциях. Определение этого происходит в контексте исходного текста программы, а не в результате изучения результата компиляции.
Начальные описания# свойств и концепции препроцессора VM/EPEX Fortran даны в [7].
7.3. Пример параллельной программы
Единственным входным параметром программы вычисления числа Пи является число интервалов, на которые разбивается исходный интервал [0,1].
Программа построена таким образом, что если запускается больше одного процесса, то каждый процесс вычисляет свою часть значения интеграла, а затем эти части суммируются. Исполнялись две разные версии примера программы, причем для каждой использовались комбинации скалярного и векторного режимов работы с разным числом (1, 2 или 4) виртуальных процессоров.
Параллельная версия программы такова:
IMPLICIT DOUBLE PRECISION (A-H.O-Z)
REAL*16 PI
DIMENSION Tl(3), TIME(3)
eSHARED/СОММ/ SUMALL
Строки 1 - 3 - стандартные декларации переменных Фортрана, они не обрабатываются препроцессором EPEX-FORTRAN. Переменная Pi, декларированная с учетверенной точностью, используется для хранения исходного значения числа Пи, которое будет сравниваться с вычисленным параллельной программой. Массивы Т1 и TIME используются для хранения времен начала и конца работы программы и обеспечения статистики затрат времени. Декларация «SHARED препроцессора EPEX-FORTRAN в строке 4 объявляет SUMALL разделяемой переменной в именованной COMMON области памяти СОММ.
F(X) - 4.D0/(l.D0*X*X)
PI - 4.Q0*QATAN(l.Q0)
READ(5,*) INTRVL
В строке 5 описывается F(X) как функциональное предложение, определяющее подынтегральную функцию. В строке 6 PI присваивается исходное значение числа Пи с учетверенной точностью. В строке 7 производится чтение входного параметра \ 2INTRVL, определяющего число интервалов, на когорте разбивается интервал интегрирования для данного выполнения теста.
CALL FCLOC(Tl)
«SERIAL BEGIN
SUMALL - 0.D0
«SERIAL END
В строке 8 производится обращение к локально доступной процедуре FCLOC, которая заносит информацию о текущем системном времени во все три элемента массива П. В системе поддерживаются три счетчика времени: астрономическое текущее время, время решения задачи пользователя, суммарное время решения задачи и работы системы. В строках 9-11 определяется начало, тело и конец раздела последовательного кода. Раздел последовательного кода - это раздел программы, который будет выполняться только одним процессом, а не всеми процессами работы. Все остальные процессы работы ждут, пока будет закончено выполнение раздела последовательного кода одним процессом. Все процессы (за исключением одного), которые при выполнении программы достигли строки 9, ждут в строке 11 до тех пор, пока выполнение строки 10 не будет завершено. Заметим, что это не точка синхронизации для всех процессов, запущенных в программе. Процессы, которые “запоздают" и достигнут строки 9 после того, как выполнение раздела
последовательного кода будет завершено, могут пропустить этот раздел без задержки. Переменная SUMALL разделяется всеми процессами теста (единственная разделяемая переменная в примере программы), так что доступ к ней необходимо тщательно контролировать. Здесь определение инициализации этой переменной в последовательном разделе обеспечивает безопасность инициализации. В противном случае могло случиться так, что некоторые процессы успеют не только инициализировать, но и модифицировать значение этой переменной до того, как другие процессы инициализируют ее (и тем самым обнулят накопленные результаты).
WIDTH « 1.DO/INTRVL
SUM « 0.D0
DO 100 IC = OMYNUM,INTRVL, ONUMPROCS
SUM - SUM + WIDTH * F( (IC - 0.5D0) * WIDTH )y
100 CONTINUE
T « DFNA(SUMALL, SUM)
18CALL FCLOC(TIME)
В строках 12-13 инициализируются интервалы, на которые разбивается интервал интегрирования, и локальные (для процессов) суммы. Цикл D0 100 - пример предпланируемого цикла DO. Если программа выполняется одним процессором, это идентично последовательной программе. Если же eNUMPROC больше 1, каждый процесс выполняет вычисления в своем интервале, равном 1/6NUMPR0C. Первоначально была идея проверить, насколько эффективно программа векторизуется, поэтому она была откомпилирована с опцией векторизации, а затем выполнялась одним процессором (т. е. последовательно). Результат интегрирования накапливался в SUM (строка 15). Этот цикл и вычисления были помечены в листинге исходного текста, выдаваемом компилятором VS FORTRAN как векторизуемые.
После накопления статистики о времени выполнения последовательной программы, была накоплена статистика о времени выполнения параллельной программы на нескольких прогонах с различным числом процессов (2 или 4) и разбиением интервала интегрирования на 10 000, 1000 000 и 10 000 000 интервалов.
После предпланированных выполнений программы вычисления числа Пи то же самое было сделано в режиме самопланирования. Эта самопланирую- щаяся версия программы идентична предпланированной версии, за исключением цикла D0 100, который заменяется на:
140D0 100 IC - 1, INTRVL, CHUNK - nnnn
15SUM ■ SUM + WIDTH * F( (IC - 0.5D0) * WIDTH )y
16100 6ENDD0 NOWAIT
17 T *DFNA(SUMALL, SUM)
Цикл ODO ... OENDDO является параллельной формой обычного последовательного цикла D0, определяемой следующим образом: когда процесс достигает начала цикла, он запрашивает работу, определяемую значением индекса цикла. После получения этого значения он выполняет тело цикла для этого значения индекса и снова запрашивает работу. При этом, вообще говоря, последовательные итерации цикла должны быть независимыми. Дополнительные сведения о 6D0 и других параллельных конструкциях, поддерживаемых ЕРЕХ, можно найти в приложении Е4.
Здесь нет конфликтов по доступу к переменной, так как каждый процесс накапливает сумму в своей локальной переменной. Для большей эффективности можно применить в цикле 8D0 дополнительную конструкцию CHUNK - nnnn с тем, чтобы при запросе работы назначать процессу последовательность значений индекса, а не одно значение. Так как в описываемом тесте действия, выполняемые при одной итерации цикла, очень малы, были проведены эксперименты с разными размерами последовательности значений в предположении, что больший размер последовательности уменьшает накладные расходы на синхронизацию и распределения значений индекса между процессами. Опция NOWAIT в предложении 8ENDD0 строки 16 определяет, что после завершения своей части работы в цикле процесс продолжает выполнение, не дожидаясь, пока другие процессы закончат свою работу, даже если эти процессы продолжают выполнение предложений до начала цикла или в теле цикла. Во многих приложениях выполнение предложений, следующих за циклом, зависит от результатов полного выполнения цикла, поэтому каждый процесс должен дождаться выполнения цикла всеми остальными процессами, но в данном случае это не так.
Вызов параллельной библиотечной процедуры DFNA в строке 17 обеспечивает синхронизованное суммирование в переменной SUHAIL частичных результатов каждого процесса, полученных в локальных переменных SUM. DFNA является библиотечной процедурой, выполняющей суммирование, при аппаратной синхронизации процессов. При этом гарантируется, что в данный момент времени это суммирование выполняет только один процесс. Это, конечно, важно и необходимо, так как результат выполнения двумя процессами записи в одну ячейку памяти в лучшем случае непредсказуем. Вызов FCLOC обеспечивает запись времени завершения выполнения программы.
19 DO 200 I - 1, 3
20TIME(I) « l.D0-6*(TIME(I)-Tl(I))
21200 CONTINUE
22 ERR - SUMALL - PI
23WRITE(6,'(A,F25.15,1.PD15.3)') 'SUMALL, ERR «', SUMALL,ERR
24WRITE(6,'(A,3F10.4)') 'TIME «',TIME
25END
В строках 19-21 вычисляется разность между временем начала выполнения программы и временем ее завершения. В строке 22 вычисляется разность между исходным и вычисленным значениями числа Пи. В строках 23 - 24 происходит вывод вычисленных значений числа Пи, разности и времен выполнения программы. Заметим, что все процессы записывают значение переменной SUMALL, даже если только один из них печатает правильный результат. Предложение 8BARRIER, помещенное между 21 и 22 строками, устранило бы эту несогласованность. Кроме того, предложение 8SERBEGIN перед строкой 22 и предложение 8SEREND после строки 24 обеспечили бы печать результатов только одним процессом.
Ловушки и проблемы
Было теплое солнечное утро, когда мы приехали в Исследовательский центр IBM в Пало-Альто, Калифорния. По-видимому, это было знаком, что дела у нас пойдут хорошо. Нас встретил д-р Алан Карп, бывший в этот день нашим гидом и наставником. Мы засели в его комнате и рассказали
К 185
ему о наших планах, в том числе и о примере программы. Через двадцать минут мы уже записывали результат первого успешного выполнения нашего теста. У д-ра Алана Карпа был богатый опыт работы с ЕРЕХ в IBM 3090, к тому же он сам вводил в машину исходные тексты программ, поэтому наши тесты развивались очень быстро. Кроме того, у него было несколько командных файлов REXX, которые он создал ранее для своих использован^ препроцессора EPEX-FORTRANa и компилятора VS FORTRAN. Эти фанта значительно повысили нашу продуктивность, вызывая все программы в нужном порядке без необходимости ручного вмешательства. Это спасло нас от набора на клавиатуре множества текстов, так как препроцессор состоит из трех фаз, вызываемых в определенном порядке.
Вначале мы выполнили последовательную версию теста, чтобы прочувствовать быстродействие машины и интерфейс пользователя. Так как мы фактически не прикоснулись к клавиатуре (по некоторым законным причинам), мы не получили опыта познания новой работы. Однако мы приобрели понимание типичной пользовательской среды.
Мы отрывали листинг очередной версии нашего теста и несли его на оценку д-ру Карпу. Его знание среды ЕРЕХ позволило нам избежать ошибки, которую он заранее предупредил: при определенных обстоятельствах препроцессор не обрабатывает должным образом предложение с декларацией функции и неправильно помещает его в результирующий фортрановский текст вслед за генерируемым препроцессором вызовом подпрограммы. При обработке этого текста компилятором VS FORTRAN генерируется сообщение об ошибке. Если бы мы работали сами (не имея понятия, как оценить сгенерированный фортрановский текст), эту ошибку было бы очень трудно обнаружить и она вынудила бы нас быстро закончить наш первый рабочий день.
Как только мы получили результаты выполнения нашего первого последовательного предпланированного теста, нам захотелось выполнить и параллельный предпланированный тест. И снова результаты быстро оказались у нас в руках. Тогда д-р Карп сказал нам, что у него есть более новая версия компилятора VS FORTRAN, и он был бы рад дать нам попробовать использовать ее. Естественно, нам хотелось испробовать новейшее программное обеспечение, и д-р Карп выполнил необходимые исправления в командном файле. Он снова запустил задание на выполнение, однако на этот раз мы вместо результатов получили сообщение:
OPERAND EXCEPTION AT LINE 0006 OF BABB
Подумав несколько минут, д-р Карп обнаружил, что необходимо поправить в одном из командных файлов ссылку на библиотеку. Быстрое редактирование, и мы снова в работе. Мы выполнили несколько тестов с использованием самопланирующегося цикла 6D0. Наши тесты шли гладко, и никакое препятствие или ошибка в программном обеспечении не мешали нам до конца дня. Мы успешно выполнили наш пример программы как последовательную предпланируемую программу, как параллельную предпланируемую программу и как параллельную самопланирующуюся программу, все это - с векторизацией и без нее. При этом использовались последние версии программного обеспечения. Мы записали результаты, полученные при разных комбинациях начальных условий: числа интервалов, на которые разбивается интервал интегрирования, числа процессоров, на которых выполнялся тест (1, 2 или 4), и вариантов размера chunk.
Производительность
Все тесты выполнялись на протяжении двух нормальных рабочих дней в Исследовательском центре IBM, без создания какой-то специальной обстановки для измерений. В среднем в это время на машине работало около 200 пользователей. В первый день один из пользователей выполнял какую-то очень большую задачу, которая занимала 95% времени одного процессора и 93% времени другого. Рис. 7.2 - 7.4 показывают результаты измерений затрат процессорного времени на выполнение тестов.
На графиках представлены средние временные затраты и разброс (в случае более одного процесса) для каждого выполненого теста. Кружками отмечены результаты выполнения теста в скалярном режиме, квадратами - в векторизованном. (Иногда результаты на графике слегка смещены по горизонтали для большей наглядности.)
х*х*Время (с) / число процессовВремя (с) / число процессов.020.018.016.014.012.010.008.006.004.002.020.018.016.014.012.010.008.006.004.002Рис. 7.2. Результаты измеренияРис. 7.2. Результаты измерениявремени при предпланировании.времени при предпланировании.Время (с) / число процессовВремя (с) / число процессовРис. 7.3. Результаты измерения времени при самопланировании с вырезкой размером 1.Рис. 7.3. Результаты измерения времени при самопланировании с вырезкой размером 1.
х*
х*
Время (с) / число процессов
Время (с) / число процессов
.020
.018
.016
.014
.012
.010
.008
.006
.004
.002
.020
.018
.016
.014
.012
.010
.008
.006
.004
.002
Рис. 7.2. Результаты измерения
Рис. 7.2. Результаты измерения
времени при предпланировании.
времени при предпланировании.
Время (с) / число процессов
Время (с) / число процессов
Рис. 7.3. Результаты измерения времени при самопланировании с вырезкой размером 1.
Рис. 7.3. Результаты измерения времени при самопланировании с вырезкой размером 1.
017017,016 -,016 -.015 I-.015 I-.014.014.013.013.012.0120.90.9.008.008.007.007.006.006.005.005.004.0040.40.4ФФ.003.0030.30.30020020.20.2.001.001п процессов /О5 итерацийп процессов /О5 итерацийп процессов 10* итерацийп процессов 10* итераций.011 Врен. (с) / чисАО процессов.009.011 Врен. (с) / чисАО процессов.009Рис. 7.4. Результаты измерения времени при самопланировании с вырезкой размером 1000.Рис. 7.4. Результаты измерения времени при самопланировании с вырезкой размером 1000.0.80.80.60.60.50.5п процессов 1О7 итерацийп процессов 1О7 итераций
017
017
,016 -
,016 -
.015 I-
.015 I-
.014
.014
.013
.013
.012
.012
0.9
0.9
.008
.008
.007
.007
.006
.006
.005
.005
.004
.004
0.4
0.4
Ф
Ф
.003
.003
0.3
0.3
002
002
0.2
0.2
.001
.001
п процессов /О5 итераций
п процессов /О5 итераций
п процессов 10* итераций
п процессов 10* итераций
.011 Врен. (с) / чисАО процессов
.009
.011 Врен. (с) / чисАО процессов
.009
Рис. 7.4. Результаты измерения времени при самопланировании с вырезкой размером 1000.
Рис. 7.4. Результаты измерения времени при самопланировании с вырезкой размером 1000.
0.8
0.8
0.6
0.6
0.5
0.5
п процессов 1О7 итераций
п процессов 1О7 итераций
Интересно, что планировщик VM не знает, что данная программа выполняется распараллелено, поэтому в некоторых случаях он предоставляет одному процессу достаточно времени для того, чтобы выполнить всю работу. Это можно видеть на графике для разбиения интервала интегрирования на 104 частей, самопланирующегося теста с chunk ■ 1000 в случае 2 и 4 процессов. Кроме того, относительная скорость векторизованных и скалярных тестов зависит от размера chunk в самопланирующихся циклах.
В приложении ЕЗ находятся таблицы, в которых представлены все результаты наших экспериментов. Измерения производились как пользовательского (или виртуального), так и общего времени. Пользовательское время есть время, затраченное процессором на выполнение задания пользователя. Общее время - это оценка времени, необходимого для выполнения задания, если бы оно было одно в системе, включая затраты на подкачку страниц виртуальной памяти и ввод/вывод.
Заключение
Производительность и время отклика на запрос в системе IBM 3090- 200VF очень впечатляющие, как можно было убедиться при измерениях. Время отклика даже при большой загрузке более чем адекватно интерактивной работе, а время работы приемлемо. Хотя измерения были ограничены, стало очевидно, что комбинация командного языка REXX, препроцессора EPEX-FORTRAN и компилятора VS FORTRAN является очень гибким и “легким для работы" набором программных средств, которые предоставляют достаточно понятный выбор программных конструкций для поддержки параллельного программирования. Впечатление таково, что после прохождения вводного обучающего курса по новой программной среде перед пользователем
открывается прямая дорога к созданию и модификации различных прикладных параллельных задач.
К сожалению, ограниченность времени пребывания в Исследовательском центре IBM не позволила поработать ни с каким доступным обеспечением для отладки программ, поэтому ничего нельзя рассказать об опыте отладки. Никаких специальных средств для отладки параллельных программ нет, так как все ошибки обнаруживаются при отладке последовательных программ. Однако одна системная функция (PER) особенно полезна при отладке параллельных программ. Она поддерживает управление пятью различными типами событий. PER позволяет отслеживать выполнение команд специального типа (например, переходы). Она также позволяет отслеживать только успешные переходы в определенную область памяти или записи по определенным адресам. Маска может быть специфицирована так, чтобы отслеживать только команды, которые измененяют определенные разряды слова (например, для того, чтобы обнаружить переменную, получившую отрицательное значение). Можно также отслеживать выполнение только тех команд, которые производят запись в определенную область памяти величин определенного значения (например, для определения обнуления делителя). В параллельных задачах каждый виртуальный процесс может определить свои параметры per. В случае аварийного окончания работы можно сравнить трассы событий в разных процессах и рассмотреть порядок, в котором разные процессы изменяли разделяемые переменные.
Благодарности
Нам бы хотелось выразить свою признательность всему персоналу Исследовательского центра IBM и особенно д-ру Алану Карпу, без помощи которого эта глава не была бы написана. Нам также хотелось бы выразить свою благодарность Фредерике Дарема-Роджерс, члену группы RP3 в Исследовательском центре IBM им. Т. Дж. Уотсона, за помощь в переговорах о доступе к машине и получении разрешения на воспроизведение Исследовательского отчета IBM в приложении Е4.
Литература
[11 “VM/SP Editor (Xedit)", IBM Program Product SC24-5220.
[21 IBM,“VS/FORTRAN“, IBM Program Product 5748-F03.
G.Pfister et al.,“The IBM Research Parallel Processor Prototype (RP3): Introduction and Architecture", in Proceedings of the 1985 International Conference on Parallel Processing, Aug. 1985, pp. 764-771.
IBM,“VM/SP Introduction",IBM Program Product GC19-6200.
F. Darema-Rogers, D.A. George, V.A. Norton, and G.F. Pfister, “Environment and System Interface for VM/EPEX", IBM Researh Report RC 11381 (#51260), Sept. 19,1985.
F. Darema-Rogers, V.A. Norton, and G.F. Pfister, “Using a Single- Program-Multiple-Data computational model for parallel execution of scientific applications", IBM Research Report RC 11552 (#51726), Nov. 19,1985.
J.M. Stone, F. Darema-Rogers, V.A. Norton, and G.F. Pfister, “Introduction to the VM/EPEX Fortran preprocessor", IBM Research Report RC 11407 (#51329), Sert. 30,1985.
8
Параллельная ЭВМ Intel iPSC
Дуглас М. Пейз, Аллан Р. Ларраби
Машина / модель:Место расположения:Процессоры: Операционная система:Языки программирования: Компиляторы:Машина / модель:Место расположения:Процессоры: Операционная система:Языки программирования: Компиляторы:Intel iPSC Concurrent Computer d5 Oregon Graduate Center, Бивертон, Орегон 32 узла, каждый с локальным ОЗУ 0.5 Мб XENIX версия R3.0 в администраторе куба NX (версия 2.0 и 3.0) в узлах Фортран-286 и Фортран Ryan-McFarland ftn286 и rmfort V2.10
Машина / модель:
Место расположения:
Процессоры: Операционная система:
Языки программирования: Компиляторы:
Машина / модель:
Место расположения:
Процессоры: Операционная система:
Языки программирования: Компиляторы:
Персональный суперкомпьютер фирмы Интел (iPSC) - одна из первых коммерчески доступных параллельных ЭВМ. Эта машина действительно работает по принципу “много команд - много данных" (Multiple Instruction Multiple Data - MIMD). Все процессорные узлы идентичны и соединены двусторонними связями по топологии гиперкуба. В 16 узловом гиперкубе каждый узел связан напрямую с 4 ближайшими соседями (рис.8.1). Любой гиперкуб размерности d содержит 2“ узлов, каждый из которых имеет d ближайших соседей. Среднее расстояние между узлами равно d/T, максимальное расстояние - d. Хотя базовый вариант машины (</-5) состоит из одного 32 узлового устройства, архитектура допускает удвоение или учетверение таких устройств (64 или 128 узла). Коммуникационная среда позволяет с помощью программ пользователя задавать и другие топологии системы, такие как сетки, кольца и деревья.
Аппаратура
Система iPSC состоит из одного, двух или четырех базовых вычислительных устройств и ЭВМ Intel 286/310, называемой администратором куба. Каждое устройство состоит из 32 идентичных одноплатных микрокомпьютеров, или узлов. Емкость памяти устройства составляет 16 Мбайт локальной памяти, распределенной по 32 узлам. В каждом узле находится свой экземпляр небольшой операционной системы (NX), центральное процессорное устройство Intel 80286 и сопроцессор с плавающей запятой Intel 80287 (см. рис.8.2). Комбинация 80286/80287 имеет среднюю производительность около 30 Kflops, а производительность всего 32 узлового устройства около 1 Mflops.*>
*>Эти вычисления производительности выполнялись в предположении, что число сложений, умножений и делений одинаково. Лучшая или худшая производительность может быть получена при изменении соотношения количества этих операций.
Рис. 8.1. Гиперкуб из 16 узлов.
Рис. 8.2. Архитектура узла.
Аппаратные средства позволяют добавлять к каждому узлу по 4 Мбайт памяти или векторный процессор. Такие добавления уменьшают наполовину число узлов в устройстве. Таким образом, каждое базовое вычислительное устройство содержит или 16 узлов, каждый из которых имеет 4,5 Мбайт ОЗУ, или векторный процессор (но не то и другое одновременно), или 32 узла с 0.5 Мбайт ОЗУ.
В каждом узле имеются 8 каналов связи. Межузловые каналы реализуются семью сопроцессорами Intel 82586 на узел. Кроме того, восемь сопроцессоров Intel 82586 реализуют глобальный канал Ethernet для связи с администратором куба. При работе под управлением NX версии 3.0 передача сообщения размера от 0 до 1024 байт ближайшим соседям по гиперкубу выполняется за время от 1 до 2.2 мс. Передача сообщений большего размера и на большее расстояние требует большего времени. Размер сообщения не может превышать 16 Кбайт. Сообщение от администратора к узлу передается за время, приблизительно в восемь раз большее (18 мс на передачу сообщения длиной 1 Кбайт), чем время передачи сообщения такого же размера от узла к ближайшему узлу. Время передачи такого сообщения зависит от размера так же, как и в случае обмена между узлами.
Взаимосвязи внутри гиперкуба для 32-узловой машины реализуются соединениями на объединительной плате. Машины, состоящие из двух или четырех 32 узловых вычислительных устройств, связаны внешними кабелями. Весь набор узлов управляется системным администратором куба, в качестве которого применена ЭВМ Intel 286/310. В этой ЭВМ используются те же процессоры, что и в узлах, но ОЗУ имеет емкость от 2 до 4 Мбайт, а так же добавлены винчестер 140 Мбайт и дисковод для гибких магнитных дисков. Работа этой ЭВМ происходит под управлением ОС XENIX.
Программное обеспечение
Процессы обмениваются информацией с другими процессами, выполняющимися в в одном и том же или соседних узлах системы, путем посылки и приема сообщений. Обмен сообщениями - единственное средство для межузловой связи и синхронизации, так как в системе iPSC нет общей памяти. Посылка сообщения может быть с блокировкой или без блокировки. Посылка с блокировкой приостанавливает работу процесса до тех пор, пока сообщение не будет отправлено. (Заметим, что это не означает, что сообщение будет получено.) Хотя использование посылки сообщений без блокировки ускоряет работу процесса, перед посылкой следующего сообщения по тому же каналу связи или перед изменением буфера с сообщением необходимо производить проверку того, отправлено ли предыдущее сообщение (с блокировкой или без блокировки). Отдельная проблема заключается в том, что программа может посылать сообщения быстрее, чем узел-получатель может их принимать.
Все сообщения сопровождаются кодом типа - неотрицательным целым числом. Наличие типа сообщения позволяет узлу-получателю принимать сообщения только определенного типа. В отличие от этого администратор куба принимает сообщения любого типа, но в зависимости от типа сообщения решения о предпринимаемых действиях будут различными. Время, необходимое для пересылки сообщения, зависит от числа килобайтных пакетов (базовой единицы пересылки), на которые разбивается сообщение, и от числа межузловых связей, по которым сообщение должно пройти. При посылке сообщения с блокировкой ни одна команда в процессе не выполняется до тех пор, пока сообщение не будет отправлено (или получено, если происходит прием
сообщения с блокировкой). При обмене сообщениями без блокировки выполнение программы продолжается, а операционная система обрабатывает сообщение по своим приоритетам. В этом случае вызов процедуры status помогает определить, можно ли снова использовать буфер, в котором находилось сообщение. Этот протокол обмена сообщениями позволяет пользователю создавать правильно синхронизованные прикладные параллельные программы и разрешает проблемы, связанные с управлением потоками сообщений.
Измерение времени, прошедшего между выполнением любых двух точек программы в администраторе куба, производится при помощи вызовов подпрограммы gettim, обеспечиваемой компилятором Ryan-McFarland Fortran. Эта программа дает значение текущего системного времени, измеренного в тысячных долях секунды. В каждом узле есть свои независимые часы, значение которых доступно с помощью целочисленной подпрограммы clock(). Это время используется для точного измерения времени выполнения разных частей программы в узлах. Однако, так как эти часы независимы, их трудно использовать для синхронизации процессов в разных узлах (см. [1]).
Разработка программ
Обсуждение проектирования
Система iPSC имеет 32 узла, поэтому максимально достижимо при решении одной задачи 32-кратное ускорение по сравнению с решением этой задачи на одном процессоре. Возможность реального достижения такого ускорения зависит от выполняемой прикладной задачи. Некоторые задачи легко разделяются на независимо выполняющиеся части, в то время как при разделении других для обеспечения их выполнения требуется выполнить значительное число обменов. Ну, а некоторые задачи вообще нельзя распараллелить, так как их выполнение по своей природе является последовательным. Если некоторый алгоритм нельзя распараллелить, возможно, существует другой алгоритм, поддающийся распараллеливанию.
Все распределенные задачи нуждаются в той или иной форме в обмене сообщениями, по крайней мере для обмена результатами. Так как обмены требуют затрат времени ЦПУ для своего выполнения или приостанавливают процессор при ожидании получения сообщения, очень важно, чтобы время, необходимое для обмена сообщениями, не доминировало над временем выполнения программы. Например, если половину всего времени решения задачи процессор находится в ожидании поступления сообщений от других узлов, то эффективная скорость работы процессора снижается в два раза.
Одним из возможных способов решения этой проблемы является уменьшение количества необходимых точек синхронизации (ожидания процессом прихода сообщений от других процессов). При больших затратах времени на синхронизацию мотут оказаться полезными и отказ от модели “одна программа - множество данных (Single Program Multiple Data - SPMD)“ и загрузка более одного процесса в узел. Однако распределение процессов по узлам - это серьезная задача по сбалансированной загрузке. Оптимальная сбалансированная статическая загрузка в общем случае является сложной проблемой (см. [2] и разд. 3 в [3]), но гиперкубическая архитектура, в частности, хорошо подходит для решения большого количества общих задач, для которых сбалансированная загрузка разрешима (см. [3] и [4]).
Для того чтобы программа использовала максимальную производительность системы iPSC, она должна выполняться достаточно долго, чтобы компенсировать накладные расходы, необходимые на загрузку и инициализацию
узлов. Гораздо легче полнее использовать вычислительную мощность iPSC, ' если решаемая задача может быть разбита на долгоживущие процессы, кото- ; рые существенно больше вычисляют, чем обмениваются.
Ключевым вопросом конструирования программ для достижения эффективного использования системы iPSC является обнаружение таких вычислительных задач, которые могут выполняться параллельно и представляют собой значительную часть всех необходимых вычислений. Для компенсации накладных расходов на пересылку сообщений отношение числа вычислительных команд к числу команд пересылки сообщений должно быть большим. Насколько большим - зависит от числа пересылаемых сообщений, от длины каждого сообщения, количества межузловых связей, по которым проходит сообщение, адресатов обмена (администратор куба или другие узлы).
Результаты вычислений могут быть отправлены непосредственно в администратор куба и записаны там в файл XENIX или отображены на экране дисплея. Другим способом является назначение одного узла для сбора и обработки данных информации, поступающих из других узлов. Такой главный узел может затем направить окончательные результаты в администратор ку- ? ба. Узлы могут и сами обращаться к функции syslog для записи данных (через администратор куба) в результирующий файл. Эта функция может использоваться узлами и для диагностических сообщений или сообщений об ошибках.
Инициализация и загрузка куба
Следующая последовательность терминальных команд могла бы быть использована для выполнения двух предварительно откомпилированных программ (в данном случае названных host и nodejprogram).
Xgetcube assigning cube
Xload -c 5
(В данном случае X - это приглашение XENIX). Команда getcube обеспечивает доступ к гиперкубу в данной момент только одному пользователю. Команда load устанавливает Node Executive (NX) и программное обеспечение взаимодействия в гиперкуб размерености 5. Опция -с запускает программу, проверяющую правильность NX и программного обеспечения взаимодействий.
Xload nodejprogram
Load successful
Xhost
Xloadkill
loadkill complete
Xvi nodejprogram.f
Xmake nodejprogram
Xload nodejprogram Load successful
Xhost
Xrecube relinquishing cube
В этом примере сеанса работы за терминалом программа администратора куба названа host. После успешного завершения загрузки nodejprogram, программа администратора куба запускается путем набора ее имени. Команда loadkill заканчивает выполнение всех существующих в узлах гиперкуба процессов и оставляет только операционную систему и программное обеспечение взаимодействия. Эта команда прерывает выполнение активных программ и удаляет завершившиеся программы. Обычно команда load вызывается только в начале сеанса диалога с кубом. Команда relcube отменяет монопольный доступ к гиперкубу.
Примитивы обмена сообщениями
Сообщения могут посылаться любым процессом любому другому процессу, существующему в каком-то узле или в администраторе куба. Каждое сообщение помечается типом этого сообщения и двухэлементным адресом, состоящим из идентификатора (ИД) узла и ИД процесса. ИД узла может принимать значения от 0 до 32 (в 32 узловом гиперкубе) и выделенное значение -32768, идентифицирующее администратор куба. ИД процесса есть число, связанное с каждым каналом и специфицированное только тогда, когда канал открыт. Таким образом, несколько каналов могут быть открыты с одним и тем же ИД процесса. Если эти каналы открыты разными задачами в одном и том же узле, каждый процесс может перехватить сообщение, предназначенное другому процессу. Тип сообщения позволяет процессу выделять свои сообщения, но только в момент приема, а не при открытии канала.
Каналы открываются и закрываются при помощи подпрограмм сореп и eclose. Каналы идентифицируются при помощи ИД канала - cid. cid - copen(pid) открывает канал, a cclose(cid) закрывает его. Значение pid - это ИД процесса для нового канала. ИД процесса, уникальный в задаче, может быть создан в узле путем обращения к процедуре mypid().
Обмен сообщениями одинаков для всех узлов, но есть разница между обменами с узлами и с администратором куба. Администратор может принимать и отправлять сообщения только с блокировкой. Он также не может производить выбор поступивших сообщений по типу сообщения. Команда администратора:
sendmsg (cid,type,bufjen,node,pid)
посылает сообщение типа type, находящееся в буфере buf, в узел и канал, специфицированные параметрами node и pid. Команда:
reevmsg (cid,type,bufjen,ent,node,pid)
принимает сообщения, адресованные администратору. Все поля в этом обращении к команде, за исключением ИД канала (cid) и длины буфера (1еп), заполняются при выполнении команды. Когда команда reevmsg выполнится, в ячейках памяти, определенных соответствующими параметрами, будет находиться сообщение, его длина в байтах (ent) и тип принятого сообщения. В ячейках памяти, определенных параметрами node и pid, будут находиться ИД узла и процесса, откуда пришло сообщение.
При обращение к процедурам send, recv, sendw и recvw задаются те же параметры, что и при обращении к sendmsg и к recvmsg, которые обрабатываются таким же образом за следующим исключением. При обращении к recvmsg параметр type модифицируется при обработке этого обращения и не может быть использован для выбора сообщения из всех поступивших сообщений разных типов. При обращении же к recv и recvw этот параметр не модифицируется и используется для выбора сообщения соответствующего типа из всех поступивших. За этим исключением, процедуры recv и recvw в узлах работают точно так же, как процедура recvmsg в администраторе куба.
Разница между send и sendw и между recv и recvw состоит в том, что процедуры send и recv выполняются без блокировки. Обоснованное применение этих процедур может увеличить скорость выполнения программы, так как без их использования выполнение программы могло оказаться приостановленным. Однако перед повторным использованием буфера, в котором находилось отправленное сообщение, необходимо убедиться в его отправлении, обращаясь для этого к процедуре status(dd). Кроме того, эта процедура может быть использована для проверки того, ожидается ли получение каких- либо сообщений по некоторому каналу.
Программа может использовать процедуру syslog для передачи специальных регистрирующих сообщений, которые не могут быть перехвачены ни одним процессом и не влияют на нормальный ход выполнения программы. Обычно эти сообщения используются при отладке программ. Кроме того, они могут быть использованы для индикации того, насколько продвинулось выполнение программы на разных стадиях счета.
Взаимодействие администратора с узлом
В ОС NX версии 2.0 с помощью опции при загрузке системы можно выбрать один из двух режимов взаимодействия - конкурирующий или опросный. При конкурирующем режиме каналы связи можно использовать только тогда, когда действительно имеется сообщение для передачи. Администратор (узел) при передаче сообщения прерывает работу узла (администратора), а в остальное время дает возможность выполнять свою работу. При опросном режиме работы администратор должен циклически опрашивать узлы о наличии предназначенных для него сообщений. Этот режим уменьшает производительность каждого узла, так как требуется время на обслуживание запросов администратора. Первоначально этот опросный режим рекомендовался для повышения надежности, даже в условиях снижения скорости. В NX версии 3.0 возможен только конкурирующий режим работы, однако здесь приводится информация об измерениях времени при работе в NX версии 2.0 в двух режимах для демонстрации эффекта изменения времени взаимодействия в рассматриваемом простом примере.
Различие в производительности при работе в этих двух режимах может быть весьма существенной (см. рис. 8.3). При небольших сообщениях затраты на передачу сообщения от администратора к узлу при работе в опросном режиме могут быть почти в 20 раз больше, чем при работе в конкурирующем режиме. (По результатам проведенных измерений среднее время передачи сообщения длины 0 равно 0.028 с при работе в конкурирующем режиме. При работе администратора в опросном режиме, время на пересылку в некоторые узлы было равно 0.26 с, а в другие узлы - 0.52 с.)
Рис. 8.3. Время пересылки сообщения от администратора куба к узлу.
Рис. 8.4. Время пересылки сообщения от узла к администратору куба.
Рис. 8.5. Время пересылки сообщения от узла к узлу.
Взаимодействие узлов
Обмены между узлами выполняются существенно быстрее, чем обмены между узлом и администратором (см. рис. 8.4 и 8.5). Это различие в скорости обмена может быть использовано, когда некоторое сообщение необходимо передать всем узлам в сети. “Наивным" подходом к решению этой задачи была бы пересылка сообщений через администратора. Более усложненный подход, обеспечивающий лучшую производительность, состоит в пересылке сообщения корневому узлу, который рассылает его своим соседям, те в свою очередь - своим соседям в соответствии с минимальным покрывающим деревом (см. [5]). Результаты в разд. 8.5 иллюстрируют эффективность влияния этой стратегии на производительность выполнения программы.
Все времена были измерены на незагруженной системе iPSC и характерны для пересылки сообщений в отсутствие конфликтов. При большом числе сообщений в системе время на пересылку может значительно возрасти.
Пример параллельной программы
В этом разделе представлены две версии простой численной программы. Программа вычисляет число Пи. В первой версии программы был реализован наивный подход к пересылке сообщений, а во второй были предприняты попытки оптимизировать взаимодействие. Полный листинг обеих версий программы находится в приложении F.
Основная стратегия разработки программы вычисления числа Пи заключалась в использовании каждого из 32 узлов гиперкуба для вычисления рав-
ной части приближенного значения интеграла. Когда вычисление частичных сумм заканчивается, все узлы пересылают свои результаты в узел 0. Узел 0 суммирует все вклады и пересылает результат администратору.
Программа в администраторе - неоптимизированная версия
1program host
2parameter (lenint«4,lendbl-8, npid-O)
Integer old, ent, copen,nid, pid, processors, cubedimy
integer typel, intervals,type
double precision sum
Как показано в строках 3 и 4, следующие переменные должны быть объявлены как целые:
cid- ИД канала связи;
ent- длина в байтах принятого сообщения;
сореп - значение функции, которая создает канал связи;
nid - ИД узла;
typel- тип сообщения;
cubed 1m - значение функции, сообщающей размерность гиперкуба;
pid- ИД процесса.
6data pid /0/, typel /1/
7cid ■ copen(pid)
8processors ■ 2**cubedim()
Канал связи, используемый для обменов между программами в узлах и администраторе, открывается в строке 7. Каналы связи автоматически закрываются при завершении процесса, но могут быть закрыты при помощи функции eclose. ИД процесса (в строке 6) используется для адресации правильного процесса в узле. Определение числа узлов завершается во время выполнения программы, как показано в строке 8, и может быть использовано для адаптации к выполнению программы в гиперкубе любой размерности.
9
10
write!*, *)
10
write!*, '("Enter number of intervals")')
11
read!*, *, err«10, end=20) intervals
12
if (intervals .le. 0) go to 20
13
do 15 i ■ 0, (processors - 1)
14
call sendmsg (cid, typel, intervals, lenint, 1, npid)
15
15
continue
Отрезок интегрирования [0,1], разделенный на число точек разбиения (intervals), введенное в строке 11, дает величину дифференциала (dx) для вычислений по методу прямоугольников. Число точек разбиения сообщается каждому узлу при помощи подпрограммы sendmsg, вызываемой в строке 14. Это посылка сообщения с блокировкой, так как программа выполняется в администраторе куба. В отличие от узлов администратор может рассылать сообщения только с блокировкой. Параметры подпрограммы sendmsg следующие:
cid - ИД канала связи, который открывается предыдущим обращением к подпрограмме сореп в строке 7;
typel - специфицирует тип сообщения, получаемого каждым узлом (используется для выделения сообщения среди всех, передаваемых по данному каналу);
intervals - дает начальную точку интегрирования (пересылка содержимого всего COMMON блока определяется указанием идентификатора первой переменной в этом блоке);
lenint - число пересылаемых байтов;
i - номер узла, который получает сообщение.
Каждый открытый канал имеет ассоциированный с ним ИД, называемый ИД процесса. ИД процесса не должны быть уникальными ни для процесса, ни для канала. Однако открытие более одного канала в узле с одним и тем же ИД процесса может привести к тому, что задача может перехватить сообщение, предназначенное другой задаче. В данном примере переменная npid - это ИД процесса, которому адресовано сообщение. Существует (в данном примере) только один процесс в узле, и в каждом узле ИД процесса установлен в нуль. Каждый узел определяет свою долю вычислений при интегрировании, используя ИД узла и число точек разбиения.
16call recvmsg(cid, type, sum, lendbl, ent, nid, pid)
17write(, 100) sum/intervals
18go to 10
19100 format('Pi is ’,f20.16)
2020 end
Каждый узел пересылает свои результаты вычислений в узел 0, который суммирует эти результаты и пересылает окончательный результат администратору куба. Этот результат принимается в администраторе при помощи вызова подпрограммы reevmsg (строка 16). Такой способ получения окончательного результата быстрее, чем пересылка частных результатов каждого узла прямо администратору и получение окончательного результата в нем, так как межузловая пересылка намного быстрее, чем пересылка сообщений между узлом и администратором. Параметры подпрограммы reevmsg следующие:
cid - тот же самый ИД канала, что и в обращении к предыдущей подпрограмме (заметим, что один и тот же канал может использоваться и для приема и для передачи сообщений);
type - устанавливается самой подпрограммой равным типу принятого сообщения;
sum - принятое сообщение;
Программа в узле - неоптимизированная версия
Следующая программа загружается в каждый узел гиперкуба. В нашем примере в каждый узел загружается один процесс, но, вообще говоря, можно загружать и несколько процессов в один узел.
program node
parameter (lenint-4, lendbl=8)
integer pid, old, copen, nid, mynode, dim, cubedim
integer type, ent, hid, node, processors, root
integer typel, type2, type3
integer intervals, i
double precision f, x, h, sum,work
data typel /1/, type2 /2/,type3 /3/
data pid /0/, hid /-32768/, root /0/
f(x) - 4.0d0/(1.0d0 + x*x)
cid = copen(pid)
nid ■ mynode()
dim = cubedim()
processors ■ 2**dim
В системе используется величина -32768 (-215) в качестве ИД (hid, инициализированного в строке 29) администратора куба. Вызов функции mynode в строке 32 позволяет определить номер узла, в котором выполняется процесс. Этот номер должен быть определен как целое. Вызов функции cubedim в строке 33 позволяет узлу определить, какую часть общей работы он должен выполнить. Значение pid, используемое для открытия канала для посылки сообщений, должно быть равным значению pid, используемому для открытия канала для приема сообщений. При приеме сообщения тип, указанный в параметрах приёма, должен соответствовать типу, определенному при передаче этого сообщения. Значение pid, используемое для всех каналов в данном примере, равно нулю. Администратор использует тип 1 для рассылки всем узлам величины интервалов, на которые разбивается отрезок интегрирования. Узлы используют тип 2 для отсылки частичных результатов интегрирования в узел 0, а узел 0 использует тип 3 для передачи окончательного результата администратору куба.
10 continue
call recvw(cid,typel,intervals,Irnint,ent,node,pid)
if (intervals .Ie. 0)goto30
h ■ 1.0d0/intervals
sum ■ O.OdO
do 20 i » nid, intervals-1,processors
x - (i + 0.5d0) * h
sum ■ sum + f(x)
20 continue
В строке 36 вызов подпрограммы reevw выполняет действительный прием сообщения, содержащего число интервалов, над которыми надо произвести вычисления. Переменная lenint содержит значение 4. Когда сообщение будет принято в узле, в ent будет находиться длина принятого сообщения. Если lenint меньше, чем ent, сообщение будет принято не полностью. Каждый
узел выполняет свою часть вычислений (вычисляя частичную сумму) в цикле D0 в строках 40 - 43.
44
if (nid .eq. root) then
45
do 25 i * 1, (processors-1)
46
call recvw(cid,type2,work,lendbl,ent,node,pid)
47
sum * sum + work
48 25
continue
49
call sendw(cid,type3,sum,lendbl,hid,pid)
50
else
51
call sendw(cid,type2,sum,lendbl,root,pid)
52
end if
53
go to 10
54 30
end
После вычисления частичные суммы пересылаются в место сбора - узел 0 - обращением к подпрограмме sendw в строке 49. В строке 44 производится проверка того, что это не корневой узел, так как узел 0 не должен пересылать частичную сумму самому себе. Если номер узла показывает, что это корневой узел (т. е. номер узла равен 0), то он должен принять и суммировать все частичные результаты остальных узлов и переслать окончательный результат администратору куба. После этого программа в узле возвращается к своему началу, чтобы начать новое интегрирование. '
Изменения с целью оптимизации
13 с1415 с-13 с1415 с-Разница между оптимизированной и неоптимизированной версиями программы состоит в способе использования каналов гиперкуба. Неоптимизиро- ванная версия программы в администраторе куба рассылает каждому узлу сообщение, содержащее число интервалов, на которые разбивается отрезок интегрирования. Оптимизированная версия программы в администраторе посылает такое сообщение только одному узлу - узлу 0, который пересылает его своим ближайшим соседям, те в свою очередь - своим и так далее, пока все узлы не получат это сообщение. Такая схема оптимально параллельна. Строки 13 - 15 неоптимизированной программы заменяются следующими:
13 с
14
15 с-
13 с
14
15 с-
call sendmsg (cid, typel, Intervals, lenint, root, npid)
Корневой узел (нулевой) начинает рассылку после приема сообщения. Это производится в результате замены строки 36 неоптимизированной программы на следующую:
36call grecvw(cid,typel,intervals,lenint,ent,dim)
Сбор распределенных частичных результатов тоже оптимизируется. В неоптимизированной версии (строки 44 - 52) каждый узел пересылает свой частичный результат прямо узлу 0. При этом ответственность за суммирова-
ние всех частичных результатов и пересылку окончательного результата администратору возлагается на корневой узел. Работа ускоряется, если узлы, далекие от корневого узла, пересылают свои частичные результаты не прямо корневому узлу, а соседу, который ближе к нему, тот суммирует полученные частичные результаты и пересылает сумму тому соседу, который еще ближе к корневому узлу и т.д. Это выполняется следующими операторами (см. приложение F4):
42call gop(cid,type2,sum,1/+',root,dim,work)
if (nid .eq. root) then
call sendw(cid,typeS,sum,lendbl,hid,pid)
end if
Подпрограммы grecvw (глобальное ожидание приема) и дор (глобальная операция) являются частью библиотеки коммуникационных утилит, поддерживаемой фирмой Intel [6].
13 с—1415 С--13 с—1415 С--Неоптимизированная версия программы может быть также существенно улучшена для работы с ОС NX версии 3.0 заменой строк 13 - 15 на следующие:
13 с—
14
15 С--
13 с—
14
15 С--
саП sendmsg (cid, typel, intervals, lenint, -1, npid)
Программа в узлах остается точно такой, как в неоптимизированной версии. При этой модификации программы используются широковещательные возможности сети Ethernet, обеспечиваемые NX версии 3.0, которые будут обсуждаться в следующем разделе.
Производительность
Предварительные измерения производительности системы Intel iPSC на научных программах со множеством вычислений с плавающей запятой показали, что администратор куба работает приблизительно в три раза медленнее, чем незагруженный VAX-11/780 с ускорителем для работа с плавающей запятой, работающий под управлением Berkeley UNIX 4.2BSD. Одиночный узел работает только в 1,9 раза медленнее, чем незагруженный VAX 11/780.
Были выполняемы как оптимизированная, так и неоптимизированная версии программы вычисления числа Пи, описанные в предыдущем разделе, под управлением ОС NX версии 2.0 с использованием коммуникаций в опросном режиме, а затем в конкурирующем режиме. Каждая версия выполнялась на 8, 16 и 32 узлах системы. Результаты приведены на рис. 8.6, 8.7 и 8.8. На рис. 8.9 сравниваются результаты выполнения программы с оптимизацией обменов и опросным/конкурирующим режимами для 32 процессоров. Были также выполнены обе версии программы под управлением ОС NX версии 3.0 (плюс дополнительная версия, в которой используются возможности широковещания сети Ethernet) для 32 процессоров (рис. 8.10). Возможности широковещания - новые для NX версии 3.0.
ZV 16 узлов0*32 узла
Ю3 104105106 ю7
Интервалы
Рис. 8.6. Неоптимизированная программа вычисления числа Пи - администратор куба в режиме опроса (NX Версия 2.0).
Рис. 8.7. Оптимизированная программа вычисления числа Пи - администратор куба в режиме опроса (NX Версия 2.0) .
Интервалы
Рис. 8.8. Оптимизированная программа вычисления числа Пи - администратор куба в режиме конкуренции (NX Версия 2.0) •
Рис. 8.9. Сравнение стратегий обмена при 32 узлах (NX Версия 2.0) ,
Рис. 8.10 Выполнение оптимизированной и неоптимизированнной программ (NX Версия 3.0).
Из проведенных экспериментов можно сделать некоторые выводы о влиянии времен обмена на общее время выполнения задачи. Легко видеть, что значительную часть времени выполнения занимает пересылка сообщений, уменьшение этих затрат вызовет уменьшение общего времени выполнения. Уменьшать затраты времени на обмен можно тремя способами:
Не занимаясь самой программой, можно ускорить ее выполнение, уменьшая число узлов, вовлеченных в выполнение задачи. Для каждого узла, участвующего в вычислениях, требуются определенные накладные расходы. Каждый узел должен выполнить работу, оправдывающую накладные расходы на его запуск. Если задача достаточно мала, использование гиперкуба меньшей размерности может дать лучшие результаты. Конечно, если нагрузка увеличивается, дополнительные накладные расходы становятся оправданными, и следует использовать дополнительные узлы.
Начальные накладные расходы можно уменьшить используя конкурирующий режим взаимодействия, а неопросный. Хотя последняя версия NX (3.0) поддерживает только конкурирующий режим, программист может соблазниться применить в задаче некоторую форму опросного режима для синхронизации выполнения отдельных частей программы. Результаты измерений и некоторый здравый смысл говорят, что надо по возможности этого избегать.
Накладные расходы можно уменьшить, подключая дополнительные процессоры для рассылки сообщений по минимальному покрывающему дереву, а не используя для этого связи администратора с узлами. Этот подход очень прост и существенно улучшает время рассылки вне зависимости от среды и количества используемых узлов. Успех обеспе-
чивается тем, что пересылка сообщений идет параллельно, по быстрым каналам, и самый длинный путь не превышает диаметра гиперкуба. В неоптимизированной версии рассылка производится последовательно и по более медленным каналам.
Другим подходом, двойственным к рассылке по покрывающему дереву, является параллельное выполнение “глобальных" операций (например, суммирование частичных результатов из каждого узла) Вместо прямой пересылки частичных результатов из каждого узла в некоторый выделенный узел (например, нулевой), каждый узел может объеденить свой результат с ре- зультом соседнего узла, что сокращает число пересылаемых сообщений и длину пути. Листья минимального покрывающего дерева должны пересылать свои результаты в ближайший промежуточный узел. Промежуточные узлы ждут результаты от соседних узлов, удаленных от корня, получив их, объединяют со своим результатом и отсылают новое значение к узлу по направлению к корню дерева. Окончательный результат формируется в корневом узле.
Последний подход - использование возможности системного широковещания. Оно состоит в быстрой рассылке сообщений из узла в любой подгиперкуб, связанный с этим узлом, или даже в полный гиперкуб. Администратор может производить рассылку только на весь гиперкуб. Это относительно простой, но достаточно быстрый способ.
По мере того как отношение времени обмена ко времени вычислений уменьшается, усилия по снижению расходов на взаимодействие дают все меньший эффект. Когда рассматриваемая программа вычисляла интеграл при разбиении отрезка интегрирования на 10 000 000 частей, даже наименее эффективная версия программы в системе из 32 узлов (неоптимизированная версия под ОС версии 2.0 в опросном режиме) выполнялась не хуже, чем самая эффективная (обмен по по минимальному покрывающему дереву под ОС версии 3.0).
Еще один вывод из выполненных экспериментов. Если число сообщений постоянно, то доля времени, затрачиваемая на пересылку сообщений, уменьшается по мере того, как возрастает вычислительная нагрузка. Можно заключить, что усилия по оптимизации обменов имеют смысл лишь тогда, когда число сообщений велико или/и время обменов сообщениями оказывается доминирующим.
Итак, для того чтобы программа была эффективной на системе iPSC, необходимо, чтобы она требовала для своего решения довольно большого процессорного времени, чтобы она естественно распараллеливалась и, что наиболее важно, чтобы в ней существенно преобладали вычисления над обменом сообщениями. Ключевым вопросом разработки программ, успешно использующих систему iPSC, является разбиение задачи на части, которые требуют для своего выполнения много времени, могут выполняться параллельно и представляют значительную долю общих вычислений, необходимых для решения задачи. Размер этих частей является функцией от длины сообщений, числа межузловых связей, по которым передаются сообщения, и от характера обмена - узел-узел или узел-администратор гиперкуба.
Ловушки и проблемы
При первых экспериментах программы выполнялись на гиперкубе в отсутствие отладочных средств. Предложения отладочной печати (вызовы syslog) вставлялись в текст исходной программы. С другой стороны, сообще-
ния, передаваемые узлами администратору, могут быть использованы для пе- ; реноса информации, однако, чтобы ее использовать, необходимо организовать соответствующий прием сообщений в администраторе куба. Недавно стал доступен параллельный отладчик (Concurrent Debugger) [7]. Его краткое описание приведено в приложении F6. Так как Фортран не проводит проверки типов параметров при вызовах подпрограмм, необходимо вручную тщательно проверять количество и типы фактических параметров при обращении к функциям обмена сообщениями.
Так как пересылка сообщений обходится довольно дорого, то в случае пересылки большого количества переменных не следует пересылать их по-от- дельности. Если данные организованы в массив, то решение очевидно. Нужно просто использовать массив как буфер, размер которого равен произведению длины массива на число байтов в каждом элементе массива. Однако даже если данные не упорядочены, то программист все же может организовать обмен, не прибегая к отдельным пересылкам.
Это можно сделать, объявив каждую пересылаемую переменную частью COMMON блока, используя первую переменную в COMMON блоке как буфер обмена. Длина сообщения при этом равна длине COMMON блока в байтах. В принимающей программе необходимо точно так же организовать принимаемые данные. Правда, при такой организации обменов в отсутствие проверки типов данных и соответствия имен никакой гарантии правильного совместного использования COMMON блока нет, и программисту необходимо чрезвычайно тщательно проверить все это самому. Как и в последовательных фортрановских программах, если две переменные поменять местами или неправильно объявить тип даже одной переменной, все переменные, размещенные в блоке ниже ошибки, могут быть искажены.
Так как параллельно могут выполняться сразу несколько задач, требующих каждая своего времени для выполнения, существенно возрастает возможность возникновения ошибок, зависящих от временных соотношений, таких как тупики, несогласованные изменения данных или нарушения последовательности выполнения действий.
Заключение
Система iPSC - это практичный и полезный параллельный процессор, к достоинствам которого относятся возможность расширения и гибкость. Хотя и не существует параллельной машины, наилучшей для всех применений, гиперкубическая топология iPSC позволяет реализовать любой крупномасштабный параллелизм в прикладной задаче. Средства разработки программ совершенствуются и расширяются.
Ввиду достаточно хорошего знакомства с последовательными машинами и явно недостачного - с параллельными машинами и алгоритмами, требуются дополнительные усилия при отображении последовательных алгоритмов на параллельные архитектуры. В частности, машины с обменом сообщениями, кажется, наиболее пригодны для задач, которые представимы в памяти узлов в виде совокупности больших пространств данных, с тем чтобы вычисления и обмены были сбалансированы и в каждом узле выполнялась одна и та же программа (или небольшой набор программ).
Как и в любой другой параллельной машине, необходимо удерживаться от соблазна создания слишком большого числа процессов, каждый из которых выпонял бы только небольшую часть общих вычислений. Следует стремиться к исключению задержек в результате необязательного обмена между узлами. В ранних версиях системы NX очень важно выполнять обмены по
возможности только с ближайшими соседями по гиперкубу. В версиях системы, начиная с 3.0, расстояние между узлами слабо влияет на время передачи сообщения, поэтому это ограничение можно игнорировать.
Благодарности
Выражаем благодарность Кливу Молеру (Cleve Moler) из Intel Scientific Computers за участия в рецензировании этой главы.
Литература
Leslie Lamport, “Time, clock and the ordering of events in a distributed system", Communications of the ACM, vol.21, no.7, July 1978, pp. 558 - 568.
S.H.Bokhari, “On the Mapping Problem", IEEE Transactions on Computers, vol. C-30, no.3, pp. 207 - 214.’
Michael T. Heath (ed.), Hypercube Multiprocessors 1986, Proceedings of the First Conference on Hypercube Multiprocessors, Knoxville, TN, Aug. 26-27, 1985, Philadelfia: SIAM, 1986. See especially sections IV and V.
Geoffrey C. Fox, “Concurrent processing for scientific calculations", in Proceedings of the IEEE COMPCON Spring 84, San Francisco, CA, Feb. 27 - Mar. 1, 1984, pp. 70 - 73.
Cleve Moler, “Matrix computation on a hypercube multiprocessor“, in Hypercube Multiprocessors 1986, Proceedings of the First Conference on Hypercube Multiprocessors, Knoxville, TN, Aug. 26-27, 1985, Philadelfia: SIAM, 1986, pp.181 - 195.
Cleve Moler and David S. Scott,“Communication Utilities for the iPSC", iPSC Technical Report No.2, Intel Scientific Computers, Aug. 1986.
Intel Corporation, “iPSC Concurrent Debugger Manual" (2d ed.), March 1987.
9
Loral Dataflo LDF 100
Дэвид Ц. ДиНиччи
Машина / модель: Местоположение: Процессоры:Операционная система:Языки программирования: Компилятор:.Другие вспомогательные средства:Машина / модель: Местоположение: Процессоры:Операционная система:Языки программирования: Компилятор:.Другие вспомогательные средства:Loral Dataflo LDF 100
Машина / модель: Местоположение: Процессоры:
Операционная система:
Языки программирования: Компилятор:.
Другие вспомогательные средства:
Машина / модель: Местоположение: Процессоры:
Операционная система:
Языки программирования: Компилятор:.
Другие вспомогательные средства:
Loral Instrumention, Сан Диего, Калифорния 4 узловых процессора (каждый с ОЗУ 128 Кбайт), 1 основной процессор (локальная память) GENIX в основном процессоре, Dataflo Kernel (версия 1.0) в узлах Фортран 77 + Loral Data Graf Language (DGL) Green Hills Fortran 77 (версия 19 июня 1986)
tass (версия 1.0) - (Tag Assigner - для приписывания тегов)
linker (Версия 1.0)
Loral Dataflo LDF 100 первоначально была разработана компанией Loral Instrumentation для расширения возможностей сбора, анализа и вывода данных в реальном времени, предоставляемых устройствами производства фирмы Loral. Эта система оказалась, однако достаточно гибкой для решения многих общих задач, требующих интенсивных вычислений. Название Dataflo отображает потоковую (от data flow - поток данных) природу этой вычислительной машины, в которой процесс планируется для исполнения только тогда, когда все его динамические входные данные стали доступными в результате работы других процессов.
Обычно управление потоком данных относится к такой вычислительной модели, в которой команды машинного уровня управляются доступностью данных, проходящих (концептуально) по дугам графа потока данных. Тако-: му принципу управления потоком данных на уровне операций можно проги- вопоставить принцип управления укрупненным потоком данных (Large-Grain ; Data Flow [1]), в котором единица планирования вычислений крупнее (воз- ; можно, намного крупнее), чем одна машинная команда. Система Loral Dataflo LDF 100 была разработана для реализации такого крупномасштабного параллелизма.
Аппаратура■
Система LDF 100 состоит из основного процессора, в котором приклад- ? ная задача готовится к выполнению, и из нескольких (от 1 до 256) узловых i процессоров, в которых эта задача выполняется.■
Каждый узловой процессор состоит из двух секций, как показано на рис. ; 9.1. Секция обработки маркера - Token Processing Section (TPS) - участву- ' ет только в приеме и сборке сообщений для процессов, выполняемых в данном узле. Секция узловой обработки - Node Processing Section (NPS) - выполняет программу процесса, определенную пользователем. Каждая секция
узлового процессора имеет свой собственный микропроцессор NS32016, ПЗУ (16 Кбайт) и ОЗУ (32 Кбайта в NTS, 128 Кбайт в NPS). В NPS имеется процессор с плавающей точкой - NS32081, а также устройство управления памятью и устройство управления прерываниями. Обе секции обмениваются сообщениями через коммуникационное ОЗУ емкостью в 16 Кбайт.
Все сообщения, которыми обмениваются узловые процессоры в одной стойке, пересылаются по общей шине, называемой FLObus. Для того чтобы послать сообщение, прикладная программа, выполняемая в NPS посылающего узлового процессора, переписывает пакеты, содержащие данные, в общую шину. Каждый пакет имеет длину в 32 бит и содержит 16 бит данных и тег также длиной в 16 бит, который идентифицирует виртуальный путь пересылки данных (или дугу), ассоциированный с данными. Эти 32-битовые пакеты (или маркеры) копируются из общей шины TPS всех узловых процессоров, в которых есть процессы, запросившие ввод по этой дуге. Принимающая TPS удаляет теги и собирает сообщение. От TPS к NPS сообщения передаются через коммуникационное ОЗУ.
Рис. 9.1. Узловой процессор ЭВМ LDF 100.
Основной процессор, в котором размещены средства разработки программ, реализуется той же аппаратурой, что и узловые процессоры, но имеет память большего размера (1 Мбайт динамической памяти против 128 Кбайт статической памяти). Обе платы узлового и основного процессора работают с тактовой частотой 10 Мгц без состояний ожидания.
Маркеры, требуемые другими стойками, переписываются из общей шины FLObus платой сетевого интерфейса (FLObus Network Interface - FNI)» которая передает их соответствующей плате FNI в другой стойке в свою общую шину. Плата FNI подобна стандартному узловому процессору на общей шине. На рис. 9.2 показана конфигурация узлового процессора и платы FNI в системной стойке. Более подробно архитектура системы LDF 100 освещена в [2, 3].
Программное обеспечение
В каждом узловом процессоре могут быть размещены для выполнения нуль или более процессов. Каждый процесс получает данные (от других процессов или от устройств ввода) по одной или нескольким дугам данных и может передавать данные (другим процессам или устройствам вывода) по нулю или более (различным) дугам. Так как эти дуги данных реализованы как тегированные сообщения в общей шине, число таких дуг у каждого процесса может быть довольно велико. Хотя архитектура обеспечивает общую память для узловых процессоров, эта память используется скорее при нейт^ рализации сбоев для снижения накладных расходов, когда очень большие структуры данных должны обрабатываться несколькими процессами1 \
Дуги данных используются как очереди, и они, подобно каналам в ОС UNIX, буферизуют одно или несколько сообщений до тех пор, пока их не получит адресат. Буфер ограничен только доступной емкостью памяти (как собственным ОЗУ TPS, так и коммуникационным ОЗУ между TPS и NPS). Из одной дуги могут получать данные несколько процессов, в этом случае все процессы-получатели копируют данные из этой дуги. Однако посылать данные по одной и той же дуге несколько процессов не могут, кроме тех случаев, когда длина сообщения не превышает 16 байт и/или содержимое сообщений несущественно. Это объясняется тем, что сообщения пересылаются как маркеры по общей шине, и длинное сообщение соответствует множеству маркеров. Протокол шины таков, что гарантирует максимальное время ожидания при доступе к общей шине и поэтому чередует длинные сообщения.
TPS определяет момент получения полного сообщения по дуге данных путем подсчета числа маркеров, которые поступили с соответствующим тегом. По умолчанию, когда процесс получит по полному сообщению по каждой из своих входных дуг, эти сообщения становятся доступными в коммуникационном ОЗУ, и процесс планируется для выполнения в NPS. Когда он реально начнет выполняться, он может считывать данные из всех своих входных дуг, записывать в любую или во все выходные дуги. Конечно, данные, которые процесс записывает в выходные дуги, обычно являются некоторым преобразованием данных, считанных из его входных дуг. Если процесс не считывает данных из входной дуги во время текущего запуска на выполнение, соответствующее сообщение безвозвратно теряется.
^ак как разделяемая память во время наших эксперимен- тов не была доступна, ее применение ниже не обсуждается.
Первая стойка
Рис. 9.2. Конфигурация стоек системы LDF 100.
Программист может специфицировать для планирования процесса и другие условия, определяемые теми входными дугами,по которым поступили сообщения, или содержанием сообщений. Эти возможности не были доступны во время выполнения описываемых тестов и поэтому не рассматриваются в этой главе. Однако правило умолчания - запуск процесса на выполнение, после того, как получены полные сообщения по всем входным дугам - вполне достаточно для реализации широкого класса задач.
Хотя параллелизм не определяется явно в этой модели (как и в большинстве других), одновременное выполнение процессов (на разных узловых процессорах) является типичным. Если на один узловой процессор получено более одного процесса, то в каждый конкретный момент выполняется только один процесс (остальные ожидают, когда выполнятся условия их выбора на выполнение).
Разработка программ
Все редактирование, препроцессирование, компиляция, сборка и т.п. выполняется на основном процессоре, который работает под управлением ОС GENIX, варианте ОС UNIX. Подготовка прикладной программы к выполнению состоит из следующих шагов:
конструирование графа потока данных программы;
запись графа потока данных на языке графов данных (Data Graph Language - DGL);
обработка записи на языке DGL;
написание прикладных программ для узловых процессов;
компиляция узловых процессов;
сборка откомпилированных программ с графом данных;
загрузка и выполнение узловых процессов.
Шаги 4 и 5 могут выполняться до, во время или после выполнения шагов 2 и 3, так как все эти четыре шага являются реализацией конструкции, полученной на шаге 1. На практике ошибки, обнаруженные или исправленные в программах процессов, часто требуют исправлений в графе данных, записанных на DGL, и наоборот, так что шаги 2 и 4 сильно взаимосвязаны.
Из этих семи шагов только шаги 1, 2 и 4 выполняются программистом. Шаги 3, 5 и 6 могут обеспечиваться средствами make ОС UNIX, а шаг 7 может быть выполнен по сценарию командного языка Shell ОС UNIX .
Эти шаги описаны в деталях в [4] и [5].
Пример параллельной программы
Существует множество подходов к решению контрольной задачи. Решение, приведенное ниже, иллюстрирует все шаги разработки программы, упомянутые в предыдущем разделе.
Конструирование графа потока данных программы
Граф потока данных программы (или граф данных) определяет связи между процессами (представленными на графе кружками) и дугами данных (представленными на графе стрелками). Граф данных специфицирует все последующее конструирование программы прикладной задачи. Его создание может потребовать немало умственных усилий и даже некоторого искусства в определении того, как разбить программу на активизируемые данными процессы для того, чтобы достичь максимального увеличения скорости выполнения.
В пределе разрабатываемая программа может быть создана в виде одного процесса, но при этом теряется параллелизм в выполнении. Возможно также создать множество очень маленьких процессов, каждый из которых будет выполнять очень небольшую часть работы, таких, например, как один оператор или даже одна арифметическая операция, но при этом накладные расходы, связанные с запуском каждого процесса, составят значительную часть всей выполняемой работы. Хотя поиск оптимального разбиения работы на процессы можно вести методом проб и ошибок, структура решаемой задачи часто наводит на хорошее первое приближение.
После того как граф данных нарисован, каждый процесс и дуга помечаются буквенно-цифровым именем, которое используется в языке DGL. Полезно также пометить каждую дугу видом данных, которые будут пересылаться по этой дуге.
Конструкция рассматриваемого примера программы использовала различное количество рабочих процессов, вычисляющих свои части интеграла и пересылающих полученные частичные результаты сумирующему процессу, который их суммировал. Задания рабочим процессам определялись процессом распределения работ, как показано на рис. 9.3. •
В графе потока данных программы дуга, помеченная doo, используется для получения процессом распределения работ параметров программы с терминала. По дугам dOl и d02 назначаются работы от процесса распределения работ рабочим процессам, а по дугам d04 и d05 передаются частичные суммы к суммирующему процессу.
Рис. 9.3. Граф потока данных для примера программы.
На рисунке также показано использование дуг констант. Дуги констант - это входные дуги данных, предназначенных для пересылки определенных константных значений, специфицированых в DGL. Эти дуги не имеют имен или тегов в отличие от других дуг. Они особенно полезны для описания текущей конфигурации программы с гибкой структурой. В нашем случае эти дуги констант служат для сообщения процессу распределения работ и суммирующему процессу, о числе используемых рабочих процессов. Число рабочих процессов может быть легко модифицировано изменением в DGL.
Использование дуги данных d03 показывает некоторую тонкость данного стиля программирования .потока данных. Эта дуга не требуется непосредст-
венно для функционирования программы, но ее использование позволяет экспериментировать с изменением программы от границ вычислений до того, что может быть названо границами запуска. Здесь это было сделано для многократного запуска рабочих процессов на одном прогоне. При каждом запуске рабочий процесс выполнял лишь некоторую часть своих вычислений. При этом суммирующий процесс должен запускаться несколько раз, и он должен знать о последнем запуске, чтобы выдать на печать окончательный результат.
Казалось бы, что для этого достаточно завести в суммирующем процессе простой счетчик. Но, к сожалению, дело обстоит не так просто, так как суммирующий процесс после каждого запуска начинает выполнение сначала, причем каждый раз выполняется и раздел инициализации. В отличие от большинства циклических программ, в которых за разделом инициализации следует тело цикла, суммирующий процесс как бы целиком находится в теле цикла. Отсюда следует такое решение: инициализировать счетчик вне тела цикла, т.е. в процессе (например, распределения работ), который выполняется только один раз и может переслать значение счетчика в суммирующий процесс.
Если счетчик находится на входной дуге суммирующего процесса, на ней должно находиться новое сообщение при каждом запуске суммирующего процесса. Если таким сообщением сделать значение счетчика (или по крайней мере признак переполнения), то это будет решением проблемы. После прибавления данных с входных дуг d04 и d05 к общей сумме при каждом запуске, суммирующий процесс считывает значение флага с дуги d03 и проверяет, не получен ли уже окончательный результат.
Запись графа потока данных на языке Data Graph Language
Перевод графа потока данных в язык DGL совершается однозначным образом. Запись на DGL, соответствующая графу данных, показанному на рис. 9.3, приведена на рис. 9.4. В записи на DGL каждый процесс представлен заголовком и списками дуг импорта (входных) и дуг экспорта (выходных). Детали форматов DGL приведены в приложении G2. Наибольшей нашей трудностью при переводе графической формы графа данных в запись на языке DGL было точное определение количества слов, пересылаемых по каждой дуге, так как оно зависит от типа данных и количества пересылаемых переменных (т.е. от определения пересылаемого по дуге сообщения).
Конструкция <Т 2000> в dOO_Jnt_npass в строке 4 рис. 9.4 является примером тега. Подобные теги добавляются к каждой дуге (и процессу) распределителем тегов на следующем этапе, но можно предварительно приписать тег дуге прямым указанием, что и было сделано. Это полезно делать для данных, которые поступают по дуге, выходящей из терминала, так как значение этого тега должно быть известно во время выполнения программы. Если не использовать этот метод, пришлось бы опрашивать выход распределителя тегов при каждом запуске для определения тега, приписанного данной дуге.
Обработка записи на языке DGL
До того как программа на языке DGL может быть обработана утилитой сборки (linker), каждый процесс должен быть приписан к узловому процессору, и каждому процессу и дуге должны быть приписаны 16-битовые целые, используемые как теги (как описано в разд. 9.1). Подобное приписывание
выполняется при помощи пометки соответствующих точек графа специальными символами. Эта работа обычно выполняется автоматически программами балансировки графа (Graph Balancer - gb) и распределителя тегов (Tag Assigner - tass).
work_distributor ■ ”work_dist” <C0> <P11>
2
import {2;
word[2]dOO_int_npass <T2000>}
export {word[6] d01_lo_hi_int ;
word[6]d02_lo_hi_int ;
wordd03_done_signal }
8
workl = "worker” <C0> <P10>
10
import {word[6] dOl_lo_hi_1nt }
export {word[4j d04_sum }
13
work2 « "worker” <C0> <P9>
15
16 import {word[6] d02_lo_hi_int }
17 export {word[4] d05_sum }
18
19 summer ■ "summer” <C0> <P7>
20
import {2;
word[2]d03_done_sygnal;
word[4]d04_sum ;
word[4]d05_sum ; 100}
Рис. 9.4. Запись * программы вычисления числа Пи на языке DGL.
Во время экспериментов утилита gb не была доступна, поэтому ее работа выполнялась вручную. Состояла она в добавлении двух элементов в строку заголовка каждого процесса. Элемент вида <с#> определяет номер стойки, а элемент вида <Р#> определяет номер внутри стойки платы процессора, на котором будет выполняться процесс.
ЭВМ, на которой выполнялись эксперименты, состояла из одной стойки (стойки 0) и узловых процессоров в разъемах 0, 2, 4, 6, 7, 9, 10 и 11. Для преодоления некоторых несовершенств распределителя тегов процессы к узловым процессорам приписывались в убывающем порядке (т.е. первый процесс к узловому процессору с наибольшим номером и т.д.). Выполнение программы распределителя тегов инициируется следующей командной строкой:
tass wirelist -о wirelist.tagged
где wirelist - имя файла, содержащего программу на DGL (приведенную на рис. 9.4), a wirelist.tagged - имя файла, в который помещается результирующая программа с распределенными тегами.
Написание прикладных программ для узловых процессоров
Обзор
Каждый процесс записывается в виде основной программы на языке Фортран (как в данном примере ) или Си. Для считывания данных с дуги используется вызов процедуры floread, а для записи данных в дугу - flowrite. В аргументах указывается дуга и соответствующий буфер.
Если не пользоваться общей памятью, то вызовы процедур для обмена сообщениями являются единственной поддержкой, необходимой для правильного параллельного выполнения программы, так как точная синхронизация процессов для моделей потоков данных не требуется. Процессы могут начинать выполнение, как только будут приняты все маркеры входных данных^.
Запросы на обмен с терминалом выполняются не вызовами floread и flowrite, а другими средствами. Утилита fip, детализированная ниже, дает возможность помещать значения данных с терминала на любую дугу, и таким образом осуществляется ввод с терминала. Вывод на терминал в настоящее время производится автоматически при обращении к функции printf в программах, написанных на языке Си (с небольшой помощью утилиты f 1р). При программировании на Фортране также можно пользоваться для этих целей специальной версией функции printf.
Опыт
При написании программ возникают некоторые проблемы. Так как при записи графа потока данных мы обдумали задачу во всей полноте, у нас сложилось полное представление о роли каждого модуля. Если же проблемы возникали на этапе перевода на язык DGL, то приходилось прибегать к модификации графа потока данных.
Мы должны были помнить, что процессы - это не подпрограммы, а программы, которые начинают свое выполнение заново при каждом вызове. Это означает, что данные, помещаемые в переменные (например, суммы), вообще говоря, отсутствуют в последующих итерациях. В нашем примере это привело к проблеме, так как для суммированиня частичных сумм» вычисляемых рабочими процессами потребовался суммирующий процесс, для которого необходимо было сохранение результатов от всех итераций одного запуска. После некоторых экспериментов мы обнаружили, что размещение переменной в именованной cOMNON-области не только сохраняет значение переменной от запуска к запуску, но обеспечивает нулевое значение неинициализированных переменных целого типа к моменту первого запуска.
Более строгий подход к использования принципа потока данных состоял бы в записи предыдущего значения суммы на некоторую дугу, которая возвращалась бы в суммирующий процесс на следующем запуске процесса. Хотя такой подход и ведет к увеличению накладных расходов, но программа при этом становится независимой от особенностей используемых загрузчика и компилятора.
^ В других моделях потоков данных добавляется ограничение: процесс не может быть запущен на выполнение до тех пор, пока все данные, помещенные им в выходные дуги во время предыдущего запуска, не будут выбраны другими процессами. Это правило строгой активации процесса приводит к так называемому потоку данных, управляемому запросом (demand driven), который до некоторой степени ограничивает параллелизм. Этот вопрос будет обсуждаться в разд.9.5.
Хотя свойство автоматического обнуления переменной при инициализации в именованной COMMON-области было использовано только для переменной, в которой хранится общая сумма, это свойство можно было бы использовать и для переменной, хранящей значение флага. Это позволило бы программе в начале каждого запуска проверять значение переменной-флага и выполнять начальную последовательность команд только при нулевом значении флага с последующей заменой его на ненулевое значение. (Если бы это было известно заранее, можно было бы избавиться от специальной дуги сЮЗ, с некоторой, однако, потерей управления во время выполнения программы.)
Аннотированный листинг программы
1 с
2 С - Work Distribution
Распределитель работ
3 С - Sends work intervals to workers and flag to summer
Рассылает обрабатываемые интервалы рабочим процессам
и флаг суммирующему процессу
4 С
5REAL*4LO, HI, SIZINTRVL
6INTEGERNSEGMENTS, INTRVLS, PASS, WORKER
7INTEGER*2 FLAG, NWORKERS, TOTINTRVLS, NPASSES, SIZE
Все переменные, которые принимаются с терминала
(с помощью fip) или
с константных дуг, должны быть описаны как INTEGER*2
(см. строку 7).
8COMMON /TERMIN/ NPASSES, TOTINTRVLS
9COMMON /WORKARC/ LO, HI, INTRVLS
Переменные помещаются в cOMMON-область в строках 8 и 9 для размещения их в последовательных ячейках памяти. Это необходимо для чтения и записи по одной дуге более одного значения. В этом случае можно считать, что определены две записи, каждая в своей COMMON-области, хотя они могли бы быть определены и в одной COMMON-области с тем же эффектом.
10 CALL FLOREAD(1, NWORKERS, 1, SIZE)
11 CALL FL0READ(2, NPASSES, 2, SIZE)
Вызов подпрограммы FLOREAD в строке 10 обеспечивает чтение данных из первой входной дуги из списка (в записи на языке DGL) входных дуг этого процесса, что указывает первый параметр обращения к этой подпрограмме (1). В данном случае это константная дуга, поэтому всегда считывается значение, равное 2. Заметим, что для этой константной дуги переменная, в которую помещается считанное значение (NWORKER), должна быть определена как INTEGER*2, а число считываемых слов - третий параметр обращения - равно 1. Вызов подпрограммы FLOREAD в строке 11 производит чтение данных из второй входной дуги, определенной в DGL (d00_1nt_pass), что задается первым параметром обращения. Эта дуга не связана ни с каким процессом (согласно записи на DGL), поэтому данные на этой дуге могут появиться только из какого-либо аппаратного устройства или, как в данном случае, из терминала, выполняющего программу ftp. Так как fip может посылать только однослов-
ные (16 битовые) значения, и так как это обращение к подпрограмме считывает два слова (на что указывает третий аргумент), будет считано два значения типа INTEGER*2. Первое значение будет помещено в переменную NPASSES, а второе - в переменную, размещенную в памяти сразу за переменной NPASSES. В данном случае, так как переменная TOTINTRVLS следует за переменной NPASSES в блоке COMMON, гарантируется их последовательное размещение в памяти и помещение второго значения, посланного ftp по этой дуге, именно в эту переменную (TOTINTRVLS).
12 CALL PRINTF(" INTRVLS - Xd npasses - Xd nworkers - Xd\n", 131TOTINTRVLS, NPASSES, NWORKERS)
Обращением к подпрограмме PRINTF в строках 12 - 13 производится вывод на терминал, но только в случае, если на терминале выполняется программа ftp, которая была инициирована с опцией -г. Для тех, кто не знаком с функцией printf языка программирования Си, поясним, что при ее выполнении производится печать первого параметра обращения к ней (строки символов), при этом выполняются следующие подстановки:
■ Замещаемые последовательности (которые начинаются с символа %) заменяются значением следующего аргумента из списка аргументов. При этом буква, следующая за %, определяет тип значения подстановки. В данном случае, Xd определяет десятичный формат значения ' типа INTEGER*2.
• Управляющие последовательности (представляемые обратным слэшем (\) и следующим за ним символом) заменяются специальным (обычно неизображаемым) управляющим символом. В данном случае \п замещается управляющим символом “новая строка" (что определяет при выводе перевод на следующую строку и возврат каретки, то есть печать следующих символов начнется с начала новой строки).
14
NSEGMENTS « NPASSES * NWORKERS
15
INTRVLS = TOTINTRVLS
16
SIZINTRVLS - 1.0 / NSEGMENTS
17
HI « 0
18
DO 100 PASS « 1, NPASSES
19
DO 90 WORKER = 1, NWORKERS
20
L0 = HI
21
HI = HI + SIZINTRVLS
22
CALL FLOWRITE(WORKER, L0, 6)
23
90CONTINUE
Вызов FLOWRITE в строке 22 помещает 6 слов (12 байт), начиная с переменной L0, в выходную дугу, определенную переменной WORKER. L0 занимает только 4 байт, a HI (4 байт) и SIZINTRVL (4 байт) следуют за ней, так как они определены в одном cOMMON-блоке. Таким образом, посылается предписание на работу (work order) рабочим процессам. Заметим, что переменная WORKER должна быть определена как переменная типа INTEGER**, так как она используется в качестве первого аргумента обращения к FLOWRITE (или к FLOREAD).
24FLAG « NPASSES - PASS
25CALL FLOWRIТЕ(NWORKERS + 1, FLAG, 1)
26100 CONTINUE
27 END
Вызов FLOWRITE в строке 25 записывает ненулевое значение в дугу d03 при каждой итерации, кроме последней, когда записывается нулевое значение. Суммирующий процесс считывает эти значения с этой дуги и определяет, когда им получены последние частичные суммы (когда полученное значение нулевое).
28
С
29
С - WORKER (Code identical for each worker node) РАБОЧИЙ ПРОЦЕСС (Программа, идентичная для каждого узла исполнителя)
30
С - Reads work assignment from work distributor, Читает рабочее предписание от распределителя работ
31
С -writes partial sum to summer
записывает частичную сумму для суммирующего процесса
32
С
33
REALLO, HI
34
REAL*8SUM, WIDTH, FARG, SIZE
35
INTEGER INTRVLS, SLICE
36
COMMON /INPATH/ LO, HI, INTRVLS
37
CALL FLOREAD(1, LO, 6, SIZE)
38
WIDTH « (HI - LO) / INTRVLS
39
SUM «0.0
30
DO 100 SLICE « 1, INTRVLS
41
FARG « LO + (SLICE - .5) * WIDTH
42
SUM « SUM + WIDTH * (4.0 / (1.0 + FARG * FARG))
43
100 CONTINUE
44
CALL FLOWRITE(1, SUM, 4)
45
END
Программа рабочий процесс, собственно, и выполняет вычисления значения интеграла (строки 33 - 45). В строке 37 обращением к подпрограмме FLOREAD считывается предписание на работу, как оно было сформировано в строке 22. Затем процесс вычисляет частичную сумму и пересылает результат суммирующему процессу (в строке 44). Заметим, что, хотя в первом параметре обращения к обеим подпрограммам FLOREAD и FLOWRITE указывается первая дуга, при обращении к подпрограмме FLOREAD идентифицируется первая дуга из списка входных дуг (dOl или d02 в зависимости от того, какой узел работает), а при обращении к подпрограмме FLOWRITE - первая дуга из списка выходных дуг (d04 или d05).
46 С
С - Summer (as in, one that sums) Сумматор (в смысле тот, кто суммирует)
С - Reads partial sum from each worker, Считывает частичные суммы каждого исполнителя
С - adds them into a total sum
прибавляет их к общей сумме
50 С
51
INTEGERSIZE
52
INTEGERS FLAG, ITER, NPASSES
53
REALMTEMPSUM, TOTALSUM
54
COMMON /STATIC/ TOTALSUM
i
Переменная TOTALSUM определена в cOMMON-блоке, поэтому:।
она сохраняет свое значение после каждой итерации и
имеет нулевое значение при начале первой итерации. Имя cOMMON-бло- ка несущественно.
55 CALL FLOREAD(1, TEMPSUM, 4, SIZE)
56 TOTALSUM - TOTALSUM + TENPSUM
57 CALL FL0READ(3, TEMPSUM, 4, SIZE)
58 TOTALSUM = TOTALSUM + TENPSUM
В строках 55 - 58 собираются частичные суммы от всех рабочих процессов и суммируются в переменной TOTALSUM. Заметим, что при изменении программы на языке DGL, связанном с добавлением рабочих процессов, эту. подпрограмму придется модифицировать.
59 CALL FL0READ(2, FLAG, 1, SIZE)
60 IF (FLAG .EQ. 0) THEN
61CALL PRINTF("pi is %F\\n", TOTALSUM)
62CALL PRINTF("diff is %F\\nw,
63& TOTALSUM-3.14159265358979323800)
64TOTALSUM « 0
65END IF
66END
В строке 59 при помощи вызова FLOREAD считывается флаг, чтобы определить, не последняя ли это итерация, и, если последняя, значение интеграла и разность между ним и ранее записанным числом Пи выводятся на терминал. Заметим, что спецификация формата в этом случае %F, что означает вывод значения числа с плавающей точкой с двойной точностью.
Компиляция узловых процессов
Процессы компилируются стандартной командой ОС UNIX f77: вызов компилятора. У нас было несколько проблем, связанных со стандартным Фортраном: строки, длинее 72 символов, и некоторые неподходящие отступы в строках. Наиболее существенной проблемой оказалась проблема записи функции, занимающей только одну строку, так как компилятор ее отвергал, из-за чего пришлось перенести вычисления в выражения. Ошибка скорее всего была связана с тем, что определение фукции или ее формальных параметров включалось в предложения определения типа.
Каждая программа собиралась при помощи стандартного загрузчика, вызываемого командой следующего формата:
Id file.о -Т 800 /u/software/Ub/flol1b.а -о file
(Опция т 800 устанавливает базовый адрес, равный 800, и больше ни для чего не требуется.)
Сборка откомпилированных программ с графом данных
Вся программа целиком собирается при помощи утилиты сборщик (11пкег)1>. Она получает как параметры программу на языке Data Graph Language с расставленными тегами и объектные модули и производит один объединенный файл, содержащий все нужные программные коды, готовые к загрузке в узловые процессоры. В командной строке сообщается только имя файла, содержащего тегированный граф данных (произведенный программой tass), так как имена объектных модулей уже присутствуют в этом файле. Утилита сборщик вызывается командой формата:
linker wlrel1st.tagged -о wirelist.code
После ее выполнения файл wlrel 1st.code содержит всю информацию (программные коды и описание сети), необходимую для загрузки и выполнения программы.
Загрузка и выполнение программы узловыми процессорами
Фактическое выполнение программы обеспечивается интерфейсным процессором FLObus (Flobus Interface Processor), или fip, который загружает программу в узловые процессоры и управляет вводом-выводом всех узлов. В сущности FLObus является периферийным устройством основного процессора, и fip взаимодействует с этим устройством. Существует драйвер ОС UNIX, взаимодействующий с устройством FLObus. Три формы использования команды fip следующие:
f1p -g wlrel1st.code
Этой командой файл wlrel 1st.code (наш выходной файл после выполнения linker) загружается в узловые процессоры.
f1p -г -s 2000 10000 -s 2000 10
Опция -г заставляет fip “слушать “ FLObus для вывода на экран результата выполнения функции prlntf. Опция -s определяет, что следующие два аргумента воспринимаются как тег дуги (2000 в нашем случае) и данные, которые нужно отправить по этой дуге (10000 и 10). Программа отправляет эти данные по дуге (или, более точно, снабжает эти данные тегом и помещает их в FLObus). Можно поместить более одного тега и элемента данных, предварив их -s опцией, но все они должны помещаться в одной командной строке (или в последовательных строках, начинающихся с символа продолжения командной строки shell). Данные
^то название сбивает с толку - программа уже связана (в традиционном смысле разрешения внешних ссылок) утилитой Id, которую все путают с утилитой linker!
должны быть в формате десятичного числа, которое передается как беззнаковое 16-ти битовое целое.
ftp -1
Эта команда прерывает выполнение программ в узловых процессорах и инициализирует их после ненормального завершения. Программа должна быть снова загружена для повторного запуска после выполнения этой команды.
Производительность
Когда мы производили измерения, программы измерения временных характеристик узловых процессоров, не были доступны, поэтому оценки времени выполнения наших программ были получены усреднением разности между абсолютным временем начала и окончания работы программы по большому числу итераций. Вот наши результаты:
Intervals
PassesTime
10000
10000
10000
10000
10000
10098sec
1010sec
5050sec
7069sec
9088sec
В теле основного цикла программы выполняется восемь операций над числами с плавающей точкой: три сложения, одно вычитание, три умножения и одно деление. Отсюда при последнем измерении получается следующая скорость вычислений:
Loops/WorkerFloating ops
90 passes * 10000 * 2 Workers * 8
PassLoop
- 0.164 Mflops
88 sec
или в среднем 0.082 Mflops/плата.
Ловушки и проблемы
Разработка программ
Хотя автор и имел некоторый опыт в программировании потоков данных, тонкие отличия этой модели потоков данных от остальных сделали работу по построению графа потока данных весьма изощренной и потребовали существенно большего времени, чем ожидалось. Несмотря на то, что базовые принципы потоков данных очевидны, методика эффективного применения этих принципов еще только развивается. Поэтому необходимо экспериментировать (хотя временами это головоломка) с разными системами потоков данных, чтобы определить наиболее эффективные структуры программ, которые можно строить для этих систем.
Первоначально мы хотели, чтобы распределитель работ (work distributor) строил или устанавливал список работ, которые нужно выполнить (work to do list), а рабочие процессы обращались к этому общему списку за очередной порцией работы после окончания предыдущей. Вся работа не должна делиться пополам заранее: один из рабочих процессов может выполнить львиную долю работы, если он будет работать быстрее. Хотя такая методика (иногда ее называют методикой “голодных щенков“) в нашем случае, вероятно, не эффективна, так как оба рабочих процесса будут завершаться практически одновременно, есть много задач (например, адаптивная квадратура, которая могла быть использована для решения нашей задачи), в которых эта методика будет эффективна.
Нашей первой мыслью было поместить список работ, которые нужно выполнить, в некоторую общую дугу, откуда рабочий процесс исполнитель выбирал бы себе задание. Такой подход был бы хорош для других моделей потоков данных, но модель потоков данных LDF 100 (по крайней мере, существовавшая во время наших экспериментов) не обеспечивает разрешение конфликтов при выборе значений данных из дуги. Скорее наоборот, каждый процесс получает свою копию данных с дуги.
И мы решили, пусть каждый рабочий процесс получает свое задание по своей дуге, а запрос на следующую порцию работы после выполнения предыдущей посылает по отдельной дуге запроса (demand arc) распределителю работ. Отсутствие (опять подчеркнем, во время наших экспериментов) возможности запуска процесса по принципу “или“ заставило нас отказаться от двух отдельных дуг запросов, так как распределитель работ должен активизироваться по запросу от любого исполнителя.
Возможным решением было бы слить дуги запросов к распределителю работ в одну дугу. Напомним, что это возможно лишь тогда, когда длина сообщения не превышает одного слова. В нашем случае это условие выполняется поскольку требуется переслать лишь номер процесса. При этом важно, чтобы метки запроса каждого процесса исполнителя находились на этой дуге в момент начала работы, с тем чтобы обеспечить все процессы работой с самого начала. Распределитель работ считывал бы запрос с этой дуги при каждом запуске и определял бы, по какой дуге послать задание на следующую порцию вычислений.
Понятие запроса очень важно. Без дуг запросов управление потоком в системе в строгом смысле не достигается, так как остается возможность, что данные будут поступать в процесс быстрее, чем он может их обрабатывать. Управление потоком, упомянутое выше, может быть названо управлением потоком любым потребителем, при котором производящий процесс запускается, как только данные понадобятся любому процессу-потребителю. Управление потоком всеми потребителями, при котором производящий процесс запускается только тогда, когда данные нужны всем потребителям, можно моделировать или раздельными дугами запросов от каждого потребителя, или одной общей дугой запросов, но при этом из этой дуги считывается сообщение в п раз длинее, чем отправляемое п разными потребителями. Этот последний способ привлекателен тем, что позволяет использовать один тег всеми потребителями, отправляющими запрос. В любом из этих двух способов в процесс-производитель не нужно встраивать никаких дополнительных действий по анализу запроса в отличие от управления потоком любым потребителем. Дуги запросов в системе LDF 100 обсуждаются более полно в [6].
Запись программ процессов
Сбивает с толку длина сообщений. В программе на DGL длина сообщений должна выражаться в 16 битовых словах, которые с самого начала выступают в качестве единиц отсчета. Длина сообщений также записывается в параметрах обращений к подпрограммам FLOREAD и FLOWRITE. А в программах длина переменных, как правило, определяется в байтах.
Вдобавок, все сообщения, принимаемые с терминала или константной дуги, являются 16 битовыми целыми. А это не является принятой по умолчанию длиной целого ни в Фортране, ни в Си.
Заключение
Система Loral LDF 100 предлагает изобретательную технику для работы с относительно новым методом программирования потоков данных. Как показал наш опыт, этот метод заставляет программиста принять поэтапный подход к программированию, что, впрочем, может действительно повысить качество программ. С другой стороны, этот метод избавляет программиста от сложностей синхронизации, присущих большинству других моделей параллелизма, и позволяет сосредоточиться на вычислениях и алгоритмах, действительно центральных в решении прикладной задачи.
Хотя скорость именно этой машины не слишком велика по сравнению с другими опробованными нами машинами, свойства архитектуры кажутся подходящими и для более быстрых параллельных процессоров.
Благодарности
Нам бы хотелось выразить свою признательность Джону Ван Зандту (Loral Instrumentation) за предоставленную возможность поработать на LDF- 100 и Яну Каплану (также из Loral) за его помощь в выполнении наших экспериментов и просмотр этой главы.
Литература
] Robert G. Babb II, “Parallel Processing with Large-Grain Data Flow Techniques", Computer. vol.17, no.7, July 1984, pp. 55 - 61.
John Van Zandt, “CPI: Beyond the von Neumann Bottleneck",Defens Electronics, January 1986.
Ian L Kaplan, “LDF 100: A Urge grain dataflow parallel processor", to appear in SIG ARCH Computer Archilecture News, 1987.
Loral Instrumentation, “LDF 100 Getting Started Manual", Document no. 49055665(A), 1986.
Ian L. Kaplan, “Programming the Loral LDF 100 dataflow machine", to appear in SIGPLAN Notices, 1987.
Ian L. Kaplan, “Demand-Driven Data Flow", Loral Instrumentation Report, March 3,1986.
Sequent Balance Series
Лайз Сторк
Машина / модель:
Sequent Balance 21000
Местонахождение:
Sequent Computer Systems, Inc. Бивертон, Орегон
Процессоры:
30
16 Мбайт физической памяти,
8 Кбайт кэш-памяти/процессор, 16 Мбайт виртуального адресного пространства / процесс
Операционная система:
DYNIX версия 2.1
Языки программирования:
Фортран 77
Компиляторы:
NS32000 Fortran 77 V2.6 Parallel Compiler
ЭВМ серии Sequent Balance являются многопроцессорными вычислительными машинами с общей памятью. Они работают под управлением операционной системы DYNIX (версией UNIX 4.2BSD). В многопользовательской среде сосуществуют параллельные и последовательные прикладные задачи. ЭВМ Balance 21000 составляют от 4 до 30 универсальных, симметричных, 32-раз- рядных микропроцессоровDYNIX обеспечивает динамически сбалансированную нагрузку при помощи общесистемной очереди готовых к выполнению процессов. Даже если не прилагать никаких усилий для распараллеливания прикладной задачи, все процессы ОС DYNIX могут потенциально выполняться параллельно. Это называется скрытым мультипрограммированием (transparent multiprogramming). Детали стратегии распараллеливания на уровне процессов DYNIX, можно найти в [1 ].
Некоторые утилиты ОС UNIX, такие как make, модифицированы для этой машины, с целью автоматизации распараллеливания процессов. Части руководства по системе DYNIX, касающиеся различных параллельных компиляторов, утилит и системных вызовов, приведены в приложении НЗ.
Кроме того, пользователи могут описывать явный параллелизм, при котором процессы DYNIX разделяют память. Доступны многозадачные библиотеки, полезные при написании параллельных прикладных программ.
Аппаратура
ЭВМ серии Balance содержит от 2 до 30 32-разрядных универсальных микропроцессоров, по два на плате (см. рис. 10.1). В ЭВМ может быть до 28 Мбайт общесистемной памяти. Блоки памяти динамически распределяются между процессами или группами процессов. Эта память может быть или ло-
Другая модель ЭВМ, Balance 8000, поддерживает от 2 до 12 процессоров.
калькой памятью процесса, или общей для группы процессов. Все процессоры, модули памяти и контроллеры устройств ввода/вывода подключены к высокоскоростной общей шине SB8OOO. Эта шина поддерживает конвейерные операции ввода/вывода и обращения памяти и может выполнять обмены данными со скоростью 26.7 Мбайт/с.
Мультиплексор Пользовательские НМЛ 1/2” Диск
16 линий устройства396 Мбайт
Рис. 10.1. Схема ЭВМ системы Balance Series.
В качестве ЦПУ используются микропроцессоры National Semiconductor 32032. Все ЦПУ работают независимо друг от друга и могут выполнять как прикладные, так и системные программы. Все ЦПУ используют одну разделяемую копию ядра ОС DYNIX. Взаимодействие с другими процессорами и подсистемами обеспечивается микросхемами системной связи и управления прерываниями - System Link and Interrupt Controller (SUC) - по специальной SUC шине. В процессоре есть 8-килобайт кэш-память, в которой нахо-
дятся копии наиболее часто используемых блоков основной памяти. У каждого процессора есть и устройство выполнения операций с числами с плавающей запятой NS32081, и устройство управления памятью NS32082, которое поддерживает виртуальное адресное пространство в 16 Мбайт для каждого процесса.
Атомарная память блокировки (ALM - Atomic Lock Memory)
Для обеспечения блокировки при работе с общими структурами данных пользователю предоставляются аппаратные замки (hard locks). Набор аппаратных замков состоит из 16 к замков. В ЭВМ могут быть установлены один или несколько таких наборов, каждый при помощи своей адаптерной платы MULTIBUS. Каждый замок поддерживает выполнение операции “проверить и записать" (синхронная запись). В ALM он представлен 32-битовым двойным словом. Младший бит показывает состояние замка: заблокирован (1) или нет (0). Чтение замка возвращает значение этого бита и устанавливает его в 1, т. е. в состояние “заблокирован". Запись 0 в замок снимает блокировку. Такие замки поддерживают множество методов синхронизации, включая ожидание освобождения, семафоры и барьеры. Например, процесс может ожидать освобождения какого-либо ресурса, просто считывая значение замка до тех пор, пока захвативший этот ресурс процесс не освободит его (т. е., пока чтение не вернет значение 0). Процесс, ожидавший освобождения ресурса, может немедленно продолжаться, так как чтение замка автоматически блокирует замок заново.
Дополнительную информацию о параллельной архитектуре ЭВМ серии Sequent Balance можно найти в [2].
Программное обеспечение
Операционная система DYNIX
DYNIX выбирает на исполнение процессы из общесистемной очереди готовых к выполнению процессов. Процесс выполняется на процессоре без прерывания до тех пор, пока он не будет заблокирован (например, до окончания операции ввода/вывода), закончен или приостановлен другим процессом с большим или равным приоритетом. В любом случае пользовательский процесс с наименьшим приоритетом выставляет прерывание с требованием на обслуживание. Таким образом, параллельная прикладная задача выполняется лучше всего, когда она имеет более высокий приоритет по сравнению с другими пользовательскими процессами, и число процессов, из которых она состоит, меньше числа доступных процессоров. Может быть также выгодно откладывать процессы, ожидающие освобождения ресурса, на некоторое определенное время.
Разделяемая память
DYNIX позволяет двум или более процессам разделять общую область системной памяти. С точки зрения языков программирования прикладных задач использование общей памяти ничем не отличается от обычной памяти программы. В Фортран-программах разделяемые переменные статически размещаются в общих областях (COMMON) или в разделяемом стеке. В Си- программах разделяемые переменные могут размещаться как статически, так
и динамически. Эта память может использоваться для межпроцессного взаимодействия (между связанными и несвязанными процессами), что более эффективно, чем использование системных вызовов ОС UNIX для обмена данными между процессами. Реализуется разделяемая память как часть виртуального адресного пространства процесса (при использовании системного вызова DYNIX шшарО).
Новые процессы создаются при помощи системного вызова fork(), накладные расходы на работу которого около 55 мс. Новый порожденный процесс является копией текущего родительского процесса, и все унаследованные данные, содержимое регистров, состояние счетчика команд, доступ к разделяемой памяти и файлам обеспечиваются родительским процессом. В типичной прикладной задаче родительский процесс получает область разделяемой памяти и один или несколько замков, а затем порождает нескольких дочерних для разделения работы. Обычно ни один из порожденных процессов не заканчивает работу, пока программа не завершится. Однако можно возвратить процессор и перезапросить его позже, на что потребуется дополнительно несколько миллисекунд.
Библиотека подпрограмм параллельного программирования DYNIX дает возможность создавать, синхронизировать и завершать параллельные процессы, обеспечивает межпроцессное вазаимодействие и взаимное исключение программ на Си, Фортране и Паскале. Для интерпретации директив компилятора по построению циклов в Фортран-программах можно использовать препроцессор. Этот препроцессор автоматически вставляет код, который создает и синхронизирует процессы, выполняющие тело цикла параллельно [3].
Синхронизация
Замки могут быть использованы для обеспечения исключительного доступа к структурам данных в общей памяти. Накладные расходы на это не превышают нескольких микросекунд благодаря непосредственной аппаратной поддержке. Атомарная память блокировки (ALM) размещается (при помощи штарО) в виртуальном адресном пространстве процесса. Так как прямой доступ пользовательских процессов к аппаратным замкам может перегрузить общую шину, более предпочтительно для процессов встроить (spin on) затененный замок в кэш-память. Процессор, в кэш-память которого встроен такой замок, вообще не нагружает общую шину. При изменении состояния замка, процессоры, связанные с этим замком теряют доступ к своей кэш-па- мяти, и стремятся обновить состояние замка. При помощи одного аппаратного замка можно организовать любое количество программируемых замков. Такие замки называется мультиплексными.
При потенциально долгом ожидании освобождения ресурса процессор может быть возвращен с помощью системного вызова (такого как sigpauseO), если необходимые для этого накладные расходы приемлемы для прикладной задачи.
Разработка программы
В документации систем параллельного программирования Sequent Balance описаны два базовых типа прикладных программ, отличающиеся методами анализа и использования отношения порядка. Проявление отношения порядка - это точка в программе, начиная с которой вычисления зависят от резуль-
татов работы предыдущей задачи, и их нельзя продолжать до тех пор, пока предыдущая задача не закончится.
Функциональное разбиение - это подход, при котором множество процессов выполняют различные операции над одним и тем же множеством данных. Одной из моделей этого подхода является ветвление объединения (fork- join), при котором процессы ветвятся, выполняют каждый свои действия и затем встречаются у “барьера". Это вполне корректная модель при условии, что действия относительно независимы. Другой моделью является конвейер, в котором отношение порядка реализуется следующим образом: процесс выполняет вычисления над подмножеством, записывает результат в общую память и затем извещает последующий процесс о том, что результаты доступны для дальнейшей обработки. Затем он повторяет те же действия над следующим подмножеством данных. Процесс завершается или переходит в состояние ожидания, когда не окажется больше работы для выполнения.
Разбиение данных - это подход, при котором множество идентичных процессов выполняет одни и те же операции над различными данными (например, цикл, в котором производится обработка массивов). Это еще называется однофазным многозадачным режимом. Разбиение данных позволяет создавать автоматически сбалансированную загрузку.
Вот простой пример, в котором можно произвести разбиение данных:
DO 10 I-1,N
A(I) - B(I) * C(I)
10 CONTINUE
Различные итерации цикла могут выполняться в произвольном порядке, который не влияет на окончательный результат, и поэтому вычисления могут производится параллельно. Это - пример полностью независимого цикла, и он является подходящим для разбиения данных и распараллеливания вычислений. Никакие данные не передаются от одной итерации к следующей. К сожалению, не все циклы таковы. Некоторые циклы имеют внутренние зависимости, которые требуют некоторых форм синхронизации, обеспечивающих правильный порядок вычислений. В других циклах могут оказаться зависимости, которые возникают при определенных реализациях. В таких случаях некоторые методы, такие как изменение порядка операторов тела цикла или переименования, могут устранить зависимость.
При распараллеливании программы требуется определенный анализ зависимостей с целью обнаружения возможных проблем. В руководстве по параллельному программированию Sequent Balance имеются указания, облегчающие этот процесс. Кроме того, можно с помощью утилиты gprof проанализировать программу и обнаружить циклы, которые следует проанализировать для возможного распараллеливания. В результате выполнения утилиты gprof строится граф вызовов процедур и профиль временных затрат отдельных частей программы. Если независимые циклы не обнаруживаются, то можно попытаться произвести функциональное разбиение.
Пример анализа зависимостей и разбиения данных для программы вычисления числа Пи приведен в разд. 10.3.
Компиляция
Для компиляции параллельной Фортран-программы (pi.f) в ОС DYNIX используется команда:
X fortran -е -u -g -mp pi.f
Флаг -е определяет совместимость последовательности вызовов с языком Си. Опция -шр определяет применение параллельного препроцессора. Опция - g позволяет последующее использование параллельного отладчика pdbx, описанного ниже в подразделе “Отладка**. Флаг -и можно использовать для сохранения копии кода, произведенного препроцессором.
Производительность
Время выполнения программы можно узнать при помощи системной подпрограммы /Ып/time. Однако, время еще тратится и на ввод/вывод.
Системные вызовы gettimeofdayO и getrusageQ доступны из программы и являются основой Си-подпрограмм start-timer, stop-timer и fprt_timer, которые разработаны для системы. Мы использовали в программе вычисления числа Пи все эти три подпрограммы. Они измеряют пользовательское, системное и абсолютное время.
Отладка
В системе Sequent есть интерактивный параллельный отладчик pdbx, созданный на базе отладчика dbx операционной системы UNIX 4.2BSD. Pdbx полностью идентичен dbx, когда отлаживается последовательная программа. Но pdbx можно использовать и для отладки нескольких (возможно независимых) процессов. С помощью pdbx процессы можно независимо запустить на выполнение, остановить, оттрассировать, прервать и проверить их состояние. Отладчик приписывает каждому процессу номер, который можно включить в список при помощи команды ps и применять в других командах отладчика для спецификации процесса. Определяемые события включают forks, execs и exits. Команды отладчика, которые запускают процесс на выполнение, имеют дополнительный параметр & для определения фонового выполнения процесса. Возможен многооконный режим, при котором разные процессы видны на разных экранах. Дополнительную информацию об отладчике можно найти в [5].
Пример параллельной программы
1с- number of processes; status returned с - число процессов; возвращаемый статус
integer*4 nprocs, status
3c- number of rectangles; loop index с - число прямоугольников; индекс цикла
integer*4 nrecs, i
5c- integration variables
с - объединяющие переменные
double precision h, x
c - pi as found by the program с - пи, вычисляемое программой
double precision tpi
c - pi tO 16 decimal places, for comparison с - пи с точностью до 16 знаков, для сравнения
double precision pi
pi « 3.1415926535897932d0
В строках 1-10 объявляются все переменные, используемые в программе. Пользователь вводит nrecs (число прямоугольников) и nprocs (число процессов) при каждом запуске задачи. Переменная status используется для хранения значения, возвращаемого библиотечной подпрограммой, которая фактически устанавливает число процессов. Переменная h - ширина прямоугольника, ах- средняя точка интервала (основание прямоугольника). Программа вычисляет приближенное значение числа Пи в переменной tpi и сравнивает его со значением Пи, которое инициализируется первыми 16 десятичными знаками точного значения числа Пи в переменной pi в строке 11.
10 write(*,*) 'Number of rectangles: '
read(*,*) nrecs
if (nrecs.le.O) goto 30
write(*,*) 'Numberofprocs: '
read(*,*) nprocs
if (nprocs.le.O)goto30
call start_t1mer
status ■ m_set_procs(nprocs)
if (status.ne.0) goto 20
В строках 12-22 уточняется задача. Считываются определенные пользователем количества прямоугольников и процессов, и, если они оказываются равными нулю, задача оканчивается. Вызов процедуры start_timer в строке 18 отмечает время начала вычислений, в которые включаются все действия, кроме ввода/вывода. Эта процедура и другие процедуры измерения времени, использованные ниже, поставляются Sequent. Библиотечная процедура i_setj>rocs позволяет программе установить число процессов, которые будут выполняться параллельно при каждом последующем вызове m_fork.
Вызов m_fork генерируется препроцессором автоматически при обработке директивы компилятора c$doacross (см. строку 23 в следующем фрагменте программы). Возвращаемое значение (status) равно нулю, если вызов ™_set_procs выполнился успешно. Программа пропускает секцию вычислений, если возвращенное значение не равно нулю. Это может произойти в случае, если nprocs больше предустановленного системой лимита (числа работающих процессоров минус один) или если запущенные ранее дочерние процессы еще существуют. По умолчанию (без вызова m_set_nprocs) число процессов равно половине числа работающих процессоров.
h ■ l.dO / nrecs
tpi ■ O.OdO
c$doacross share(h), local(x), reduction(tpi)
do 100 i ■ 1, nrecs
x « (i-0.5d0) * h
tpi « tpi + 4d0 / (IdO + x * x)
100 continue
tpi ■ h * tpi
В строках 21-22 инициализируется ширина прямоугольника и приближенное значение числа Пи для последующих вычислений в строках 23-28. Директива компилятора c$doacross интерпретируется препроцессором, который автоматически генерирует код для создания и синхронизации процессов, вы-
И 185
полняющих цикл параллельно. Число процессов определено предыдущим вызовом m_set_nprocs. Информация для синхронизации извлекается из классификаторов переменных в предложении c$doacross.
Эти классификаторы появляются в результате анализа зависимостей, выполненного программистом. Индекс цикла (1) не анализируется, равно как и другие переменные, присутствующие только в управляющем предложении цикла (nrecs). Определены следующие классификаторы переменных:
Разделяемые (shared) переменные (например, h) - это скалярные переменные, которые только считываются, но никогда не записываются, или массивы, в которых каждый элемент используется только в одной итерации цикла. Таким образом, они могут быть помещены (препроцессором) в общей памяти для использования процессами, выполняющими цикл, без синхронизации.
Локальные переменные (например, х) - это скалярные переменные, которые инициализируются в каждой итерации цикла до их использования. Каждый процесс может завести собственную локальную копию такой переменной.
Редукционные переменные (например, tpi) - это переменные, используемые циклом для накопления результатов всех итераций. Для того чтобы переменная была определена как редукционная, необходимо, чтобы она встречалась только в предложениях вида:
var - var op expr где var - редукционная переменная, op - одна из операций:
+, -, ♦, /, and, or, exclusive or a expr - выражение, в которое не входят редукционные переменные. Все такие предложения для конкретной редукционной переменной должны использовать одну и ту же операцию. Использование переменных этого типа производится так, чтобы обеспечить обновление значения сначала локальной копии этой переменной, а затем общей копии, защищенной замком. Это ограничивает чрезмерную конкуренцию за замок.
Препроцессор отделяет итерации цикла друг от друга, интерпретирует классификаторы переменных и соответственно использует их, создает подпрограмму, содержащую модифицированное тело цикла, и создает процессы, которые выполняют эту подпрограмму параллельно. Код, генерируемый препроцессором, можно найти в приложении Н2. В этом коде все необходимые синхронизации сгенерированы автоматически из предложения c$doacross. В не- = которых прикладных задачах встречаются циклы, для описания которых требуются дополнительные директивы компилятора и/или вызовы библиотечных процедур, вставленные пользователем для правильной синхронизации (см. ; приложения Н4 и Н5).
29callm_kill_procs
3020call stop^timer
31If (status.ne.0) then
32write(*,*) ' Error setting number of processes'
33else
34call fptr_timer(nprocs,nprocs,nprocs)
35
write(*,500) tpi
36
500
formatC Ph \fl8.16)
37
wr1te(*,600) pi - tpi
38
600
formatf Error: ’ ,fl8,16)
39
endif
40
goto 10
41
30
stop
42
end
Вызов m_k1llj>rocs в строке 29 завершает дочерние процессы, созданные c$doacross. Чтобы уменьшить накладные расходы, можно также использовать ■_parkj>rocs для приостановки процессов и m_rele_procs для повторной активизации их.
В строках 30 - 39 печатается результат данного выполнения программы, а затем программа возвращается на начало для следующих вычислений.
Производительность
Тест выполнялся при отсутствии других пользователей системы. Препроцессор разделял итерации цикла между процессами, предоставляя каждому процессу выполнять nrecs/nprocs итераций при помощи цикла, управляющее предложение которого:
do 10 1 ■ myid,nrecs,nprocs
где my1d - номер, идентифицирующий процесс (нумерация начинается с 0), возвращаемый библиотечной подпрограммой m_get_myid. Это подходит для нашего цикла, так как каждая итерация выполняет один и тот же объем вычислений. Таким образом, нагрузка хорошо сбалансирована. Единственное обновление общей переменной tpi выполняется после того, как каждый процесс выполнит все итерации своего цикла. Для 107 прямоугольников (соответственно 60 миллионов операций с плавающей запятой для выполнения всей программы) наши измерения дали следующие результаты:
processors
1
29
seconds
794.79
27.30
Mflops
.081
2.20
mf lops/proc
.081
.070
(Это пользовательское время, но абсолютное время в сущности такое же).
Рис. 10.2 и 10.3 показывают результаты ускорения работы программы вычисления числа Пи для 107 прямоугольников при увеличении числа процессоров от 1 до 29. Ускорение S(n) вычислялось делением базового (последовательного) времени счета на Т(п), где Т(п) - время счета распараллеленной программы на п процессорах. Рис. 10.2 показывает ускорение пользовательского и абсолютного времен при базовом времени Т(1). Рис. 10.3 показывает ускорение пользовательского и абсолютного времен работы программы при базовом времени T(seq), где T(seq) - время работы последовательной версии программы вычисления числа Пи, полученной из исходной программы удалением директивы компилятора c$doacross и вызовов подпрограмм i_set_procs и mjcllljprocs* п*
Число процесоров
Рис. 10.2. Кривая ускорения работы программы при Т(1).
Число процессоров
Рис. 10.3. Кривая ускорения работы программы при T(seg).
Ловушки и проблемы
Программа вычисления числа Пи, описанная в разделе 10.3, легко реализуется. Наибольшим препятствием при реализации было стремление сделать ее более сложной, чем она есть на самом деле. Директива компилятора c$doacross является единственным новым предложением, действительно требующимся для распараллеливания программы. И совсем немного других предложений необходимо для того, чтобы позволить пользователю управлять числом процессов. Все остальное делает препроцессор. Однако именно препроцессор не разрешает употребление вызова функции в строке 26 для уменьшения переменного выражения.
Анализ зависимостей в нашем примере очень несложен, и документация системы Sequent очень хороша для данного случая. Полный анализ переменных цикла с помощью схемы классификации переменных может оказать существенную помощь в распараллеливании и поможет избежать таких ошибок, как неправильная синхронизация. Однако пользователь, не знакомый с рассматриваемыми концепциями распараллеливания, должен затратить некоторое время на их освоение. Кроме того, так как доступные в настоящее время вспомогательные средства не производят анализ автоматически, для анализа больших и сложных циклов может потребоваться очень большое время.
Знакомство со средой операционной системы UNIX делает разработку программ приятной. Документация по системе Sequent вообще-то хорошая, с примерами программ, но ни одна документация не бывает идеальной, и большее число примеров было бы только полезней.
Таким образом, для программы вычисления числа Пи нам не потребовалось вручную создавать процессы, синхронизировать их работу, завершать их, распределять и явно использовать общую память, что потребуется в более сложных прикладных параллельных программах. Перечислим некоторые проблемы, с которыми мы столкнулись:
зависание из-за того, что забыли создать нужный процесс;
зависание из-за того, что забыли снять блокировку с замка после работы с ним;
зависание из-за того, что забыли поместить переменную в общую память;
двойной вывод результатов из-за того, что забыли завершить дочерний процесс.
Параллельный отладчик pdbx для нашего примера не понадобился, но в других случаях он приносит существенную помощь в отладке работы множества процессов и их взаимодействия.
Плохо только то, что запись данных в один и тот же файл многими процессами может создать проблемы. Это может произойти из-за смены процессов на процессоре во время доступа к диску. Поэтому лучше выполнять ввод/вывод в последовательной части программы или иметь вспомогательный процесс ввода/вывода.
Заключение
Система, на которой мы работали, очень практична с точки зрения введения мигрирующей части при работе параллельных процессов. Система предлагает хорошие решения большинства первоочередных проблем, возника-
ющих при переходе к параллельному выполнению программы, особенно связанных со стоимостью перехода и увеличением производительности. Большая часть программного обеспечения, разработанного для последовательных ЭВМ, может быть без переделок перенесена в эту систему и использовать ее увеличенную пропускную способность и распараллеленные утилиты. Тот факт, что DYNIX является расширением знакомой и проверенной операционной системы (UNIX), тоже является большим плюсом.
Поддержка распараллеливания программ достаточно разнообразна для того, чтобы позволить пользователю постепенно войти в мир параллельного программирования. Директивы компилятора, библиотеки, системные вызовы и возможности применения специальных средств поддержки параллельного программирования могут ошарашить новичка своим разнообразием, но они дают возможность хорошо подогнать средства к прикладной программе. Некоторые пользователи могут посчитать анализ зависимостей чересчур сложным, но похоже, что в будущем он будет автоматизирован.
Благодарности
Нам бы хотелось поблагодарить Майкла Сквайреса, Гэрри Файленда, Джо ДиМартино и Аниту Остерхауг, да и всех в Sequent Computer System за их огромную помощь и обеспечение практически неограниченного доступа к машинам Balance 8000 и Balance 21000.
Литература
В.Beck and D.Oliven, "A parallel ргодгавя1пд process Model”, in Proceeding of the Winter 1987 USENIX Technical Conference, Washington,D.C., Jan. 1987, pp. 84 - 101.
S.Thakkar, P.Gifford, and G.Fielland, “A shared memory multiprocessor", to appear in Proceeding of the Second Internatlnal Conference on Supercomputing, Santa Clara, CA, May 1987.
G.Graunke, “Translating sequential languages for concurrent execution", in Proceeding of the IEEE Spring CompCon 1987, pp. 338 - 342.
Sequent Computer Systems, “Balance Guide to Parallel Programming", September 1986.
Sequent Computer Systems, “DYNIX Pdbx Debugger User's Manual", September 1986.
Приложение А
Alliant FX/8
Содержание
Al. Распечатка программы Р1 (без явного параллелизма) 167
А2. Распечатка программы Pi (с явным параллелизмом) 168
АЗ. Извлечения из руководства "FX/FORTRANProgrammer's Handbook" 170
АЗЛ. Основные понятия 170
Ограничения оптимизации цикла D0 173
Векторные конструкции 174
Целочисленные величины постоянного приращения 175
Косвенная адресация 175
Замена скалярной переменной 176
АЗ.2. Механизмы оптимизации 176
Директивы оптимизации 176
АЗ.З. Отказ от оптимизации 177
Отказ от векторизации 178
Отказ от режима параллельной обработки 179
АЗ.4. Зависимость данных 180
Рекурсивное использование скалярных переменных 181
Исключение рекурсивно используемых скалярных.переменных 182
Ассоциативные преобразования 183
Обратные ссылки на элементы массива 183
Повторная запись в элемент массива 187
Синхронизация параллельных операций 187
Сохранение последнего значения 188
Условное присваивание значений скалярнымииндексным переменным 188
Внешние процедуры 189
Директива CNCALL 190
Интерфейсный блок Фортрана 190
Предостережения 192
Параллельное выполнение независимых подпрограмм 193
AL Распечатка программы Pi (без явного параллелизма)
С
С — Программа Р1 выполняет в цикле вычисление площадей
С - - элементарных прямоугольников и их суммирование
С
real ttl(2), tt2(2)
1nteger*4 intrvls, cut
double precision sumall,width, f, x
С
f(x) - 4d0 / (IdO + х * х)
С
Иread(*,*) intrvls
t2 - etime(ttl)
С
С - - Вычисление ширины элементарного прямоугольника
С
width ■ l.dO / intrvls
sumall « O.OdO
C
C — Цикл no числу прямоугольников и суммирование площадей
20 С
21 do 100 cut ■ 1, intrvls
22sumall ■ sumall + width * f((cut - .500) * width)
23100 continue
C
C — Вычисление времени выполнения и печать результатов
С
27 tl « etime(tt2)
28write(6,*) ’Time in main «’, tl - t2,’, sum ■’, sumall
29write(6,*) ’Error « \ sumall - 3.14159265358979323846d0
30stop
31end
A2. Распечатка программы Pi (с явным параллелизмом)
С
С - - Основная программа запускает подпрограммы work
С - - и печатает ответ и время выполнения каждой подпрограммы
С
double precision sumall, tame(50)
real ttl(2), tt2(2)
1nteger*4 prenum, nprocs, intrvls
common /comm/ sumall, time
C
read(*,*) nprocs, intrvls
t2 ■ etime(ttl)
C
C - - Вызов параллельно выполняемых подпрограмм
С
CVD$L CNCALL
do 100 prenum ■ 1, nprocs
call work(prcnum, intrvls,nprocs)
100 continue
tl - etime(tt2)
write(6,*) ’Time is main «’,tl- t2,’, sum ■’, sumall
21
write(6,*) ’Error -• ’, sumall - 3.14159265358979323846d0
22
do 200 i ■ 1, nprocs
23
write(6,*) ’Process ’, 1, ’ Time ■ ’, time(i)
24 200
continue
25
stop
26
end
27
subroutine work(idproc, intrvls, nprocs)
28 CVD$R NOCONCUR
29 C
30 C - -
Вычисление интеграла методом прямоугольников для
31 C - -
каждого из nprocs элементарных прямоугольников.
32 C - -
Число прямоугольников - параметр типа 1nteger*4.
33 C
34
Integer * 4 cut, intrvls, idproc, nprocs
35
Real dummy(2)
36
Double precision sum, sumall, width, f, x, time(50)
37
common /comm/ sumall, time
38
common /synch/ lock
39
f(x) - 4d0 / (IdO + x * x)
40 C
41
tl ■ etime(dummy)
42 C
43 C - -
Получение количества элементарных прямоугольников
44 C - -
и вычисление их ширины.
45
width ■ l.dO / intrvls
46 C
47 C - -
Вычисление площадей элементарных прямоугольников
48 C - -
и их суммирование.
49 C
50
sum ■ O.OdO
51
do 100 cut ■ idproc, intrvls, nprocs
52
sum ■ sum + width * f((cut - .5d0) * width)
53 100
continue
54
time(idproc) ■ etime(dummy) - tl
55 C
56 C - -
Возврат результата в базовый узел
57 C
58
call lockoff(lock)
59
sumall - sumall + sum
60
call lockoff(lock)
61
return
62
stop
63
end
АЗ. Извлечения из руководства "FX/FORTRAN Programmer’s Handbook"
A3.L Основные понятия
В реализации языка FX/ Fortran оптимизируются следующие операции параллельного и векторного режимов:
Операции с массивами, выполняемые в векторном параллельном (ВП) режиме (описание ВП режима см. ниже).
Итеративные циклы D0 вида D0 1*1, N. Такие циклы не должны содержать ни операторов, ни зависимостей данных, ограничивающих оптимизацию выполнения цикла в векторном параллельном режиме. Циклы D0 в заисимо- сти от определенных ограничений могут выполняться в векторном, скалярном параллельном или скалярном режимах. Кроме того, циклы D0 могут быть представлены в виде нескольких циклов, некоторые из которых реализуются в скалярном режиме, а другие в зависимости от ограничений подвергаются оптимизации определенного вида.
Циклы DO WHILE, не содержащие недопустимых операторов и зависимостей данных, ограничивающих оптимизацию, выполняются в скалярном параллельном режиме.
Все другие программы выполняются в скалярном режиме. Отметим, что последовательность скалярных операций с элементами некоторого массива не оптимизируется, например следующая программа не будет выполняться ни в параллельном, ни в векторном режимах;
А(1) = А(1) + S
А(2) - А(2) + S
А(3) - А(3) + S,
А(4) - А(4) + S
Режимы выполнения программ определяются следующим образом:
Скалярный режим. В этом режиме операции выполняются последовательно. Процессор FX/Fortran планирует выдачу команд так, чтобы по возможности использовать преимущества конвейеризации вычислений.
Векторный режим. В этом режиме операции выполняются аппаратно по специальным векторным командам над группами из 32 элементов (можно использовать группы из меньшего числа элементов).
Параллельный (скалярный параллельный) режим. В этом режиме операции различных итераций одного и того же цикла выполняются параллельно рядом вычислительных элементов (ВЭ).
Векторный параллельный режим. Операции в этом режиме выполняются параллельно несколькими ВЭ над группой из 32 элементов.
На рис. А3.1 иллюстрируются различные возможные режимы выполнения.
В предположении, что длительность такта процессора составляет 170 нс, время выполнения цикла, в котором производится 8192 арифметические операции с двойной точностью, составит 0.02 с в скалярном режиме (достигаемая производительность 0.39 миллиона операций с плавающей запятой в секунду), 0.0041 с в векторном режиме (1.99 миллиона операций с плавающей запятой в секунду), 0.0027 с в скалярном параллельном режиме при восьми ВЭ (3.02 миллионов операций с плавающей запятой в секунду), 0.00065 с в
векторном параллельном режиме при восьми ВЭ (12.5 миллионов операций с плавающей запятой в секунду).
Отметим, что цикл в этом простом примере содержал точно одну операцию с плавающей запятой, и поэтому указанная скорость вычислений, выраженная в миллионах операций с плавающей запятой в секунду, является минимальной. Если в цикле будет содержаться больше арифметических операций и особенно если операция умножения будет использоваться в сочетании с операцией сложения или вычитания, то скорость вычислений возрастет. Например, если в цикле содержится оператор присваивания A(I)-A(I)*T+s (в этом случае на одну итерацию приходятся две операции с плавающей запятой), время в векторном параллельном режиме возрастет только на 4883 такта, а время выполнения цикла при длительности такта в 170 нс будет равно 0.00083 с, что соответствует скорости в 19.7 миллиона операций с плавающей запятой в секунду. Если в цикле содержится оператор присваивания A(I)-A(I)*T+S+A(I)*S*T, т.е. в каждой итерации выполняется пять операций с плавающей запятой, то время выполнения цикла в векторном параллельном режиме возрастет только на 9995 тактов. При этом время выполнения составит 0.0017 с, а скорость вычислений будет равна 24.1 миллиона операций с плавающей запятой в секунду.
В случае вложенных циклов внутренний цикл выполняется в векторном режиме, а внешний - в параллельном. Если один цикл вложен в другой, то говорят, что такая конструкция выполняется в режиме “внешний - параллельно, внутренний - векторно" (ВПВВ). В следующем примере итерации цикла по J выполняются параллельно, а итерации цикла по I внутри каждого цикла по J - в векторном режиме:
D0 И J - 1, L 00 12 I - 1, N .A(I,J) - A(I,J) * S
12 CONTINUE 11 CONTINUE
Операция с массивом в цикле также выполняется в режиме ВПВВ. При этом операция с массивом векторизуется, а итерации цикла выполняются параллельно. Следующая программа эквивалентна программе, представленной выше:
00 И J - 1, L
A(1:N,J) - A(1:N,J) * S
11 CONTINUE
Операции с многомерными массивами также выполняются в режиме ВПВВ. Цикл по самому левому индексу векторизуется, циклы по следующим индексам реализуются в параллельном режиме. Следующая программа эквивалентна программе, представленной выше.
A(1:N,1:L) - A(1:N,1:L) + S
На рис. А3.2 представлен режим ВПВВ.
Скалярный
АС8192)АС8192)А(1> | | А(2) | | А(3)
АС8192)
АС8192)
122472 такта
Векторный
А(1:32) АСЗЗ:64) .... А(8161:8192)
24184 такта
А(9)
...
А(8185)
А(10)
АС8186)
АС11)
А(8187)
А(12)
А(8188)
А(13)
А(8189)
А(14)
А(8190)
А(15)
А(8191)
А(16)
А(8192)
Параллельный А(1) А(2) А(3) А(4) А(5) А(6) А(7) А(8)
15970 тактов
Программа может быть записана как цикл:
00 12 I - 1, N
12 А(1) - A(I) * S
или в виде следующей операции с массивами:
A(I:N) - A(1:N) + S
где А и s имеют двойную точность Векторный параллельный
А(1:249:8)
А(7937:8185:8)
А(2:250:8)
А(7938:8186:8)
А(3:251:8)
А(7939:8187:8)
А(4:252:8)
А(7940:8188:8)
А(5:253:8)
А(7941:8189:8)
А(6:254:8)
А(7942:8190:8)
А(7:255:8)
A(7943i8191:8)
А(8:256:8)
А(7944:8192:8)
3848 тактов
Рис. АЗЛ. Режимы работы.
A(1:32,1)A(1:32,2)A(1:32,3)A(1:32,4)A(1:32,5)AC 1:32,6)A(1:32,7)A(1:32,8)4825A(1:32,1)A(1:32,2)A(1:32,3)A(1:32,4)A(1:32,5)AC 1:32,6)A(1:32,7)A(1:32,8)4825A(992:1024,1) A(993:1024,2)A(993:1024,3) A(993:1024,4)A(993:1024,5) A(993:1024,6)AC993:1024,7)A(993:1024,8) цикловA(992:1024,1) A(993:1024,2)A(993:1024,3) A(993:1024,4)A(993:1024,5) A(993:1024,6)AC993:1024,7)A(993:1024,8) цикловПрограмма может быть записана как цикл:
A(1:32,1)
A(1:32,2)
A(1:32,3)
A(1:32,4)
A(1:32,5)
AC 1:32,6)
A(1:32,7)
A(1:32,8)
4825
A(1:32,1)
A(1:32,2)
A(1:32,3)
A(1:32,4)
A(1:32,5)
AC 1:32,6)
A(1:32,7)
A(1:32,8)
4825
A(992:1024,1) A(993:1024,2)
A(993:1024,3) A(993:1024,4)
A(993:1024,5) A(993:1024,6)
AC993:1024,7)
A(993:1024,8) циклов
A(992:1024,1) A(993:1024,2)
A(993:1024,3) A(993:1024,4)
A(993:1024,5) A(993:1024,6)
AC993:1024,7)
A(993:1024,8) циклов
00 12 J - 1, L
12 ^I^JI^aIi.S) S
или в виде следующей операции с массивами:
A(1:N,1;L)-A(1:N,1:L)+S где N ■ 1024. L«8, а А и S имеют двойную точность
Рис. А3.2. Режим “внешний - параллельно, внутренний - векторно“ (режим ВПВВ).
Ограничения оптимизации цикла 00
Оптимизация цикла 00 путем выполнения его в параллельном режиме или посредством векторизации ограничена в следующих случаях:
Не оптимизируется пустой цикл.
В операторе GOTO нельзя использовать переменную, определенную с помощью оператора ASSIGN.
Векторные кострукции
В оптимизируемой программе рассматриваются следующие данные:
Вектор - последовательность элементов. Вектор может быть образован посредством операции над массивом или с помощью операций цикла, в которых используются элементы массива. Например, в следующей программе используется вектор, состоящий из N последовательных элементов массива А:
A(1:N) - A(1:N) + S
Другая программа, использующая вектор из N последовательных элементов массива А:
00 12 I ■ 1, N
A(I) = A(I) + S
12 CONTINUE
Отметим, что скалярные конструкции от А(1) до A(N) являются элементами вектора.
Скаляры цикла - это отдельные элементы, используемые в векторной операции. Скаляр цикла - ссылка на скалярную переменную или на отдельный элемент массива. В некотором цикле ссылка на элемент массива является скаляром цикла (а не вектором), если индекс элемента массива не изменяется во время выполнения итераций цикла. Так, в следующем примере переменная s и элемент массива B(J) являются скалярами цикла по отношению к вектору, образуемому элементами массива от А(1) до A(N):
DO 12 I * 1, N
A(I) - A(I) + B(J) + S 12 CONTINUE
Индекс - отдельный целочисленный элемент, значение которого изменяется на постоянное приращение для каждого элемент вектора. Точнее, индекс должен удовлетворять требованиям целочисленной величины постоянного приращения, обсуждаемым в следующем разделе. В следующем примере I и J являются индексами по отношению к векторам, образуемым элементами массива от а(1) до A(N) и элементами массива от В(J) до B(J-N+ 1):
DO 12 I - 1, N
A(I) - B(J) J - J - 1
12 CONTINUE
Целочисленные величины постоянного приращения
Целочисленная величина постоянного приращения (ЦПП) является некоторой индуктивной переменной, которая в пределах некоторого цикла определяется следующим образом:
Рекуррентная ЦПП - переменная плюс или минус некоторое выражение, инвариантное в пределах цикла.
ЦПП, определяемая по предшествующей ЦПП, - ранее встречавшаяся в цикле ЦПП, связанная знаком сложения, вычитания или умножения с выражением, инвариантным в пределах этого цикла.
В следующем примере I, J и к - целочисленные величины постоянного приращения. Величина I увеличивается на константу в каждой итерации цикла. Величина J уменьшается на 1 в каждой итерации цикла. Переменной к присваивается значение, определяемое ЦПП I, значение которой умножается на константу, а к получаемому произведению затем прибавляется константа.
DO 12 I ■ 1, N J - J - 1 К - I * 2 + 3 A(J) - В(К)
12 CONTINUE
Нарушение указанных правил означает, что целочисленная величина не является ЦПП. Например, если первый оператор присваивания записать как j ■ J * 2, то J не будет являться ЦПП, поскольку в произведении использована не предварительно определенная ЦПП. Если второй оператор записать в виде к ■ 1/2, то, хотя I и является предварительно определенной ЦПП, переменная К не будет ЦПП, так как по правилам в таких выражениях нельзя использовать операцию деления.
Косвенная адресация
Оптимизация программ возможна, если индексы удовлетворяют следующим требованиям:
Прямая адресация реализуется при условии, что хотя бы один из индексов элемента массива допускает для определяемого вектора преобразование к виду
ЦПП * инвариантное выражение + или - инвариантное выражение
Косвенная адресация реализуется при следующих условиях. Если индекс сам является элементом индексируемого массива, то определяемый вектор адресуется косвенно. Кроме того, если индексы не удовлетворяют требованиям для прямой адресации, то определяемые векторы адресуются косвенно. .
Из-за косвенной адресации снижается эффективность программ. Затраты времени могут значительно возрасти, если будет делаться попытка выполнения двух операций записи в один и тот же элемент массива.
Замена скалярной переменной
Скалярная переменная, используемая в некотором оптимизируемом цикле: вместе с элементами массивов, может быть временно (на время выполнения ! цикла) заменена на вектор, что позволяет реализовать векторный параллельный режим вычислений. Например, в следующей программе компилятор автоматически заменяет скалярную переменную S на временный вспомогательный вектор:
DO 12 I ■ 1, N
S = А(1) + В(1) С(1) = S + 1.0/S 12 CONTINUE
Потребность во временном массиве станет очевидной, если тот же самый цикл запрограммировать с использованием операций над массивами:
S(1:N) = A(1:N) + B(1:N)
C(1:N) - S(1:N) + 1.0 / S(1:N)
A3.2. Механизмы оптимизации
Параметр (опция) 0 команды fortran включает режим оптимизации, он может использоваться в комбинации с подпараметрами q, с и v. Полезны следующие комбинации параметров:
0 - Глобальная оптимизация, параллельное выполнение и векторизация;
0q - Только глобальная оптимизация;
Oqc - Глобальная оптимизация и параллельное выполнение
(без векторизации);
Oqv - Глобальная оптимизация и векторизация (без параллельного выполнения).
Если параметр о опущен, то оптимизация не производится. Если все циклы в программе короткие (не более трех итераций), то можно воспользоваться параметрами Oqc. Однако векторизацию целесообразно подавлять с помощью директивы NOVECTOR.
Параметр -AS (ассоциативное преобразование) вместе с параметром -о позволяет определенные конструкции циклов рассматривать как функции, допускающие оптимизирующие преобразования, которые в противном случае не были бы подвергнуты оптимизации. Однако порядок выполнения ассоциативных преобразований не определяется. Разрешение на ассоциативные преобразования можно также давать с помощью директивы ASSOC.
Директивы оптимизации
Чтобы изменить действия по оптимизации, предполагаемые по умолчанию, можно поместить в исходную программу специальные директивы. Директивы оказывают действие только в программах, компиляция которых выполнялась с параметром -о (без подпараметров) или с параметром -о и подпараметрами, включающими с или v. Формат директивы оптимизации пока-
зан на рис. АЗ.З. Директивы оптимизации и правила применения их по умолчанию представлены на рис. АЗЛ.
CDV$[s] директива, где
s Умолчание
Описание
G
Директива действует глобально (до конца файла)
R
Директива действует до конца программы
L *
Директива действует до конца цикла
Рис. АЗ.З.
Формат директив оптимизации.
Директива
Умолчание
Описание
ASSOC
NOASSOC
*
Оптимизация с помощью ассоциативных преобразований
CNCALL
Разрешение функциональных ссылок и вызовов
NOCNCALL
*
подпрограмм в оптимизируемом цикле
CONCUR NOCONCUR
*
Оптимизация посредством распараллеливания
DEPCHK
HODEPCHK
*
Проверка зависимостей данных между итерациями цикла
LSTVAL
*
Сохранение последних значений исходных
NOLSTVAL
индексов и замена скалярной переменной
SYNC
*
Проверка необходимости синхронизации итераций цикла
NOSYNC
VECTOR
NOVECTOR
*
Векторизация
Рис. А3.4. Директивы оптимизации.
АЗЗ. Отказ от оптимизации
В некоторых ситуациях использование векторизации или распараллеливание циклов DO может привести к резкому снижению производительности. В таких случая специальные директивы позволяют отказаться от оптимизации.
!2 1SS
Отказ от векторизации
От векторизации можно отказаться с помощью директивы NOVECTOR. Отказ от векторизации может повысить производительность в следующих ситуациях. (Во всех случаях, чтобы быть уверенным в характере влияния векторизации на время выполнения программы, необходимо определить время выполнения векторизованной и невекторизованной программ.)
• Циклы с небольшим числом итераций. Если число итераций цикла не превосходит трех (длина векторов равна числу итераций), то отказ от векторизации обычно приводит к уменьшению времени выполнения программы. Например, на машине, имеющей восемь ВЭ, следует уверенно отказаться от векторизации, если число итераций одного (не вложенного) цикла меньше 24. Векторизация автоматически подавляется, если цикл содержит известное число итераций, не превосходящее 5. В следующем примере цикл записывается дважды и подавляется векторизация коротких циклов.
IF (N .LT. 25) THEN
CVD$ NOVECTOR
DO 12 I * 1, N
A(I) - A(I) + S
CONTINUE
ELSE
DO 14 I ■ 1, N
A(I) - A(I) + S
CONTINUE
END IF
Однако, отказавшись от распараллеливания, можно получить лучшие результаты.
• Наличие в цикле условных операторов, используемых для обработки массивов. В таких циклах во время выполнения векторных команд в действительности может выполняться мало вычислений. Например, если в следующем цикле много значений А(1), меньших по величине, чем EPS, то можно отказаться от векторизации.
CVD$ NOVECTOR
DO 12 I - 1, N
IF (ABS(A(I)) .GT. EPS) A(I) - A(I) + S
CONTINUE
Однако, если на одном и том же множестве разреженных элементов производится вычисление несколько раз, то для обеспечения эффективной векторной обработки может иметь смысл сборка разреженных элементов. Следующая программа предназначена для сборки в массиве J индексов подлежащих обработке элементов.
DO 12 I « 1, N
IF (ABS(A(I)) .GT. EPS) THEN
II - II + 1 J(II) - I END IF
CONTINUE
В программах, в которых требуется использовать только те элементы массива, которые больше, чем EPS, это можно сделать с помощью массива индексов J. Для оптимизации этой программы необходимо использовать директиву NODEPCHK, поскольку значение индекса не может быть определено во время компиляции.
CVD$ NODEPCHK
DO 14 I - 1, II
A(J(I)) - A(J(I)) + S
CONTINUE
• Разветвление типа “выбор". Если в каждом цикле выбирается одна из нескольких возможных ветвей, невозможно эффективно сгруппировать скалярные операции в целях векторизации. В случае использования вложенных операторов if (в такой структуре содержится сочетание ELSE IF) такая ситуация распознается и векторизация автоматически отменяется. В других случаях векторизацию должен отменять пользователь с помощью директивы NOVECTOR. Например, если используется логический оператор IF для образования структуры типа “выбор", следует предусмотреть подавление векторизации.
CVD$ NOVECTOR
DO 12 I - 1, N
IF (A(I).EQ.1)A(I)-A(I)*SI
IF (A(I).EQ.2)A(I)-A(I)+S2
IF (A(I).EQ.3)A(I)-A(I)*S3
IF (A(I).EQ.4)A(I)-A(I)*S4
CONTINUE
Неравномерно нагруженные итерации. Если в теле цикла число ветвей не велико (как, например, в простом блоке IF ... ELSE), а ветвь, в которой используются элементы массива, выбирается очень редко, то от векторизации целесообразно отказаться с помощью директивы NOVECTOR.
Чтобы быть уверенным в повышении эффективности программы с помощью векторизации, следует измерить время ее выполнения как с векторизацией, так и без нее.
Отказ от режима параллельной обработки
Для отказа от распараллеливания используется директива NOCONCUR. Если в вашей системе только один ВЭ и не планируется увеличивать их число, то можно попытаться отказаться от распараллеливания. Также можно отказаться от распараллеливания циклов, в которых большая часть кода не может быть оптимизирована вследствие зависимостей данных. В любом случае необходимо провести измерение времени выполнения программы, чтобы быть уверенным в достижении желаемого эффекта.
Отказ от распараллеливания, а не от векторизации, может обеспечить уменьшение времени выполнения циклов с небольшим числом итераций (однако не менее пяти). Ниже представлен пример отказа от распараллеливания коротких циклов, которые имеют не менее пяти итераций.
IF (N .LT. 6) THEN CVD$ NOVECTOR DO 10 I - 1. N A(I) - A(I) + S 10 CONTINUE
ELSE IF (N .LT. 25) THEN CVD$ NOCONCUR
DO 12 I - 1, N A(I) - A(I) + S 12 CONTINUE
ELSE
DO 14 I - 1, N A(I) - A(I) + S 14 CONTINUE
END IF
При определенных обстоятельствах можно изменить стандартный метод обработки (внешний - параллельно, внутренний - векторно) вложенных циклов. Если число итераций во внешнем цикле меньше числа доступных ВЭ, то в режиме ВПВВ не используется часть имеющихся в распоряжении ВЭ. В этом случае может оказаться более эффективным простое выполнение внутреннего цикла в векторно-параллельном режиме. Такой вариант оптимизации вложенных циклов обеспечивается помещением директивы NOCONCUR перед внешним циклом.
CVD$ NOCONCUR
DO 12 J - 1, N
DO 11 I - 1, 2048 A(I,J) « A(I,J) + S
CONTINUE
CONTINUE
Режим ВПВВ может оказаться неэффективным и при коротких внутренних циклах. Можно снова использовать векторно-параллельный режим, применив директиву NOCONCUR к самому внешнему циклу.
А3.4. Зависимость данных
Зависимости данных могут ограничить применение оптимизации циклов do. Зависимость данных между итерациями цикла (она также называется рекурсией данных, или обратной связью по данным) означает, что вычисления в некоторой итерации цикла зависят от результатов вычислений, полученных на предыдущей итерации, так что должен поддерживаться нужный порядок выполнения операций. Зависимость данных может оказаться причиной запрета векторизации и распараллеливания цикла. В итоговой распечатке такой цикл может быть снабжен комментарием ЗАВИСИМОСТЬ ДАННЫХ в цикле D0. Зависимость данных может частично запретить векторизацию и распараллеливание в цикле. Такие циклы снабжаются сообщениями (ВЕКТОРНО-ПАРАЛЛЕЛЬНО, ВЕКТОРНО или ПАРАЛЛЕЛЬНО) в комментариях к циклу D0, при этом в колонке DP? ставится признак Y.
По исходной программе не всегда можно установить, обнаружится ли во время выполнения программы зависимость данных. Процессор FX/Fortran подавляет оптимизацию полностью или частично всякий раз, когда существует возможность возникновения такой зависимости. С помощью директивы NODEPCHK можно потребовать выполнения оптимизации, однако надо иметь уверенность в том, что во время выполнения программы зависимость данных действительно не проявится.
В следующих разделах описаны обычные проблемы, связанные с зависимостью данных и отмечены те ситуации, в которых может быть уверенно использована оптимизация.
Рекурсивное использование скалярных переменных
Скалярная переменная, на которую в теле цикла есть ссылка и значение которой после этого изменяется, называется рекурсивно используемой скалярной переменной, поскольку ее значение устанавливается в одной итерации цикла и используется в следующей итерации. Обычно такие скалярные переменные используются в функциях редукции в рекурсивных вычислениях. В следующем примере показана распечатка программы, в которой результат вычисления над элементами массивов А и В сводится к скалярному значению S. Как видно, этот цикл не оптимизируется.
DO 12 I - 1, N
S - S * А(1) + В(1)
12 CONTINUE
Строка 7 Информационное сообщение • 1219
Распараллеливание и векторизация запрецены из-за наличия в цикле переменной S, использованной слева и справа в одном операторе присваивания.
Как показано на рис. А3.5, вычисление S в каждой итерации цикла зависит от значения s, вычисленного на предыдущей итерации. Следовательно, этот цикл не может быть оптимизирован. Если, например, пять итераций представленного выше цикла выполнить независимо, то переменная s получит отдельные значения (1, 2, 3, 4 и 5), четыре из которых будут неверными. Оптимизацию в этой ситуации применять нельзя.
S-S * А(1) * В(1) где
А - (1, 2, 3, 4, 5)
В - (1, 2, 3, 4, 5) S - О
Итерация 1: 0*1+1 - 1
Итерация 2: 1*2+2 - 4
Итерация 3: 4*3+3 - 15
Итерация 4: 15*4+4 - 64
Итерация 5: 64*5+5 - 325
Ряс. АЗЛ. Рекурсивное использование скалярной переменной.
Исключение рекурсивно используемых скалярных переменных
В определенных ситуациях рекурсивно используемые скалярные переменные в циклах могут быть исключены. В следующем примере определяется скалярная переменная т, затем в каждой итерации цикла ей присваивается значение, а в следующей итерации это значение используется. В этом случае рекурсивно используемая переменная не накапливает и не вычисляет рекурсивно данные, она просто сохраняет результат, вычисленный в предыдущей итерации.
Т - 0.0
DO 12 I - 1, N
S = А(1) + В(1)
С(1) - S + Т
Т * S
CONTINUE
Этот пример может быть записан так, чтобы вычисления в каждой итерации не зависели от результатов предыдущей итерации (т.е. без применения рекурсивно используемых переменных). Для этого в каждой итерации дополнительно выполняется умножение, зато цикл удается полностью оптимизировать. Несмотря на дополнительную операцию умножения, этот цикл выполняется при полной оптимизации в несколько раз быстрее предыдущего:
С(1) = А(1) * В(1)
D0 2 I - 2, N
C(I) - А(1) * В(1) + А(1-1) * В(1-1)
CONTINUE
Рекурсивные вычисления часто могут быть заменены явными нерекурсивными вычислениями, которые неэффективны в скалярном режиме, но допускают полную оптимизацию. Предположим, что симметричная матрица хранится в виде вектора, как показано на рис. А3.6.
В следующей программе при обращении к диагональным элементам матрицы используется рекурсивное определение индекса. Эта программа из-за такой рекурсии не может быть векторизована или распараллелена.
J « 0
D0 12 I ■ 1, N
J - J + I
A(J) - A(J) + B(I) * T
CONTINUE
Однако рекурсивное вычисление может быть заменено явной формулой (формулой для вычисления суммы ряда). Здесь необходимо использовать директиву NODEPCHK, поскольку индекс J не является ЦПП.
CVD$ NODEPCHK
DO 12 I - 1, N
J « (I * I + I) * 0.5
A(J) - A(J) + B(I) * T
CONTINUE
1.1
1.2
1.3
1.4
1.2
2.2
2.3
2.4
1.3
2.3
3.3
3.4
1.4
2.4
3.4
4.4
хранится в памяти в виде векторахранится в памяти в виде вектора
хранится в памяти в виде вектора
хранится в памяти в виде вектора
Диагональный элемент четвертого столбцаДиагональный элемент четвертого столбцаДиагональный элемент второго столбца
Диагональный элемент четвертого столбца
Диагональный элемент четвертого столбца
Рис. АЗ.б. Схема хранения симметричной матрицы в виде вектора.
Ассоциативные преобразования
Если использован параметр -as или директива ASSOC, то процессор FX/Fortran рассматривает некоторые рекурсии как функции редукции и оптимизирует соответствующие циклы. В цикле могут обнаруживаться следующие функции редукции: суммирование, произведение, скалярное произведение, максимальное значение, минимальное значение и 44счетчик44. Например, следующий цикл распознается как цикл для вычисления скалярного произведения и оптимизируется:
S = 0.0
CVD$ ASSOC
DO 12 I « 1, 8192
S - S + A(I) * B(I) 12 CONTINUE
Результат выполения этой программы будет таким же, как и результат применения стандартной функции dotproduct для массивов A(1:N) и B(1:N). Следует отметить, что при такой оптимизации программы порядок вычислений считается несущественным. Если порядок вычислений имеет значение, например, когда от него зависят ошибки округления, ассоциативные преобразования допускать нельзя.
Обратные ссылки на элементы массива
Обратная ссылка на элемент массива происходит тогда, когда элемент массива определяется во время одной итерации, а значение его используется в следующей итерации. Если определение значения и ссылка на тот же элемент массива происходят параллельно, то будет получен результат, отличаю-
щийся от того, который был бы получен при последовательных действиях, Эта ситуация обычно обнаруживается по увеличению значения индекса в левой части оператора присваивания. В следующих примерах обратные ссылки используются для распространения значения по элементам массива.
А(1) = 1.0D0 12 I « 1, NА(1) = А(1-1)12 CONTINUEА(1) = 1.0D0 12 I « 1, NА(1) = А(1-1)12 CONTINUEА(1) - 1.0
А(1) = 1.0
D0 12 I « 1, N
А(1) = А(1-1)
12 CONTINUE
А(1) = 1.0
D0 12 I « 1, N
А(1) = А(1-1)
12 CONTINUE
D0 12 I « 1, N или А(1+1) = А(1)
12 CONTINUE
Как показано на рис. А3.7, значение, присваиваемое в каждой итерации цикла элементу массива, зависит от значения, присвоенного на предыдущей * итерации предыдущему элементу массива. Если эту операцию векторизовать | или выполнять параллельно, то отсутствие обратных ссылок должно привести ' к неверному результату. Следовательно, эта программа не оптимизируется. На рис. А3.7 сравниваются результаты для представленного выше цикла при А - (1.0, 0.0, 0.0, 0.0).
Часто для достижения того же самого результата могут быть использованы нерекурсивные методы, допускающие оптимизацию. Если в приведенном выше примере целью является присвоение значения 1.0 каждому элементу сегмента массива A(1:N), то рекурсивная программа может быть заменена простым присваиванием массиву нужного значения:
A(1:N) = 1.0
Для присваивания нужного значения можно использовать следующий цикл:
D0 12 I - 1, N
А(1) « 1.0 12 CONTINUE
Эти конструкции оптимизируются. Обращение к одинаковым данным в представленном выше примере также является причиной обратных ссылок.
ПоследовательноеПоследовательноевыполнениевыполнениеПараллельное выполнениеПараллельное выполнение(без обратной связи)(без обратной связи)
Последовательное
Последовательное
выполнение
выполнение
Параллельное выполнение
Параллельное выполнение
(без обратной связи)
(без обратной связи)
Рис. А3.7. Последовательное и параллельное выполнение присваивания для массивов.
Присваивание одному элементу массива значения другого элемента массива не обязательно приводит к обратным ссылкам из одной итерации к другой итерации. В следующих ситуациях оптимизация допустима.
Ситуация 1: Запись в предыдущий элемент.
DO 11 I - 1, 3
А(1) - А(1+1) И CONTINUE
где А - (1, 2, 3, 4)
А(1) - А(2) - 2
А(2) - А(3) - 3
А(3) - А(4) - 4
Ситуация 2: При определенном шаге перекрытия нет.
DO 11 I - 1, N, 2 А(1+1) - А(1)
И CONTINUE
Где А - (1, 2, 3, 4, 5, 6)
А(2)-А(1)-1
А(4)-А(3)-3
А(6)-А(5)-5
Ситуация 3: При определенном сдвиге перекрытия нет.
DO 11 1 - 1, N, 3 А(1+3) - А(1)
11 CONTINUE
где А - (1, 2, 3, 4, 5, 6)
А(4) - А(1) - 1
А(5) - А(2) - 2
А(6) - А(3) - 3
В некоторых ситуациях обратные ссылки могут возникать или не возникать во время выполнения программы в зависимости от значений переменных. Если существует уверенность в том, что обратные ссылки не возникнут, то для разрешения оптимизации программы следует использовать директиву NODEPCHK. Следующие два примера, уже использованные ранее, иллюстрируют описанную ситуацию.
CVD$ NODEPCHK
DO 14 I - 1, II
A(J(I)) - A(J(I)) + S
14 CONTINUE
CVD$ NODEPCHK
DO 12 I - 1, N
J - (I * I * I) * 0.5
A(J) - A(J) + B(I) * T
12 CONTINUE
В обоих случаях индексы элементов массива не могут быть определены во время компиляции, и поэтому существует возможность возникновения обратных ссылок. Однако программисту известно, что в том и другом случае вычисляемые во время выполнения программы индексы не будут приводить к обратным ссылкам, и, следовательно, оптимизация этих циклов допустима.
Те циклы, которые не оптимизируются или оптимизируются частично, должны быть проанализированы с целью определения возможности преобразования их к виду, допускающему оптимизацию. Например, следующий цикл не оптимизируется, так как элементы массива А, определенные в одной итерации, используются в следующей итерации.
D0 12 I - 2, N
В(1) - А(1-1)
А(1) - А(1) + С(1)
12 CONTINUE
Однако те же самые действия можно выполнить с использованием двух циклов, в которых уже не возникают обратные ссылки.
D0 12 I - 2, N
А(1) - А(1) + С(1)
12 CONTINUE
DO 14 I ■ 2, N
B(I) « A(I-l)
14 CONTINUE
Некоторые циклы, частично оптимизированные для векторно-параллельного режима, выполняются значительно быстрее в скалярно-параллельном режиме. Например, компилятор FX/ Fortran частично оптимизирует следующий цикл посредством выполнения первого оператора присваивания в векторно-параллельном, а второго - в скалярно-векторном режиме:
D0 12 I - 2, N
А(1) - А(1) + А(1) * В(1)
В(1) - В(1-1) + А(1) + В(1)
12 CONTINUE
На самом деле этот цикл может выполняться быстрее, если он полностью реализуется в скалярно-параллельном режиме, поскольку синхронизация в
этом режиме обеспечивает лучшую загрузку ВЭ во время выполнения программы. Скалярно-параллельный режим задается с помощью директивы
CVD$ NOVECTOR
DO 12 I - 2, N'
A(I) - A(I) * A(I) * B(I)
B(I) - B(I-l) + A(I) * B(I)
12 CONTINUE'
Повторная запись в элемент массива
Распараллеливание (но не векторизация) запрещается, если существует возможность записи нескольких значений в один и тот же элемент массива, поскольку в таком случае должен быть сохранен порядок записи значений. Такая ситуация возможна, если при работе с массивом используется косвенная адресация. Так, в следующем примере индекс элемента массива А не может быть определен во время компиляции программы.
DO 12 I - 1. N A(J(D) - B(I)
12 CONTINUE
Например, если в массиве J содержатся значения 1, 2, 3, 1, то должны выполниться следующие присваивания:
А(1) - В(1)
А(2) - В(2)
А(3) - В(3)
А(1) - В(4)
Если заранее известно, что во время выполнения программы не может произойти повторная запись в какой-либо элемент массива, например известно, что все элементы массива имеют различные значения, то для разрешения оптимизации можно воспользоваться директивой NODEPCHK.
CVD$ NODEPCHK
DO 12 I - 1, N
A(J(D) - B(I)
12 CONTINUE
Синхронизация параллельных операций
Потенциально работа в векторно-параллельном и скалярно-параллельном режимах при последовательной записи значений в массивы может приводить к искажению результатов записи. Предположим, например, что выполняется следующий цикл:
DO 12 I - 1, N
А(1) - А(М)
12 CONTINUE
Если ВЭО выполняет пересылку А(32)-А(33) в то время, как ВЭ1 выполняв пересылку А(33)-А(34), то, прежде чем ВЭ1 изменит значение А(33), ВЭО дол жен произвести запись значения а(33) в А(32). Процессор FX/Fortran обеспечивает синхронизацию операций в тех случаях, когда должна быть сохранена определенная последовательность записей. Такая синхронизация не приво дит к большой потере производительности.
С помощью директивы NOSYNC компилятор извещается об отказе от синх ронизации, но, кроме того, отменяется действие директивы NODEPCHK. Директиву NOSYNC необходимо использовать лишь в тех случаях, когда нужно отказаться от синхронизации (не следует использовать директиву NOSYNC вместе директивы NODEPCHK) и есть уверенность, что во время выполнения программы из-за отсутствия синхронизации не возникнут ошибки. В следующем примере отключение синхронизации допустимо, поскольку при записи перекрытие невозможно.
К - N
CVD$L NOSYNC
DO 12 I ■ 1, N
A(I) - A(K)
12 CONTINUE
Отметим, что представленный фрагмент программы может быть заменен следующим оператором присваивания для массивов:
A(1:N) - A(1+K:N+K)
Сохранение последнего значения
В оптимизированной программе нет необходимости сохранять исходные индексы в цикле. Однако, если необходимо для дальнейшей обработки в программе, значения индексов при выходе из цикла вычисляются. Это делается, если только процессор определяет, что последнее значение индекса необходимо сохранить. Аналогично, когда скалярная переменная заменяется временным вектором или локальными скалярами, исходный скаляр в цикле сохранять не требуется. Однако при завершении цикла скаляру может быть присвоено последнее временное значение.
Если после завершения цикла значение индекса или скалярной переменной не используется, то можно незначительно упростить программу, применив директиву NOLSTVAL. Эта директива записывается следующим образом:
CVD$R NOLSTVAL
Условное присваивание значений скалярным и индексным переменным
Если в цикле имеется скалярная переменная, значение которой изменяется в условном операторе и используется затем в том же цикле, то она является рекурсивно используемой скалярной переменной. Такие циклы оптимизировать запрещено. В следующем примере значение скалярной переменной s изменяется в условном операторе и затем используется в следующем операторе присваивания, что препятствует оптимизации.
DO 12 I ■ 1, N
IF (A(I) .GT. X) S » B(I) + 2
A(I) - S
12 CONTINUE
Если изменение скалярной переменной и ее последующее использование производится в одном условном операторе, то цикл, содержащий такой оператор, оптимизируется. Так, может быть оптимизирован следующий цикл:
DO 12 I - 1, N
IF (A(I) .GT. X) THEN
S « B(I) + 2
A(I) - S
END IF
12 CONTINUE
Если скалярная переменная используется в цикле так, что ее последнее значение сохраняется (по умолчанию), то цикл оптимизируется только для скалярно-параллельного режима. Если последнее значение сохранять не требуется, то цикл оптимизируется полностью. В рассмотренном примере нужно определенно использовать директиву NOLSTVAL, если скалярное значение S не потребуется за пределами цикла.
Внешние процедуры
По умолчанию векторизуемые или распараллеливаемые циклы не могут содержать вызовы внешних процедур, поскольку в таких случаях нельзя произвести проверку зависимости данных. С помощью директивы CNCALL или посредством соответствующим образом специфицированного интерфейсного блока языка Фортран можно явно разрешить распараллеливание циклов, содержащих ссылки на внешние процедуры. Следующий пример иллюстрирует, хотя и неэффективное, параллельное выполнение подпрограммы:
DOUBLE PRECISION А(8192)
CVD$L CNCALL
DO 12 I - 1, 8192
CALL SUB(1.ODO, A(I))
12 CONTINUE
RECURSIVE SUBROUTINE SUB(S, A)
DOUBLE PRECISION A, S
A = A + S
END
Вызываемая в цикле подпрграмма Фортрана должна обладать свойством повторного вхождения. Для придания подпрограмме или функции этого свойства необходимо в предложении SUBROUTINE или FUNCTION использовать, как показано ниже, ключевое слово RECURSIVE или выполнить компиляцию подпрограммы с использованием параметра -recursive:
fortran -recursive -с sub.f
При каждом обращении к такой подпрограмме происходит распределение новой области памяти для локальных переменных подпрограммы. Такое распределение отличается от принятого по умолчанию распределения локальных переменных в Фортране в одной статической области, используемой всеми вызовами подпрограммы.
Директива CNCALL
Директива CNCALL позволяет явно включать внешнюю программу в распараллеливаемый цикл. Следующий пример демонстрирует использование этой директивы:
DOUBLE PRECISION А(8192)
CVD$L CNCALL
DO 12 I - 1, 8192
CALL SUB(1.0D0, A(I))
12 CONTINUE
RECURSIVE SUBROUTINE SUB(S, A)
DOUBLE PRECISION A, S
A * A + S
END
При использовании директивы CNCALL не требуется ни интерфейсный блок, ни специальный параметр в команде на компиляцию.
Интерфейсный блок Фортрана
Интерфейсный блок Фортрана является универсальным механизмом, предназначенным для определения отношений между программными единицами. Предполагаемые изменения стандарта языка Фортран предусматривают включение таких средств. (В стандарте языка Фортран-77 такие средства отсутствуют.) В языке FX/ Fortran интерфейсные блоки используются для того, чтобы, не прибегая к специальным директивам в исходной программе, включать подпрограммы в параллельные циклы, а также для создания библиотек параллельных программ.
Для создания интерфейсного блока, определяющего некоторую включаемую в параллельный цикл подпрограмму, эта подпрограмма компилируется с использованием параметра -interface. При использовании этого параметра образуется файл, содержащий интерфейсный блок. Имя этого файла совпадает с именем файла исходной программы, но оно имеет расширитель .Interface (файл исходной программы имеет расширитель .f). В следующем примере предусмотрено образование объектного файла sub.о и файла интерфейсного блока с именем sub. interface:
fortran -0 -с -recursive -interface sub.f
Вызывающие программные единицы, т.е. программы, содержащие параллельный цикл, должны в соответствующем командном файле иметь параметр -cncall. Параметр -cncall определяет интерфейсный файл, связанный с под-
программой в параллельном цикле. Следующая команда на компиляцию программы sub, которая может быть вызвана в параллельном цикле:
fortran -0 -cncall sub.interface main.f sub.о
Имя файла, содержащего интерфейсный блок, должно непосредственно следовать за параметром -cncall. Командная строка может содержать более чем один параметр -cncall.
Некоторый файл с раширителем .interface может содержать несколько интерфейсных блоков. Например, можно объединить выходные файлы нескольких подпрограмм, компилируемых с целью образования одной библиотеки:
cat sub.interface » lib.interface
Если интерфейсный файл или файлы, специфицированные в командной строке, содержат более одного интерфейсного блока для одной и той же подпрограммы, то используется первый интерфейсный блок, при этом библиотеки выбираются по умолчанию с использованием некоторого механизма предпочтения.
Интерфейсный файл (файл в коде ASCII) - это последовательность интерфейсных блоков. Каждый интерфейсный блок имеет следующий формат:
INTERFACE
Предложение FUNCTION или SUBROUTINE
[Предложение описания типа формальных параметров...]
[Предложение DIMENSION для формальных параметров...]
[Предложение INTENT для формальных параметров...] . END INTERFACE
Предложение INTENT включено в предполагаемое расширение стандарта Фортрана и специфицируется следующим образом:
INTENT (in-out) имя-формального-параметра, ...
где in-out может принимать одно из следующих значений:
in - имена формальных параметров, которые в подпрограмме используются, но не определяются;
out - имена формальных параметров, которые определяются в подпрограмме, но не используются;
inout - имена определяемых и используемых в подпрограмме формальных параметров.
Примером может служить следующий интерфейсный блок:
INTERFACE
RECURSIVE SUBROUTINE SUB(S, A)
DOUBLE PRECISION S
DOUBLE PRECISION A
INTENT (IN) S
INTENT (INOUT) A
END INTERFACE
Предостережения
Необходимо уделять большое внимание обработке нелокальных данных в подпрограммах, вызываемых в параллельном цикле, поскольку такая параллельная программа будет выполняться асинхронно на нескольких процессорах. В частности, нельзя обращаться к одному и тому же элементу данных в памяти класса COMMON или EQUIVALENCE при “ наложении “ аргументов, т.е. при передаче одного и того же элемента данных двум различным формальным параметрам, если только этот элемент данных не определяется в подпрограмме. Например, следующая программа при последовательном выполнении обеспечивает суммирование элементов массива А, однако она не делает этого в параллельном режиме, поскольку несколько вызовов подпрограммы используют и определяют общую переменную S одновременно без осуществления синхронизации. Такая программа не может выполняться в параллельном режиме.
С *** Пример, которому не стоит следовать ***
DOUBLE PRECISION А(8192), S
COMMON S
A(l:8192) « 1.0
CVD$ CNCALL
DO 12 I = 1, 8192
CALL SUB(A(I))
12 CONTINUE
PRINT 1, S
1 FORMAT(G) END RECURSIVE SUBROUTINE SUB(A) DOUBLE PRECISION A, S
COMMON S
S « S + A END
Программист должен быть уверен в том, что подпрограмма не вводит в программу необнаруживаемой зависимости данных. Например, в следующей программе рекурсия не обнаруживается, поскольку эти программные единицы при раздельном их рассмотрении к рекурсии не приводят:
DIMENSION А(8192)
D0 12 I - 1, 8192 CVD$ CNCALL
CALL SUB(A(I+1),A(I))
12 CONTINUE
END RECURSIVE SUBROUTINE SUB(A1,A) Al - A ЕЧО
Во избежание опасности существования необнаруживаемой зависимости данных рекомендуется использовать чистые функции. (В чистой функции никакие аргументы не определяются; определяется лишь возвращаемое значение.) Например, если представленную выше программу переписать следующим образом, то рекуррентность будет обойдена.
DIMENSION А(8192)
DO 12 I - 1, 8192 CVD$ CNCALL
A(I+1) - FUNC(A(I)) 12 CONTINUE
END
RECURSIVE FUNCTION FUNC(A)
FUNC - A
END
Использование интерфейсных блоков Фортрана позволяет обнаруживать рекуррентности в ситуациях, подобных описанной выше. Рекуррентность подобного рода не может быть обнаружена компилятором, возможен анализ объединяемых программных единиц лишь программистом. К параллельным подпрограммам также применимы следующие правила:
Подпрограмма, параллельное выполнение которой допускается благодаря интерфейсному блоку или обусловлено директивой CNCALL, не может иметь альтернативных возвратов.
Функциональная ссылка в цикле не может индексировать некоторый массив в цикле, даже если использован интерфейсный блок или директива CNCALL.
Кроме того, повторно входимые подпрограммы не могут сохранять локальные данные от вызова к вызову. Все локальные данные инициализируются или определяются при каждом вызове подпрограммы.
Параллельные независимые подпрограммы
Цикл DO может использоваться для выполнения отдельных подпрограмм или независимых вызовов подпрограммы параллельно, если каждый отдельный вызов подпрограммы осуществлять по условию в определенной итерации цикла. Если одна подпрограмма вызывается повторно, гарантировано, что она рекурсивна. В следующей программе три подпрограммы выполняются параллельно:
DIMENSION Х(512), Y(1024), Z(2048)
CVD$ CNCALL
DO I - 1, 3
IF (I .EQ. 1) THEN CALL XYZ(X, 512, XT)
ELSE IF (I .EQ. 2) THEN CALL XYZ(Y, 1024, YT)
13 185
ELSE IF (I .EQ. 3) THEN
CALL XYZ(Z, 2048, ZT) END IF END DO
T - (XT * YT + ZT) / 3
PRINT *, T
END
RECURSIVE SUBROUTINE XYZ(A, N, T)
DIMENSION A(N)
T - (SUM(A)-MAXVAL(A)-MINVAL(A)) / (N-2) END
Приложение В
BBN Butterfly
Содержание
Распечатка программы Pi 196
Однородная системная библиотека 198
AbortGen 198
ActivateGen 198
Allocate 200
AllocateC 200
Allocatelocal 201
AllocateOnPhysProc 201
AllocateOnUsProc 201
AllocateOnUsProcC 201
AllocScatterMatrix. 201
AllocScatterMatrixC 202
AsyncGenOnA 202
AsyncGenOnAAbortable 202
AsyncGenOnAFull 202
AsyncGenOnALimited 203
AsyncGenOnHA 203
AsyncGenOnHAAbortable 203
AsyncGenOnHAFul! 203
AsyncGenOnHALimited 204
AsyncGenOnI 204
AsyncGenOnlAbortable 204
AsyncGenOnIFull 204
AsyncGenOnllinited 205
Atomic_add_long 205
BEGIN_SHARED_DECL 205
ConfigureUs 206
OtstinctMemoriesAvailable 206
END SHARED_DECL 206
FreeAll 207
GenOnA 207
GenOnAAbortable 207
GenOnAFull 207
GenOnALimited 208
GenOnArray 208
GenOnArrayA 208
GenOnHA 209
GenOnHAAbortable 209
GenOnHAFull 210
GenOnHALtmited 210
GenOnHalfArray 211
GenOnI 211
GenOnI Abortable 211
GenOnIFull 212
GenOnIL tint ted212
GenOnlndex212
GenOnlndexA213
GenTaskForEachProc213
GenTaskForEachProcLimited213
GenTaskForeList213
GetRtc214
InitializeUs 214
Initial IzeUsForBenchMark214
LOCK214
MakeSharedVar tables215
MemoriesAvai table215
PhysProcToUsProc215
Procs InUse215
SHARED215
Share215
ShareBlk216
SharePtrAndBlk216
ShareScatterMatrix217
TieeTest217
TiineTestFull218
TimeTestPrint218
TotalProcAvat lable218
UNLOCK219
UsProcToPhysProc219
UsSetClass219
UsWait219
WaitForTasksToFinish219
WorkOn220
Bl. Распечатка программы Pi
#include <us.h>/*предложения include должны записы-*/
#include <stdio.h>/*ваться в указанном порядке*/
BEGEN_SHARED_DECL/*в этой программе демонстрируются*/
double tpt;/* три способа совместного использо- */
END_SHARED_DECL;/* вания данных*/
int nrecs, nprocs, timel, time2;
short *lock, *nodecount;
UserinputO
{
printf("\n4MCAO прямоугольников ”);
scanfCXd”, &nrecs);
printf(”\n4HCAO процессоров");
scanf("Xd", &nprocs);
}
ShareData()
{
MakeSharedVartables
nodecount ■ (short *)Allocate(stzeof(short));
*nodecount ■ nprocs;
lock ■ (short *)Allocate(sizeof(short));
*lock - 0;
SHARED tp1 « 0;
Share(&nrecs); Share(&nprocs); Share(Snodecount); Share(&lock);
}
PrintAnswerO
{
int elapsed_ti”e;
double pi;
if (time2 > timel)
{
elapsed_time ■ time2 - timel;
printf (w\nT1me is %d\n", elapsed_time);
}
pi « 3.1415926535897932384626433; /* 24 десятичных знака */
pi ■ pi - SHARED tpi;
printf("\nBu4HCAeHHoe значение pi % 27.20e\n", SHARED tpi);
printf("\Pi минус вычисленное значение pi %27.20e\nM,pi);
}
Dummy2() {};
Partial_Pi(Dummy2, index)
int index;
{
int me, nl, n2, j, section;
double h, x, sum;
{
me■index;
h■(double) 1.0/nrecs;
section ■ nrecs / nprocs;
nl■(me* section)+ 1;
n2■nl + section-1;
if (me « (nprocs -1)) n2 ■ nrecs;
sum ■ 0.0;
for (j ■ nl; j <« n2; ++j)
{
x - (j - 0.5) * h;
sum ■ sum + (4.0/(1.0 + x * x));
}
L0CK(lock, 0);
SHAREDtpi - SHARED tpi + (h * sum);
UNLOCK(lock);
Atomic_add(nodecount, -1);
while (*nodecount>0) UsWait(O);
}
}
DummylO {}; Dummy3() {};
66 main ()
67 {
68 UserlnputQ;!
69 timel ■ GetRtc(); /* считывание значения времени */
70Initial1zeUs();
71ShareData();
72 GenOnIFull(Dummyl, Partial_Pi, Dummy3, 0, nprocs, 0, 0);
73 timeZ - GetRtcO;
74 PrintAnswerO;
75 }
Программы однородной системной библиотеки (Uniform System Library)
В этом приложении описана каждая операция, поддерживаемая однородной системной библиотекой. Операции упорядочены по алфавиту.
AbortGen
AbortGen(GenHandle, code)
UsGenDesc * GenHandle; int code;
Прекращает действие активного генератора задач, специфицированного i параметром GenHandle. Любые выполняющиеся задачи завершаются. Значение параметра code равно коду результата для этого генератора. Если операция AbortGen выполняется для данного генератора более одного раза, то параметр code получает наименьшее значение.
Параметр GenHandle должен указывать на генератор, допускающий прекращение функционирования.
ActivateGen
UsGenDesc *
ActivateGen(Init, Worker, Final, Arg, Rangel, RangeZ,
Type, Gen, Async, MaxProcsToUse, Abortable, Result?)
int (* Init) (), (* Worker) (), (* Final) ();
int Arg, Rangel, RangeZ, Type;
int Async, MaxProcsToUse, Abortable, * ResultP;
ActivateGen -• универсальная процедура активизации генератора. Она вызывается всеми процедурами активации генератора, имена которых начинаются с GenOn... . Операция ActivateGen может непосредственно использоваться прикладными программами для конструирования (образования) новых генераторов. Здесь, как и в описаниях генераторов в этом приложении, программы Init, Worker и Final являются соответственно системной программой инициализации выполнения задачи процессором, системной программой управления задачей, системной программой завершения выполнения задачи на процессоре. Параметр Arg является указателем на структуру данных, которая передается системным программам Init, Worker и Final. Параметр Туре должен иметь значение GENERATOR, а параметры Rangel и RangeZ - целочисленные значения. Gen -
системная программа генерации задачи, подробно описанная ниже. Параметру Async должно быть дано булево значение true, если генератор синхронный, и значение false для асинхронного генератора. Параметр MaxProcsToUse специфицирует число процессоров для генератора. Если значение этого параметра равно 0 или -1, то число процессоров не лимитировано; положительное значение определяет число процессоров, которое может быть использовано этим генератором. Булев параметр Abortable определяет, допустимо ли прекращение работы генератора. Наконец, параметр Result? является указателем, который используется, когда параметр Abortable имеет значение true. Он указывает место записи кода результата генератора в случае прекращения работы генератора (этой информацией может воспользоваться программа активации генератора).
Системная программа генерации задачи имеет следующую форму:
Gen(TD);
UsGenDesc * ТО;
где ТО - указатель на структуру данных дескриптора задачи в глобальной общей памяти (тип UsGenDesc определяется в файле usgen.h, который подключается макрокомандой «include, используемой во время выполнения операции ActivateGen). Эта структура данных имеет следующий вид:
struct
{ short started;
short type;
/* Определяемые типы: */
«define IDLETASK 1
«define GOAVAYTASK 2
fdefine GENERATOR 4
short incarnation_nueber;
short state;
/* Определяемые состояния: */
♦define ACTIVE 1
«define INACTIVE 2
short us_1ock;
short lock;
1nt (*1nit) ();
int (*call) ();
int (*gen) ();
int (*final) ();
int arg;
char *currentShare;
int rangel;
int rangeZ;
QH returnQ;
int post_pending;
short MaxProcsToUse;
int end;
int abortable;
short retcode;
/* Определяемые коды*/
#define genEXHAUSTED -1 short endlock;
union {
long Long;
short Short;
} index;
,union {
unsigned long Long;
unsigned short Short;
} index2;
short locka[nlocks];
short indexla[nlocks];
short index2a[nlocks];
Параметры Init, Worker, Final, Arg, Rangel, Range2, Type, Gen, MaxProcsToUse и Abortable операции ActivateGen используются для определения значений полей call, init, final, gen, arg, range, range2, type, MaxProcsToUse и abortable структуры данных дескриптора задач. Полю lock и массиву locka присваивается начальное значение 0, они используются как замки системной программой Gen; программа Gen использует для работы по генерации задач поля index и index2, а также массивы indexla и index2a структуры данных дескриптора задач (этим полям первоначально присваивается значение 0). Оставшиеся поля (например, started, state, shareCount, returnQ и др.) используются операцией ActivateGen для внутренних целей. После инициализации структуры данных дескриптора задачи операцией ActivateGen он становится доступным для других процессоров. Если значение параметра Async равно true, программа ActivateGen передает управление вызвавшей ее программе, при этом производится и передача указателя на структуру данных дескриптора задачи, в противном случае процессор, на котором выполнялась программа ActivateGen, вызывает процедуру генерации задачи Gen. Этот и другие процессоры по мере их освобождения используют дескриптор генератора задачи (то) и процедуру генерации задачи Gen для генерации и выполнения вызовов по процедуре Worker.
Allocate
char * Allocate(size) int size;
Процедура Allocate используется для выделения блока памяти размером size байтов в общей глобальной памяти. Этот блок выделяется в памяти, имеющей наибольшее свободное пространство.
AllocateC
char * AllocatreC(size, class) int size, class;
Процедура AllocateC используется для выделения блока памяти размером *1» байтов в общей глобальной памяти. Этот блок выделяется в памяти класса class, имеющей наибольшее свободное пространство.
AllocateLocal
char * AllocateLocal(size) int size;.
Процедура AllocateLocal используется для выделения в памяти локального процессора блока размером size байтов в общей глобальной памяти.
AllocateOnPhysProc
char * AllocateOnPhysProc(physproc, size) int physproc, size;
Процедура AllocateOnPhysProc используется для выделения в глобальной общей памяти блока размера size. Блок выделяется в памяти процессора, аппаратный номер которого равен physproc.
AllocateOnUsProc
char * AllocateOnUsProc(proc, size) int proc, size;
Процедура AllocateOnUsProc используется для выделения в глобальной общей памяти блока размера size. Блок выделяется в памяти процессора, системный процессорный номер которого равен proc.
AllocateOnUsProcC
char * Al1ocate0nUsProcC(proc, size, class)
Int proc, size, class;
Процедура AllocateOnUsProcC аналогична процедуре AllocateOnUsProc. Отличие состоит в том, что блок памяти будет выделяться, если процессор принадлежит классу class, в противном случае происходит отказ. См. также описание процедуры UsSetClass.
AllocScatterMatrtx
char * * AllocScatterMatr1x(nrows, ncolumns, element_s1ze)
Int nrows, ncolumns, elementsize;
Процедура A1locScatterMatr1x используется для размещения строк матрицы по устройствам памяти машины. Размещается вектор указателей длины nrows и nrows отдельных векторов, состоящих из ncolumns элементов размера element_size байтов. Эти векторы размещаются в памяти различных процессоров. Процедура возвращает указатель на вектор указателей, определяющих
местонахождение векторов-строк матрицы. Для ссылок на элементы массива А, размещенного таким способом, используются обычные средства языка С, например A[i][j].
AllocScatterMatrixC
char * * AllocScatterMatrixC(nrows, ncolumns, element_size, class) int nrows, ncolumns, elements ize;
Процедура AllocScatterMatrixC аналогична процедуре AllocScatterMatrix. Отличие состоит в том, что для размещения строк матрицы и вектора указателей строк используются только устройства памяти, отнесенные к классу class. См. также описание процедур AllocScatterMatrix и UsSetClass.
AsyncGenOnA
UsGenDesc *
AsyncGenOnA(Worker, Rangel, Range2) int (* Worker) (); int Rangel, Range2;
Процедура AsyncGenOnA является асинхронной версией процедуры GenOnA. Операция AsyncGenOnA эквивалентна операции AsyncGenOnAFull(О, Worker, 0, 0, Rangel, Range2, 0, false)
AsyncGenOnAAbortable
UsGenDesc *
AsyncGenOnAAbortable(Worker, Rangel, Range2) int (* Worker) (); int Rangel, Range2;
Асинхронная версия GenOnAAbortable.
Операция AsyncGenOnAAbortable эквивалентна операции
AsyncGenOnAFull(0, Worker, 0, 0, Rangel, Range2, 0, true)
AsyncGenOnAFull
UsGenDesc *
AsyncGenOnAFull(Init, Worker, Final, Arg, Rangel, Range2, limited, Abortable) int (*Init) (), (* Worker) (), (* Final) ();
int Arg, Rangel, Range2, limited, Abortable;
Процедура AsyncGenOnAFull является асинхронной версией процедуры GenOnAFulL Процедура AsyncGenOnAFull передает управление вызвавшей ее программе, как только активизируется генератор задачи, после чего эта программа может работать параллельно с запущенной задачей. Процедура AsyncGenOnAFull возвращает ссылку на генератор задачи, которая может использоваться процеду-
рами WorkOn и WaitForTasksToFinish. Описание параметров этой процедуры смотри в описании процедуры GenOnAFull.
AsyncGenOnALimited
UsGenDesc *
AsyncGenOnALimited(Worker, Rangel, Range2, MaxProcsToUse)
Int (* Worker) ();
int Rangel, Range2, MaxProcsToUse;
Асинхронная версия GenOnALimited.
Операция AsyncGenOnALimited эквивалентна операции
Async6en0nAFull(0, Worker, 0, 0, Rangel, Range2, 0, MaxProcToUse, false)
AsyncGenOnHA
UsGenDesc *
AsyncGenOnHA(Worker, Rangel, Range2) int (* Worker) (); int Rangel, Range2;
Асинхронная версия GenOnHA.
Операция AsyncGenOnHA эквивалентна операции
AsyncGenOnHAFull(O, Worker, 0, 0, Rangel, Range2, 0, false)
AsyncGenOnHAAbortable
UsGenDesc *
AsyncGenOnHAAbortable(Worker, Rangel, Range2) int(* Worker) (); int Rangel, Range2;
Асинхронная версия GenOnHAAbortable.
Операция AsyncGenOnHAAbortable эквивалентна
AsyncGenOnHAFull(O, Worker, 0, 0, Rangel, Range2, 0, true)
AsyncGenOnHAFull
UsGenDesc *
AsyncGenOnHAFull(Init, Worker, Final, Arg, Rangel, Range2, Limited, Abortable) int (* Init) (), (* Worker) (), (* Final) (); int Arg, Rangel, Range2, Limited, Abortable;
Процедура AsyncGenOnHAFull является асинхронной версией процедуры GenOnHAFull. Процедура AsyncGenOnHAFull передает управление вызвавшей ее программе как только активизируется генератор задачи, после чего эта программа может работать параллельно с запущенной задачей. Процедура AsyncGenOnHAFull возвращает ссылку на генератор задачи, которая может использоваться процедурами WorkOn и WaitForTasksToFinish. Описание параметров этой процедуры смотри в описании процедуры GenOnHAFull.
AsyncGenOnHALimited
UsGenDesc *
AsyncGenOnHALimited(Worker, Rangel, Range?, MaxProcToUse) int (* Worker) ();
int Rangel, Range?, MaxProcToUse;
Асинхронная версия GenOnHALimited.
Операция AsyncGenOnHALimited эквивалентна операции
AsyncGenOnHAFull(0, Worker, 0, 0, Rangel, Range?, 0, MaxProcToUse, false)
AsyncGenOnI
UsGenDesc *
AsyncGenOnI(Worker, Range) int (* Worker) (); int Range;
Асинхронная версия GenOnl.
Операция AsyncGenOnI эквивалентна операции
AsyncGenOnIFull(O, Worker, 0, 0, Range, 0, false)
AsyncGenOnIAbortable
UsGenDesc *
AsyncGenOnIAbortable(Worker, Range) int (* Worker) (); int Range;
Асинхронная версия GenOnlAbortable.
Операция AsyncGenOnIAbortable эквивалентна операции
AsyncGenOnIFull(O, Worker, 0, 0, Range, 0, true)
AsyncGenOnIFull
UsGenDesc *
AsyncGenOnIFull(Init, Worker, Final, Arg, Range, Limited, Abortable)
int (* Init) (), (* Worker) (), (* Final) ();
1nt Arg, Range, United, Abortable;
Процедура AsyncGenOnlFull является асинхронной версией процедуры GenOnIFull. Процедура AsyncGenOnlFull, как только активизируется генератор задачи, передает управление вызвавшей ее программе, после чего эта программа может работать параллельно с запущенной задачей. Процедура AsyncGenOnlFull возвращает ссылку на генератор задачи, которая может использоваться процедурами WorkOn и WaitForTasksToFinish. Описание параметров этой процедуры смотри в описании процедуры GenOnIFull.
Async6en0nILinited
UsGenDesc *
Async6en0nIlinited(Worker, Range, MaxProcsToUse) int (* Worker) ();
int Range, MaxProcsToUse;
Асинхронная версия GenOnllimited.
Операция AsyncGenOnlLinited эквивалентна операции
AsyncGenOnlFull(0, Worker, 0, 0, Range, MaxProcsToUse, true)
Aton1c_add_long
Atonic_add_long(loc, val) int * loc, val;
Процедура Atonic_add_long обеспечивает выполнение неделимого сложения значения параметра val с содержимым ячейки, адресуемой с помощью указателя 1ос. Операция Atomic_add_long подобна 16-разрядной операции Atomic_add, реализованной в операционной системе Chrysalis. Отличие состоит в том, что операция Atoni c_add_long выполняется над 32-разрядными числами и не поддерживает часть “выбрать" операции “выбрать-и-прибавить“, реализуемой процедурой Atoni c_add. Кроме того, важно отметить, что в современной реализации операция Atomic_add_long является неделимой только по отношению к другой операции Atonic_add_long (другому вызову этой процедуры). В частности, если возможно выполнение операции считывания в режиме чередования с выполнением операции Atoni c_add_long, то результат считывания может оказаться неверным (противоречивым). Это может случиться, если старшие 16 разрядов будут считаны после того, как младшие 16 разрядов уже увеличены при выполнении операции Atonic_add_long, а перенос (если он есть) еще не успел распространиться.
BEGIN_SHARED_DECL
BEGIN_SHARED_DECL
Описания на языке Си;
END_SHARED_DECL;
BEGIN_SHARED_DECL - макрокоманда. Она используется вместе с макрокомандой END_SHARED_DECL для выделения описания переменных, которые должны быть разделяемыми глобальными переменными для всех процессоров. Описанные таким образом переменные используются с префиксом SHARED. Место в памяти для таких переменных должно быть выделено с помощью процедуры NackeSheredVariaMes после выполнения процедуры InltlalizeUs (ссылки на такие переменные допустимы только после выполнения указанных процедур).
В программе, реализуемой в системе Uniform System описание BE6IN_SHARED_DECL/END_SHARED_DECL может быть использовано только один раз. Все переменные, описанные таким образом, размещаются в одной и той же физической памяти. В некоторых ситуациях это может приводить к возникновению конфликтов при обращении к памяти.
ConflgureUs
ConflgureUs(Spec, n)
1nt * Spec, n;
Процедура ConflgureUs может использоваться перед обращением к процедуре InltlalizeUs для определения значений параметров конфигурации, отличающихся от значений, принятых по умолчанию в процедуре InltlalizeUs. Параметр spec - массив типа int, используемый для определения конфигурации. Массив содержит п блоков спецификации параметров. Каждый блок спецификации параметров содержит целочисленное значение configuration_code, служащее для идентификации подлежащего установке параметра, за которым следует одно или несколько целых чисел, определяющих значение параметра.
В настоящее время определены следующие варианты “кодов конфигурации":
КодПараметр
configProc целое число - число процессоров, включаемых в конфигурацию системы Unifoni Systen
configMaxSars целое число ■ число регистров описания сегментов, используемых для определения адресных пространств процессов
DistinctMemoriesAvailable
DistinctHeBorlesAvailable()
Процедура DistinctMemorlesAvaitable возвращает целое число, равное числу устройств памяти, доступных для прикладных программ. Это число обычно равно значению параметра TotalProcsAvai table, однако возможны случаи, когда оно будет меньше из-за того, что память не может быть получена на некотором конкретном процессорном узле.
. END_SHARED_DECL
Макрокоманда END_SHARED_DECL используется совместно С макрокомандой BEGIN_SHARED_DECL для ограничения описания глобальных общих переменных.
. FreeAll
FreeA1l()
Процедура FreeAll используется для освобождения всей глобально распределенной памяти.
. GenOnA
6enOnA(Worker, Rangel, Range?)
int (* Worker) ():
tnt Rangel, Range?;
Процедура GenOnA предназначена для генерации задач, которые с использованием процедуры Worker(О, Indexl, Index?) выполняются параллельно для всех комбинаций индексов Indexl и Index?, определяемых отношениями 0 <■ Indexl < Rangel и 0 <- Index? < Range?. Вызвавший процедуру GenOnA процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Когда процедура GenOnA завершается, выполнение всех сгенерированных задач должно быть закончено.
GenOnAAbortable
GenOnAAbortable(Worker, Rangel, Range?)
int (* Worker) ();
int Rangel, Range?;
Процедура GenOnAAbortable является версией процедуры GenOnA, допускающей преждевременное прекращение ее выполнения. Сгенерированные задачи с использованием процедуры Worker(0, Indexl, Index?, GenHandle) выполняются параллельно. Параметр GenHandle является идентификатором генератора, он может использоваться процедурой AbortGen для прерывания соответсвующего генератора. Операция GenOnAAbortable эквивалентна операции
GenOnAFul1(0, Worker, 0, 0, Rangel, Range?, О, true)
Отметим, что процедура GenOnAAbortable возвращает значение, указывающее, была ли использована процедура AbortGen для прерывания работы генератора.
. GenOnAFul1
GenOnAFull(In1t, Worker, Final, Arg, Rangel, Range?, Limited, Abortable)
Int (* Inlty) (), (* WORKER) (), (* Final) (); int Arg, Rangel, Range?, Limited, Abortable;
Процедура GenOnAFul 1 предназначена для генерации задач, которые выполняются параллельно для всех комбинаций индексов indexl и index?, определяемых отношениями 0 <- Indexl < Rangel и о <- index? < Range?. При значении параметра Abortable, равного false, используется процедура Worker(0, Indexl, index?), а при значении этого параметра, равного true, используется процедура Vorker(Arg, Indexl, Index?, GenHandle). Вызвавший процедуру GenOnAFull процес-
сор и, возможно, другие процессоры используются для выполнения сгенерированных задач.
Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа Init(Arg). После того как на процессоре, предназначенном для выполнения задач, произведен последний вызов системной программы Worker, выполняется вызов системной программы Final(Arg). Для задания числа процессоров, используемых генератором, предусмотрен параметр United. Если параметр Limited равен 0 или -1, то число процессоров не ограничено. Положительное значение этого параметра определяет предельное число процессоров.
Когда процедура GenOnAFull завершается в связи с завершением выполнения всех сгенерированных задач (в этом случае процедура GenOnAFull возвращает значение genEXHAUSTED) либо в связи со значением true параметра Abortable, то работа генератора прерывается и некоторые задачи могут оказаться невыполненными (в этом случае процедура GenOnAFull возвращает код, передаваемый процедуре AbortGen).
GenOnALimited
GenOnALimited(Worker, Rangel, Range2, MaxProcsToUse) int ( Worker) ();
int Rangel, Range2, MaxProcsToUse;
Ограниченная версия процедуры GenOnA. Операция GenOnALimited эквивалентна GenOnAFull(O, Worker, 0, 0, Rangel, Range2, MaxProcsToUse, false)
GenOnArrayA
GenOnArrayA(Init, Worker, Arg, Rangel, Range2) int (* Init) (), (* Worker) (), (* Final) (); int Arg, Rangel, Range2;
Процедура GenOnArray предназначена для генерации задач, которые с использованием процедуры Worker(Arg, indexl, 1ndex2) выполняются параллельно для всех комбинаций индексов indexl и 1ndex2, определяемых отношениями 0 о indexl < Rangel и 0 <■ 1ndex2 < Range2. Вызвавший процедуру GenOnArray процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа Init(Arg). Когда процедура GenOnArray завершается, выполнение всех сгенерированных задач должно быть закончено.
всех комбинаций индексов index 1 и 1ndex2, определяемых отношениями о <- indexl < Rangel и 0 <- 1ndex2 < Range2. Вызвавший процедуру GenOnArrayA процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа inlt(Arg). После того как на процессоре, предназначенном для выполнения задач, произведен последний вызов системной программы Worker, выполняется вызов системной программы Final (Arg). Когда процедура GenOnArrayA завершается, выполнение всех сгенерированных задач должно быть закончено.
. GenOnHA
GenOnHA( Worker, Rangel, Range2) int (* Worker) ();
int Rangel, Range2;
Процедура GenOnHa предназначена для генерации задач, которые с использованием процедуры Worker(Arg, Indexl, 1ndex2) выполняются параллельно для всех комбинаций индексов indexl и lndex2, которые “пробегают" нижнюю “половину" массива размера Rangel на Range2. Эти индексы принимают следующие значения:
1ndex2-0,index1-1, .... (Rangel-1)
1ndex2-l,indexl-2, ..., (Rangel-1)
1ndex2-R~2, indexl-(R-l), .... (Rangel-1)
где R - nin(Rangel, Range2)
Вызвавший процедуру GenOnHA процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Когда процедура GenOnArrayA завершается, выполнение всех сгенерированных задач должно быть закончено.
GenOnHAAbortable
GenOnHAAbortable(Worker, Rangel, Range2) int (* Worker) ();
int Rangel, Range2;
Процедура GenOnHAAbortable является версией процедуры GenOnA, допускающей преждевременное прекращение ее выполнения. Сгенерированные задачи с использованием процедуры Worker(О, Indexl, 1ndex2, GenHandle) выполняются параллельно. Параметр GenHandle является идентификатором генератора, он может использоваться процедурой AbortGen для прерывания соответсвующего генератора. Операция GenOnHAAbortable эквивалентна операции
GenOnAFu11(O, Worker, 0, 0, Rangel, Range2, 0, true) Отметим, что процедура GenOnHAAbortable возвращает значение, указывающее, была ли использована процедура AbortGen для прерывания работы генератора.
. GenOnHAFull
GenOnHAFull(In1t, Worker, Final, Arg, Rangel, Range2, Limited, Abortable) int (* Init) (), (* Worker) (), (* Final) ();
int Arg, Rangel, Range2, Limited, Abortable;
Процедура GenOnHAFull предназначена для генерации задач, которые выполняются параллельно для всех комбинаций индексов index 1 и tndex2, которые “пробегают" нижнюю “половину" массива размера Rangel на Range2. Эти индексы принимают следующие значения:
1ndex2-0,indexl-1, .... (Rangel-1)
1ndex2-l,indexl-2, .... (Rangel-1)
index2-R-2, indexl=(R-l), .... (Rangel-1)
где R - min(Rangel, Range2)
При значении параметра Abortable, равном false, используется процедура Worker(Arg, indexl, index2), а при значении этого параметра, равном true, используется процедура Worker(Arg, indexl, 1ndex2, GenHandle). Вызвавший процедуру GenOnHAFull процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач.
Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа init(Arg). После того как на процессоре, предназначенном для выполнения задач, произведен последний вызов системной программы Worker, выполняется вызов системной программы Final (Arg). Для задания числа процессоров, используемых генератором, предусмотрен параметр Limited. Если параметр Limited равен 0 или -1, то число процессоров не ограничено. Положительное значение этого параметра определяет предельное число процессоров.
Когда процедура GenOnHAFull завершается в связи с завершением выполнения всех сгенерированных задач (в этом случае процедура GenOnHAFull возвращает значение genEXHAUSTED) либо в связи со значением true параметра Abortable, то работа генератора прерывается и некоторые задачи могут оказаться невыполненный (в этом случае процедура GenOnHAFull возвращает код, передаваемый процедуре AbortGen).
. GenOnHALimited
6enOnHALimited(Worker, Rangel, Range2, MaxProcsToUse) int (* Worker) ();
int Rangel, Range2, MaxProcsToUse;
Ограниченная версия GenOnHA.
Операция GenOnHALimited эквивалентна
GenOnAFull(O, Worker, 0, 0, Rangel, Range2, MaxProcsToUse, true)
GenOnHalfArray
GenOnHalfArray(Init, Vbrker, Arg, Rangel, Range2) int (* Init) (), (* Vbrker) ();
int Arg, (* Vbrker) (), Rangel, Range2;
Процедура GenOnHalfArray предназначена для генерации задач, которые с помощью процедуры Vorker(Arg,indexl, 1ndex2) выполняются параллельно для всех комбинаций индексов indexl и index2, которые “пробегают" нижнюю “половину" массива размера Rangel на Range2. Эти индексы принимают следующие значения:
index2«0,indexl«l, ..., (Rangel-1)
1ndex2-l,indexl>2, ..., (Rangel-1)
1ndex2«R-2, indexl«(R-l), ..., (Rangel-1)
Где R - ein(Rangel, Range2)
Вызвавший процедуру GenOnHalfArray процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Перед первым вызовом системной программы Vorker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа intt(Arg). Когда процедура GenOnHAFull завершается, заканчивается выполнение всех сгенерированных задач.
• GenOnI
GenOnI (Vorker, Range) int (* Vbrker) (); Int Range;
Процедура GenOnI предназначена для генерации задач, которые с использованием процедуры Vorker(0, index) выполняются параллельно для всех значений индекса index в диапазоне 0 <« index < Range. Вызвавший процедуру GenOnI процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Когда процедура GenOnI завершается, выполнение всех сгенерированных задач должно быть закончено.
GenOnIAbortable
GenOnIAbortable(Vorker, Range)
int (* Vorker) ();
int Range;
Процедура GenOnlAbortable является версией процедуры GenOnI, допускающей преждевременное прекращение ее выполнения. Сгенерированные задачи с использованием процедуры Vorker(0, index, GenHandle) выполняются параллельно. Параметр GenHandle является идентификатором генератора, он может использо-
ваться процедурой AbortGen для прерывания соответствующего генератора. Операция GenOnlAbortable эквивалентна операции
6en0nIFu11(0, Worker, 0, 0, Range, 0, true)
Отметим, что процедура GenOnlAbortable возвращает значение, указывающее, была ли использована процедура AbortGen для прерывания работы генератора.
GenOnIFull
GenOnIFull(Init, Worker, Final, Arg, Range, Limited, Abortable) int ( Init) (), (* Worker) (), (* Final) ();
int Arg, Range, Limited, Abortable;
Процедура GenOnIFull предназначена для генерации задач, которые выполняются параллельно для всех значений индекса index в диапазоне о <- index < Range. При значении параметра Abortable, равном false, используется процедура Worker(Arg, index), а при значении этого параметра, равном true, используется процедура Worker(Arg, index, GenHandle). Вызвавший процедуру GenOnIFull процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач.
Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа Init(Arg). После того как на процессоре, предназначенном для выполнения задач, произведен последний вызов системной программы Worker, выполняется вызов системной программы Final (Arg). Для задания числа процессоров, используемых генератором, предусмотрен параметр Limited. Если параметр Limited равен 0 или -1, то число процессоров не ограничено. Положительное значение этого параметра определяет предельное число процессоров.
Когда процедура GenOnIFull завершается в связи с завершением выполнения всех сгенерированных задач (в этом случае процедура GenOnIFull возвращает значение genEXHAUSTED), либо в связи со значением true параметра Abortable, то работа генератора прерывается и некоторые задачи могут оказаться невыполненными (в этом случае процедура GenOnIFull возвращает код, передаваемый процедуре AbortGen).
Процедура GenOnlndex предназначена для генерации задач, которые с использованием процедуры Worker (Arg, index) выполняются параллельно для всех значений индекса index в диапазоне 0 <- index < Range. Вызвавший процедуру GenOnlndex процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа init(Arg). Когда процедура GenOnlndex завершается, выполнение всех сгенерированных задач должно быть закончено.
GenOnlndex*
6en0nIndexA(Init, Worker, Final, Arg, Range) int (* Init) (), (* Worker) (), (* Final) (); int Arg, Range;
Процедура GenOnlndexA предназначена для генерации задач, которые с использованием процедуры Worker (Arg, index) выполняются параллельно для всех значений индекса index в диапазоне 0 <- index < Range. Вызвавший процедуру GenOnlndexA процессор и, возможно, другие процессоры используются для выполнения сгенерированных задач. Перед первым вызовом системной программы Worker на каждом процессоре, предназначенном для выполнения задач, вызывается системная программа init(Arg). Когда процедура GenOnlndexA завершается, выполнение всех сгенерированных задач должно быть закончено.
GenTaskForEachProc
GenTaskForEachProc(Worker, Arg)
int (* Worker) ();
int Arg, NProcs;
Процедура GenTaskForEachProc генерирует для каждого процессора только одну задачу вида Worker(Arg).
GenTaskForEachProcLieited
GenTaskForEachProcL1mited(Worker, Arg, NProcs)
int (* Worker) ();
int Arg, NProcs;
Процедура GenTaskForEachProcLimited генерирует для каждого из NProcs процессоров точно одну задачу вида Worker (Arg).
Предупреждение: если ProcsInUse меньше, чем NProcs, то вызов “зависает".
GenTaskForMList
GenTaskFomL1st(Routine_L1st, Arg_L1st, n)
int * (* Routine_List, Arg_L1st, n)
int * Arg_List;
int n;
Процедура GenTaskForBList генерирует п задач; 1-я задача имеет вид ri(argl). Routine_L1st - список из п программ (rl, ... , rn), a Arg_Ltst - список из аргументов argl, .... argn.
GetRtc
GetRtcO
Процедура GetRtc возвращает значение времени с момента запуска системы (единица времени - 62.5 мкс).
InitializeUs
Initial1zeUs()
Процедура Initialized используется для инициализации системы Uniform System. Инициализация состоит в образовании и запуске на каждом доступном процессоре процесса Uniform System в определении глобальной памяти для совместного использования процессами системы Uniform System, инициализации программы распределения памяти системы Uniform System. Процедура initializeUs должна быть вызвана прежде, чем будет использована любая другая программа системы Uniform System, и должна быть вызвана только один раз.
InitialIzeUsForBenchMark
Initial1zeUsForBenchMark()
Процедура Initial IzeUsForBenchMark используется для инициализации системы Uniform System. Она подобна процедуре InitializeUs. Отличие состоит в том, что программа, если она стартовала не на главном узле, использует этот главный узел (посредством параметра -on включается либо команда run, либо утилита us). Такая возможность полезна, когда испытывается программа, и желательно, чтобы на измерения не оказывала влияния работа программ управления терминалом и окнами, которые выполняются на главном узле системы.
. LOCK
LOCK(lock, n) short * lock; int n;
Процедура LOCK устанавливает замок, специфицированный параметром lock. Предполагается, что переменная (short), на которую указывает lock первоначально (при инициализации), принимает значение 0. Процедура LOCK реализует блокировку типа "занятое ожидание". Переменная int п определяет интервал времени (в десятках микросекунд) между попытками установить замок. Если п равно 0, то по умолчанию длительность интервала равна 1 мс. Процедура LOCK не завершается до тех пор, пока замок не будет установлен. (См. процедуру UNLOCK.)
MakeSharedVariables
MakeSharedVariables;
Макрокоманда MakeSharedVariables используется для размещения в глобальной общей памяти переменных, описанных с помощью макросов BEGIH_SHARED_DECL и END_SHARED_DECL как глобальные совместно используемые переменные. Местонахождение этих переменных становится известным другим процессорам. Процедура MakeSharedVariables вызывается после вызова процедуры InitializeUs.
MesoriesAvaitable
MesoriesAvai 1аЫе()
Процедура MeeoriesAvailable возвращает целочисленное значение, равное емкости памяти, доступной прикладным программам. Единица измерения - 64 Кбайт.
PhysProcToUsProc
PhysProcToUsProc(PhysProc) int PhysProc;
Процедура PhysProcToUsProc возвращает число процессоров системы Uniform System, соответствующее числу физических процессоров (PhysProc).
ProcsInUse
ProcsInUse()
Процедура ProcslnUse возвращает число процессоров, доступных прикладной программе. В этом количестве не учитываются процессоры, удаленные с помощью процедуры TieeTest или TieeTestFull.
. SHARED
Макрокоманда SHARED используется как префикс переменных, описанных с помощью макросов BEGIN_SHARED_DECL и END_SHARED_DECL как глобальные совместно используемые переменные. Например, если N описана указанным способом, то для ссылок на нее можно использовать запись SHARED N:
SHARED N - SHARED N % 7;
Предупреждение: ссылки на такие переменные допустимы только после того, как для них выделена память с помощью процедуры MakeSharedVariables.
• Share
Share(N) 1nt * N;
Процедура Share предназначена для передачи значения, определяемого по > указателю N, всем процессорам, используемого для выполнения впоследствии ' генерируемых задач. N должен указывать на переменную, размещенную в собственной памяти процесса и описанную как глобальная или статическая. , Кроме того, эта переменная должна занимать в памяти 4 байта. Процедура | Share осуществляет пересылку значения, указанного N (в процессоре, вызыва- ! ющем процедуру Share, в это время вызывается процедура Share), по адресу, определяемому указателем N, в каждый процессор, используемый для выпол- ; нения задач, сгенерированных генераторами, активированными после вызова I процедуры Share.
. ShareBlk
ShareBlk(X, size)
1nt * X; 1nt size;
Процедура ShareBlk предназначена для передачи блока данных размера size байтов, адресуемого указателем х, всем процессорам, используемым для выполнения впоследствии генерируемых задач, х должен указывать на переменную, размещенную в собственной памяти процесса и описанную как глобальная или статическая. Процедура ShareBlk обеспечивает пересылку блока данных, адресуемого указателем х, (в процессоре, вызывающем процедуру ShareBlk, в это же время вызывается процедура ShareBlk) в память, начиная с адреса х, каждого процессора, использованного для выполнения задач, сгенерированных генераторами, активированными после вызова процедуры ShareBlk.
SharePtrAndBlk
SharePtrAndBlk(P, size) 1nt * * P;
1nt size;
Процедура SharePtrAndBlk передает указатель, определяемый по указателю р, и блок данных размера size байтов, на который указывает передаваемый указатель, всем процессорам, используемым для выполнения впоследствии создаваемых задач. Указатель р должен указывать на переменную-указатель, размещенную в собственной памяти процесса и объявленную как глобальная или статическая. Процедура SharePtrAndBlk обеспечивает пересылку указателя, определяемого по указателю Р, и блока данных, на который он указывает (в процессоре, вызывающем процедуру SharePtrAndBlk, в то же самое время вызывается процедура SharePtrAndBlk), каждому процессору, используемому для выполнения задач, сгенерированных генераторами, активированными после вызова процедуры SharePtrAndBlk. Эта процедура выполняется следующим образом: сначала в памяти процессора размещается область памяти, затем блок данных, определяемый по указателю, определенному указателем р, пересылается во вновь размещенную область памяти, указатель на вновь размещенную область памяти записывается в ячейку, определяемую указателем р.
о ShareScatterMatrix
ShareScatterMatr1x(P, nrows)
Int * * P;
Int nrows;
P указывает на глобальную или статическую переменную, размещенную с помощью процедуры
AllocScatterMatr1x(nrows, ncols, element_s1ze)
Процедура ShareScatterMatrix копирует вектор указателей строк, размещенных с помощью процедуры AllocScatterMatrlx в памяти каждого процессора, используемого для выполненя впоследствии сгенерированных задач. Затем устанавливается переменная, указываемая указателем р, на эту копию. Процедура ShareScatterMatrix функционально эквивалентна процедуре SharePtrAndBlk, однако выполняется гораздо быстрее, так как делает копии не только с оригинала, но и с других копий.
TlBeTest
T1meTest(In1t, Execute, PrlntResults) .
int (* In1t) (), (* Execute) (), (* PrlntResults) ();
Процедура TlmeTest обеспечивает определение времени выполнения программы Execute при различных конфигурациях процессора, определяемых пользователем с помощью ввода с клавиатуры. Процедура TimeTest обеспечивает запуск программ Init, Execute и PrlntResults последовательно на каждом процессоре определенной конфигурации. Определяется время выполнения только программы Execute и передается программе PrlntResults полученное время выполнения, число процессоров и эффективное число процессоров:
PrtntResults(t1me, procs, effprocs) 1nt time, procs;
float effprocs;
Эффективное число процессоров (тип float) определяется следующим образом:
(time 1 proc) / (time n procs)
Эта величина является хорошей мерой ускорения, достигаемого при выполнении программы Execute на п процессорах по сравнению с временем выполнения программы на одном процессоре. Если в первом тестовом прогоне используется более чем один процессор (к процессоров), то эффективное число процессоров равно
k * (time к proc) / (time n procs)
Программа PrlntResults специфицируется прикладной программой. Однородная системная библиотека содержит программу (см. ниже программу T1msTestPr1nt), которая может использоваться для этой цели. Но пользователь может разработать собственную программу печати.
Процедура TimeTest предлагает пользователю специфицировать конфигурации процессора. При этом задается начальное число процессоров (start), шаг (delta) и конечное число процессоров (end). Первый прогон выполняется на start процессорах, затем используется start4delta процессоров и т.д. до последнего прогона, в котором используется end процессоров. Если значение start или end равно 0, то для тестового выполнения программы используются все возможные количества процессоров из имеющегося в распоряжении количества процессоров. В частности, выполняется прогон для предельного значения доступного количества процессоров независимо от того, получается ли это значение в процессе прибавления шага delta. Если значение delta равно О, то число используемых процессоров растет как степень 2 (1, 2, 4, 8 и т.д.). Правила использования чисел start и end сохраняются.
TimeTestFull
TimeTestFull(Init, Execute, PrintResults, start, delta, end) int ( Init) (), (* Execute) (), (* PrintResults) ();
int start, delta, end;
Процедура TimeTestFull подобна процедуре TimeTest (см. выше). Различие состоит только в том, что конфигурации процессора определяются параметрами start, delta и end, а не вводятся с помощью клавиатуры. Однако если delta имеет отрицательное значение, то в этой процедуре в начале прогона пользователю также предлагается ввести значения start, delta и end.
TimeTestPrint
TimeTestPrint(runtime, procs, effprocs) int runtime, procs;
float effprocs;
Процедура TimeTestPrint используется процедурами TimeTest или TimeTestFull для печати временных характеристик прогона программы для конкретной конфигурации процессора. Печатается время выполнения, число использованных процессоров, эффективное число использованных процессоров (это число равно ускорению, достигаемому при использованиии нескольких процессоров) и эффективность использования процессоров в данной конфигурации. В процедуре TimeTestPrint эта информация выводится в следующем формате:
[procs] time ■ runtime
ticks ■ S sec; ep « effprocs; eff ■ E, где E ■ effprocs/procs.
(См. процедуры TimeTest и TimeTestFull.)
UNLOCK
UNLOCK(lock) short * lock;
Процедура UNLOCK снимает блокировку, специфицированную параметром lock. (См. процедуру LOCK.)
UsProcToPhysProc
UsProcToPhysProc(UsProc)
Int UsProc;
Процедура UsProcToPhysProc возвращает физическое число процессоров, соответствующее числу процессоров системы Uniform System (UsProc).
» UsSetClass
UsSetClass(proc, class) tnt proc, class;
Процедура UsSetclass переводит память специфицированного процессорного узла в заданный класс class. Первоначально память каждого процессорного узла входит в класс 0. См. также процедуры AllocateC, AllocScatterMatrlxC, Al locateOnUsProcC.
. UsWatt
UsVait(n) int n;
Процедура UsVait обеспечивает задержку на 10*n мкс. Если значение п равно О, то по умолчанию задержка равна 1 мс. Эта процедура реализует задержку типа “занятое ожидание".
WattForTasksToFinish
Wa 1 tForTasksToF1n i sh(GenHandle)
UsGenDesc * GenHandle;
Процедура WattForTasksToFinish обеспечивает задержку до тех пор, пока генератор задач, специфицированный параметром GenHand 1е, не завершит свою работу. Параметр GenHand 1е должен специфицировать некоторый асинхронный генератор задач, активизируемый вызывающим процессом. Процедура WattForTasksToFinish возвращает значение (код результата генератора), которое указывает, завершил ли генератор работу или его работа была прервана процедурой AbortGen.
WorkOn
WorkOn(GenHandle) UsGenDesc * GenHandle;
Процедура WorkOn обеспечивает выполнение задач, созданных генератором задач, специфицированным параметром GenHandle. Параметр GenHandle должен специфицировать некоторый асинхронный генератор задач, активизируемый вызывающим процессом. Процедура WorkOn возвращает значение (код результата генератора), которое указывает, завершил ли генератор работу или его работа была прервана процедурой AbortGen.
Приложение С
CRAY Х-МР
Содержание
С1. Распечатка программы для вычисления числа Пи (версия V2) 222
С2. Извлечения из руководства по использованию многозадачного режима в операционной системе CTSS ("NPDOC - CTSS Multiprocessor Support”) 224
С2.1. Введение 224
С2.2. Структура задачи 225
Очередь готовых к выполнению задач 226
Образование задачи 226
Переключение задач 227
Завершение задачи 228
Свзь между задачами 228
С2.3. Подпрограммы для организации многозадачного режима работы 228
С2.4. Управление блокировками 229
LOCKASGN - Образовать идентификатор новойблокировки 229
LOCKOFF - Снять блокировку 230
LOCKON - Установить блокировку 230
LOCKPARM Установить параметры программы управления блокировками 231
LOCKREL - Освободить идентификатор блокировки 231
LOCKSET - Установить блокировку 232
LOCKSTAT - Получить статус блокировки 232
LOCKTEST - Проверить и установить блокировку 233
LOCKTSET - Проверить и установить блокировку 234
Список ошибок, обнаруживаемых программой управления блокировками 234
С2.5. Программа управления событиями 235
EVASGN - Образовать идентификатор нового события 235
EVCLEAR - сбросить событие 235
EVPARM - Установка параметров администратора событий 236
EVPOSCLR - Установить и сбросить событие 236
EVPOST - Установка события 237
EVREL - освобождение идентификатора события 237
EVSTAT - Получить статус события 237
EVTEST - Проверить состояние события 238
EVWAIT - Хдать установки события 238
Список ошибок, обнаруживаемых администратором событий 239
С2.6. Управление задачами (Администратор задач) 239
TSKPARM - Установить параметры администратора задач 239
TSKSTART - Образовать и запустить новую задачу 240
TSKSTAT - Получить статус задачи 241
TSKTERM - Завершить выполнение задачи (но не программы) 242
TSKTEST - Проверить факт завершения задачи 242
TSKTIME - Получить временную информацию о задаче 243
TSKVALUE - Получить слово-значение задачи для вызывающей задачи 243
TASKWAIT - Хдать завершения задачи 243
Список ошибок, обнаруживаемых программой управления задачами 244
Cl. Распечатка программы для вычисления числа Пи (версия V2)
1
2
3
4
5
6
7
8
9
10
REALSUM
INTEGERTL(4), INTRVLS, NTASKS, TLOCK
COMMON SUM ,TL ,INTRVLS , NTASKS , TLOCK
INTEGERLSTAT
REALPI
C INTEGERITC(3,4)
EXTERNAL TASK C CALL LINK(’UNIT59-TTY//’)
И 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
PI « 3.14159265358979323846433E0 CALL LOCKASGN (TLOCK,LSTAT) C C *** УСТАНОВКА МАССИВОВ УПРАВЛЕНИЯ ЗАДАЧАМИ И *** С *** ЗАПУСК ЗАДАЧ***
DO 15 I - 1 , 4 ITC(1,I) « 3 С *** ЗАПИСЬ НОМЕРА ЗАДАЧИ В МАССИВ УПРАВЛЕНИЯ ЗАДАЧЕЙ *** 1ТС(3,1) « I 15 CONTINUE С 100 WRITE(59,*) ’ENTER INTERVALS & TASKS (UP TO 4) 0->END’ READ (59,*,END - 10 , ERR -10 ) INTRVLS,NTASKS WRITE(59,*) ’INTERVALS - ’, INTERVLS VRATE(59,*) ’NUMBER OF TASK » ’ , NTASKS SUM - 0.0E0 C *** ЦИКЛ ПОКА ЧИСЛО ИНТЕРВАЛОВ НЕ РАВНО 0 ***
IF ( INTRVLS .EQ. 0 ) GO ТО 999 С С *** РЕГИСТРАЦИЯ ВРЕМЕНИ НАЧАЛА ВЫЧИСЛЕНИЙ *** С
ISTART - IRTC() С С *** ЗАПУСК ВСЕХ ЗАДАЧ *** С
DO 50 I - 1 , NTASKS CALL TSKSTART ( ITC(1,I) , TASK ) 50 CONTINUE C *** ОЖИДАНИЕ ЗАВЕРШЕНИЯ КАЖДОЙ ЗАДАЧИ *** DO 60 I « 1 , NTASKS CALL TSKWAIT ( ITC(1,I) ) 60 CONTINUE C
44
С *** РЕГИСТРАЦИЯ ВРЕМЕНИ ЗАВЕРШЕНИЯ ВЫЧИСЛЕНИЙ ***
45
46
47
48
49
50
51
52
10
с с ***
IEND - IRTCO
WRITE(59,*) ’ TASKS COMPLETED '
VRITE(59,*) * PI COMPUTED ■ * , SUM
WRITE(59,*) * ERROR AMOUNT - ', PI - SUM
WRITE(59,*) * CPU TIME -', TL
WRITE(59,*) 'ELAPSED WALL CLOCK TAME - ', IEND - ISTART
ПЕРЕХОД К ВВОДУ СЛЕДУЮЩИХ ЗНАЧЕНИЙ INTRVLS И NTASKS ***
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
с
с 999
С
с ***
с
с
с
с *** с
с
10 с
с ***
с
60 ТО 100
CONTINUE CALL EXIT END
SUBROUTINE TASK
ЗАДАЧИ 1-4 *** REALSUM
INTEGERTL(4), INTRVLS , NTASKS , TLOCK
COMMON SUM , TL .INTRVLS , NTASKS , TLOCK
REALLSUM
INTEGERMEI
INTEGERTL1(3)
INTEGERI
REALH , F ,X
F(X) - 4.0E0 / ( l.OEO- X**2 )
ТОЧКА ВХОДА ЗАДАЧИ ***
CALL TSKVALUE (MEI) H - l.OEO / INTRVLS LSUM - O.OEO INTEND - INTRVLS / 2
DO 10 I - MEI. INTEND, NTASKS LSUM - LSUM + F((I-0.50E0)*H) + F((I+INTEND-0.50E0)*H)
CONTINUE
LSUM - LSUM * H ПРИБАВЛЕНИЕ РЕЗУЛЬТАТА К СУММЕ *** CALL LOCKON (TLOCK) SUM - SUM + LSUM
CALL LOCKOFF (TLOCK)
91CALL TSKTIME (TL1 , 3 )
92TL(MEl) - TL1(1)
93RETURN
94 C
95END
C2. Извлечения из руководства по использованию многозадачного режима в операционной системе CTSS1) ("MPDOC - CTSS Multiprocessor Support")
НАЗНАЧЕНИЕ* MPDOC - руководство по использованию многозадачного режима, дающее описание многозадачной среды, в которой реализуется выполнение программы. Многозадачная среда включает в себя процедуры редактирования связей, библиотеки, утилиты, средства управления памятью, подпрограммы реализации многозадачного режима и модификацию операционной системы, необходимой для поддержки многозадачного режима работы.
ПРИМЕНЕНИЕ: Многопроцессорные системы фирмы Cray Research Inc.
С2.1. Введение
Переход к многопроцессорным архитектурам влечет за собой существенные изменения в принципах и процедурах разработки программ. Сейчас коммерчески доступны машины с небольшим числом процессоров. Машины с большим числом процессоров, вероятно, будут доступны пользователю в 1988 - 1990 гг. Наша задача состояла в разработке подхода к созданию программного обеспечения, которое должно эффективно поддерживать функционирование многопроцессорных систем в указанный период. Для сравнения можно отметить, что потребовалось более пяти лет активных усилий по разработке программного обеспечения для поддержки и применения техники векторной обработки. Реализация средств поддержки и применения параллельных вычислений на многопроцессорной системе значительно сложнее.
Для архитектуры рассматриваемой вычислительной машины характерно, что она состоит из нескольких одинаковых процессоров, использующих общую основную память. В данном случае не рассматриваются средства поддержки распределенных вычислений в распределенной вычислительной системе. Предполагается, что в системе с такой архитектурой также должны быть реализованы механизм для обеспечения доступа к общим данным по принципу взаимного исключения и возможность одному процессору прерывать или останавливать работу любого другого процессора. Программная среда в такой машине строится на основе программного обеспечения для однопроцессорной системы, расширяемого средствами параллельного программирования с целью достижения эффективного использования многопроцессорной системы.
Процессоры используются наиболее эффективно, когда затраты времени на организацию их применения к программе пользователя малы по
^ В руководстве MPDOC описываются включенные в операционную систему CTSS (Cray Time Sharing System - система разделения времени для суперЭВМ фирмы Cray Research Inc.), средства поддержки многозадачного режима, реализованные в Национальном вычислительном центре MFE. Эта версия MPDOC от 13 февраля 1986 г.
сравнению с временем решения задачи. Ускорение вычислений достигается тогда, когда действительно удается использовать несколько процессоров для решения одной задачи. С точки зрения системы с целью получения более высокой полной производительности назначение доступных процессоров для выполнения программ одна за другой представляет собой наилучшую стратегию при отсутствии другой информации, которая позволила бы использовать эти процессоры более эффективно. Для поддержки многопроцессорной системы на этой основе достаточна незначительная модификация операционной системы CTSS и нет необходимости в изменении большого объема основного программного обеспечения. Теперь представим небольшой набросок работ, которые необходимо выполнить для достижения этого.
При небольшом числе процессоров рабочая нагрузка естественно больше или равна числу процессоров и стратегия планирования оказывает влияние на пропускную способность (производительность); возможность применить более одного процессора к некоторой программе мала. Такая возможность появляется главным образом для работ, требующих всю или почти всю память. Однако мы предполагаем, что число процессоров относительно размера памяти будет в будущем возрастать так, что возможность применения нескольких процессоров для выполнения одной программы будет возрастать. Необходимо сделать существенные изменения в программных средствах поддержки и в прикладных программах. Проектирование таких средств - основная тема этой статьи.
Для обеспечения поддержки работы многопроцессорных систем необходимо совместное планирование создания ряда сложных взаимозависимых средств. К ним относятся операционная система, компилятор, библиотеки, программы управления памятью, основные утилиты и прикладные программы.
С2.2. Структура задачи
Задача - это программная единица, которая может быть независимо назначена для выполнения на одном процессоре. Все задачи программы совместно используют одну и ту же область памяти, однако каждая задача имеет и собственную среду. Структурно задача состоит из стека и дескриптора задачи. Память для них выделяется из динамической области при образовании задачи. Дескриптор задачи содержит область сохранения состояния задачи, стандартную пару указателей для включения задачи в очередь, в которой задача переходит в состояние ожидания, область системного вызова и другую информацию о задаче. Задаче необходим быстрый доступ к ее дескриптору, следовательно, метод доступа должен с необходимостью зависеть от выбора соглашений о связях и организации стека.
Был принят и всюду использовался стандартный механизм организации очередей, который стал базовым атрибутом структуры задачи. Упомянутая выше стандартная пара указателей для образования очередей обозначена как (TaskQpJaskQn). Элементы пары - указатели (абсолютные адреса) предыдущего и последующего элементов очереди (или начала очереди) соответственно. Каждая очередь задач имеет начало (голову очереди), которая одинакова для всех очередей.
Условие
TaskQp(qhead)*TaskQn(qhead)«qhead
соответствует пустому состоянию всех очередей задач. Очереди задач представляются двунаправленными связанными списками. Разработчики средств
поддержки многозадачного режима обеспечили стандартный механизм управления очередями, позволяющий в любое время перевести задачу в состояние ожидания.
Очередь готовых к выполнению задач
Очередь готовых к выполнению задач (RunQ) - это очередь задач, которые готовы к выполнению, но в данное время не назначены на процессоры. Задачи добавляются к этой очереди по мере их образования или при ожидании наступления некоторого события (например, завершения системного вызова, предоставления права исключительного доступа и др.). Начало очереди RunQ размещается в ячейке с известным системе абсолютным адресом. Для этого используется слово с абсолютным адресом 3. Когда очередь пуста, то RunQfp(3)-RunQbp(3)-3. Первоначально очередь содержит корневую задачу, которую устанавливает загрузчик.
Образование задачи■
Начальная задача образуется загрузчиком с помощью команды LDR операционной системы CTSS. Следовательно, все программы первоначально состоят из одной и только одной задачи. Однако эта и все последующие задачи могут в любой момент их существования образовать некоторое число других задач. Все существующие в данное время задачи логически выполняются параллельно и могут выполняться параллельно физически в различные периоды существования программы. Объем действительно параллельных вычислений зависит от текущей загрузки машины, числа физических процессоров и от того, как много задач, готовых к запуску, находится в системе, когда процессоры становятся доступными.
Быстрота образования и завершения задач и скорость переключения с задачи на задачу являются главными факторами, определяющими, может ли эффективно использоваться многозадачный режим. Образование задачи и управление ею требует дополнительной памяти и затрат времени центрального, процессора. Следовательно, использование небольшого числа задач может привести к росту эффективности. Так как активно не может выполняться больше задач, чем число процессоров, для суперЭВМ CRAY Х-МР/22 оптимальным количеством будут две задачи, а для суперЭВМ CRAY-2 - четыре задачи. Образование большого числа дополнительных задач не приводит к полезным результатам.
Алгоритм образования задач состоит из следующих шагов:
Выделить память для дескриптора задачи в динамически распределяемой области.
выделить в динамической области память для стека.
инициализировать поля в дескрипторе задачи.
инициализировать стек. При инициализации производится включение в стек фиктивной (пустой) записи активации для подпрограммы TSKTERM, предназначенной для неявного завершения задачи, если произойдет возврат из начальной задачи.
включение нового дескриптора задачи в очередь RunQ с целью ее выполнения, когда процессор станет доступным.
Переключение задач
Способность быстрого переключения задач - одно из основных свойств, от которого зависит успешность многопроцессорной обработки, так как быстрота переключения непосредственно определяет величину накладных расходов, связанных с использованием параллелизма. Как уже говорилось, желательно, чтобы пользователи имели возможность специфицировать большое количество параллельных задач, не слишком беспокоясь о накладных расходах, обусловленных спецификацией большего числа задач, чем число физических процессоров. Несмотря на то что накладные расходы все-таки существуют, они могут быть весьма незначительными: в действительности расходы эти соизмеримы с затратами времени на обычное обращение (вызов и возврат) к подпрограмме. Описанный здесь механизм - сердце многозадачного режима.
Вызов подпрограммы Q8WAIT1) выглядит аналогично любому другому вызову. Следовательно, весь контекст перед вызовом подпрограммы Q8WAIT размещается в памяти или в В- и Т-регистрах. Сюда включается контекст всех векторных регистров. Таким образом, для сохранения полного состояния задачи необходимо при входе сохранять только все В- и Т-регистры. Блокировки “ведут себя хорошо44, так как они защищают лишь выполнение соглашения о связях, длительность которого очень коротка. Следовательно, все процессоры в конечном счете становятся доступными в течение конечного (короткого) отрезка времени. Независимо от соглашений о связях в стеке должно быть доступно место для сохранения состояния. В соответствии с существующими соглашениями о связях для последовательности входа в подпрограмму предусмотрен буфер переполнения. Таким образом, последовательность входа в подпрограмму и сохранение состояния совместно используют эту область и для сохранения состояния дополнительной памяти не требуется.
Попытка отделить управления очередями от механизма переключения контекстов может привести к проблеме “состязаний44, преодолеть которую можно лишь структурированием и усложнением механизма очередей. Например, если принять соглашение о том, что вызывающая программа из очереди обращается к подпрограмме Q8WAIT (без параметров), которая просто должна временно сохранить содержимое регистров В и Т, то после этого возможен выход задачи из очереди до завершения переключения. Чтобы избежать этого, необходимо предусмотреть возможность включения информации типа “меня брать нельзя44, которая должна устанавливаться во время управления очередью в критической области, безусловно сбрасываться подпрограммой Q8WAIT после сохранения состояния и проверяться при каждой попытке выхода из очереди. Это должно привести к усложнению конструкции всех средств многозадачного режима. Представленный же выше механизм обладает “концентрированной44 сложностью, и на его основе можно строить все другие средства синхронизации.
Архитектура большинства многопроцессорных вычислительных систем предусматривает аппаратные средства синхронизации, базирующиеся на ис-
Ц Системная интерфейсная подпрограмма Q8WAIT задерживает выполнение текущей задачи и, если это возможно, запускает другую задачу из очереди задач RunQ. Если в очереди задач RunQ нет задач пользователя, то подпрограмма Q8WAIT выполняет обмен с операционной системой, чтобы предоставить ей центральный процессор. Подпрограмма Q8WAIT является внутренней программой библиотеки многозадачного режима MULTILIB и не может вызываться непосредственно из программ пользователя.
пользовании памяти (физические замки на ячейках памяти, семафоры, реализованные в адресном кэше или в структуре памяти). Архитектура многопроцессорных вычислительных систем фирмы Cray Research Inc. имеет несколько аппаратных одноразрядных семафоров, которые не допускают произвольной установки. Следовательно, управление аппаратными семафорами может быть трудоемким. Наша же схема нацелена на универсальные многопроцессорные архитектуры, с тем чтобы ее реализация была действительно пор- табельной. Использования одиночных глобальных семафоров вполне достаточно для любых многопроцессорных систем.
Завершение задачи
Для завершения задачи используется подпрограмма TSKTERM. В результате завершения задачи производится освобождение всех сегментов стека, связанных с этой задачей. Следовательно, алгоритм освобождения проектируется так, чтобы он был в состоянии освободить всю свою собственную память.
Связь между задачами
Все задачи одной и той же программы используют одну и ту же область памяти. Таким образом, наиболее эффективным методом связи задач друг с другом является непосредственное использование памяти. Система не поддерживает и не запрещает обмен сообщениями между задачами одной программы. Если некоторая задача желает связаться с другой, внешней к этой программе задачей, она может это сделать, используя стандартные системные средства передачи сообщений и включить в передаваемое сообщение свой собственный протокол.
С23. Подпрограммы для организации многозадачного режима работы
Рассматриваемые ниже подпрограммы для поддержки многозадачного режима были разработаны фирмой Cray Research Inc. (CRI). Мы также реализовали эти программы, но с некоторыми изменениями и расширениями. Эти средства в своей основе являются экспериментальными, поэтому их оценка нуждается в широкой апробации.
Соглашения о выборе имен:
А. Число символов в имени не должно превышать восьми. Б. Имена имеют следующий вид: <префикс><мнемоника> С. Следующие префиксы связаны с наименованиями управляющих программ
EV : Программа управления событиями
LIB : Администратор библиотеки
LOCK : Программа управления блокировками
MZ : Программа управления памятью
NZ : Программа управления прерываниями $STK : Программа управления стеком TSK : Администратор задач (Программа управления задачами)
По возможности будем следовать соглашениям о выборе имен, соглашениям о связях и функциональным возможностям фирмы Cray Research Inc. Расширения реализуются как вызовы подпрограмм.
Соглашения фирмы Cray Research Inc. о многозадачном режиме для суперЭВМ CRAY Х-МР и их использование в Национальном вычислительном центре MFE вызывают некоторые затруднения:
А. Не могут использоваться метки альтернативных возвратов (ALTERNATIVE RETURN LABELS), поскольку в компиляторах CIVIC0 и CFT метки операторов передаются различными способами, которые полностью несовместимы. Вместо передачи меток мы возвращаем некоторую целочисленную переменную, указывающую, успешно или нет завершилось выполнение подпрограммы.
Б. Не используются логические функции и значения, поскольку в компиляторах CIVIC и CFT применяются различные способы для представления значения .TRUE.. Может быть реализована некоторая целочисленная функция, которая возвращает значение 0 (соответствует значению .FALSE.) и -1 (для значения .TRUE.), однако вместо нее мы используем подпрограмму, которая возвращает целочисленное значение 0 или +1, для указания соответствующе- . го условия.
* В. Функция языка Фортран должна иметь имя, которое начинается с одной из букв от I до N, с тем чтобы сделать функцию неявно целочисленной. Поскольку используется соглашение о выборе имен, функция управления событиями (EVENT) и функция управления задачами (TASK) будут неявно вещественными. В связи с этим требуется, чтобы пользователь явно описывал эти функции в тех подпрограммах, где они используются. Так как это оказалось главным источником ошибок, было решено преобразовать все целочисленные функции в подпрограммы, к параметрам которых был добавлен целочисленный аргумент, используемый для получения значения, которое раньше возвращалось как значение функции.
С2.4. Управление блокировками
LOCKASGN - Образовать идентификатор новой блокировки
Подпрограмма LOCKASGN предназначена для образования идентификатора новой блокировки, используемой для синхронизации. Если при проверке, выполняемой подпрограммой LOCKASGN, оказывается, что переменная lockvar содержит корректный идентификатор блокировки, то этот вызов эквивалентен пустому оператору (значение переменной lockvar при этом не изменяется). Если это не так, то образуется новая блокировка и ее идентификатор (ID) присваивается переменной lockvar. Эта подпрограмма должна вызываться до того, как переменная lockvar будет использоваться в других подпрограммах, применяющих блокировки.
Обращение к подпрограмме LOCKASGN)
CALL LOCKASGN(lockvar,asgnstat)
lockvar :«: Целочисленная переменная, значением которой является идентификатор блокировки.
asgnstat :« Статус вызова подпрограммы LOCKASGN.
CIVIC - это компилятор с языка LRLTRAN расширенной версии Фортрана 77. ОС CTSS и ее утилиты написаны на языке LRLTRAN.
Замечания:
asgnstat .EQ. 0 если при вызове образована новая блокировка и зарегистрирован ее идентификатор
.EQ. 1 если во время вызова подпрограммы переменная lockvar уже содержала корректный идентификатор блокировки
В исходном состоянии блокировка снята (имеет значение OFF).
LOCKOFF - Снять блокировку
Подпрограмма LOCKOFF используется для снятия блокировки и передачи управления вызвавшей подпрограмме. Снятие блокировки может позволить некоторым другим задачам продолжить выполнение, но это скрыто от задачи, вызвавшей подпрограмму LOCKOFF..
Обращение к подпрограмме
CALL LOCKOFF(lockvar)
lockvar : целочисленная переменная, содержащая идентификатор снимаемой блокировки
Замечания:
При попытке снять блокировку, не установленную этой задачей, возникает фатальная ошибка.
LOCKON - Установить блокировку
Подпрограмма LOCKON используется для установки блокировки и возврата в вызвавшую программу. Если блокировка уже установлена, то задача задерживается до тех пор, пока эта блокировка не будет снята другой задачей, после чего она может быть установлена задержанной задачей. В любом случае блокировка устанавливается задачей, когда она продолжает выполнение программы пользователя.
Кетда блокировка установлена, идентификатор задачи, установившей блокировку, связывается с этой блокировкой. Такая связь рассматривается как право владения блокировкой, т. е. владельцем блокировки является задача ID - единственная задача, которая может снять блокировку (она установила блокировку и только она может ее снять).
Обращение к подпрограмме
CALL LOCKON(lockvar)
lockvar : целочисленная переменная, значением которой является идентификатор блокировки, которая должна быть установлена.
Замечания:
При попытке повторно установить уже установленную этой задачей блокировку возникает фатальная ошибка.
LOCKPARM - Установить параметры программы управления блокировками
Подпрограмма LOCKPARM изменяет значения параметров, которые управляют выполнением программы управления блокировками.
Обращение к подпрограмме
CALL LOCKPARM (рагв11 st tnu«parni)
panelist : массив целочисленных значений параметров, подлежащих изменению
nusparm : число слов/параметров, передаваемых в массив parmlist (это число должно быть больше нуля)
Замечания:
Если parmlist(i) « -2, то i-й параметр принимает по умолчанию исходное значение.
Если panelist(1) - -1, то значение i-го параметра остается неизменным.
Параметры в массиве имеют следующий смысл:
parmlist(l) - Флаг отладочного режима программы управления блокировками.
- О, выполняется минимальная проверка аргументов
« 1, выполняется максимальная проверка аргументов panelist(2) - Начальное число дескрипторов блокировок, размещаемых, когда выполняется первый вызов подпрограммы LOCKASGN panelist(3) - Следующее число дескрипторов блокировок, выделяемых тогда, когда больше нет свободных дескрипторов и требуется еще один, чтобы удовлетворить вызов подпрограммы LOCKASGN.
По умолчанию параметры программы управления блокировками имеют следующие значения:
pamlist(l) ■ 1
parmlist(2) ■ 10 parmlist(3) ■ 10
LOCKREL - Освободить идентификатор блокировки
Подпрограмма LOCKREL используется для освобождения блокировки, связанной с заданным идентификатором блокировки. Переменной, содержащей идентификатор блокировки, присваивается нулевое значение. Если делается попытка использовать уже освобожденную переменную блокировки или копию исходного идентификатора блокировки, то происходит ошибка. Переменная блокировки может быть использована повторно другим вызовом подпрограммы LOCKASGN. Освобождение блокирорвки эквивалентно ее уничтожению.
Обращение к подпрограмме
CALL LOCKREL(lockvar)
lockvar :■; Целочисленная переменная, содержащая идентификатор блокировки (при возврате устанавливается значение 0)
Замечания:
Фатальная ошибка возникает при обращении к уже освобожденной блокировке, т.е. при передаче идентификатора блокировки другой подпрограмме управления блокировками.
Фатальная ошибка возникает при попытке освободить блокировку, являющуюся собственностью другой задачи.
Фатальная ошибка возникает при попытке освободить блокировку, по которой выполняет ожидание другая задача.
LOCKSET - Установить блокировку
Подпрограмма LOCKSET работает подобно подпрограмме LOCKON, но отличается тем, что не завершается с ошибкой, если делается попытка установить блокировку, которая уже установлена, и что она возвращает информацию о состоянии соответствующей блокировки.
Обращение к подпрограмме
CALL LOCKSET(lockvar,lockstat,lockownr)
lockvar : целочисленная переменная, содержащая идентификатор блокировки lockstat :- состояние блокировки на момент вызова CALL LOCKSET
(либо установлена, либо свободна)
.EQ.-1 блокировка была собственностью другой задачи
.EQ. О блокировка была сброшена ранее
. EQ. 1 блокировка была собственностью этой задачи lockownr :- Идентификатор задачи, владеющей блокировкой в момент, когда был сделан вызов CALL LOCKSET.
Если блокировка не установлена, то lockownr - 0.
Замечания:
Значения lockstat и lockownr устанавливаются после того, как задача установливает блокировку, т.е. блокировка lockvar защищает также аргументы lockstat и lockownr.
LOCKSTAT - Получить статус блокировки
Подпрограмма LOCKSTAT возвращает статус специфицированной блокировки. Статус определяет, установлена или сброшена данная блокировка, идентификатор (Ю) задачи, владеющей блокировкой, если блокировка установлена и если на ней есть ждущие задачи.
Обращение к подпрограмме
CALL LOCKSTAT(lockvar,statlock,lenstat,maxstat)
lockvar : целочисленная переменная, содержащая идентификатор блокировки statlock :■ целочисленный массив, содержащий информацию о
состоянии, связанном со специфицированной блокировкой
lenstat :- число слов в массиве statlock
■xstat : максимальное число слов, возвращаемых в массиве statlock (число слов в массиве должно быть не меньше, чем Maxstat)
Замечания:
Элементы массива statlock имеют следующий смысл: statlock(l) указывает установлена, блокировка или сброшена statlock(2) идентификатор ID задачи, владеющей блокировкой (если такая имеется) statlock(3) указывает, ждут ли другие задачи, когда эта блокировка станет доступной.
Возвращаемые значения элементов массива statlock имеют следующий смысл:
statlock(l) - -1, если блокировка установлена другой задачей - О, если блокировка не установлена - 1, если блокировка установлена этой задачей statlock(Z) - .0, если блокировка не установлена - 1 идентификатор (ID) задачи, владеющей блокировкой, если блокировка установлена statlock(3) - 0, если блокировка не установлена - 0, если блокировка установлена и нет задач, ждущих на этой блокировке - 1, если блокировка установлена и одна
или несколько задач ждут на этой блокировке
LOCKTEST - Проверить и установить блокировку
Подпрограмма LOCKTEST выполняет операцию “проверить и установить" на специфицированной блокировке. Возвращается значение, равное о, если блокировка была установлена этой задачей и значение +1, если блокировка была установлена до обращения к этой подпрокрамме. Если блокировка ранее не была установлена, то она устанавливается. При выполнении этой подпрограммы ожиданий не происходит.
Обращение к подпрограмме
CALL LOCKTEST(lockvar,lockstat)
lockvar : целочисленная переменная, содержащая идентификатор блокировки. Идентификатор блокировки состоит из двух частей: в одной содержится уникальный идентификатор блокировки, который никогда не используется повторно, и индекс дескриптора блокировки, который указывает дескриптор блокировки, поддерживаемый программой управления блокировками.
lockstat :- Статус идентификатора блокировки (установленной
или неустановленной)
.EQ. 0 блокировка была предварительно сброшена и теперь является собственностью задачи
.EQ. 1 блокировка была собственностью этой или другой задачи
Замечания:
Возникает фатальная ошибка, если подпрограмма LOCKTEST выполняется на блокировке, являющейся собственностью этой задачи (см. подпрограмму LOCKTSET).
LOCKTSET - Проверить и установить блокировку
Подпрограмма LOCKTSET выполняется аналогично подпрограмме LOCKTEST, но отличается тем, что приводит к ошибке, если задача пытается проверить блокировку, которая уже была установлена. Кроме того, она возвращает дополнительную информацию о состоянии соответствующей блокировки.
Обращение к подпрограмме
CALL LOCKTSET(lockvar,lockstat,lockownr)
lockvar : целочисленная переменная, содержащая идентификатор блокировки
lockstat :- Статус идентификатора блокировки (установленной или неустановленной)
.EQ.-1 блокировка была собственностью другой задачи
.EQ. О блокировка была предварительно сброшена
.EQ. 1 блокировка была собственностью этой задачи
lockownr :-: Идентификатор (ID) задачи, владеющей
блокировкой. Если блокировка не установлена, то lockownr - 0.
Замечания:
Если блокировка была собственностью этой задачи до вызова данной подпрограммы, то lockstat при возврате принимает значение 1, a lockownr устанавливается равным идентификатору этой задачи (см. подпрограмму Q8TASKID).
Список ошибок, обнаруживаемых программой управления блокировками
- Аргумент уже содержит допустимый идентификатор блокировки
- Некорректное число аргументов, передаваемых подпрограмме
» Адрес аргумента меньше 200 (восьмеричное значение)
» Адрес аргумента превышает длину поля (>FLL)
- Аргумент не является допустимым идентификатором блокировки
- Аргумент, который должен содержать идентификатор блокировки, вместо этого содержит допустимый идентификатор события или задачи
- Аргумент не содержит осмысленного значения
- Некоторый элемент списка параметров не имеет осмысленного значения
- Недопустимая операция с блокировкой, которой владеет другая задача
- Операция некорректна, если другие задачи ждут на этой блокировке
- Свободный дескриптор “событие/блокировка" установлен неправильно
- Попытка повторной установки блокировки, которая уже является собственностью этой задачи
- Попытка снять блокировку, которая не была установлена этой задачей
- Попытка снять неустановленную блокировку
» Адрес дескриптора задачи из очереди RunQ меньше 200 (восьмеричное значение)
- Адрес дескриптора задачи из очереди RunQ превышает длину поля (>FLL)
- Попытка повторной установки аппаратного семафора
• Попытка открыть неустановленный аппаратный семафор
- Ошибка MZEALLOC - недостаточно памяти, распределенной для подпрограммы
- В дескрипторе блокировки обнаружено некорректное условие ожидания в очереди
С2.5. Программа управления событиями
EVASGN - Образовать идентификатор нового события
Подпрограмма EVASGN образует идентификатор нового события, используемого для управления задачами. Если переменная eventvar содержит правильный идентификатор события тогда, когда подпрограмма EVASGN оценивает его, то вызов этой подпрограммы рассматривается как пустой оператор (при этом значение переменной eventvar не изменяется). В противном случае производится создание нового события и запись его идентификатора (ID) как значения переменной eventvar. Эта подпрограмма должна вызываться до того, как переменная eventvar будет использована любой другой подпрограммой управления событиями.
Обращение к подпрограмме
CALL EVASGN (eventvar, asgnstat)
eventvar :-: целочисленная переменная, которой присваивается идентификатор нового события
asgnstat :- целое число - статус вызова подпрограммы EVASGN
Замечания:
asgnstat - 0, если при этом вызове было образовано новое событие и проведена регистрация его идентификатора
asgnstat - 1, если на момент вызова подпрограммы переменная eventvar уже содержала правильный идентификатор события
Первоначальное состояние (статус) события - CLEAR (не установлено)
EVCLEAR - сбросить событие.
Подпрограмма EVCLEAR сбрасывает событие и передает управление вызвавшей задаче. В ситуациях, коша требуется одиночное событие (простой сигнал), подпрограмма EVCLEAR должна вызываться непосредственно после подпрограммы EWAIT с тем, чтобы отметить, что установленное событие было обнаружено.
Событие может быть сброшено любое число раз любой задачей, независимо от того, когда и кем оно в последний раз было установлено.
Обращение к подпрограмме
CALL EVCLEAR(eventvar)
eventvar : целочисленный идентификатор сбрасываемого события
EVPARM - Установка параметров администратора событий
Подпрограмма EVPARM устанавливает список параметров, используемых для управления работой администратора событий. Содержание этого списка не определяется, но может включать информацию о начальном числе размещаемых дескрипторов событий, о числе дескрипторов, добавляемых к текущему во время работы администратора событий, о флагах, используемых для отладки, и др.
Обращение к подпрограмме
CALL EVPARM(parB11st,nuparm)
panillet : целочисленный массив, содержащий значения параметров, которые могут изменяться
пияраг* : число слов (параметров), передаваемых в массив paralist. Это число должно быть больше нуля.
Замечания:
Если pamllst(l) - -2, то i-му параметру, по умолчанию, возвращается исходное значение
Если parallst(l) - -1, то сохраняется текущее значение i-ro параметра
Параметры в массиве имеют следующий смысл: paralist(l) - флаг отладки администратора событий - О, отключение отладчика администратора событий - 1, включение отладчика администратора событий
По умолчанию параметры администратора событий имеют следующее значение
parallst(l) - 1, включение отладчика администратора событий
EVPOSCLR - Установить и сбросить событие
Подпрограмма EVPOSCLR выполняет операцию POST/CLEAR (установить/сбросить). Операция CLEAR выполняется непосредственно после операции post. Все задачи, ждущие этого события, продолжают выполнение.
Обращение к подпрограмме
CALL EVPOSCLR(eventvar)
eventvar : идентификатор события, которое должно быть сначало установлено, а затем сброшено.
EVPOST - Установка события
Подпрограмма EVPOST устанавливает событие и возвращает управление вызвавшей программе. Установка события приводит к тому, что все задачи, ждущие этого события, продолжат свое выполнение. Событие может быть установлено любое число раз любой задачей, независимо от того, когда и кем оно было в последний раз сброшено.
Обращение к подпрограмме
CALL EVPOST(eventvar)
eventvar : целочисленный идентификатор события, которое должно быть установлено
EVREL - Освобождение идентификатора события
Подпрограммы EVREL освобождает событие, связанное с заданным идентификатором события. Переменной события,, содержащей идентификатор события, присваивается значение нуль. Если делается попытка использовать освобожденную переменную события или произвести копирование исходного идентификатора события, то возникает ошибка. Переменная события может быть повторно использована после повторного вызова подпрограммы EVAS6N. Освобождение события эквивалентно его уничтожению. При попытке освобождения некоторого события, с которым связано ожидание других задач, или при обращении к некоторому событию, которое было освобождено, возникает фатальная ошибка.
Обращение к подпрограмме
CALL EVREL(eventvar)
eventvar :-: идентификатор события, которое должно быть освобождено. После вызова идентификатор eventvar принимает значение нуль.
Замечание:
Недопустимо освобождение события, в то время как другие задачи ожидают этого события.
EVSTAT - Получить статус события
Подпрограмма EVSTAT возвращает статус специфицированного события. Статус включает в себя независимо от того установлено или не установлено это событие, идентификатор задачи (ID), которая в последний раз изменяла этот статус, и информацию о том имеются ли задачи, ждущие этого события.
Обращение к подпрограмме
CALL EVSTAT(eventvar,statevent, lenstat.eaxstat)
eventvar : Целочисленный идентификатор события, статус которого должен быть определен
statevnt :- Целочисленный массив, содержащий информацию о состоянии, связанную со специфицированным событием
lenstat :- Число слов, возвращаемых в массиве statevnt maxstat : Максимальное число слов, возвращаемых в массиве statevnt. Массив statevnt должен иметь по крайней мере maxstat слов.
Замечание:
Элементы массива statevent имеют следующие значения: statevnt(l) - указывает, является ли событие установленным или сброшенным
О, если событие установлено
1, если событие сброшено (происходит ожидание при вызове EWAIT) statevnt(2) - идентификатор задачи (ю), которая последней вызывала подпрограммы EVPOST или EVCLEAR для этого события. statevnt(3) - 0, если событие установлено (в этом случае нет ожидающих задач)
О, если событие сбрасывается и нет задач, ожидающих это событие
1, если событие сбрасывается и есть задачи, ждущие это событие
EVTEST - Проверить состояние события
Подпрограмма EVTEST предназначена для получения информации о состоянии (установлено/сброшено) специфицированного события.
Обращение к подпрограмме
CALL EVTEST(eventvar,evntstat)
eventvar : содержит идентификатор анализируемого события
evntstat :-: целочисленная переменная, содержащая состояние специфицированного события (установленного или неустановленного)
Замечания:
1. evntstat - 0, если событие установлено evntstat - 1, если событие неустановлено
EWAIT - Ждать установки события
Подпрограмма EWAIT переходит в состояние ожидания до тех пор, пока не будет установлено специфицированное событие. Если событие уже установлено, то задача не ждет и продолжает выполнение.
Обращение к подпрограмме
eventvar : идентификатор события, по которому происходит ожидание
Список ошибок, обнаруживаемых администратором событий
■ Аргумент уже содержит допустимый идентификатор события
- Некорректное число аргументов, передаваемых подпрограмме
* Адрес аргумента меньше 200 (восьмиричное значение)
■ Адрес аргумента превышает длину поля (>FLL)
* Аргумент не является допустимым идентификатором события
■ Аргумент, который должен содержать идентификатор события, вместо этого содержит идентификатор библиотеки, блокировки или задачи
■ Аргумент не содержит осмысленного значения
■ Некоторый элемент списка параметров не имеет осмысленного значения
* Дескриптор события имеет недопустимое состояние. Задачи находятся в состоянии ожидания в связи с установленным событием
■ Операция некорректна, если другие задачи ждут этого события
■ Свободный дескриптор события установлен неправильно
С2.6. Управление задачами (Администратор задач)
TSKPARM - Установить параметры администратора задач
Подпрограмма TSKPARM устанавливает список параметров, используемых для управления работой администратора задач. Содержание этого списка не определяется, но он может содержать информацию о начальном числе размещаемых дескрипторов задач, о числе дескрипторов добавляемых к текущему во время работы администратора событий, о флагах, используемых для отладки, о первоначальном размере сегмента для стека, о последующем размере сегмента для стека, о максимальном числе аргументов и др.
Обращение к подпрограмме
CALL TSKPARM(pane11st,nuBparm)
panelist : Целочисленный массив, содержащий параметры, подлежащие заданию
nunpane : Число слов/параметров, передаваемых в массив panelist. Это число должно быть больше нуля.
Замечания:
Если panel 1st(1) - -2, то i-му параметру по умолчанию возвращается исходное значение
Если parmlist(i) - -1, то сохраняется текущее значение i-ro параметра
Расположение параметров в списке и их смысл
рагш! 1 st(1) » Флаг отладки администратора задач (0 - выключена, 1 - включена) panel 1st(2) - не используется pane! 1st(3) - не используется
panelist(4) - режим проверки адреса
(слово/слог - “WORD* или “PARCEL") pared ist(5) - первоначальный размер стекового сегмента panelist(6) - размер по умолчанию приращения стекового сегмента panelist(7) - метод размещения дескриптора задачи во время завершения задачи
par*Hst(8) - максимальное число аргументов в подпрограмме TSKSTART.
Значения panel 1»t(5) и parwlist(6) подавляются, если используется общая таблица задач ($TASKCOM), в которой определены эти значения.
По умолчанию действуют следующие значения параметров администратора задач:
parwlist(l) - 1 panel 1st(2) - N/A paredist(3) - N/A parmlist(4) - "WORD par*list(5) - 2500B paralist(6) - 2500B panel ist(7) - 0 pane1ist(8) - 100
TSKSTART - Образовать и запустить новую задачу
Подпрограмма TSKSTART используется для образования, инициализации и запуска новой задачи для специфицированной точки входа в подпрограмму со специфицированным (не обязательным) списком аргументов. Эта подпрограмма предназначена для образования новой задачи. Вызов подпрограммы TSKSTART аналогичен вызову CALL SUBENTPT(subargs), за исключением того, что SUBENTPT выполняется как новая задача, а не как подпрограмма, и при возврате SUBENTPT новая задача завершается. В пределах задачи не разрешены ни аварийные, ни альтернативные возвраты.
Обращение к подпрограмме
CALL TSKSTART(tkcontr1,subentpt[,subargs])
tkcontrl :-: Целочисленный массив управления задачей. Этот массив должен быть построен вызывающей программой. Его длина должна быть равна по крайней мере двум словам. При необходимости может быть включено и третье слово. Впоследствии могут быть определены и дополнительные слова, однако они также будут необязательными, что позволит использовать без изменений существующие программы.
subentpt : Внешняя точка входа, с которой начинается выполнение задачи. В программе, вызывающей подпрограмму TSKSTART, это имя должно быть объявлено как внешнее (EXTERNAL). ЗАМЕЧАНИЕ: компилятор CFT не позволяет использовать в некоторой подпрограмме ее собственные имена для этого параметра.
subargs : Необязательный список ар1ументов, передаваемых подпрограмме, специфицированной параметром subentp, когда начинает выполняться новая задача. Аргументы не должны быть литералами (например, 1) или выражениями (например, х*у. См. замечание 2). В пределах задачи не разрешены альтернативные возвраты. Если один из аргументов является адресом точки входа подпрограммы или функции, то, для того чтобы обеспечить выполнение контроля правильности, адресации, до вызова подпрограммы TSKSTART должна быть вызвана подпрограмма TSKPARMS.
Замечания:
Массив управления текущей задачей имеет следующую структуру:
tkcontrl(l) : Целое число, определяющее длину в словах массива управления задачей. Это число в зависимости от использования необязательного параметра (“значения" задачи) может быть равно 2 или 3. Значение этого параметра должно быть установлено вызывающей задачей до вызова этой подпрограммы.
tkcontrl(Z) :- Идентификатор дескриптора задачи, назначаемый администратором задач при ее образовании. Этот идентификатор однозначно идентифицирует данную задачу среди всех задач, которые метут быть образованы в данной программе.
tkcontrl(3) : “Значение" задачи (необязательный параметр).
Это значение произвольно устанавливается вызывающей программой перед образованием задачи. Если “значение задачи" используется, то параметр tkcontrl(l) должен быть установлен равным 3. “Значение" задачи может использоваться для разных целей. Возможно использование этого необязательного параметра как указателя локальной области памяти задачи. Во время выполнения некоторая задача может получить свое собственное “значение" задачи с помощью подпрограммы TSKVALUE. “Значение" задачи другой активной задачи может быть получено с помощью подпрограммы TSKINFO.
Литералы и выражения представляются компилятором временно и могут “исчезнуть" до фактического выполнения задачи.
TSKSTAT - Получить статус задачи
Подпрограмма TSKSTAT используется для получения желаемого объема информации относительо специфицированной задачи. Содержание этого списка пока не определено, однако, он, вероятно, будет содержать информацию относительно состояния задачи, слов управления задачей, адреса информационного блока задачи и т.д.
Обращение к подпрограмме
CALL TSKSTAT(tkcontrl.stattask,lenstat,*axstat)
tkcontrl : Целочисленный массив управления задачей из TSKSTART stattask :- Целочисленный массив, содержащий информацию относительно специфицированной задачи.
lenstat :- Число слов информации в массиве stattask.
■axstat : Максимальное число слов, возвращаемых в массиве stattask.
Массив stattask должен содержать по крайней мере naxstat слов.
TSKTERM - Завершить выполнение задачи (но не программы)
Подпрограмма TSKTERM может быть вызвана явно или неявно. Если в подпрограмме, специфицированной в списке аргументов подпрограммы TSKSTART, выполняется оператор RETURN, то происходит неявный вызов подпрограммы TSKTERM. В некоторой задаче может быть выполнен явный вызов подпрограммы TSKTERM (в любой точке выполняемой задачи). Подпрограмма TSKTERM приводит к завершению вызвавшей ее задачи, но вся программа при этом не завершается. Любые задачи, ждущие завершения данной, продолжат свое выполнение (см. подпрограмму TSKWAIT).
Обращение к подпрограмме
CALL TSKTERM
TSKTEST - Проверить факт завершения задачи
Подпрограмма TSKTEST используется для проверки завершенности специфицированной задачи.
Обращение к подпрограмме
CALL TSKTEST(tkcontrl,taskstat)
tkcontrl : Целочисленный массив управления задачей из TSKSTART taskstat :■ Целочисленная переменная, значение которой при возврате из подпрограммы указывает, в каком состоянии (завершается/завершена) находится задача на момент вызова подпрограммы TSKTEST. Если taskstat-O, то задачи больше не существует. Если taskstat-1, то задача еще не завершена.
Замечания:
Возникает ошибка, если во время обращения к подпрограмме TSKTEST использован некорректный или несформированный идентификатор задачи (элемент tkcontrl(2) в массиве управления задачей должен содержать значение, возвращаемое при вызове подпрограммы TSKSTART).
TSKTIME - Получить временную информацию о задаче
Подпрограмма TSKTIME предназначена для передачи временной информации вызывающей задаче. Эта информация включает в себя полное время использования центрального процессора, системное время, время ввода-вывода.
Обращение к подпрограмме
CALL TSKTIME(tlBe11st,tiaelen) '
tlnHst :- Целочисленный массив для хранения временной информации Helen : Число слов массива, содержащих временную информацию
Замечания:
В массиве timellst содержится следующая информация: tinelist(l) - Полное процессорное время, затраченное на выполнение задачи. Это время выражается в числе машинных тактов, которое дает датчик реального времени (для суперЭВМ CRAY-1 длительность такта равна 12.5 нс, а для суперЭВМ CRAY Х-МР - 9.5 нс) tlel1st(2) - Полное время ввода-вывода.
tiaellst(3) - Полное системное время решения задачи.
Массив tlwlist должен иметь по крайней мере tielen элементов.
TSKVALUE - Получить слово-значение задачи для вызывающей задачи.
Подпрограмма TSKVALUE выбирает 64-разрядное слово, переданное в подпрограмму TSKSTART в качестве третьего слова массива управления задачей для этого образования задачи. Это слово может использоваться пользователем по желанию для передачи любого значения, имеющего размер слова. Обычно оно применяется для передачи указателя на рабочую область задачи или указателя на общую область.
Обращение к подпрограмме
CALL TSKVALUE(tkcontrl)
tkcontrl : 64-разрядное слово, которое является содержимым слова-значения задачи для этой задачи
TASKWAIT - Ждать завершения задачи
Подпрограмма TASKWAIT вызывает задержку выполнения данной задачи и перевод ее в состояние ожидания до тех пор, пока не завершится специфицированная задача. Если задача уже завершилась, то выполнение этой задачи будет немедленно продолжено.
Обращение к подпрограмме
CALL TASKWAIT(tkcontrl)
tkcontrl : Целочисленный массив управления задачей, установленный при вызове TSKSTART.
Список ошибок, обнаруживаемых программой управления задачами
- Пользователь затер поле TASK_COM в блоке COMMON администратора задач
- Некорректное число аргументов, передаваемых подпрограмме управления задачей
- Адрес аргумента меньше 200 (восьмеричное значение) 200
- Адрес аргумента превышает длину поля (>FLL)
- Аргумент не является допустимым идентификатором задачи
- Аргумент, который должен содержать идентификатор задачи, вместо этого содержит допустимый идентификатор события, библиотеки или блокировки
- Аргумент не имеет осмысленного значения
- Некоторый элемент списка параметров не имеет осмысленного значения
- Нарушение сохранности свободного дескриптора задачи, некорректные контрольные слова
- Размер стека дескриптора новой задачи недостаточно велик для обрабоки числа аргуметов подпрограммы при вызове подпограммы TSKSTART
- Дескриптор новой задачи установлен неправильно, TKDESCID <> "FREEDESC"
- Некорректная длина (<2 или >3) массива управления
- Произведен вызов подпрограммы TSKWAIT для ожидания по идентификатору задачи вызывающей задачи
- Ошибка при образовании новых (свободных) дескрипторов задач при выполнении подпрограммы TSKSTART /TSK_XPN
- Адрес слога подпрограммы находится не на границе слова
- Параметр задачи MMSTKLEN не может быть установлен после первого вызова подпрограммы TSKSTART
- Параметр задачи MMSTKLEN не может превышать размер стекового сегмента
- Ошибка при возврате из системного выхода
- Некорректный размер дескриптора новой задачи
- Сумма размеров общих областей задачи, указанной этой задачей, не совпадает со значением суммы, специфицированной в шаблоне $TASKCOM (обнаружена ошибка целостности загрузчика)
- Обнаружено внутреннее противоречие, обратитесь к консультантам.
Приложение D
Параллельный процессор FPS
Содержание
D1. Листинг программы вычисления числа Пи246
D2. "Обзор серии Т"250
Ричард К. Хелм и Филлип Ц. Миллер
02.1 Введение250
Обзор250
Аппаратура250
Программное обеспечение251
02.2 Среда программирования внешней машины252
Вспомогательные средства системы программирования на языке Оккам252
Язык программирования Оккам252
Система программирования Оккам253
Транспьютерная система разработки программ253
Вспомогательные средства программирования внешней машины254
Диалоговый диспетчер ЭВМ серии Т254
Сеансовые подпрограммы обслуживающего модуля254
02.3 Программное обеспечение ЭВМ серии Т255
Шаблоны программирования векторной платы255
Программное обеспечение программируемого векторного устройства255
02.4 Определение терминов255
1
2 3
4 5
6 7
8
9 10 И 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
38 39 40
41 42 43 44
D1. Листинг программы вычисления числа Пи
— source code
— исходный текст программы
DEF one - TABLE[#00000000,#3FF00000]:
DEF half - TABLE[#00000000,#3FE00000]:
DEF four - TABLE[#OOOOOOOO,#40100000]:
DEF pi - TABLE[#54442D18,#400921FB]:
VAR ftemp[2],temp,ntemp,xn[2]:
VAR h[2],a[2],b[2],sum[2],new[2]:
VAR nt,np,npp,ns,lnsp,vlen,xoffs[2]:
VAR xa,x2a,xb.x2b:
VAR begin,midi,mid2,mid3,end:
VAR elapl,elap2,elap3,elap4:
VAR h.adrs,xoffs.adrs,xn.adrs:
VAR sum.adrs,new.adrs,one.adrs;
SEQ
— define addresses
— определение адресов addr.v(h,h.adrs) addr.v(xoffs,xoffs.adrs) addr.v(xn,xn.adrs) addr.v(sum,sum.adrs) addr.v(new,new.adrs) addr.v(one,one.adrs) — report value of pi -- вывод значения числа Пи IF
(processor-0) SEQ rel.control hkeep(status) hwrite.string(" True value for PI is ",0, processor,status)
ct.hwrite.real64(pi,2,20,0,processor,status) hwtiteln(0,processor,status hwt1 te 1 n (0,processor,status hrealese(status)
get.control
TRUE SKIP
— define x at BankA,Bl boundaries
— определения x в границах банка А и Bl ха :■ VP.BankA xb :- VP.BankBl
— sync all processors for timing run
— синхронизация всех процессов для отсчета времени SEQ j-[0 FOR 5]
SEQ 1*[1 FOR dimension] set.links(i,-l,-1,-1) release.links
45
TIME ? begin
— partition domain
-- домен частичных вычислений
пр :« 1 «dimens ion — number of processors * 2*dim
— число процессоров
48vlen :* 128— original vector length * slice
-- начальная длина вектора
Insp :* 7—Insp * log2(ns)
ns :* l«lnsp—number of slicens * 2*Insp
число вырезок
51nt :* np*ns*128—total quad points (2*14 per processor)
общее число точек разбиения
52прр :* nt/np—quad points perprocessor
число точек разбиения для процессора
53— specify interval [a,b]
— определение интервала
54а[0]:«0
55а[1]:«0
56Ь[0]:•опе[0]
57Ь[1]:«опе[1]
58
59— compute h :« (b-a)/nt
— вычисление
60—ftemp :« float(nt)
61IntegerToDRea1(ftemp,nt)
62
63—hb-a
64DRealOp(h,b,Sub,a)
65
66—h:■(b-a)/nt
67DReal0p(h,h,Div,ftemp)
68— x[1] :« xoffs + h*1
69
70—xoffs • a + (p*npp+0.5)*h
71
ntemp :* processor*npp
IntegerToDRea1(ftemp,ntemp)
DRealOp(ftemp,ftemp,Mui,h)
DRealOp(xoffs,ftemp,Mu1,h)
xwrd :« (xa»2)
SEQ 1 « [0 FOR vlen]
78 SEQ
79PUTV0RD(1,xwrd)
80PUTV0RD(0,xwrd+l)
81xwrd :* xwrd+2
82—float sequence of integers & add offsets
— последовательность целых как число с плавающей запятой
836N.V0(xa,AF.Xfloat,ха,VP.null,vlen)—float 1
846N.SV0(h.adrs,xa,MF.XYmul,xa,VP.null,vlen)-h*1
856N.SV0(xoffs.adrs,xa,AF.XYadd,xa,xb,vlen)—h*1+xoff
86
—xn«float(vlen)*h
IntegerToDRea1(ftemp,vlen)
DRealOp(xn,ftemp,Mui,h)
TIME 7 midi vlen,xn and xi doubled Insp times vlen,xn и xt дублируются Insp раз
SEQ j-[l FOR Insp]
SEQ
x2a :- xa+(vlen*8) x2b :■ xb+(vlen*8)
GN.VSO(xa,xn.adrs,AF.XYadd,x2a,x2b,vlen) DRealOp(xn,xn,Add,xn) vlen :■ vlen+vlen
store x in both A and 8 banks запись x в оба банка А и В
TIME 7 mid2
compute sum over ns slices
- вычисление суммы ns вырезок l+x(i)*x(i)
GN.VVOSO(xa,xb,MF.XYmul,one.adrs,AF.XYadd,xb,VP.null,vlen) SN.VRECIP(xb,l,xb,lvlen)
GN.VRO(xb,AF.XYadd,sum.adrs,vlen)
TIME 7 mid3
communicate and collapse sums over processors -- взаимодействия и сбор сумм от всех процессоров
rel.control
set.links(l,2,3,4)
SEQ i = [1 FOR dimension]
SEQ
exchange sums обмен суммами
PAR
byte.s11ce.output(i,sum.adrs,8) byte.slice.input(i,new.adrs,8)
accumulate sums сбор сумм
DRea Юр (sum, sum, Add, new)
release.links
compute sum :■ sum*4*h
- вычисление
DReal0p(sum,sum,Mui,four) DRealOp(sum,sum,Mul,h) TIME 7 end
output results
вывод результатов elapl :■ midl-begin elap2 :■ mid2-begih elap3 :■ mid3-begin elap4 :■ end-begin hkeep(status)
90 91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 111
112 ИЗ
114
115
116
117
118
119
120
121
122 123
124
125
126
127
128
129
130 131
132
133
134
135
hwrite.string(w Est. value for PI is H,0,processor,status)
136ct.hwrite.rea!64(sum,2,20,0,processor,status)
137hwrite.string!”on processor ”,0,processor,status)
138hwrite. 1nt(processor,0,processor,status)
139hwr1te1n(0,processor,status)
140hwrite.string(” Elapsed tines .‘float; gen. pts; sum pts; comm ”,0,
141processor,status)
142hwr i te1n(0,processor,status)
143hwri te.1nt(elapl,0,processor,status)
144hwrite.string(” ”,0,processor,status)
145hwr1te.1nt(elap2,0,processor,status)
146hwrite.string(” ”,0,processor,status)
147hwrite.1nt(elapS,0,processor,status)
148hwrite.string!” ”,0,processor,status)
149hwrite.1nt(elap4,0,processor,status)
150hwrite.string!” ”,0, processor, status)
151hwr1te1n(0,processor,status)
152 hwrite.string!” total t pts, nt- ",0,processor,status)
153hwr1te.int(nt,0,processor,status)
154hre1ease(status)
155get,control
D2. "Обзор серии Т"1)
Ричард К» Хелм и Филлип Ц. Миллер
D2.1 Введение
Обзор
FPS серии Т представляет собой ЭВМ параллельной матричной архитектуры, организованной в конфигурацию гиперкуба. К системе присоединяется внешняя машина (front-end computer), которая обеспечивает возможности по разработке программ и управлению системой. Функции управления системой включают в себя загрузку прикладных программ и инициализацию системы.
Аппаратура
На рис. D2.1 показана конфигурация ЭВМ модели Т-40 серии Т. Базовой единицей серии Т является узел с формальным названием “векторная пла- та“. По сути, векторная плата является одноплатным матричным процессором. Она включает в себя транспьютер Inmos Т414, векторное процессорное устройство (ВПУ) и основную память.
Рис. D2.1 Вариант конфигурации А00.
^ Это приложение является сжатой версией следующих руководств фирмы FPS: “Programming the FPS Т Series: Program Development Guide** (Release B), FPS Order No. 860-0001-003; “Programming the FPS T Series: System Services Reference Manual** (Release B), FPS Order No. 860-0001-004A; “Programming the FPS T Series: Math Library Manual" (Release B), FPS Order No. 860-0001-008B. Воспроизводится с разрешения фирмы Floating Point Systems.
Транспьютер Inmos Т414 является управляющим устройством векторной платы. Он управляет работой ВПУ и обеспечивает все обмены между ВПУ и внешними устройствами. ВПУ является векторным арифметическим устройством узла, содержащим по одному устройству сложения и умножения и четыре регистра сдвига. Основная память векторной платы состоит из четырех банков памяти, по 64 Кслова каждый. Транспьютер имеет прямой доступ к i каждому банку, в то время как ВПУ имеет доступ к памяти только через регистры сдвига.
Модуль является основной конструктивной единицей ЭВМ серии Т. Он состоит из восьми векторных плат, системной платы, содержащей транспьютер Inmos Т414, и системного диска. На рис. D2.2 показаны компоненты модуля.
Для объединения компонентов ЭВМ серии Т используются две локальные сети: системная сеть и сеть гиперкуба. Системная сеть используется операционной системой и соединяет:
Внешнюю машину с системной платой для внешних взаимодействий.
Системные платы между собой для кольцевых взаимодействий.
Системную плату с восемью ее векторными платами для радиальных взаимодействий.
На рис. D2.3 показаны связи системной сети серии Т.
Программное обеспечение
Программное обеспечение ЭВМ FPS серии Т можно разбить на составляющие в соответствии с основными аппаратными компонентами ЭВМ. Программное обеспечение серии Т включает в себя:
программное обеспечение внешней машины;
программное обеспечение системной платы;
i • программное обеспечение векторной платы.
Рис. D2.2 Компоненты модуля.
Программное обеспечение внешней машины состоит из средств разработки программ, поддерживающих язык программирования Оккам для Inmos Т414, и библиотек программного обеспечения внешней машины, обеспечивающей интерфейс с ЭВМ серии Т. Средствами, обеспечиваемыми Inmos, являются система программирования Оккам (СПО) и транспьютерная система разработки программ (ТРС).
Программное обеспечение системной платы состоит из операционной системы, обеспечивающей системные связи, взаимодействие векторных плат с внешней машиной и работу с файловыми ресурсами.'
Программное обеспечение векторной платы реализует арифметические I векторные операции, обмен данными с системным диском, взаимосвязь RS232 i UART и коммуникации по гиперкубу. На рис. D2.4 представлена блок-схема программного обеспечения ЭВМ серии Т.
D2.2. Среда программирования внешней машины
Вспомогательные средства системы программирования на языке Оккам
Язык программирования Оккам,
Оккам - это язык программирования, первоначально разработанный специально для семейства транспьютеров фирмы Inmos. Разработка Оккама базировалась на концепции представления системы как набора параллельных процессов. Программы на ' языке Оккам конструируются с использованием трех примитивных процессов: присваивание, ввод и вывод. Процессы Оккама могут объединяться в последовательные, параллельные и альтернативные конструкции. Межпроцессное взаимодействие и синхронизация достигаются использованием каналов языка Оккам.
Пользовательская
Рис. D2.4. Программное обеспечение серии Т.
Система программирования Оккам
Система программирования Оккам (СПО) является средой программирования на языке Оккам, разработанной в Inmos Corporation. СПО позволяет программисту создавать и компилировать исходные тексты на языке Оккам на внешней машине. Следует заметить, что СПО не создает код, который может выполняться на ЭВМ серии Т. Это дело транспьютерной системы разработки программ. Основными функциями СПО являются:
редактирование исходных текстов программ на языке Оккам;
проверка синтаксиса исходных текстов программ на языке Оккам;
компиляция исходных текстов программ на языке Оккам;
интерпретация оккам-программ на внешней машине.
Транспьютерная система разработки программ
Транспьютерная система разработки программ (ТСП) является средой программрования на языке Оккам, разработанной в Inmos Corporation для создания оккам-программ, которые могут выполняться в транспьютерах. Операции ТСП идентичны операциям в СПО, дополненным поддержкой оккамов- ских спецификаторов PLACED PAR, PROCESSOR и PLACE АТ, что позволяет “отображать" оккамовские процессы на векторные платы ЭВМ серии Т.
Вспомогательные средства программирования на внешней машине
Простая операционная система - диалоговый диспетчер ЭВМ серии Т (Т Series Session Executive - TSE) - служит для выполнения программ и обеспечения среды выполнения. Это - полезное средство для выполнения большинства обычных программ. Для более специализированных прикладных программ у пользователя есть возможность обращаться средствами языков Си и Фортран к средствам операционной системы ЭВМ серии Т.
Диалоговый диспетчер ЭВМ серии Т
TSE выполняется на внешней машине и обеспечивает пользователю интерактивное управление выполнением программ на ЭВМ серии Т. Команды командного языка ЭВМ серии Т могут использоваться для управления выполнением программ на ЭВМ серии Т. В командном языке есть следующие команды:
LOGIHВыделение ресурсов ЭВМ серии Т.
LOGOUTОсвобождение ресурсов ЭВМ серии Т.
RUNЗапуск программы на выполнение на выделенных ресурсах
ЭВМ серии Т.
SHOWВывод на экран данных и времени.
COPYКопирование файлов из (в) внешней машины на (из) диск
узла ЭВМ серии Т.
DELETEУдаление файла с диска узла ЭВМ серии Т.
RENAMEИзменение имени файла на диске узла ЭВМ серии Т.
DIRECTORY Просмотр содержимого директории на диске узла ЭВМ серии Т.
QUITОкончание сеанса взаимодействия с ЭВМ серии Т.
Сеансовые подпрограммы обслуживающего модуля
Обслуживающий модуль ЭВМ серии Т (Т Series Service Module - TSM) реализует обслуживание операционной системой ЭВМ серии Т в среде программирования языков Си и Фортран. Программист может использовать средства TSM при разработке специализированных, размещаемых на внешней машине программ для управления выполнением прикладных программ в ЭВМ серии Т. Эти средства включают в себя:
захват ресурсов (вхождение в) ЭВМ серии Т;
запуск на выполнение и останов программ в ЭВМ серии Т;
связь с векторными платами ЭВМ серии Т;
утилиты работы с файлами на системном диске;
преобразование типов данных между представлением на внешней машине и представлением в ЭВМ серии Т;
■ преобразование кодов ошибки в сообщения об ошибке.
D23. Программное обеспечение ЭВМ серии Т
Пользователь создает программы для векторных плат ЭВМ серии Т, используя язык программирования Оккам и библиотеки математических подпрограмм, подпрограмм связи и сервисных системных подпрограмм. При написании программ используются шаблоны программирования векторной платы, система программирования и математические библиотеки, описанные ниже.
Шаблоны программирования векторной платы
В существующую версию программного обеспечения ЭВМ серии Т включены подготовленные шаблоны программ, которые содержат все библиотеки программного обеспечения, необходимые для создания программ векторных плат ЭВМ серии Т. В этих примерах программ есть область, называемая “Пользовательский—Процесс - UserProcess*, в которую пользователь может поместить код программы для выполнения на ЭВМ серии Т.
Программное обеспечение программируемого векторного устройства
Пользователь может выбирать из математических библиотек подпрограммы нескольких уровней. На самом верхнем уровне пользователь может обращаться к процедурам, которые распределяют вычисления по всей системе, автоматически производя обмен частичными результатами между узлами.
На среднем уровне пользователь может выполнять операции над локальными наборами данных независимо от других узлов. Пользователь может явно организовывать межпроцессорное взаимодействие. Можно также выполнять операции над дисковыми файлами системных дисков узлов.
На более низких уровнях пользователь может программировать на векторном ассемблере. На этих уровнях пользователь может программировать работу аппаратуры в терминах операций над “вырезками* данных на векторных регистрах и в банках памяти.
Хотя пользователю дается возможность прямо управлять работой аппаратуры векторного устройства, он лишен возможности делать это на уровне микропрограмм.
D2.4. Определение терминов
п-Куб - n-Cube
Гиперкуб ЭВМ серии Т, содержащий 2 в степени п векторных плат.
Адрес байта - Byte Adress
Целочисленное значение, указывающее на место в основной памяти, в случае, если основная память считается набором восьмибитовых элементов.
Адрес слова - Word Adress
Целочисленное значение, указывающее на место в основной памяти, в случае если основная память считается набором 32 битовых элементов.
Банк А - Bank А
Компонент основной памяти объемом в 256 Кбайт, состоящий из одного подбанка, называемого sub-bank 3.
Банк В - Bank В
Компонент основной памяти объемом в 768 Кбайт, состоящий из трех подбанков, называемых sub-bank 0, sub-bank 1 и sub-bank 2.
Векторная плата - Vector Board
Базовый процессорный элемент гиперкуба. Векторная плата состоит из векторного процессорного устройства (ВПУ), транспьютера Inmos Т414, 1 Мбайт основной памяти и 16 мультплексных внешних каналов сети гиперкуба. Также называется узлом.
Векторная форма - Vector Form
Пользовательский интерфейс к функциям, реализованным микропрограммно в векторном процессорном устройстве векторной платы.
Внешние связи - External Communications
Связь между внешней машиной и системной платой.
Внешняя машина - FEC - Front-End Computer
Используется для разработки программ, загрузки прикладных программ в ЭВМ серии Т и для обменов данными между прикладной программой и системными дисками.
ВПУ - VPU
Векторное процессорное устройство (Vector Processing Unit). Арифметическое устройство - компонент векторной платы, которое выполняет высокоскоростные векторные вычисления.'
Гиперкуб - Hypercube
Для описания связей векторных плат в ЭВМ серии Т используется двухточечная схема. Взаимосвязи векторных плат можно изобразить в виде п- мерного гиперкуба в декартовом 2п-мерном пространстве, чьи вершины и ребра соответствуют векторным платам и связям между ними.
Гиперкуба сеть - Hypercube Network
Сеть связи между векторными платами, используемая прикладными программами. Каждая векторная плата имеет п последовательных каналов связи с ближайшими соседями в системе Т с конфигурацией л-мерного гиперкуба.
Канал связи - Link
Последовательный канал связи транспьютера, обеспечивающий возможность параллельного обмена сообщениями между векторными платами.
Кольцевые связи - Ring Communications
Связь между системными платами построена по топологии кольца.
Модуль - Module
Название, данное восьми векторным платам, соединенным с системной платой серии Т и системным диском. Как минимальная аппаратная конфигурация модуль представлен моделью Т-10 ЭВМ серии Т.
Оккам - Occam
Основной язык программирования для ЭВМ серии Т.
Основная память - Main Memory
Компонент векторной платы для динамического хранения данных. Объем I Мбайт, подразделяется на два банка, А и В, и на 4 подбанка, от 0 до 3.
Подбанк - Sub-bank
Название, данное 256-Кбайт сегменту основной памяти. Вся основная память состоит из четырех подбанков.
Радиальные связи - Ray Communications
Связи внутри модуля между системной платой и восемью ее векторными платами.
Сдвиговый регистр - Shift Register
Название, данное регистрам в векторном процессорном устройстве (ВПУ) - компоненте векторной платы. Каждый сдвиговый регистр используется для обмена данными между основной памятью векторной платы и арифметическими устройствами ВПУ.
Системная плата - System Board
Часть модуля серии Т, выполняющая связи и пересылку данных между подключенными восемью векторными платами, внешней машиной и другими системными платами. Каждая системная плата является также интерфейсом для исключенного контроллера диска. Возможности интерфейса с диском используются для организации внешней памяти для файловой системы векторных плат серии Т и операционной системой системной платы.
Системная сеть - System Network
Сеть связи с низкой пропускной способностью ЭВМ серии Т, функционально распадающаяся на три подсети, которые классифицируются как внешняя, радиальная и кольцевая. Системная сеть используется для системных взаимодействий между внешней машиной и системными платами, системными платами и подключенными к ним векторными платами, системными платами и другими системными платами.
Системное кольцо - System Ring
Название, данное части системной сети, состоящей из системных плат, соединенных по топологии кольца.
Слово -Word
Четыре байта.
СПО - OPS
Система программирования на языке Оккам фирмы Inmos. СПО является средством разработки программ на языке Оккам, работающим на внешней машине. СПО содержит интерпретатор, который можно использовать для разработки и тестирования программ в среде внешней машины.
Транспьютер - Transputer
Микропроцессор InmosT414, который используется как управляющий процессор векторной платы.
I7 185
ТСП - TDS
Транспьютерная система разработки программ фирмы Inmos. ТСП есть средство разработки программ на языке Оккам, работающее на внешней машине. ТСП используется для разработки программ для ЭВМ серии Т.
Узел - Node Векторная плата.
Приложение Е
IBM 3090
Содержание
El. Программа вычисления числа Пи - вход препроцессора ЕРЕХ261
Программа вычисления числа Пи - выход препроцессора ЕРЕХ262
Результаты измерений времени выполнения программы вычисления числа Пи264
"Описание препроцессора VM/EPEX FORTRAN".270
Янис М. Стоун, Фредерика Дарема-Роджерс, В. Алан Нортон и Грегори Фистер
Предисловие270
Введение271
Среде VM/EPEX272
Использование препроцессора272
Вызов препроцессора273
Вызов препроцессора из XEDIT273
Вызов второй фазы275
Вызов третьей фазы275
Трансляция параллельных конструкций 277
Объявления разделяемых данных277
Явно определенные разделяемые данные277
Объявления выделенных переменных279
Неявно генерируемая разделяемая память для синхронизации280
Параллельные конструкции280
Параллельный цикл: 6D0 ... 0DOEND281
Предостережения (Caveats)284
Нестандартный выход из цикла: 0SCRAM285
Последовательная секция: «SERIAL BEGIN.. .8SERIAL END285
Дополнительные предостережения285
Окружение процессов: 0BARRIERS или 8WAITF0R286
Инициализация287
Примеры программ288
Пример параллельного цикла, который ожидает окончания288
Пример параллельных циклов с WAIT и NOWAIT288
Пример конвейеризованных параллельных циклов289
Разветвление процессов с учетом предостережений289
Реализация макроконструкций в VM/EPEX290
Реализация параллельногоцикла290
Реализация последовательнойсекции293
Реализация барьера293
Реализация 8WAITF0R294
I?»
Подпрограммы VM/EPEX294
Подпрограммы высокого уровня294
Подпрограммы нижнего уровня295
Подпрограммы "выбор=и=операция"296
El. Программа вычисления числа Пи - вход препроцессора
IMPLICIT DOUBLE PRECISION(A-H.O-Z)
REAL*16 PI
DIMENSION Tl(3), TIME(3)
«SHARED/СОММ/ SUMALL
F(X) - 4.DO/(1.DO*X*X)
PI - 4.Q0*QATAN(l.Q0)
READ(5,*) INTRVL
CALL FCLOC(Tl)
«SERIAL BEGIN
SUMALL - 0.D0
«SERIAL END
WIDTH - l.DO/INTRVL
SUM - 0.D0
«DO 100 IC - 1, INTRVL, CHUNK - 1000
SUM - SUM + WIDTH * F( (IC - 0.5D0) * WIDTH )
100 «ENDDO NOWAIT
T - DFNA(SUMALL,SUM)
CALL FCLOC(TIME)
DO 200 I - 1, 3
TIME(I) - l.D0-6*(TIME(I)-Tl(I))
200 CONTINUE
ERR - SUMALL - PI
23WRITE(6,'(A,F25.15,1.PD15.3)') 'SUMALL, ERR-', SUMALL,ERR
24WRITE(6,'(A,3F10.4)') 'TIME -'.TIME
END
Программа вычисления числа Пи - выход препроцессора ЕРЕХ
С *** Processed by VM/EPEX preprocessor ***
СОбработано препроцессором VM/EPEX
IMPLICIT DOUBLE PRECISION(A-H,O-Z)
REAL*16 PI
DIMENSION Tl(3), TIME(3)
C
C ***** generated by the VM/EPEX preprocessor ***** сгенерировано препроцессором VM/EPEX
8C0MM0N/$$PRIV/C28903(100)
9INTEGER*4 C28903
10C0SHARED_UNIQUE/C38478/I46759(26)
11C0MM0N/C38478/I46759(26)
12INTEGER 146759
C
C OSHARED/COMM/ SUMALL
15COMMON/COMM/ SUMALL
16INTEGERМ85788/0/
17INTEGER¥76575/0/
18INTEGERR07643/0/
19INTEGERХ18274/0ЛС27253/0/
20 C
21 c ***** generated by the VM/EPEX preprocessor ***** сгенерировано препроцессором VM/EPEX
22F(X) - 4.D0/(l.D0+X*X)
23PI « 4.Q0*QATAN(l.Q0)
24READ(5,*) INTRVL
25CALL FCLOC(Tl)
26C«SERIAL BEGIN
27CSynch array Is at 146759(7)
Массив синхронизации в 146759(7)
28CPrivate clock variable isM85788
Переменная с локальным временем - M85788
29.CALL SERSET(M85788,0, 146759(7),*!)
30SUMALL ■ 0.D0
31C,0SERIAL END
32CALL SEREND(M85788,146759(7))
331CONTINUE
34¥76575 -0
352IF (M85788.LE.146759(7)) GOTO 3
36CALL $$WAIT(2, ¥76575)
37GOTO 2
383 CONTINUE
39WIDTH « l.DO/INTRVL
40SUM ■ 0.D0
41 C 8D0 100 IC ■ 1, INTRVL, CHUNK « 1000
42 C Synch array is at 146759(18)
Массив синхронизации в 146759(18)
43 C Private clock variable is R07643
Переменная с локальным временем - R07643
44 CALL D0SET(R07643, 146759(18),*100)
45 4 CALL D0BEG(l,INTRVL,l,1000,X18274,C27253,R07643, 146759(18),*100)
46 DO 5 IC-X18274,C27253,1
47SUM - SUM + WIDTH * F((IC - 0.5D0) * WIDTH)
48C 100 OENDDO NOWAIT
495CONTINUE
50GOTO 4
51100 CONTINUE
52T - DFNA(SUMALL, SUM)
53CALL FCLOC(TIME)
54DO 200 I - 1, 3
55TIME(I) - l.D0-6*(TIME(I)-Tl(I))
56200 CONTINUE
57 ERR - SUMALL - PI
58 WRITE(6,'(A,F25.15,1.PD15.3)') 'SUMALL, ERR-', SUMALL,ERR
59WRITE(6,'(A,3F10.4)') 'TIME-',TIME
60END
. Результаты измерений времени выполнения программы вычисления числа Пи
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.330D-08
1
0.0020
0.0015
0.0014
1000000
-8.428D-11
1
1.4288
1.3978
1.3876
10000000
-8.431D-10
1
14.6937
13.9871
13.8895
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.328D-10
1 2
0.0106
0.0071
0.0072
0.0071
0.0071
0.0070
1000000
-5.389D-11
1 2
0.7099
0.7520
0.7006
0.7028
0.6930
0.6924
10000000
-5.392D-10
1 2
9.8704
7.6181
7.0020
6.9838
6.9408
6.9341
Четыре процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1
0.0037
0.0036
0.0035
2
0.0036
0.0036
0.0035
3
0.0036
0.0036
0.0035
4
0.0036
0.0036
0.0035
1000000
-6.448D-12
1
0.8185
0.3506
0.3464
2
0.3976
0.3498
0.3462
3
0.7543
0.3516
0.3467
4
0.7909
0.3508
0.3468
10000000
-6.532D-11
1
8.3449
3.5032
3.4692
2
7.5709
3.4983
3.4661
3
8.0620
3.5009
3.4678
4
6.0459
3.4937
3.4650
Таблица Е3.1. Предраспределенный DO скалярного типа.
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.3330-10
1
0.0054
0.0054
0.0053
1000000
-5.7660-13
1
0.5525
0.5354
0.5288
10000000
-6.577D-12
1
5.8726
5.8933
5.8931
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1 2
0.0028
0.0030
0.0028
0.0029
0.0027
0.0027
1000000
-3.401D-13
1 2
0.2707
0.3033
0.2677
0.2675
0.2642
0.2643
10000000
-4.211D-12
1 2
3.8384
3.0279
2.6780
2.6655
2.6448
2.6405
Четыре процесса
Интервалы
Погрешность вычисления Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1
0.0015
0.0015
0.0015
2
0.0015
0.0015
0.0013
3
0.0018
0.0015
0.0014
4
0.0014
0.0014
0.0013
1000000
3.0080-14
1
0.2233
0.1336
0.1320
2
0.2287
0.1341
0.1321
3
0.1930
0.1343
0.1322
4
0.1905
0.1342
0.1320
10000000
-5.0970-13
1
2.9375
1.3336
1.3209
2
3.0489
1.3358
1.3213
3
1.4391
1.3293
1.3199
4
2.4349
1.3353
1.3202
Таблица E3.2. Предраспределенный DO векторного типа.
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.325D-10
1
0.0694
0.0694
0.0674
1000000
-8.428D-11
1
7.0048
6.7714
6.7200
10000000
-8.431D-10
1
75.2523
67.8334
67.2660
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.328D-10
1 2
0.0694
0.0003
0.0679
0.0002
0.0674
0.0000
1000000
-5.389D-11
1 2
3.9872
3.9682
3.7903
3.6304
3.7621
3.5983
10000000
-5.3920-10
1 2
56.4691
56.4216
33.4573
37.4653
33.2051
37.1796
Четыре процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.3330-10
1
0.0691
0.0678
0.0673
2
0.0020
0.0002
0.0000
3
0.0009
0.0003
0.0000
4
0.0007
0.0001
0.0000
1000000
-6.4480-12
1
4.2897
1.8742
1.8519
2
4.2179
1.9124
1.8942
3
4.2842
1.8427
1.8225
4
4.1860
1.7670
1.7542
10000000
-6.532D-11
1
54.7136
16.6669
16.5160
2
54.6479
18.2826
18.1065
3
54.6089
18.8680
18.6773
4
54.4742
18.5009
18.3273
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.325D-10
1
0.1321
0.1305
0.1284
1000000
-8.428D-11
1
13.4511
12.9499
12.8273
10000000
-8.431D-10
1
137.123
129.6471
128.3561
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.326D-10
1
2
0.1156
0.0245
0.1146
0.0183
0.1125
0.0176
1000000
-5.389D-11
1
2
8.0273
7.9383
6.7808
6.7869
6.7042
6.7146
10000000
-5.392D-10
1 2
75.3012
75.1876
70.3458
65.3354
69.8364
64.7479
Четыре процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1
2 3
4
0.0785
0.0674
0.0714
0.0002
0.0129
0.0622
0.0637
0.0002
0.0121
0.0613
0.0611
0.0000
1000000
-6.448D-12
1
2
3 4
7.2567
7.3504
7.2740
7.3552
0.8142
0.9684
5.9992
5.8263
0.79999
0.9544
5.9994
5.7680
10000000
-6.532D-11
1
2 3
4
73.8832
73.7836
73.7840
73.8058
12.5445
48.3848
36.9797
37.9934
12.4309
47.9689
36.5658
37.6852
Таблица E3.4. Самораспределенный DO векторного типа, chunk * 1 •
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.325D-10
1
0.0148
0.0141
0.0014
1000000
-8.4280-11
1
1.48000
1.03866
1.3740
10000000
-8.4310-10
1
14.6604
13.8518
13.7366
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.3330-10
1
2
0.0152
0.0002
0.0142
0.0002
0.0139
0.0000
1000000
-5.3330-11
1 2
0.8177
0.7926
0.6181
0.7707
0.6106
0.7631
10000000
-5.3920-10
1 2
7.4026
7.3618
7.0571
6.8075
6.9978
6.7489
Четыре процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.327D-10
1
0.0169
0.0117
0.0111
2
0.0003
0.0002
0.0000
3
0.0031
0.0031
0.0029
4
0.0002
0.0002
0.0000
1000000
-9.320D-12
1
0.7573
0.4464
0.4409
2
0.6892
0.3669
0.3423
3
0.5144
0.2222
0.2190
4
0.6603
0.3552
0.3509
10000000
-6.532D-11
1
7.3033
3.1434
3.1154
2
7.2640
3.0425
3.0170
3
7.2298
3.6701
3.6402
4
7.1587
4.0045
3.9679
Один процесс
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1
0.0055
0.0055
0.0054
1000000
-5.914D-13
1
0.5479
0.5423
0.5371
10000000
-6.753D-12
1
5.4705
5.4081
5.3699
Два процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1 2
0.0058
0.0004
0.0057
0.0001
0.0054
0.0000
1000000
-3.3920-13
1 2
0.3131
0.2493
0.3017
0.2436
0.2979
0.2398
10000000
-4.2440-12
1 2
4.1860
4.1006
2.8657
2.5610
2.8391
2.5369
Четыре процесса
Интервалы
Погрешность вычисления
Пи
Процесс
Время
Абсолютное
Общее
Виртуальное
10000
8.333D-10
1
0.0058
0.0057
0.0054
2
0.0002
0.0002
0.0000
3
0.0002
0.0002
0.0000
4
0.0001
0.0001
0.0000
1000000
-3.898D-14
1
0.3445
0.0790
0.0765
2
0.1738
0.0824
0.0802
3
0.3303
0.1686
0.1657
4
0.2392
0.2183
0.2157
10000000
-9.030D-13
1
3.2052
0.9967
0.9855
2
3.1790
1.1946
1.1819
3
3.1421
1.3291
1.3152
4
3.0911
1.9163
1.8961
."Описание препроцессора VM/EPEX FORTRAN"]) "The VM/EPEX FORTRAN Preprocessor Reference"
RC 11408 (#51330) 9/30/85
Computer Science
Янис M. Стоун, Фредерика Дарема-Роджерс, В. Алан Нортон и Грегори Фистер
Janice M.Stone, Frederica Darema-Rogers, V. Alan Norton, and Gregory F. Phister
IBM TJ. Watson Research Center
Yorktown Heights,NY
Аннотация:
Препроцессор VM/EPEX расширяет язык программирования путем добавления набора высокоуровневых параллельных конструций. Следуя VM/EPEX, программа выполняется параллельно на нескольких VM виртуальных машинах, каждая из которых имеет доступ по чтению/записи к разделяемому сегменту виртуальной памяти. Препроцессор транслирует параллельные конструкции в вызовы подпрограмм, а дополнительный код - в программу на выходном языке. Этот документ описывает версию препроцессора для Фортрана.
Предисловие
Система VM/EPEX разработана в рамках проекта RP3 как часть продолжающихся исследований, направленных на понимание сущности крупномасштабной параллельной обработки. Этот отчет является одним из набора отчетов, описывающих систему VM/EPEX. В каждом основной автор назван первым, а остальные разработчики перечисляются в алфавитном порядке. Вот эти отчеты:
F. Darema-Rogers, D.A. George, V.A. Norton, and G.F. Pfister, “VM/EPEX - A VM Environment For Parallel Execution", IBM Researh Report RC 11225 (#49161), 1/23/85
F. Darema-Rogers, D.A. George, V.A. Norton, and G.F. Pfister, “Environment and System Interface for VM/EPEX“.
E.A. Melton and G.F. Phister, “A Wireable Shared Segment Operating System".
J.M. Stone, F. Darema-Rogers, V.A. Norton, and G.F. Pfister, “Introduction to the VM/EPEX Fortran preprocessor.
. J.M. Stone, F. Darema-Rogers, V.A. Norton, and G.F. Pfister, “The VM/EPEX FORTRAN Preprocessor Reference".
Список разработчиков приводится ниже, и с каждым именем в списке связан тот аспект VM/EPEX, в развитие которого данный разработчик внес наибольший вклад. Читателей приглашают для получения дополнительной информации вступать в непосредственный контакт с соответсвующим разработчиком. Вот их список:
Фредерика Дарема-Роджерс Разработка прикладных и системных программ
Frederica Darema-Rogers
Application and system design
Дэвид А Джордж David A George
Интерфейс с системой VM VM system interface
Эвелин А Мелтон Evelin A Melton
Разработка операционной системы Operating system design
Алан Нортон Alan Norton
Разработка прикладных и системных программ Application and system design
Грегори Фистер Gregory F. Phister
Разработка системных программ System design
Кимминг Co
Kimming So
Анализ производительности Performance analysis
Янис M. Стоун Janice M. Stone
Препроцессор языка высокого уровня High-level language preprocessor
Введение
Препроцессор VM/EPEX расширяет язык программирования путем добавления набора высокоуровневых параллельных конструкций. Он действует в параллельной среде VM, в которой программа выполняется параллельно несколькими виртуальными машинами VM, каждая из которых имеет доступ по чтению/записи к общему сегменту виртуальной памяти. Препроцессор транслирует параллельные конструкции в комбинации вызовов подпрограмм и расширения кода.
Этот документ описывает версию препроцессора для языка Фортран. Макросы VM/EPEX FORTRAN следующие:
Разделяемые данные: «SHARED
Выделенные переменные: «MYNUM и др.
Параллельный цикл: в(Ю ...00(00, flSCRAK
Последовательный раздел: «SERIAL BEGIN ...«SERIAL END
Связывание процессов: «BARRIER, «WAITFOR
Употребление макросов неформально (серией примеров) вводится в следующем разделе. Синтаксис и семантика этих макросов обсуждаются ниже.
Хотим высказать свою благодарность за консультации, плодотворные дискуссии и поддержку, полученные нами от Аллана Готлиба (Allan Gottlieb) из Университета Нью-Йорка (NYU). Мы бы хотели поблагодарить и наших первых пользователей за их терпение и готовность мириться с ошибками, неизбежными в любой новой системе программного обеспечения: Пата Теллера (Pat Teller) из Университета Нью-Йорка, Паоло Карневали (Paolo Carnevali) из Римского исследовательского центра IBM, Андрея Хейлепра (Andrei Heilper) из Исследовательского центра IBM в Хайфе, Джима Кайия (Jim Kajiya) из Калифорнийского технологического института, но особенно Алана Карпа (Alan Karp) из Исследователького центра в Пало-Альто и Тима Кэя (Tim Kay) из Калифорнийского технологического института - первых пользователей, которые разработали командные файлы execs, добавленные в ЕРЕХ.
Среда VM/EPEX
УМ/ЕРЕХ - среда для параллельного выполнения программ под управлением ОС УМ была задумана как средство для приобретения опыта в параллельном программировании с вовзможностью увеличения производительности за счет использования нескольких параллельных процессоров. В этой параллельной среде программы пишутся так, как будто для их выполнения есть несколько параллельных процессоров, работающих одновременно. В операционной системе УМ каждый такой идеализированный процессор является виртуальной УМ машиной. В этом документе эти виртуальные машины будут рассматриваться как процессы. Различные виртуальные машины выполняют одну программу, но в каждый момент времени могут выполнять разные операции над разными порциями разделяемых данных. Бее процессы имеют доступ по чтению/записи к одним и тем же разделяемым сепиентам виртуальной памяти.
Прикладные программы, выполняемые в этой среде, обычно возникают из существующих последовательных программ непосредственным образом: программист указывает, какие циклы мшут выполняться параллельно, и какие структуры данных будут разделяемыми для параллельных процессов. Препроцессор УМ/ЕРЕХ расширяет VS FORTRAN добавлением нескольких параллельных конструкций и определений разделяемых данных, полезность которых доказана. Такой расширенный язык называется ЕРЕХ-FORTRAN. Препроцессор работает как прямой транслятор, преобразовывая параллельные конструкции в соответствующий встраиваемый фортран-код и вызовы процедур. Выходные файлы препроцессора соответствуют входу компилятора VS FORTRAN. После компиляции результирующие файлы TEXT мотут загружаться и выполняться под управлением супервизора WSSOS (Writeable Shared Segment Operating System - операционная система разделяемых по записи сегментов). Данный документ описывает препроцессор УМ/ЕРЕХ. Приведены примеры написания и работы программ УМ/ЕРЕХ.
Использование препроцессора
Работа препроцессора УМ/ЕРЕХ FORTRAN состоит из трех фаз: PPREPROC, PPBLOCK и EPEXLOAD. PPREPROC транслирует определения и команды ЕРЕХ в VS/FORTRAN. PPBLOCK генерирует подпрограмму BLOCK DATA в соответствии с
объявлениями разделяемых данных. EPEXLOAD производит выполняемый модуль. Роль этих фаз в подготовке и компиляции программ показана на рис. Е4.1.
На первой фазе препроцессор обрабатывает исходный ЕРЕХ/FORTRAN файл, транслируя его в исходный VS FORTRAN файл. Во время второй фазы препроцессор просматривает набор исходных фортран-файлов, которые были созданы в результате работы первой фазы, для объединения ссылок на разделяемые данные в одну подпрограмму BLOCK DATA и описания тех данных, которые должны размещаться в разделяемой памяти. Для определения размеров COMMON блоков, определенных для размещения блоков разделяемых данных, на этой фазе читаются соответствующие TEXT файлы.
В случае если исходная пользовательская программа помещена в один ЕРЕХ-FORTRAN файл, все три фазы работают последовательно. Первая фаза создает исходный VS FORTRAN файл, который затем должен быть откомпилирован. Вторая фаза читает FORTRAN и TEXT файлы и создает VS FORTRAN BLOCK DATA файл. Третья фаза генерирует выполняемый модуль. В случае, если программа занимает несколько файлов, первая фаза транслирует каждый исходный файл по отдельности, затем каждый полученный файл компилируется, и только после этого вторая фаза объединяет весь набор оттранслированных и откомпилированных файлов в один файл BLOCK DATA.
Вызов препроцессора
Первая фаза препроцессора вызывается командой:
PPREPROC source-file [, destination-file ]
где допустимы любые описания CMS файлов, а в квадратных скобках приведен необязательный параметр. По умолчанию имя исходного файла определено как XFORT, а имя выходного - FORTRAN.
Следует избегать употребления имени FORTRAN для исходного файла. Такое имя вообще не подходит, так как ЕРЕХ-FORTRAN есть прямое расширение Фортрана. Но особенно это опасно, когда имя выходного файла берется по умолчанию - в этом случае у выходного файла окажется то же имя, что и у входного. В таком случае препроцессор запрашивает подтверждения у пользователя, предупреждая его о небрежности описания файлов, приводящей к конфликту. Однако в случае подтверждения, выходной файл будет записан во входной.
Вызов препроцессора из XEDIT
Первая фаза препроцессора может быть вызвана и из редактора XEDIT во время редактирования исходного файла. Для этого употребляется следующая подкоманда:
PPREP [ destination-file ]
После выполнения обработки препроцессор вернет управление терминалу, при этом выходной файл станет текущим файлом редактора XEDIT. Употребление команд FILE или QUIT приведет к тому, что управление вернется к терминалу с исходным файлом в качестве текущего, как было вначале.
IS 185
Рис. Е4.1. Три фазы работы препроцессора VM/EPEX FORTRAN.
Макрокоманда XEDIT PPREP является просто вторым слоем первой фазы препроцессора VM/EPEX. На внешнем слое на уровне среды CMS макрокоманда PPREPROC накапливает в стеке команду QUIT, команду FILE и вызов макрокоманды PPREP. PPREPROC затем активизирует XEDIT для редактирования исходного файла, а находящаяся в стеке команда PPREP является первой командой, которую будет выполнять XEDIT. PPREP создает выходной файл. Когда выполнение команды PPREP заканчивается, записанные в стек команды обеспечивают запись выходного файла и выход из XEDIT, т. е. возврат в среду CMS после выполнения PPREPROC.
Вызов второй фазы
Вторая фаза препроцессора создает подпрограмму BLOCK DATA для разделяемых переменных и структур данных всей программы. Она вызывается командой
PPBLOCK filename [, filename ...] : destination-file
где имена входного и выходного файлов по умолчанию FORTRAN. В нормальной ситуации входные файлы - это файлы, которые были созданы первой фазой препроцессора.
PPBLOCK сканирует входной файл и ищет определения разделяемых данных. Этот тип определения является обобщением обычного определения глобальной переменной, так как данные разделяются не только подпрограммами программы, но и параллельными процессами. Форма и особенности определения разделяемых переменных будут обсуждаться ниже. В фортран-программе, созданной PPREPROC, определения разделяемых переменных оттранслированы в определения именованных cOMON-блоков.
После идентификации определений разделяемых данных в файле FORTRAN PPBLOCK читает ТЕХТ-файл этой программы для выяснения размера COMMON блока, определенного для разделяемых переменных компилятором, и производит в BLOCK-DATA-подпрограмме определение COMMON для каждого отдельного определения разделяемых данных.
Фортран-программа может состоять из нескольких отдельных исходных файлов. Определения разделяемых данных обычно повторяются и могут встретиться в любом из исходных файлов. XFORT-файлы транслируются независимо друг от друга в первой фазе препроцессора. При этом производятся соответствующие FORTRAN-файлы. FORTRAN-файлы компилируются также независимо друг от друга. Затем PPBLOCK сканирует каждый FORTRAN-файл в поисках определений разделяемых данных. Читаются соответствующие ТЕХТ- файлы, и для каждого определения разделяемых данных определяется результирующий именованный COMMON-блок, размер которого равен размеру наибольшего COMMON-блока с этим именем.
Если изменения внесены в один из исходных файлов программы, то этот файл должен быть перекомпилирован отдельно. Затем PPBLOCK перезапускается для всего набора файлов для генерации обновленной подпрограммы BLOCK DATA для всей программы.
Вызов третьей фазы
Третья фаза препроцессора вызывается командой
EPEXLOAD
EPEXLOAD - это выполняемый командный файл, который создает выполняемый загрузочный модуль программ ЕРЕХ. Создается либо загрузочный модуль для выполнения в среде ЕРЕХ, либо загрузочный модуль для выполнения на собственной пользовательской виртуальной машине. В интерактивном режиме EPEXLOAD запрашивает у пользователя спецификации основного файла, файла оъединенных в блок рованных данных и всех остальных файлов, которые должны быть загружены, а также запрашивается и загрузочный адрес, имя загрузочного модуля и точки входа в него. Кроме того, пользователь определяет, будет ли затрузочный модуль выполняться в среде ЕРЕХ или на его собственной виртуальной машине.
EPEXLOAD может быть вызван с параметрами, описанными в командной строке или в отдельном файле, что позволяет избежать интерактивного режима работы с экраном. Таким образом, EPEXLOAD вызывается из выполняемой программы. Параметры для EPEXLOAD задаются в именованной форме, причем параметры, занимающие более одного слова, берутся в одинарные кавычки. Параметры показаны на рис. Е4.2.
TXTUB MAIN SHARED-DATA LOAD-ADDR SHARED-DATA-ADDR OTHERS OPTIONS LOAD-MODULE ENTRY-MODULE LOAD-MODULE-MODE ENVIRONMENTTXTUB MAIN SHARED-DATA LOAD-ADDR SHARED-DATA-ADDR OTHERS OPTIONS LOAD-MODULE ENTRY-MODULE LOAD-MODULE-MODE ENVIRONMENTОписание глобальной библиотеки текстов Основная программа Файл разделяемых данных Загрузочный адрес основной программы Загрузочный адрес разделяемых данных Дополнительные файлы для загрузки Общережимные опции Имя загрузочного модуля Вход в модуль Режим загрузочного модуля ЕРЕХ или своя виртуальная машина
TXTUB MAIN SHARED-DATA LOAD-ADDR SHARED-DATA-ADDR OTHERS OPTIONS LOAD-MODULE ENTRY-MODULE LOAD-MODULE-MODE ENVIRONMENT
TXTUB MAIN SHARED-DATA LOAD-ADDR SHARED-DATA-ADDR OTHERS OPTIONS LOAD-MODULE ENTRY-MODULE LOAD-MODULE-MODE ENVIRONMENT
Рис. Е4.2 Параметры EPEXLOAD.
Основная программа и файл разделяемых данных должны быть определены обязательно. Остальные параметры не обязательные, их значение по умолчанию приведено на рисунке. Параметр среды показывает, будет ли модуль выполняться в среде VM/EPEX или на пользовательской виртуальной машине. Для выполнения в среде ЕРЕХ в модуль включаются подпрограммы для связи с ЕРЕХ. При выполнении на пользовательской виртуальной машине модуль будет обрабатываться в однопроцессном режиме с заменяющими подпрограммами для связи с ЕРЕХ. В заменяющих подпрограммах оператор ожидания приводит к ненормальному заверщению программы с выдачей сообщения. Это сделано потому, что оператор ожидания обычно встречается, когда процесс ожидает изменения какого-либо условия. В однопроцесной ситуации такое изменение никогда не произойдет.
Для определения параметров в командной строке вызов надо производить следующим образом:
EPEXLOAD MAIN: malnprog, SHARED-DATA: shared-data-file, ...
где каждый параметр задается в именованном виде, т. е. в виде пары имя- значение, как показано. Если значение не есть одиночное слово, ограниченное пробелами, то его надо взять в кавычки. Параметры задаются в произвольном порядке.
Для выбора параметров из файла вызов производится следующим образом:.
EPEXLOAD @file-spec
Файл параметров при этом составлен из строк следующего формата:
<parameter-name> : <value>
<имя-параметра> : <значение>
Имя параметра и значение такие же, как и выше. Пример файла параметров для вызова показан на рисунке Е4.3.
EPEXLOAD сохраняет в файле с именем EPEXLOAD RUN значения всех параметров вызова, как заданных прямо, так и взятых по умолчанию. Можно повторно вызвать эту фазу с теми же параметрами:
EPEXLOAD OEPEXLOAD RUN
или просто:
EPEXLOAD 0
Трансляция параллельных конструкций
Функции препроцессора VM/EPEX могут быть разделены на две категории: определения разделяемых данных и реализация параллельных конструкций.
Определения разделяемых данных
Разделяемые несколькими параллельными процессами данные должны быть идентифицированы соответствующим образом. Любая переменная является частной, доступной только процессу, выполняющему программу, если только она не определена явно как разделяемая.
Препроцессор VM/EPEX обрабатывает определения трех категорий памяти:
Явно определенные разделяемые данные для использования несколькими отдельными процессами.
Небольшой блок частной памяти для выделенных переменных, в которых хранится число отдельных процессов, номер, приписанный данному процессу, и значения некоторых системных параметров.
Неявно сгенерированные разделяемые данные для синхронизации доступа к разделяемой памяти.
Использование этих категорий памяти описано в нижеследующих разделах.
MAIN : FFTEST SHARED-DATA : FFSHARED LOAD-ADDR : 20000 SHARED-DATA-ADDR : 200000 LOAD-MODULE : FFTEST ENVIRONMENT : EPEX
Рис. E4.3. Файл параметров EPEXLOAD.
Явно определенные разделяемые данные
Память для структур данных, разделяемых несколькими про- цессами, определяется явно, как показано на рис. Е4.4.
•SHARED / blockname / variable [, variable ...]
Рис. E4.4. Определение eSHARED.
Эта конструкция является обобщением именованного COMMON-блока, так как данные здесь разделяются не только между подпрограммами одной программы, но и между различными параллельными процессами. Если один из процессов, который разделяет доступ к этим данным, изменит значение разделяемой переменной, все остальные процессы немедленно получают доступ к измененному значению этой переменной.
Синтаксис определения eSHARED соответствует синтаксису предложений именованного COMMON-блока. Если в строке eSHARED заменить на COMMON, то получится правильное фортрановское описание COMMON-блока.
Во время первой фазы работы препроцессора все esHARED-блоки преобразуются в cOMMON-блоки с тем же именем. Вторая фаза копирует все эти COMMON- блоки в подпрограмму BLOCK DATA и проверяет, что каждый eSHARED-блок, встречавшийся в наборе исходных файлов, встречается ровно один раз в выходном файле BLOCK DATA.
Подпрограмма BLOCK DATA генерируется при сканировании составляющих программу FORTRAN-файлов и поиске определений разделяемых данных, а затем при чтении соответствующих ТЕХТ-файлов и определении размеров COMMON-блоков, выделенных для каждого такого блока. В случае программы, состоящей из нескольких файлов, размер блока принимается равным размеру максимального одноименного блока. Каждый такой блок определяется как массив переменных типа REAL*8, каждая из которых занимает блок, подравненный по границе двойного слова.
Как и в случае COMMON-блока, определение eSHARED-блока должно быть включено в каждую подпрограмму, которая обращается к разделяемым переменным. Вместо непосредственного включения таких определений, программист может создать отдельный файл, содержащий набор определений CSHARED, и обращаться к нему при необходимости, используя eiNCLUDE.
Заметим, что INCLUDE в VS FORTRAN добавляет в исходный файл предложения из библиотеки. Ожидается, что в будущем препроцессор будет выполнять и функции, выполняемые INCLUDE в FORTRAN. Рис. Е4.5 показывает формат директивы eiNCLUDE.
Когда препроцессор встречает директиву eiNCLUDE, он выполняет команду XEDIT Get и копирует содержимое указанного файла в рабочий файл на то место, где встретилась директива eiNCLUDE. По умолчанию имя обрабатываемого файла то же, что и имя по умолчанию файла для препроцессора. Для препроцессора VM/EPEX FORTRAN это - XFORT. Препроцессор работает с выходным файлом как с текущим файлом XEDIT. Это означает, что команда Get с неопределенным именем файла будет выбирать файл, с тем же именем, что и выходной. Так как это, вообще говоря, не то, что нужно, препроцессор не оставляет имя файла неопределенным, а подставляет вместо него свое умолчание.
•INCLUDE file-spec [ , file-spec ... ]
Рис. Е4.5. Директива • INCLUDE.
Ограниченный вид определений 0SHARED используется препроцессором для его внутренних целей. Эти определения, называемые @SHARED_UNIQUE (подробно они будут обсуждаться ниже, в разделе Неявно генерируемая разделяемая память для синхронизации), также могут использоваться в прикладной программе.
Определения выделенных переменных
Препроцессор распознает несколько выделенных переменных, в которых запоминается общее число параллельных процессов, номер, приписанный данному процессу, и значения некоторых системных параметров. Обращения к этим переменным в программах VM/EPEX производятся по специальным именам, которые не являются допустимыми именами переменных Фортрана. Список этих выделенных переменных (постоянно пополняющийся) в настоящий момент следующий:
0NUMPROCS - общее число параллельных процессов. Значение этой переменной не может изменяться программой.
0MYNUM - номер, идентифицирующий выполняющийся процесс. Это приватная переменная, обеспечивающая уникальный идентификатор каждому процессу, такой что
О < 0NYNUM <> 0NUMPROCS
Значение этой переменной не может изменяться программой.
0DAGW - время в микросекундах, использованное в подпрограмме DAGW, . которая организует немедленную откачку вызывающего процесса; процесс становится доступнынм для планирования только по истечении указанного в 0DAGW времени. Значение этой переменной по умолчанию принимается равным времени выполнения, но оно может изменяться пользовательской программой. Каждый процесс должен модифицировать собственную копию этой переменной, если такие изменения необходимы.
0MAXWAIT - максимальное число попыток обращения к разделяемому ресурсу или замку до окончания выполнения. Значение этой переменной определяется по умолчанию, но может быть модифицировано программой. Когда число попыток получить доступ к замку превышает 0MAXWAIT, программа считается зависшей и данный процесс завершается. Может оказаться необходимым изменить значение 0MAXWAIT в зависимости от числа параллельных процессов или от системной нагрузки. Если процесс повторно завершается с сообщением о превышении времени ожидания, необходимо исследовать соответствующий алгоритм. Опыт показал, что причиной подобного завершения служит ожидание выполнения условия, которое никогда не встречается. Если алгоритм выглядит корректным, можно поэкспериментировать с увеличением времени ожидания.
Пока значения этих переменных являются разделяемыми операционной системой и процессом, сами переменные являются локальными для процесса. Каждый процесс снабжен своими собственными копиями этих переменных, т.е. они являются локальными для процесса и глобальными внутри него. В Фортране препроцессор определяет массив $$PRIV целочисленных переменных в именованном 0СОММОМ-блоке для размещения выделенных переменных. Так
как этот блок разделяется между подпрограммами, возможно откомпилированными независимо, имя $$priv резервируется для этой цели и не может употребляться как имя блока в пользовательских определениях cOMMON-блоков. Препроцессор генерирует уникальное имя переменяй для массива, содержащего локальные копии переменных eNUMPROCS, BMYNUM, 0DAGW и 0MAXWAIT. Обращения к этим переменным заменяются на обращения к соответствующим элементам массива. Значения этих . переменных устанавливаются подпрограммой инициализации, которая взаимодействует с операционной системой. Препроцессор вставляет обращение к подпрограмме инициализации как первое выполняемое предложение основной программы.
Неявно генерируемая разделяемая память для синхронизации
Некоторые разделяемые переменные, невидимые для прикладного программиста, определяются препроцессором и управляются подпрограммами VM/EPEX. В их число входят, например, замки синхронизации, флаги и счетчики циклов. Место для этих переменных выделяется в синхронизационном массиве в блоке BSHARED, называемом синхронизационным блоком, который .определяется препроцессором для этих целей. Почти все конструкции VM/EPEX вызывают расширение синхронизационного массива.
Для каждого исходного файла препроцессор определяет один синхронизационный блок и генерирует для него уникальное имя. Это значит, что в многофайловой программе существует один синхронизационный блок для каждого файла. Существенно, что в файлах-компонентах многофайловой программы нет повторений имен синхронизационных блоков. Как показано на рис. Е4.1, файлы-компоненты в первой фазе препроцессора обрабатываются независимо. Хотя сходство присваиваемых имен минимизируется именованием блоков случайно сгенерированными именами, которые уникальны внутри исходного файла, гарантировать, что имена синхронизационных блоков будут уникальны во всем наборе исходных файлов, невозможно. В маловероятном случае, когда в первой фазе препроцессора генерируются одинаковые имена синхронизационных блоков в разных исходных файлах, это будет обнаружено во второй фазе. Такое совпадение приводит к сообщению об ошибке и к включению в подпрограмму BLOCK-DATA соответствующего COMMON определения, которое признало бы компиляцию этой подпрограммы (из-за совпадения имен блоков) ошибочной. Совпадение имен может быть исправлено вручную или повторением первой фазы для одного или нескольких исходных файлов.
Требование уникальности имен блоков указывается определением этого блока не просто BSHARED (разделяемым), но OSHARED_UNIQUE (разделяемым-уни- кальным). Вторая фаза препроцессора интерпретирует определение BSHARED_UNIQUE таким образом: определение этого блока может повторяться в данном файле, например в подпрограммах, но имя этого блока не может повторяться в разных файлах. Определение блока BSHARED_UNIQUE может использовать и прикладной программист.
В конце первой фазы препроцессора определения выделенных переменных и синхронизационных блоков в каждой подпрограмме FUNCTION и SUBROUTINE помещаются в файл, они доступны каждой такой подрограмме.
Параллельные конструкции
Препроцессор преобразует макровызовы VM/EPEX в компилируемый исходный текст. Это освобождает прикладного программиста от определенных
повторяющихся действий по преобразованию в параллельный код и позволяет пользоваться базовыми операциями для указания параллельных циклов и последовательных секций кода. Каждое вхождение макровызова VM/EPEX заменяется на вызовы подпрограмм и вставки текста. Имеются следующие макроопределения VM/EPEX:
Параллельный цикл: 0DO ... 0DOEND;
Последовательная секция: ^SERIAL BEGIN ... 0SERIAL END;
Согласование процессов; 0BARRIER, 0WAITFOR.
Синтаксис и семантика этих макроопределений обсуждается в следующих разделах.
Параллельный цикл: 0DO ... 0DOEND
Конструкция обеспечивает параллельную форму обычного последовательного DO цикла. Она позволяет выполнять задание цикла в виде раздельных параллельно выполняющихся процессов, путем назначения последовательных значений индекса цикла различным процессам и выполнения каждым процессом одновременно итерации.
Форма обычного последовательного DO цикла показана на рисунке Е4.6. У цикла есть область действия, индекс цикла с его начальным и конечным значенями и тело цикла (или задание).
Сценарий параллельного выполнения цикла предполагает, что все итерации взаимонезависимы. Когда процесс достигает начала цикла, его локальный “индекс цикла44 получает значение, и процесс выполняет тело для этого значения индекса. После окончания выполнения тела процесс снова выходит на начало цикла для получения нового значения индекса, и так повторяется до тех пор, пока цикл не будет завершен. В простейшей форме, показанной на рис. Е4.7, параллельный цикл соответствует традиционному последовательному циклу.
DO 100 index - nl,n2 ... loop body ...
100 CONTINUE
Рис. E4.6. Обычный последовательный DO цикл.
ODO [stmt ft] tndx - nl,n2 ... loop body ...
[stmt ft] OENDDO
Рис. E4.7. Простая форма параллельного цикла.
Подобно последовательному циклу, параллельный цикл имеет область действия и индекс цикла с начальным и конечным значениями. Как и в последовательном цикле, в параллельном возможно дополнительное задание приращения. Кроме того, есть и другой аналог приращения. В некоторых случаях более эффективно задавать процессу не одно значение индекса, а серии значений, или порцию (chunk). В конструкции 0DO разрешено задавать дополнительную спецификацию CHUNK для определения серии значений. На-
пример, если размер порции определен как 10, первый процесс будет выполнять цикл с первыми десятью значениями индекса, второму будут заданы следующие 10 значений и т.д. Эта опция очень привлекательна в тех случаях, когда в теле необходимо выполнять очень небольшой объем работы на каждой итерации, и расходы на получения значений разделяемого индекса превышают время выполнения одной итерации.
На рис. Е4.8 показан полный синтаксис параллельного цикла. Этот рисунок иллюстрирует соглашения, положенные в основу синтаксиса конструкций VM/EPEX;
зарезервированные слова записываются прописными буквами. . значение по умолчанию записывается первым.
необходимая часть слова записывается прописными буквами, а остальная - строчными.
дополнительные параметры записаны в квадратных скобках.
альтернативные конструкции разделены вертикальной чертой.
Например, запись:
NENDO [label] [Walt | NOWalt]
показывает, что SENDO - зарезервированное слово, label - дополнительный параметр, значением дополнительного параметра по умолчанию является WAIT, в записи которого достаточно ы, а альтернативным значением этого параметра является NOVAIT, для записи которого достаточно NOW. Препроцессор в действительности не различает, на каком регистре записаны символы, поэтому на практике и зарезервированные слова, и метки, и переменные могут записываться как прописными, так и строчными буквами в соответствии с требовавшими объектного языка. Однако следующие друг за другом пары меток должны записываться с учетом регистра.1
DO [label | stmt#] index - nl, n2 [,n5] [CHUNK - chunksize] ... loop code ...
[stmt#] eENDDO [label] [ Wait | NOWalt ]
Рис. E4.8. Синтаксис параллельного цикла
Интерпретация параллельного цикла, показанного на рис. Е4.8, отличается от обычного фортрановского D0 цикла в следующем:
Индекс цикла должен быть локальной целочисленной переменной, так как разные значения индекса будут заданы разным участвующим в вычислениях процессам.
Начальное значение nl, конечное значение п2 и дополнительное приращение пЗ должны быть целочисленными выражениями. Значения nl, п2 и пЗ должны быть одними и теми же для всех процессов. Нижеследующее обсуждение того, как управляются эти циклы, сделает ясным фундаментальность этого условия. Если не придавать ему значения, то при реализации можно получить неповторяющиеся или ошибочные результаты.
Для того чтобы цикл проработал хотя бы один раз, начальное значение индекса nl должно быть меньше или равно конечному значению
n2. В отличие от последовательного фортрановского DO цикла цикл ODO не будет выполняться ни разу, если nl больше, чем п2. Кроме того, приращение пЗ, если оно задано, должно быть положительным целым числом. Увеличение производительности достигается при ограничении планирования цикла положительными итерациями, и, так как итерации цикла должны быть независимыми, порядок, в котором они выполняются, не должен иметь значения.
Для того чтобы программа выполнялась правильно, значения nl, п2 и пЗ не должны изменяться до тех пор, пока процессы продолжают работу в цикле.
Для координации выполнения цикла разными процессами программа не должна иметь переходов из тела цикла 000. Препроцессор отмечает флагом как ошибку любое предложение GOTO в теле цикла, которое выводит за пределы 0DO цикла. Нестандартный выход из тела цикла 0DO должен оформляться при помощи макрокоманды 0SCRAM.
Точно так же не должно быть переходов и внутрь тела цикла 0DO.
Конец цикла 0DO обозначается 0ENDD0. Препроцессор воспринимает следующие строки как синонимы:
0ENDO 0ENDDO0ENDO 0ENDDO«ENDOW0«ENDOW0
0ENDO 0ENDDO
0ENDO 0ENDDO
«ENDOW0
«ENDOW0
0END.DO 6D0END
Процесс, выполнивший тело цикла 0DO1 получает следующее значение индекса цикла (или порцию значений) и будет выполнять одну или несколько дополнительных итераций цикла. Когда все значения индекса цикла будут распределены, то работы для распределения не остается. В общем случае появится такой интервал времени, когда распределены все значения индекса цикла, но не все процессы завершили работу. В некоторых программах выполнение последующего кода может начинаться немедленно, не дожидаясь полного завершения выполнения цикла, как только освободится процесс, который сможет выполнять этот код. Но в некоторых случаях выполнение последующего кода зависит от результатов выполнения всего цикла целиком, и процессы должны ждать, пока все задания не будут выполнены. Синхронизационная опция в конце цикла позволяет программисту управлять тем, что должен делать процесс по окончанию выполнения своей части работы в цикле. По умолчанию 0WAIT определяет, что процесс должен дождаться полного завершения всех итераций цикла. Опция NOWAIT разрешает процессу продолжать работу, как только будет распределены все значения индекса цикла, даже если остальные процессы еще не завершили работу над итерациями цикла. Если задана опция WAIT явно или по умолчанию, препроцессор вставляет код, который вынуждает все процессы ждать завершения всех итераций цикла, и лишь после этого становиться возможным продолжение выполнения процессами следующего кода.
9 Это не опечатка. Эта строка включена в список синонимов, так как первые пользователи ЕРЕХ предпочитали именно такую форму.
• Дополнительная метка может быть произвольной алфавитно-цифровой строкой. При ее употреблении, она должна также встретиться в предложении, которое завершает цикл.
Предостережения
Употребление параллельного цикла возможно в некоторых, но не во всех случаях. Например, интерпретация 0DO делает вложенность 0DO циклов бессвязной, так как не все комбинации индексов будут распределены между процессами. По этой причине вложенность 0DO циклов помечается препроцессором как ошибка.
Типичное применение параллельных циклов показано в примере на рис. Е4.9, на котором последовательность параллельных циклов вложена в последовательный цикл. Каждый процесс, выполняющий этот код, имеет один и тот же разделяемый поток управления в том смысле, что каждый процесс подходит к началу каждого цикла, взаимодействует с другими процессами внутри цикла и продолжает свое выполнение до следующего цикла. Код, генерируемый препроцессором, подходит к такой ситуации. Если же условные переходы определяют поток управления таким образом, что на разных итерациях внешнего последовательного цикла процесс выполняет разные параллельные циклы, то в этих случаях препроцессор генерирует несоответствующий код. Причина этого будет пояснена в обсуждении деталей реализации, отложенном до раздела Е4.6.
DO 100 1-1,100
0DO 10 Л-1,1000 ... loop kxiy ...
10 0ENDDO NOWAIT
0DO 90 09-1,200,20 ... loop body ...
90 0ENDDO WAIT
100 CONTINUE
Рис. E4.9. Параллельные циклы, вложенные в последовательный цикл.
Это ограничение не означает, что все процессы должны иметь одинаковый поток управления. Например, процессы могут разветвляться для выполнения разных частей программы в зависимости от номера, идентифицирующего процесс. Это означает, что процесс выбирает один и тот же путь каждый раз, как приходит в точку выбора. В этом случае препроцессор генерирует удовлетворительный код.
Даже если это условие не выполняется, возможно, что к некоторым частным программам это ограничение неприложимо. Тщательное изучение деталей реализации препроцессора позволит выполнить анализ конкретного случая.
Несомненно, что последующее развитие препроцессора устранит это ограничение.
Нестандартный выход из цикла: 8SCRAH
Эта конструкция применяется для того, что окончить выполнение цикла необычным способом предположительно до того, как все значения индекса цикла будут распределены. Когда процесс наталкивается на предложение 0SCRAN, он немедленно выполняет предложение 8ENDD0, а остальные процессы, работающие в цикле, будут выполнять это предложение только тогда, когда они потребуют очередное значение индекса или порции индексов. Важно отметить, что итерации, которые были уже запущены на других процессах, будут завершены.
Препроцессор заменяет 8SCRAM вызовом подпрограммы, в которая увеличивает индекс цикла до своего конечного значения, тем самым эффективно завершая распределение последующих работ в цикле.
Последовательная секция: 8SERIAL BEGIN...8SERIAL END
Конструкция последовательной секции применяется для определения той секции кода, которая выполняется только одним процессом. Программист может выбрать, будет ли эта секция выполняться любым процессом, первым пришедшим к ней, или определенным процессом. Синтаксис последовательной секции приведен на рис. Е4.10.
8SER1al BEGin [label | stmt#! [RROCESS«process-/io] ... serial cod ...
[stmt#] 8SERial END [label! [Wait | NOWait]
Рис. E4.10. Синтаксис последовательной секции.
Эта конструкция имеет те же метку и синхронизационные опции, что и параллельный цикл. Дополнительный номер процесса определяет идентификационный номер определенного процесса, который будет выполнять эту секцию. Если никакой процесс не определен, то секцию будет выполнять процесс, первым пришедший к ней.
Как и в случае 8D0 цикла, препроцессор расширяет синхронизационный массив, чтобы включить в него место для синхронизационных переменных последовательной секции. Если определена опция WAIT, явно или по умолчанию, препроцессор вставляет код, который заставляет процессы ожидать, окончания выполнения итераций цикла.
Дополнительные предостережения
При обсуждении параллельных циклов говорилось об ограничениях на их употребление в сегментах программы, которые выполняются неоднократно. Некоторые ограничения налагаются и на последовательную секцию. В коде, который выполняется неоднократно, поток управления должен быть таким, что процесс или всегда выполняет определенную последовательную секцию, или никогда не делает этого. Как и в случае параллельного цикла, возможно, что анализ конкретной программы с полным пониманием всех деталей реализации препроцессора покажет, что к данному случаю ограничение не относится.
Окружение процессов: 8BARRIERS или 8WAITF0R
Две макроконструкции управляют ожиданием процессом достижения некоторыми или всеми процессами определенной точки. “Барьеры" являются широко распространенным методом, с помощью которого все определенные процессы собираются в одной точке программы. Макроконструкция 8WAITF0R останавливает выполнение любого процесса, достигшего этой точки, до тех пор, пока не будет выполнено определенное условие. Обе макроконструкции обсуждаются в этом разделе.
Макроконструкция 0BARRIER заставляет все процессы ждать, пока все участвующие процессы не достигнут этой точки программы, и только после этого разрешается их дальнейшее выполнение. Синтаксис этой макроконструкции прост:
0BARRIER
Прибытие процесса к барьеру, фиксируется выполнением примитива “считать и прибавить44 в счетчике достижения барьера. Пока содержимое счетчика остается меньше общего числа процессов в программе (0NUMPROCS), процесс ждет у барьера. Заметим, что может возникнуть тупиковая ситуация, если найдется процесс который никогда не достигает барьера. В этом случае ожидающие процессы завершаются по истечении времени, указанного значением 0MAXWAIT.
Если макроконструкция барьера создает барьер, который существует, пока все участвующие в задаче процессы не достигнут его, то макроконструкция ожидания устанавливает барьер, который существует, пока верно заданное условие. Форма команды ожидания следующая:
0WAITFOR ( wait-condition )
где wait-condition является логической комбинацией wait-expressions:
wait-expression» или( wait-expression ),
или. NOT. wait-expression»
илиwait-expression [logical-operatorwalt-expression-Д
Wait-expression является:
меткой ЕРЕХ или параллельной конструкцией, или выраженим из переменных Фортрана и ЕРЕХ.
Logical-operator является:
.AND. | .OR. | .EQV. | .NEQV.
Ссылка на метку ЕРЕХ в wait-expression может быть только в подпрограмме, в которой она встречается. Ссылка на метку ЕРЕХ интерпретируется как неявное ожидание завершения параллельного цикла или последовательной секции, на которые эта метка указывает. Эта условие удовлетворяется, когда разделяемое время цикла или секции по крайней мере не меньше, чем локальное время соответствующих процессов. Детали реализации - в разделе Е4.6.
Примеры:
Wait-condition является простым логическим выражением:
0VAITFOR (N .6Е. 256)
Watt-condition включает переменные Фортрана и ЕРЕХ:
WAITFOR (ALPHA.NE.O.AND.0NUMPROCS.GE.4)
Ожидание помеченного параллельного цикла или последовательной секции:
0WAITFOR (6100)
Watt-condition - выражение, включающее переменные и метки:
0WAITFOR ((X.LS.1O.AND.(0LOOP1O.OR.(.NOT.XFUNC(Y))))
Инициализация
Препроцессор вставляет в основную программу пользователя, непосредственно перед первым ее предложением, обращение к инициализирующей подпрограмме. Эта подпрограмма устанавливает значения выделенных переменных 0NUMPROCS, 0MYNUM, 0DA6W и 0MAXWAIT. Эта же подпрограмма приостанавливает процессы до тех пор, пока не будет выделено достаточное число процессов для выполнения задачи в соответствии с определенным для этой задачи максимальным и минимальным числом процессов. После того как достаточное число процессов выделено, всем им разрешается начать выполнение. Фактическое число процессов, выделенных задаче, передается вызывающей программе как значение переменной 0MUMPROCS, и оно не может определяться или модифицироваться в исходном тексте программы.
Препроцессор вставляет в программу обращение к инициализирующей подпрограмме в формате:
CALL GETNUM {distinguished-vars, synchr-vars} где distinguished-vars есть ссылка на массив выделенных переменных в блоке $$PRIV СОМОМ, a synchr-vars - синхронизационный массив, определенный препроцессором.
Е4.5 ПРИМЕРЫ ПРОГРАММ
Пример параллельного цикла, который ожидает окончания
Пример, приведенный на рис. E4.ll, показывает два 0D0 цикла с явно специфицированным ожиданием окончания. Так как спецификация по умолчанию - WAIT, явная спецификация производится для наглядности и документирования. В этом примере WAIT определяет, что выполнение второго цикла начнется только после полного завершения первого. Процесс, на долю которого не достанется работы в первом цикле, должен будет ожидать, пока все процессы, получившие работу, не закончат выполнение своих итераций. После этого все процессы приступят к выполнению второго цикла. Точно также и во втором цикле процесс, не получивший работы, будет ожидать, пока остальные процессы не выполнят всех итераций второго цикла.
ODO firstloop I - 20,50 ... loop text ...
ODOEND firstloop WAIT
ODO secondloop I - 1,100 ... loop text ...
ODOEND secondloop WAIT
Рис. E4.ll. Пример двух параллельных циклов с ожиданием окончания.
Пример параллельных циклов с wait и nowait
В примере, показанном на рис. Е4.12, приведены три параллельных цикла с различными разделяемыми зависимостями. В этом примере выполнение второго цикла может начинаться до полного завершения выполнения первого цикла, так как в первом цикле задана спецификация NOWAIT. Так как второй цикл специфицируется по умолчанию как WAIT, выполнение третьего цикла начинается только после завершения выполнения второго. Однако заметим, что некоторые процессы могут все еще выполнять первый цикл, когда уже начнется выполнение третьего цикла.
ODO firstloop I - 20,50 ... loop text ...
ODOEND firstloop NOWAIT ODO secondloop J - 1,100 ... loop text ...
ODOEND secondloop 0D0 thlrdloop К - 2,30 ... loop text ...
ODOEND thlrdloop
Рис. E4.12. Пример трех параллельных циклов.
Пример конвейеризованных параллельных циклов
Пример на рис. Е4.13 иллюстрирует эффект конвейера, полученный включением двух параллельных циклов без ожидания в код, который выполняется неоднократно. Процессы приходят к циклу loopl и получают работу в нем до тех пор, пока все значения индекса этого цикла не будут распределены. Предположим, что процесс 7 вышел из цикла loopl или пришел к этому циклу слишком поздно, и ему уже не досталось в нем работы. Тогда он немедленно начнет выполнять цикл 1оор2, даже если остальные процессы еще продолжают выполнять свои итерации цикла loopl. Когда процесс 7 перестает получать работу в цикле loop2, он переходит к предложению 200 и возвращается на начало последовательности кодов, приступая второй раз к циклу loopl. В это время остальные процессы могут продолжать работу в цикле 1оор2. Конечно, может так случиться, что некоторые процессы еще продолжают работу в цикле loopl, которая была запущена, когда процесс 7, не найдя работы, в цикле loopl, в первый раз подошел к следующему циклу.
В этом случае процесс 7 приостановится в начале цикла loop до тех пор, пока предыдущее вычисление цикла не будет завершено. Когда все процессы завершат работы, полученные ими при первом проходе цикла loopl, процесс 7 начнет второй проход.
Этот аспект синхронизации процессов осуществляется при помощи локальных переменных loopi-clock для каждого процесса и глобальной переменной loopi-clock. Детали препроцессорной реализации этого механизма приведены в разделе Е4.6.
100 800 loopl I - 20,50
' 8D0END loopl N0MIT «DO loop2 J - 1,100
NDOEND loop2 N0MIT
200 IF( ...condition... ) GOTO 100
Рис. E4.13. Пример конвейеризованных циклов.
Разветвление процессов с учетом предостережений
В примере на. рис. Е4.14 два процесса ответвляются от остальных для выполнения одного цикла, в то время как другие начинают одновременно выполнять другой цикл. Пока процессы 1 и 2 выполняют цикл loopl, остальные процессы выполняют цикл 1оор2, а завершив его выполнение, могут приступать к выполнению цикла 1оорЗ. Параллельное выполнение циклов 1оор2 и 1оорЗ невозможно, так как цикл 1оор2 специфицирован по умолчанию как выполняющийся с ожиданием завершения, поэтому выполнение цикла 1оорЗ не начнется до завершения всех итераций цикла 1оор2. Макроконструкция NBARRIER дает уверенность в том, что ни один процесс не будет продолжать свою работу до тех пор, пока все процессы не завершат работу, полученную ими до этой точки. Предупреждение о возможных синхронизационных проблемах при разветвлении процессов не касаются данного случая, так как путь, проходимый любым процессом, один и тот же при каждом прохождении этого кода. Если этот код будет выполняться повторно, напри-
мер, как часть тела последовательного цикла, и если условие ветвления в предложении 1 управляется порядком достижения этой точки процессами, а не идентификационным номером процесса, то в этом случае возможно, что процесс иногда выполняет цикл loopl, а иногда - 1оор2. Выше предупреждалось, что в этом случае могут быть неудовлетворительные результаты.
1 IF(&MYNUM.GT.2) GOTO 100
С Неге is a parallel loop to be done by two processes:
С Этот параллельный цикл выполняется двумя процессами:
ООО loopl I - 1,1000
... compute some function of I...
0DOEND loopl NOWAIT
GOTO 200
C The other processes do the following two loops, C finishing the first one before starting the second. С Остальные процессы выполняют следующие два цикла, С заканчивая выполнение первого до начала выполнения второго 100 0DO Ioop2 J«l,25
... do something with J...
0ENDDO loop2
ODO loop3 K«2,50,2
... do sometMng with even values of К ... ODOEND loopS
C Wait until all processes get here before proceeding С Ожидать пока все процессы не достигнут этой точки 200 0BARRIERS
Рис. Е4.14. Пример расходящихся процессов.
Реализация макроконструкций VM/EPEX
Макроконструкции VM/EPEX реализованы в Фортране как вызовы подпрограмм из библиотеки текстов EPEXLIB. Для каждого вхождения параллельной конструкции препроцессор определяет набор разделяемых переменных для синхронизации процессов и для доступа к разделяемым данным. Управление критическими секциями осуществляется подпрограммами, которые реализуют вариант алгоритма “читатели-писатели44. Этот алгоритм является специальной формой версии программы “читатели-писатели44 ^ которая лежит в основе алгоритма VM/EPEX.
Реализация параллельного цикла
Семантика конструкций циклов показана на рисунках. На рис. Е4.15 показан простой параллельный цикл. Он не описывает размер порции и явля-
^ Allan Gottlieb, B.D. Lubachevsky. Lary Rudolf, “Basic Techniques for the Efficient Coordination of Very Large Numbers of Cooperating Sequential Processors", ACM Transactions on Programming Languages and Systems, vol. 5, no. 2, April 1983.
ется циклом без ожидания окончания. Интерпретация этого цикла показана ниже на этом же рисунке.
Препроцессор определяет набор переменных SHARED INTEGER для управления циклом. Делая это, он увеличивает единственный синхронизационный массив, называемый здесь ISYNCR, на величину требуемого набора переменных. Ссылки на набор переменных цикла делаются при помощи ссылки на первый элемент этого набора: ISYNCR(L) при некотором значении L. Кроме этого, для каждого цикла препроцессор определяет и локальную целочисленную переменную, инициализированную нулем, с уникальным идентификатором, заменяющим MYTIME.1
Макровызов:
100 8D0 200 1-11,12,13 ... loop body ...
200 eENDDO NOWAIT
Его интерпретация:
СЮО еоо 200 1-11,12,13
100 CALL DOSET(I1,12,13,MYTIME, ISYNCR,*200)
101 CALL DOBEG(I,MYTIME,ISYNCR,*200) ... loop body ...
GOTO 101
C200 GENDDO NOWAIT
200 CONTINUE
Рис. E4.15. Реализация параллельного цикла. Интерпретация WAIT
switch ■ О
300 IF(privateclock.LE.sharedclock) GOTO 301 CALL $$WAIT(1, switch) GOTO 300
Рис. E4.16. Код, генерируемый для WAIT.
Если цикл специфицирован как цикл с ожиданием окончания выполнения, то препроцессор вставляет после конца цикла код, управляющий приостановкой процессов до тех пор, пока вся работа цикла не будет завершена. На рис. Е4.16 показана форма кода, генерируемая препроцессором для управления приостановкой. Приостановка управляется сравнением локальных часов цикла {loop-clock) процесса с разделяемыми часами цикла. Препроцессор определяет целочисленную переменную для локальных часов цикла. Разделяемые часы цикла являются одной из разделяемых переменных в синхронизационном массиве цикла, называемом здесь ISYNCR. Значение локальных часов получает приращение в подпрограмме управления циклом VM/EPEX, перед тем как процесс выйдет из цикла. Значение разделяемых часов получает приращение только после выхода из цикла последнего процесса - после завершения последней итерации цикла. Как только это произойдет, приостановленный процесс продолжает работу.
На рис. Е4.17 показан параллельный цикл, в котором описан размер порции. Его интерпретация, приведенная ниже, демонстрирует добавление внутреннего цикла, который обеспечивает выполнение порции итераций цик-
ла. Препроцессор определяет переменные Л и J2 так же, как и MYTIME, и генерирует уникальные имена для них.
Макровызов:
100 000 200 1-11,12,13 CHUNK-10 ... loop body ...
200 OENDDO NOWAIT
Его интерпретация:
C100 ODO 200 1-11,12,13 CHUNK-10 100CALL DOSET(MYTIME,ISYNCR,*200)
101CALL DOBEG(I1,12,13,10,JI,J2,MYTIME,ISYNCR,*200)
DO 102 I-J1,J2,I3 ... loop body ...
102 CONTINUE
GOTO 101
C200 OENDDO NOVAIT 200 CONTINUE
Рис. E4.17. Реализация параллельного цикла с описанием размера порции.
Макровызов:
100 0D0 200 1-11,12,13
IF ( ...condition...) GOTO 150 OSCRAM
200 OENDDO NOVAIT
Его интерпретация:
C100 0D0 200 1-11,12,13 100 CALL DOSET(I1,I2,I3,MYTIME,ISYNCR,*200) 101 CALL DOBEG(I,MYTIME,ISYNCR,*200)
IF (...condition...) GOTO 150
CALL SCRAM(ISYNCR,12, *200)
GOTO 101
C200 OENDDO NOVAIT 200 CONTINUE
Рис. E4.18. Реализация параллельного цикла с выходом OSCRAM.
Препроцессор обеспечивает макроконструкцией OSCRAM для возможности преждевременного выхода процесса из цикла и прекращения распределения работ в цикле. Рис. Е4.18 показывает реализацию макроконструкции OSCRAM.
Реализация последовательной секции
Семантика последовательной секции показана на рисунках, которые помещены ниже. Рис. Е4.19 показывает код, генерируемый для последовательной секции, который определяет, какой процесс будет ее выполнять.
Если идентификационный номер процесса не равен 1, процесс обходит последовательную секцию и немедленно приступает к выполнению следующей части программы. В противном случае, процесс выполняет последовательную секцию, а затем продолжает выполнение следующей части программы. Если номер процесса не описан, то в сгенерированном препроцессором обращении к процедуре SERSET вместо номера процесса будет ноль. Это указывает процедуре SERSET, что последовательную секцию будет выполнять первый пришедший к ней процесс. Если в программе не употреблен номер предложения 0SERIAL END, препроцессор сгенерирует соответствующий номер для него. Опции WAIT и NOWAIT имеют тот же смысл, что и в случае параллельного цикла. Если специфицирована опция WAIT, то препроцессор вставляет код, который приостанавливает процесс до тех пор, пока не будет выпол- нена последовательная секция.
Макровызов:
100 0SERIAL BEGIN PROCESS-1 ... serial code ...
200 0SERIAL END
Его интерпретация:
C100 0SERIAL BEGIN PROCESS-1
100 CALL SERSET(MYTIME,1,ISYNCR,*2OO) ... serial code ...
CALL SEREND(MYTIME,ISYNCR)
C200 0SERIAL END
200 CONTINUE
Рис. E4.19. Реализация последовательной секции.
Реализация барьера
Барьер вынуждает процесс ждать, пока все процессы работы не придут в эту точку. Код, генерируемый препроцессором, показан на рис. Е4.20.
Макровызов: 0BARRIER
Его интерпретация:
С 0BARRIER
СSynch array 1s at syncharray
С Синхронизационный массив в syncharray C Private clock variable is privateclock С Локальные часы - это privateclock
CALL BARRIER(privateclock,syncharray)
Рис. E4.20. Реализация макроконструкции 0BARRIER.
Реализация evAHFOR
Макроконструкция WAITFOR вынуждает процесс ждать, пока не будет выполнено определенное условие. Реализация WAITFOR показана на рис. E4.2L
Макровызов:
WAITFOR ( ...condition...)
Его интерпретация:
С WAITFOR ( ...condition...)
537 SWITCH - О
538 IF ^... condition...) GOTO 539
CALL $$WAIT(4, SWITCH)
GOTO 538
539 CONTINUE
Рис. E4.21. Реализация макроконструкции WAITFOR.
E4.7. Подпрограммы VM/EPEX
Макроконструкции VM/EPEX реализуются при помощи вызовов различных синхронизационных подпрограмм. Здесь приводится список таких подпротрамм, которые фактически могут быть использованы и самим пользователем точно так же, как и препроцессором. Эти подпрограммы хранятся в текстовой библиотеке EPEXLIB. Пользователь должен разместить необходимые переменные в разделяемой памяти, что можно сделать при помощи определения 9SHARED.
Подпрограммы высокого уровня
Инициализация выделенных переменных:
GETNUM(IPRIVS,ISYNCR)
. Инициализация параллельного цикла:
DOSET(MYTIME,ISYNCR,*апив)
Выдача работы по циклу:
00ВЕ6(И,12,13,ICHUNK,III,II2,MYTIME,ISYNCR,*snum)
Нестандартный выход из параллельного цикла:
SCRAM(ISYNCR,I2,*snuB)
Инициализация последовательной секции:
SERSET(MYTIME,IPROCNO,ISYNCR,*snum)
Завершение последовательной секции:
SEREND(MYTIME.ISYNCR)
Получение (неисключительного) доступа к ресурсу:
BEGRDR(S)
Освобождение (неисключительного) доступа к ресурсу:
ENDRDR(S)
Получение (исключительного) доступа к ресурсу:
BEGWTR(S)
Освобождение (исключительного) доступа к ресурсу:
ENDWTR(S)
Подпрограммы нижнего уровня
Подпрограммы на языке ассемблера, которые поддерживают попрограммы высокого уровня VM/EPEX, также могут использоваться прикладным программистом.
. CS (I,JSHARE,К)
Эта подпрограмма выполняет операцию “сравнения и замены" Систе- . мы 370: если JSHARE равно I, то JSHARE устанавливается равным к. В противном случае I устанавливается равным JSHARE. Это используется в первую очерадь для установки замков: JSHARE будет разделяемым замком, при этом если JSHARE равно 0, то замок открыт, если же JSHARE не равно 0, то замок заблокирован. К этой подпрограмме обращаются со следующими значениями параметров: к не равно 0, а I равно 0. Если в результате работы этой подпрограммы у I останется значение 0, то захват произведен, и JSHARE присвоено значение К. Иначе захват не произошел, а I содержит предыдущее значение JSHARE.
. DAGW(N)
Эта подпрограмма приостанавливает процесс на N микросекунд. Значение N должно быть выбрано достаточно большим с тем, чтобы планировщик завершил временной квант программы. (Это зависит от параметров в планировщике VM/EPEX, но обычно 20000 бывает достаточно.) К" этой программе обращаются тогда, когда процесс должен ждать, пока другие процессы выполнятся, например при ожидании снятия захвата.
. LOCKl№(L)
Эта подпрограмма позволяет процессу получить доступ к замку L, используя подпрограмму “сравнения и замены" CS, описанную выше. Если L не доступен, процесс ждет (используя DAGW), пока он не станет доступен.
. UNLOCK(L)
Эта программа снимает блокировку с L, устанавливая его в нуль.
. LOKTRY(L)
Эта логическая функция пытается захватить L, возвращая значение .TRUE., если попытка удалась, или .FALSE, в противном случае. Она полезна в ситуациях, когда у процесса есть другая полезная работа для выполнения при ожидании снятия захвата.
Подпрограммы “выбор=и=операция“
Подпрограммы “выбор" и “операция" выполняют атомарные операции чтения-модификации-записи, как описаные ниже.
Объектная переменная в этих операциях - обычно разделяемая переменная. Определение ее в виде массива гарантирует обращение к ней в этих подпрограммах по ссылке, а не по значению. Это требование подчеркнуто в приведенных ниже описаниях подпрограмм записью объектной переменной в виде элемента массива.
. IFNA(ISHARE,MYVAL)
Это целочисленная функция, которая использует подпрограмму “срав- ненить-и-заменить“ для выполнения целочисленной операции “вы- брать-и-прибавить“, прибавляя значение переменной MYVAL к значению разделяемой переменной ISHARE(I). Функция возвращает значение, равное предыдущему значению ISHARE(l); после выполнения функции значение ISHARE(I) увеличивается на величину MYVAL.
. IFAND(ISHARE,MYVAL)
Это , целочисленная функция, которая выполняет операцию “вы- брать-и-логически умножить", логически умножая значение MYVAL на значение ISHARE(I). Она возвращает предыдущее значение ISHARE(I).
. IFOR(ISHARE,MYVAL)
Это целочисленная функция, которая выполняет операцию “вы- брать-и«логически сложить", логически складывая значение MYVAL со значением ISHARE(I). Она возвращает предыдущее значение ISHARE(I).
. IFMIN(ISHARE.MYVAL)
Эта целочисленная функция выполняет операцию “выбрать-и-заме- нить на минимум". Она возвращает предыдущее значение ISHARE(I). Оно замещается на значение MYVAL, если величина MYVAL меньше, чем величина ISHARE(I).
. IFMAX(ISHARE,MYVAL)
Эта целочисленная функция выполняет операцию “выбрать-и-за- менить на максимум. Ее действие аналогично действию предыдущей функции при замене минимума на максимум.
. ISWAP(I.J)
Эта целочисленная функция приводит к замене значения целочисленной разделяемой переменной 1(1) на значение J и возвращает пред-
идущее значение 1(1). Ее можно толковать как замену или как “вы- брать«и-записатьи.
FADD(SUM,ADD)
Эта подпрограмма подобна подпрограмме “выбрать-и-прибавить“, описанной выше, но она выполняет сложение ADD и SUM(1) с плавающей запятой. Эта подпрограмма не возвращает никакого значения. Она применяется для получения глобальной суммы чисел с плавающей запятой. Так как в аппаратуре RP3 не реализуется операция “вы- брать«и-прибавить“ с плавающей запятой, разделяемые суммы с плавающей запятой, к которым часто обращаются, реализуются целочисленными операциями “выбрать-и-прибавить“ с достаточно большими целочисленными полями. К такой сумме можно прибавить значение при помощи двух-трех операций “выбрать и прибавить44, однако в этом случае более сложно получить предыдущее значение с плавающей запятой без явных замков.
Приложение F
INTEL iPSC
Содержание
Fl. Неоптимизированная программа вычисления числа Пи - листинг программы администратора куба 299
F2. Неоптимизированная программа вычисления числа Пи - листинг программы узла300
F3. Оптимизированная программа вычисления числа Пи - листинг программы администратора куба301
F4.Оптимизированная программа вычислениячислаПи-листингпрограммы узла302
F5."Язык Си и администраторкуба”303
F6.Выдержки из "Руководствапо параллельномуотладчикуiPSC"305
F6.1. Введение305
F6.2. Описание305
F6.3. Функции306
Fl. Неоптимизированная программа вычисления числа Пи - листинг программы администратора куба
program host
parameter (lenint»4, lendbl-8, np1d=O)
integer old, ent, copen,nid, pid, processors,cubedim
integer typel, intervals, type
double precision sum
data pid /0/, typel /1/
cid = copen(pid)
processors ■ 2**cubedim()
10 write(*, *)
write(*, '("Enter number of intervals")')
read(*, *, err»10, end=20) intervals
if (intervals .le. 0) go to 20
do 15 i ■ 0, (processors - 1)
call sendmsg (cid, typel, intervals, lenint, i, npid)
15 continue
call recvmsg(cid, type, sum, lendbl, ent, nid, pid)
write(*, 100) sum/intervals
go to 10
100 format('Pi is ',f20.16)
20 end
F2. Неоптимизированная программа вычисления числа Пи - листинг программы узла
program node
parameter (lenint»4, lendbl-8)
integer pid, old, copen, nid, mynode, dim, cubedim
integer type, ent, hid, node, processors, root
integer typel, type2, type3
integer intervals, i
27 double precision f, x, h, sum, work
28data typel /1/, type2 /2/,type3/3/
29 data pid /0/, hid /-32768/, root /0/
30f(x) - 4.0d0/(1.0d0 + x*x)
31cid■ copen(pid)
32nid■ mynode()
33dim■ cubedim()
34processors ■ 2**dim
35 10 continue
36 call recvw(cid,typel,intervals,Irnint,ent,node,pid)
37if (intervals. Ie. 0)goto30
38h « 1.0d0/intervals
39sum ■ O.OdO
40do 20 i ■ nid,intervals-1,processors
41x « (i + 0.5d0) * h
42sum • sum + f(x)
43 20 continue
44if (nid .eq. root) then
45do 25 1 ■ 1, (processors-1)
46call recvw(cid,type2,work,lendbl,ent,node,pid)
47sum ■ sum + work
4825continue
49call sendw(cid,type3,sum,lendbl,hid,pid)
50else
51call sendw(cid,type2,sum,lendbl,root,pid)
52end if
53go to 10
54 30 end
F3. Оптимизированная программа вычисления числа Пи - листинг программы администратора куба
program host
parameter (lenint-4, lendbl-8,npid-O)
Integer old, ent, copen,nid,pid,root
integer typel, type
integer intervals
double precision sum
data pid /0/, typel /1/
cid ■ copen(pid)
10 write(*, *)
write(*, '("Enternumber of intervals")')
read(*, *, err«10,end=20) intervals
if (intervals .le.0) go to 20
call sendmsg (cid, typel, intervals, lenint, i, npid)
call recvmsg(cid, type, sum, lendbl, ent, nid, pid)
write(*, 100) sum/intervals
go to 10
100 format('P1 is ’,f20.16)
20 end
F4. Оптимизированная программа вычисления числа Пи - листинг программы узла
programnode
parameter (lenintM, lendbl*8)
integerpid, cid, copen, nid, mynode, dim, cubedim
integer type, ent, hid, node, processors, root
integertypel, type2, type3
integerintervals, 1
double precision f, x, h, sum, work
data typel /1/, type2 /2/, type3 /3/
data pid /0/, hid /-32768/, root /0/
28f(x) - 4.0d0/(1.0d0 + x*x)
cid = copen(pid)
nid ■ mynode()
dim = cubedim()
processors ■ 2**dim
33 10 continue
34call grecvw(cid,typel,intervals,lenint,ent,dim)
35if (intervals .Ie. 0)goto30
36h ■ 1.0d0/intervals
37sum ■ O.OdO
38do 20 i ■ nid, intervals-1,processors
39x - (1 + 0.5d0) * h
40sum ■ sum + f (x)
41 20continue
42call gop(cid,type2,sum, 1/+',root,dim,work)
43if (nid .eq.root)then
44call sendw(cid,type3,sum,lendbl,hid,pid)
45end if
46go to 10
47 30 end
F5. "Язык Си и администратор куба"
Концептуально алгоритмы, записанные на языке Си для iPSC, те же самые, что и алгоритмы, записанные на языке Фортран. Однако язык Си дает возможность определения гибких структур данных и прямого интерфейса с операционной системой XENIX, в отличие от Фортрана, где ни то ни другое не доступно. Еще одним отличием Си от Фортрана является механизм передачи параметров в подпрограмму “по значению". Таким образом, каждая подпрограмма при вызове имеет доступ только к значению каждой переменной в списке параметров, а не к месту в памяти, где хранится это значение. Например, во фрагменте программы
х - 3;
У - f(х);
функции f передается значение з, а не адрес в памяти переменной х. Если программист намерен дать возможность f модифицировать значение переменной х, то должен быть передан ее адрес:
у - f(&х):'
а функция f должна произвести разыменование х в тех случаях, когда требуется ее значение, т. е.:
z ■ 5 + *х; /* использование значения х */
*х ■ z/2;/* присваивание нового значения х */
Можно использовать эти свойства языка Си для передачи одиночного значения (целого, числа с плавающей запятой и т.д.), массива или структуры как сообщения. Главное, что нужно помнить, это то, что подпрограммы всех передач (send, sendw, sendmsg) и всех приемов (recv, recvw, recvmsg) воспринимают адрес сообщения, а не его значение. Единственной “хитростью" является то, что идентификатор массива представляет собой адрес его первого элемента, поэтому никакой знак и&“ не должен приписываться к идентификатору массива при передаче его адреса. Так как в языке Си различаются адреса разных типов памяти, то обычно необходимо производить явное приведение типов (char*) при имени буфера.
Рассмотрим следующий пример:
int ci, type, node, pid, ent
struct s_ship {
char name[20];
float px, py, pz; float vx, vy, vz; float mass;
} ss,sr;
ci ■ copen (myp1d());
sendw(ci,type,(char*)&s s,s1zeof(ss),node,p1d);
sendw(ci,&type,(char*)&sr,sizeof(sr),&cnt,&node,&pid);
cclose(ci);
Вызовы подпрограмм sendw и recvw выполняться точно так же, если ss и sr объявлены переменными, например типа float, int или doubl. Если же переменные ss и sr объявлены массивами любого типа, даже структур, то в обращении к подпрограммам надо лишь отбросить знак "&“, который предшествовал идентификаторам ss и sr. Например:
int ci, type, node, pid, ent
struct s_ship {
char name[20]; float px, py, pz; float vx, vy, vz; float mass;
} as[5],ar[5]; cl ■ copen (mypidO);
sendw(ci,type,(char*)as,sizeof(ss),node,pid);
sendw(ci,&type,(char*)ar,sizeof(sr),&cnt,&node,&p1d);
cclose(ci);
будет подходящей записью.
Объем буфера сообщений в основной машине iPSC ограничен. Когда все буферы заполнены, никакое дополнительное сообщение не сможет быть отправлено или принято до тех пор, пока хотя бы один буфер не будет освобожден. Если процесс администратора собирается сформировать все данные для узлов, а прием результатов производить только после того, как будут отправлены последние данные, то может возникнуть взаимная блокировка в случае, если результаты, отправленные узлами, займут больше буферного пространства, чем ожидалось. Очевидным способом исключения подобной ситуации является применение в администраторе двух параллельных процессов. Родительский процесс порождает дочерний процесс (с помощью системного примитива fork()), который формирует данные для узлов, после чего заканчивается, В это время родительский процесс ожидает поступления результатов и обрабатывает их в темпе поступления. Каналы ввода и вывода могут быть открыты до или после разветвления процессов без всяких конфликтов. Оба процесса могут даже использовать один и тот же канал. До тех пор пока они не попытаются оба читать из одного и того же канала, не будет никаких конфликтов. Если же они попытаются одновременно читать, то один процесс может перехватить сообщение, адресованное другому процессу. Например, в следующей записи
ci ■ сореп(...);
proc ■ fork();
if (proc I* 0) { /* родительский процесс */ while(...) { recvmsg(ci, ...);
} exit(O); } else { /* дочерний процесс */ whi1e(...) { sendmsg(ci, ...);
} exit(O); }
дочерний процесс быть может оказаться производителем данных, а родительский процесс - потребителем. Разветвление процессов в настоящее время поддерживается только в администраторе куба.
F6. Выдержки из "Руководства по параллельному отладчику iPSC"1'
F6.1. Введение
Цели
Данное руководство описывает параллельный отладчик iPSC - средство, созданное в помощь разработчику при отладке его параллельных прикладных задач в системе iPSC.
Обзор
Параллельный отладчик предоставляет разработчику средства отладки на уровне исходных текстов прикладных программ на языке Си и Фортран в системе iPSC. Он обеспечивает поддержку отладки на уровне исходных текстов пользовательских процессов, выполняющихся как в администраторе куба, так и в узлах, с минимальным влиянием на их выполнение.
Параллельный отладчик поддерживает два уровня отладочных возможностей:
Первый уровень сконцентрирован на последовательной природе процессов и предоставляет возможности для отладки определенных пользователем объектов, совпадающие с возможностями обычного последовательного отладчика. К этим возможностям относятся такие функции, как доступ к данным, расстановка условных контрольных точек и трассировка, а также управление выполнением. Этот уровень отладочной поддержки может быть использован при отладке одного процесса или нескольких процессов, с незначительным взаимодействием или без него.
Второй уровень обеспечивает набор инструментов на уровне процессов, созданных специально для того, чтобы помочь отладить прикладную задачу в параллельной среде. Этот уровень может быть использован для отладки синхронизации на уровне процессов и взаимодействий компонентов пользовательской программы. Среди этих дополнительных средств есть наблюдение за процессами, управление взаимодействием процессов и наблюдение за состояниями узлов или сообщений.
F6.2. Описание
Параллельный отладчик работает как в администраторе куба, так и в самом кубе. Компонент, который размещается в администраторе куба, называется отладочный процесс администратора куба» а другие компоненты, кото-
9 Воспроизводится с разрешения Intel Scientific Computers.
рые размещаются в каждом узле куба, называются отладочными процессами куба. Они взаимодействуют при помощи глобального канала Ethernet.
Отладочный процесс администратора куба
Отладочный процесс администратора куба управляет пользовательскими процессами, реализуемыми в администраторе куба, и выполняет такие задания, как обработка отладочных команд пользователя, выданных с терминала, выполнение отладочных команд типа загрузки пользовательских процессов, создания и обслуживания загрузки небольшой базы данных для отладки с символьной информацией и локальными объектами отладчика, вычисления выражений, обработки контрольных точек отладки, поддержки взаимодействия администратора куба с кубом, обработки сигналов клавиатуры и управления виртуальной памятью.
Отладочный процесс куба
Отладочный процесс куба (по одному в каждом узле) управляет прохождением пользовательских процессов, выполняющихся в узлах, и служит средством выполнения. Он выполняет такие основные задания, как доступ к памяти, обработка контрольных точек отладки, наблюдение/управление обращениями к процессам взаимодействием процессов, управление выполнением процессов и наблюдение за системными очередями. Отладочные процессы куба пользуются операционной системой узла и взаимодействуют с отладочным процессом администратора куба.
F6.3. Функции
Параллельный отладчик поддерживает следующие отладочные возможности:
Управление выполнением программы.
Доступ к данным и состоянию.
Загрузка процессов.
Управление отладкой.
Управление выполнением программы
Команды управления выполнением программы обеспечивают механизмы для инициализации, приостановки и завершения выполнения процессов. Они также позволяют устанавливать, перемещать, приостанавливать и восстанавливать различные отладочные контрольные точки и точки трассировки (как данных, так и кода) и связанные с ними действия.
В дополнение к предоставленным возможностям по установке отладочных контрольных точек и точек трассировки на уровне определенных пользователем объектов, включая переменные, предложения языка, метки и подпрограммы, параллельный отладчик позволяет устанавливать такие сторожевые точки на уровне процессов. Эта возможность, совместно с возможностью описания событий на уровне процессов, позволяет достичь понимания межпроцессного взаимодействия и действий по синхронизации в пользовательской программе.
Доступ к данным и состоянию
Команды доступа к данным и состоянию помогают проверить состояние процесса после того, как было приостановлено его выполнение. Кроме проверки исходной программы, можно проверить и модифицировать значения данных. В параллельном отладчике есть сведения о форматировании данных при выводе на экран для всех типов данных в среде текущего языка программирования. Он также производит контроль совместимости присваивания, когда переменным присваиваются новые значения.
Кроме того, параллельный отладчик позволяет проверить состояние узлов и состояние сообщений. Эта возможность должна помочь в обнаружении взаимной блокировки или зацикливания.
Загрузка процессов
Команды загрузки процессов позволяют загрузить пользовательский процесс в администратор куба или в узлы. Загруженный процесс может затем выполнятся под управлением параллельного отладчика. Обычно это первые команды, используемые после вызова параллельного отладчика.
Управление отладчиком
Команды управления отладчиком обеспечивают механизм для управления операциями параллельного отладчика. Эти операции включают запоминание всех выполняемых команд отладчика, выход в операционную систему XENIX для дополнительных действий, определение синонимов и возможности подсказки. при отладке.
Приложение G
Loral Dataflo LDF 100
Содержание
Gl. Программа вычисления числа Пи.309
G2.Форматы DGL311
G3.DGL - как выход после обработкиРаспределителемТегов(TagAssigner)312
G4.Подпрограммы FLOREAD и FLOWRITE(Фортран)313
G5.Функции Floread и Flowrite (Си)314
G6.Подпрограмма PRINTF (Фортран)315
G7.Листинг файла утилиты make (makefile) программывычислениячислаПи316
Gl. Программа вычисления числа Пи
Приводится листинг трех узловых программ: распределителя работ, рабочего процесса и суммирующего процесса. Исходный текст всех трех программ находился в трех разных файлах.
1
2
С
С - Work Distribution
Распределитель работ
3
С - Sends work intervals to workers and flag to summer Рассылает обрабатываемые интервалы исполнителям и флаг сумматору
4
5
6 7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
С
REALML0, HI, SIZINTRVL
INTEGERNSEGMENTS, INTRVLS, PASS, WORKER
INTEGERS FLAG, NWORKERS, TOTINTRVLS, NPASSES, SIZE COMMON /TERMIN/ NPASSES, TOTINTRVLS COMMON /WORKARC/ L0, HI, INTRVLS CALL FLOREAD(1, NWORKERS, 1, SIZE) CALL FL0READ(2, NPASSES, 2, SIZE) CALL PRINTF(" INTRVLS « %d npasses - %d nworkers - %d\n", 1TOTINTRVLS, NPASSES, NWORKERS)
NSEGMENTS - NPASSES * NWORKERS INTRVLS - TOTINTRVLS SIZINTRVLS - 1.0 / NSEGMENTS HI ■ 0 DO 100 PASS « 1, NPASSES DO 90 WORKER - 1, NWORKERS LO - HI HI - HI + SIZINTRVLS CALL FLOWRITE(WORKER, LO, 6) 90CONTINUE
FLAG ■ NPASSES - PASS CALL FLOWRITE(NWORKERS + 1, FLAG, 1) 100 CONTINUE
END C C - WORKER (Code identical for each worker node) РАБОЧИЙ ПРОЦЕСС (Программа, идентичная для каждого узла исполнителя)
30
С - Reads work assignment from work distributor, Читает рабочее предписание от распределителя работ
31
С -writes partial sum to summer
записывает частичную сумму для суммирующего процесса
32
33
34
35
36
37
38
39
С
REALL0, HI
REAL*8SUM, WIDTH, FARG,SIZE
INTEGERINTRVLS, SLICE
COMMON /INPATH/ LO, HI, INTRVLS CALL FLOREAD(1, LO, 6, SIZE) WIDTH - (HI - LO) / INTRVLS
SUM - 0.0
30
41
42
43
44
45
46
47
DO 100 SLICE - 1, INTRVLS FARG - LO + (SLICE - .5) * WIDTH SUM - SUM + WIDTH * (4.0 / (1.0 + FARG * FARG)) 100 CONTINUE
CALL FLOWRITE(1, SUM, 4)
END
C
C - Summer (as in, one that sums)
Суммирующий процесс (в смысле тот, кто суммирует)
48
С - Reads partial sum from each worker, Считывает частичные суммы каждого рабочего процесса
49
С -adds them into a total sum
прибавляет их к общей сумме
50
51
52
53
54
55
56
С
INTEGERSIZE
INTEGER*2FLAG, ITER, NPASSES
REAL*4TEMPSUM, TOTALSUM
COMMON /STATIC/ TOTALSUM
CALL FLOREAD(1, TEMPSUM, 4, SIZE) TOTALSUM « TOTALSUM + TENPSUM
57CALL FL0READ(3, TEMPSUM, 4, SIZE)
58
59
60
61
62
63
64
65
66
TOTALSUM - TOTALSUM + TENPSUM
CALL FL0READ(2, FLAG, 1, SIZE)
IF (FLAG .EQ. 0) THEN
CALL PRINTF("pi is %F\n", TOTALSUM) CALL PRINTF("diff is %F\n°,
&TOTALSUM-3.14159265358979323800)
TOTALSUM « 0
END IF
END
G2. Форматы DGL
При кодировании графа потока данных на языке Data Graph Language, каждый процесс представляете^ в виде заголовка, списка входных дуг (import) и списка выходных дуг (export):
nodename - “nodeeode“
Import { arc-type arc—list ;
arc-type arc—list ;
• • •}
export { arc—type arc-list ;
arc-type arc-list ;
. . .}
где:
nodename - имя, уникально иденфицирующее узел;
nodecode - имя файла, содержащего собранный программный код процесса, созданный загрузчиком Id (после компиляции);
arc—type - это или open (обозначающее переменную длину сообще
ния), или word[n], где п - целое, определяющее число 16 битовых слов, составляющих полное сообщение (если п равно 1, [и] можно опустить);
arc—list - разделенный запятыми список определенных пользователем
имен дуг (обычно из графа потока данных), используемый впоследст- вие распределителем тегов для определения вхождений дуг в узлы. Константная дуга определяется записью требуемой целочисленной константы вместо комбинации arc—type arc—list
G3. DGL - как выход после обработки Распределителем Тэгов (Tag Assigner)
Распределитель тегов добавляет теги формата <Т#> каждой дуге и каждому процессу (к которым теги не были заранее приписаны пользователем) в записи графа на языке DGL Теги на дугах используются для иденфикации пакетов данных, передаваемых по дугам, в то время как теги процессов используются для иденфикации программных кодов при загрузке в процессор. Ниже приводится лиснг программы на языке DGL после обработки распределителем тегов.
1 workdistributor ■ Hwork_dist” <С0> <Р11> <Т64335>
2
import*{2;
word[2]d00_1nt_npass <Т2000> }
export {word[6] d01_lo_hi_int <T64319>;
word[6]d02jojiijnt <T64318>;
wordd03_done_signal <T64317>}
8
9 workl ■ "worker” <C0> <P10> <T64351>
10
11 import {word[6] dOlJoJiiJnt <T64319>}
12 export {word[4] d04_sum <T64316>}
13
14 work2 * "worker" <C0> <P9> <T64367>
15
16 import {word[6] d02jo_hijnt <T64318>}
17 export {word[4] d05_sum <T64315>}
18
19 summer ■ "summer" <C0> <P7> <T64399>
20
21 import {2;
22word[2]d03_done_sygnal <T64316>;
23word[4]d04_sum <T64316>;
24word[4]d05_sum <T64315>; 100}
G4. FLOREAD и FLOWRITE (Фортран)
В языке Фортран вызовы FLOREAD и FLOWRITE имеют следующий формат:
CALL FLOREAD Цагспо, record, ireclen, ngot}
CALL FLOWRITE ^iarcno, record, ireclen} где:
iarcno - номер дуги, по которой производится чтение или запись - в Языке описания графа потока данных первые входные и выходные дуги в списке процесса имеют номер 1;
record - имя записи (переменной или константы), которая считываются или записывается;
ireclen - длина записи в 16 битовых словах;
ngot - целочисленная переменная, значение которой устанавливается FLOREAD равным числу слов, фактически считанных с дуги, - обычно это значение равно ireclen, если только тип в Языке описания графа не есть open.
Если считывается или записывается только одно значение (переменной или константы), то record - это просто данное значение, но если таких значений более одного, то они должны быть размещены последовательно в (именованном) common-блоке, и имя первой переменной должно быть использовано как record. Пользователю надо в каждом случае самому вычислять, сколько 16 битовых слов используется в вызове (имея в виду, что целые числа и числа с одинарной точностью занимают 32 бита).
G5. Floread И Flowrite (Си)
В языке программирования Си функции floread и flowrite имеют следующий формат:
ngot ■ floread(arcno, &read_buf_ptr);
flowrite(areno, &write_buf, sizeof(syruct wr fields));
где фактические параметры определены следующим образом:
int areno; /* Смотри выше описание Фортрана */
struct rd_fields *read_buf_ptr;
/* Где тип "struct rd_fields" определен пользователем так, что он содержит поля, которые считываются */
int ngot ;/* Смотри выше описание Фортрана */
struct wr_fields write_buf;
/* Где тип "struct wr_fields" определен пользователем так, что он содержит поля, которые записываются*/
Заметим, что floread может изменить указатель read_buf_ptr так, что он будет указывать на внутреннюю буферную область ("коммуникационную" память) процессора, так что некорректное обращение с этим указателем (например, обращение за конец записи) может привести к непредсказуемым последствиям.
G6. PRINTF (Фортран)
Для печати из узла (на Фортране) нужно использовать:
CALL PRINTFVformatstrlng, value, value, ...)
где
formatstring - строка символов, содержащая форматирующий код, используемый в языке Си. Отличие заключается в том, что конвертирующие символы d и f используются только для значений INTEGER*2 и REAL*4 (по умолчанию) соответственно. Для значений INTEGER*4 нужно использовать конвертирующий символ D, а для значений REAL*8 - конвертирующий символ F.
value - переменная или константа, которую надо печатать.
Так как функция printf не определена как часть языка Си, фортран-про- граммисту понадобится помощь в овладении средствами Си (консультации или справочное руководство) для эффективного использования вызова этой функции.
G7. Листинг файла утилиты make ^makefile) программы вычисления числа Пи
Как упоминалось в разделе 9.2, для многих шагов разработки программы может быть полезным использование утилиты make ОС UNIX. Ниже приводится листинг этой утилиты - makefile, использованной для процессов Языка описания графа и программ узлов.
OBJS■ summer.о work_dist.o worker.о
LIBROOT « /u/software/lib
LINKED ■ summer worker work_dist
FLAGS ■ -I/u/software/include
TAGGED « wirelist.tagged
CODE ■ wirelist.code
GRAPH « wirelist
8
ACTIVENODE « 800
10
CC = cc ((FLAGS)
12
doit: ((CODE)
14
((CODE): ((OBJS) ((LINKED) ((TAGGED) ((LIBROOT)/flolib.a
/u/software/src/graph.lang/linker/1inker ((TAGGED) -o ((CODE)
17
((TAGGED): ((GRAPH)
cc -E ((GRAPH) у
/u/software/src/graph.lang/tag.ass/tass -o ((TAGGED)
21
summer: summer.о
Id -msummer.map summer.о -T ((ACTIVENODE)
((LIBROOT)/flolib.a -o summer
25
worker: worker.о
Id -inworker.mapworker.о -T ((ACTIVENODE)
$(LIBROOT)/flolib.a -o worker
29
work_dist: work_dist.o
Id -mwork_dist.map work_dist.o -T ((ACTIVENODE)
((LIBROOT)/flolib.a -o work_dist
Приложение Н
Seguent Balance Series
Содержание
Hl. Программа вычисления числа Пи - до обработки препроцессором318
Н2. Программа вычисления числа Пи - после обработки препроцессором319
НЗ. Параллельные компиляторы, утилиты и системные вызовы321
сс - компилятор Си322
fortran - компилятор с языка Фортран324
pascal - компилятор с языка Паскаль328
make - поддержка группы программ330
pbdx, dbx - параллельный отладчик [отрывок]334
mmap - размещение открытого файла в адресном пространстве процессора341
Н4. Директивы компилятору346
Н5. Библиотека параллельного программирования ОС DYNIX347
intro - введение в библиотеку параллельного программирования348
brk, sbrk - изменяет размер сегмента локальных данных350
cpus_online - возвращает количество работающих ЦПУ351
shbrk, shsbrk - изменяет размер сегмента разделяемых данных352
shmalloc, shfree - распределение разделяемой памяти354
m_fork - параллельное выполнение подпрограммы355
m-get_myid - возвращает идентификатор процесса357
• m-get_numprocs - возвращает число дочерних процессов357
ni-kill-procs - оканчивает дочерние процессы358
m_lock, m_unlock - устанавливают и снимают блокировку с замка359
m_next - увеличивает общий счетчик360
m_park_procs, m_rele_procs - приостанавливает и запускает дочерние процессы360 m_set_procs - устанавливает число дочерних процессов361
m_single, mjnulti - разметка раздела единственного процесса363
m_sync - проверка барьера 364
з_1 nit jock, S-lock, S-dock, s_unlock - инициализация, блокировка и снятие блокировки с замка.365
s_init-barrier, S-Wait-barrier - инициализация барьера и ожидание у барьера366
HI. Программа вычисления числа Пи - до обработки препроцессором
с - number of processes; status returned - число процессов; возвращаемый статус
1nteger*4 nprocs, status
с - number of rectangles; loop index с - число прямоугольников; индекс цикла
integer*4 nrecs, i
5c- integration variables
с - переменные интегрирования
double precision h, x
c - pi as found by the program с - пи, вычисляемое программой
double precision tpi
c - pi tO 16 decimal places, for comparison с - пи с точностью до 16 знаков, для сравнения
double precision pi
pi - 3.1415926535897932d0
10 write(*,*) 'Number of rectangles: '
read(*,*) nrecs
if (nrecs.le.O) goto 30
write(*,*) 'Number ofprocs: ’
read(*,*) nprocs
if (nprocs.le.O) goto30
call start_timer
status ■= m_set_procs(nprocs)
if (status.ne.0) goto20
h ■ l.dO / nrecs
22tpi « O.OdO
23 c$doacross share(h), local(x), reduction(tpi)
24do 100 i ■ 1, nrecs
25x - (1-0.5d0) * h
26tpi - tpi + 4d0 / (IdO + x * x)
27100 continue
tpi ■ h * tpi
call m_kill_procs
20 call stop.timer
if (status.ne.0) then
write(*,*) ' Error setting number of processes'
else
34call fptr_timer(nprocs,nprocs,nprocs)
35wr1te(*,500)tpi
36500formatf Pi:’,fl8.16)
37write(*,600)pi- tpi
38600formatC Error;’,fl8,16)
endif
goto 10
30 stop
end
Н2. Программа вычисления числа Пи - после обработки препроцессором
(system
(line 3 pi.fpp
external dolOOX
INTEGER m_fork
(line 2 pi.f
integer*4 nprocs, status
integer*4 nrecs,1
doubleprecisionh, x
doubleprecisiontpi
doubleprecisionpi
pi - 3.1415926535897932d0
10 write(*,*) 'Number of rectangles: '
read(*,*) nrecs
if (nrecs.le.O) goto 30
write(*,*) 'Number of procs: '
read(*,*) nprocs
if (nprocs.le.O) goto 30
call start_timer
status ■ m_set_procs(nprocs)
if (status.ne.0) goto 20
h ■ l.dO / nrecs
tpi « O.OdO
c(doacross share(h), local(x), reduction(tpi)
24 if (m_fork(dol00%,l,nrecs,l,h,tpi).ne.0) stop 'do 100 '
tpi - h * tpi
call m_kill_procs
20 call stop-timer
if (status.ne.0) then
write(*,*) ' Error setting number of processes'
else
call fptr_timer(nprocs,nprocs,nprocs)
write(*,500)tpi
33500format(' Pi:’,fl8.16)
34write(*,600)pi- tpi
35600formats Error:',fl8,16)
endif
goto 10
30 stop
end
40 (line 50 pi.fpp
41subroutine dol00%(i%l,i%2,i%3,h,tpi%)
42integeri%l,i%2,i%3,i%chunk,mjget_myid,mjget_numprocs,1%end
43integeris%l,isX2,is%3,is%4,1s%5,1s%6,1s%7
INTEGERi
DOUBLE PRECISION x,h,tpi,tpi%
IXend «1X2
i ■ mjget_numprocs()
if (i .gt. 1) then
49
iXchunk - 1X3 * t
50
1 - 1X1 + 1X3 * nrgetjnyidO
51
else
52
iXchunk « 1X3
53
1 - 1X1
54
endif
55 99998
tpi - 0
56
do 99996, 1 - i, IXend, iXchunk
57 $11пе 26 pt.f
58
x « (i-0.5d0) * h
59
tpi ■ tpi + 4d0 / (IdO + x * x)
60100
continue
61 $11пе
71 pi.fpp
62 99996
continue
63 99997
call m_lock
64
tpiX -"tpiX + tpi
65
call m_unlock
66
end
НЗ. Параллельные компиляторы, утилиты и системные вызовы
Параллельное программирование в Balance Series может производиться на трех языках программирования: Си, Фортране и Паскале. В это приложение включены страницы Руководства по операционной системе DYNIX^, на которых описаны соответствующие компиляторы. Дополнительные сведения о параллельном программировании на этих языках можно найти в соответствующих “Руководствах пользователю по компилятору" {Compiler Users Manual).
Утилита make сохраняет согласованное состояние группы программ путем обновления файлов, которое производится только при необходимости. Makefile состоит из последовательности команд, определяющих зависимости файлов друг от друга. Целевой файл {target file) обновляется, если он зависит от файлов, в которых были произведены модификации, или если он не существовал ранее. Утилита make расширена в Sequent Balance Sereies возможностями по разметке файлов, которые могут выполняться одновременно. Подробности - на страницах раздела “Поддержка параллелизма", посвященных описанию утилиты make.
Pbdx кратко описана в разделе 10, подробно - в *DYNIX Pbdx Debugger Users Manual*.
Мшар используется в системе при реализации разделяемой памяти и доступе к аппаратным замкам.
9 Страницы Руководства по ОС DYNIX воспроизводятся с любезного разрешения Sequent Computer Systems Inc. Некоторые страницы руководства были сокращены для выделения аспектов параллелизма, в чем состоит отличие от соответствующих стандартных (последовательных) утилит ОС UNIX.
21 I.S5
Название
сс - компилятор Си.
Краткое описание
сс [ опция ] ... файл ...
Описание
сс - это компилятор Си в операционной системе DYNIX. Этот компилятор поддерживает параллельное программирование (см. Balance Guide to Parallel Prpgramming).
сс воспринимает параметры нескольких типов. Параметры, чье имя оканчивается “.с“, воспринимаются как исходные тексты программ на языке Си; они компилируются, и каждая объектная программа помещается в файл, имеющий такое же имя, что и файл с исходным текстом, только окончание “.с* заменяется на “.о“. Файл с окончанием “.о“ обычно удаляется, если единственная си-программа и компилируется, и загружается сразу. Аналогичным образом файлы, чье имя оканчивается u.s“, воспринимаются как файлы, содержащие исходные тексты программ на Ассемблере, они компилируются, порождая файлы с окончанием “.о“.
сс интерпретирует следующие опции (см. также ld(l) - опции загрузки):
с Подавляется фаза загрузки при компиляции; создается объектный файл, даже если компилируется единственная программа.
д Создается дополнительная таблица символов для pbdx(l). Также переправляется флаг -1g для ld(l).
go Создается дополнительная таблица символов для старого отладчика sdb(l).Taкжe переправляется флаг -1g для ld(l).
w Подавляется вывод предупредительной диагностики.
р Создается код, который подсчитывает число вызовов каждой подпрограммы. При загрузке стандартная начальная подпрограмма заменяется на подпрограмму, которая в начале работы автоматически обращается к функции monit1r(3) и обеспечивает запись файла mon.out в случае нормального завершения работы объектной программы. Профиль выполнения программы можно получить при использовании prof(l).
-pg Создается код счетчика, как и при опции -р, но дополнительно включается механизм записи во время выполнения программы, который собирает более обширную статистику и создает файл gmon.out в случае нормального завершения. Кроме того, поиск производится в библиотеке профилирования, а не в стандартной библиотеке Си. Профиль выполнения программы можно получить при использовании gprof(l).
О Вызывается оптимизатор объектного кода. Избегать применения к программам, содержащим предложения на ассемблере asm, так как результат может быть неверен.
Y Все внешние и статические переменные, не объявленные явно разделяемыми или локальными, становятся разделяемыми. (Дополнительную информацию о ключевых словах shared и private см. в “Balance 8000/ 21000 С Compiler User“s Manual").
1 Не производится оптимизация, которая не корректна, когда содержимое памяти может внезапно изменять свое значение. Такое свойство
есть у регистров приписки. Эта опция игнорируется, если не выбрана опция -о.
R Передается ассемблеру as, при этом инициализируемые переменные становятся разделяемыми, но только по чтению.
S Компилируются заданные си-программы, ассемблерные файлы сохраняются в файлах с соответствующими именами, но с окончанием ”.s“.
SO Компилируются заданные си-программы, результат направляется на стандартный вывод.
Е Выполняется только макропрепроцессор, результат направляется на стандартный вывод.
С При работе макропрепроцессора не удаляются примечания.
о output
Выходной файл именуется output. Эта опция применяется для сохранения файла a.out.
v Создается список (через стандартный вывод) всех утилит, к которым обращался компилятор сс, со всеми аргументами. Эта информация бывает полезна при отладке.
vn Аналогично опции -у, но сами обращения не производятся.
D namesdef
D name
Определяется имя name для препроцессора, так же как при “#define“. Если не задано определение, то по умолчанию имя определяется как
U name
Анулируется начальное определение name. (Препроцессор Си первоначально определяет имена sequent, ns32000 и unix как “1“).
I dlr
Файлы (включаемые при помощи “#include“), чьи имена не начинаются с “/“, ищутся сначала в справочнике, определяемом аргументом “file“, затем в справочниках, определяемых опциями -I, а затем в справочниках, определенных стандартным списком.
В string
Ищутся заменяющие фазы компилятора в файлах с именем string и суффиксами “.срр", ".ссош" и “.с2“. Если string - пуст, то используется стандартное сохранение.
t [ р02 ]
Ищутся только заданные фазы компилятора в файлах, чьи имена конструируются при помощи опции -в. Если опция -В1 отсутствует, то имя принимается равным “/usr/c/o".
И Выполняется только макропрепроцессор над заданными си-програм- мами, запрашивая их в соответствии с зависимостями, сгенерированными утилитой make, и направляя результат на стандартный вывод. Остальные параметры воспринимаются как параметры или опции загрузчика, или как объектные модули программ, созданные ранее компилятором Си, или как библиотеки си-совместимых подпрограмм. Эти модули вместе с результатами всех заданных компиляций загружаются (в заданном порядке) и создается выполняемая программа с именем a.out.
Файлы
file.сисходный файл
i le. ообъектный модуль
file.sфайл с текстом на языке ассемблера
a.out
загружаемый выводной файл
/tmp/ctm?
временный файл
/lib/срръ
препроцессор
/lib/ccom
компилятор
/’iib/c2
дополнительный оптимизатор
/lib/crtO.o
начальная подпрограмма
/lib/mcrtO.o
начальная подпрограмма для профилирования
/usr/lib/gcrtO.o
начальная подпрограмма для профилирования gprof стандартная библиотека (см. intro(3))
/lib/libc.a
/usr/lib/libc_p.a
библиотека профилирования (см. intro(3))
/usr/lib/libpps.a
библиотека параллельного программирования (см. intro(3))
/usг/include
стандартный справочник для поиска файлов “finclude“
/usr/local/include
локальный справочник для поиска файлов “#include“
mon.out
файл, создаваемый для дальнейшего анализа командой prof
gmon.out
файл, создаваемый для дальнейшего анализа командой gprof
Смотри также
B.W. Kernighan and D.M. Ritchie, The C Programming Language, Prentice- Hall, 1978
B.W. Kernighan, Programming in C-A Tutorial
D.M. Ritchie, C Reference Manual
Balance 8000/21000 C Compiler User's Manual
Balance Guide to Parallel Programming
monitor(3), prof(l), gprof(l), ddt(l), ld(l), pdbx(l), as(l)
Диагностика
Диагностика, создаваемая Си-компилятором, задумана как самодостаточная. Отдельные сообщения могут выдаваться ассеблером или загрузчиком.
Недостатки
Компилятор игнорирует запросы на размещение переменных типа char, unsigned char, short и unsigned short на регистрах.
Название
fortran - компилятор с языка Фортран.
Краткое описание
fortran [ option ] ... file ...
Описание
fortran - это компилятор с языка Фортран в операционной системе DYNIX. Фортран DYNIX совместим со стандартом ANSI Фортран 77, но у него есть расширение и препроцессор, поддерживающие параллельное про-
граммирование. (Дополнительную информацию о параллельное программировании см. в Balance Guide to Parallel Programming.)
У fortran есть параметры нескольких типов. Параметры, чьи имена оканчиваются иЛи или “.for", воспринимаются как файлы, содержащие исходный текст програм на языке Фортран; те, чьи имена оканчиваются на “.р" или “.pas" - как файлы с исходным текстом программ на языке Паскаль; те, чьи имена оканчиваются на ".с" - как файлы с исходным текстом программ на языке Си. Заданные исходные файлы компилируются, и объектные модули помещаются в файлы с именами “х.о“, где "х“ - имя соответствующего исходного файла. Аналогичным образом параметры с именами, оканчивающимися на “.su, воспринимаются как файлы, содержащие исходный текст программ на языке ассемблера, они транслируются ассемблером, создающим файлы с окончанием “.о".
Если не специфицирована опция -с, все файлы с окончанием ".о" собираются с библиотеками Фортрана, Паскаля и Си и загружаются, как единое целое. В этом случае все файлы с окончанием “.о" удаляются.
fortran интерпретирует следующие опции (см. также ld(l) - опции загрузки):
-0[0-4]
Проводится компиляция с заданным уровнем оптимизации. Уровени оптимизации определяются следующим образом. Уровень 0 - компиляция без оптимизации. При уровне 1 производится свертка констант, при этом компилятор вычисляет все константные выражения и помещает их значения в объектный код. При уровне 2 производится свертка констант и распределение регистров на уровне подпрограмм. При уровне 3 производится свертка констант и распределение регистров на уровне ниже, чем подпрограммы. При уровне 4 производится та же оптимизация, что и при уровне 3, плюс оптимизация вычислений индексов массивов в циклах DO. По умолчанию полагается заданным уровень 4.
Опции -о и -д не совместимы. Если в команде специфицированы обе опции, опция, которая встретилась раньше, подавляет вторую, при этом генерируется предупреждающее сообщение. Если опция -д подавила опцию -0, уровень оптимизации принимается равным 1.
шр Вызывается параллельный препроцессор. Этот препроцессор генерирует код Фортрана для параллельного выполнения циклов DO.
Если программные модули, содержащие параллельные циклы DO, компилируются раздельно, опция -шр должна быть специфицирована при компиляции каждого модуля для того, чтобы обеспечить размещение всех переменных в разделяемой памяти. Если подпрограмма, вызываемая из тела параллельного цикла DO , компилируется раздельно, то она тоже должна компилироваться с опцией -яр.
v “Многословный" режим. На экран отображаются все шаги компиляции.
vn Аналогично опции -v, но фактическая компиляция программы не производится.
с Подавляется фаза загрузки при компиляции; создается объектный файл, даже если компилируется единственная программа.
е Компиляция в режиме совместимости с языком Си. Параметры будут возникать в месте вызова, а не в месте возврата. Это позволяет ком- пановать си-модули с фортран-эи паскаль-модулями. Запись внешних имен (используемых 1d(l)) преобразуется в запись строчными буквами. (По умолчанию запись всех внешних имен производится пропис-
ными буквами.) Дополнительные детали - в Balance 8000/21000 Fortran Compiler Users Manual.
1 listname
Генерируется листинг программы в файле "2listname". Если имя файла не описано, то его имя по умолчанию - list.
х Генерируется список перекрестных ссылок в файле листинга.
-prompt
При обнаружении ошибки (и выводе сообщения о ней на экран) производится стандартное приглашение на ввод. Это препятствует "убеганию" сообщения об обнаруженной ошибке с экрана до того, как оно будет прочитано.
р Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты prof. Также профилируются библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль.
pg Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты gprof. Также профилируются библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль.
Р Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты prof. Библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль НЕ профилируются.*
PG Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты gprof. Библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль НЕ профилируются. Флаг -Pg воспринимается также, как и -PG1.
и Файлы с окончанием ".obj" не удаляются. Для каждого исходного файла компилятором создаются промежуточные файлы с суффиксом “.obj". При нормальном режиме работы (опция -и не специфицирована) эти файлы удаляются при завершении компиляции, так как они не выполняют никаких функций в текущей реализации.
о output
Выходной файл именуется output. Эта опция применяется для сохранения файла a.out.
g В текущем рабочем справочнике создается файл с отладочной информацией для отладчика pdbx. Имя файла будет иметь суффикс M.dbgw. Для модулей Фортрана информация о переменных в COMMON-области заносится только о тех переменных, на которые имеются ссылки в данном разделе программы (подпрограмме, функции или основной программе). Это ускоряет загрузку очень больших программ, но не позволяет получать информацию о состоянии тех переменных в COMMON- области, на которые не было ссылок.
Опции -0 и -д не совместимы. Если в команде специфицированы обе опции, опция, которая встретилась раньше, подавляет вторую, при этом генерируется предупреждающее сообщение. Если опция -д подавила опцию -0, уровень оптимизации принимается равным 1.
gv В текущем рабочем справочнике создается файл с отладочной информацией для отладчика pdbx. Имя файла будет иметь суффикс -.dbg". Для модулей Фортрана информация о всех переменных в COMMON-области заносится даже в тех случаях, если на переменную нет ни одной ссылки в данном разделе программы (подпрограмме, функции или основной программе). Этот флаг увеличивает время инициа-
лизации при отладке очень больших программ. Опции -о и -gv не совместимы. Если в команде специфицированы обе опции, опция, которая встретилась раньше, подавляет вторую, при этом генерируется предупреждающее сообщение. Если опция -gv подавила опцию -о, уровень оптимизации принимается равным 1.
-ansi
Любое отклонение от стандарта ANSI Fortran 77 воспринимается как ошибка. По умолчанию, компилятор некоторые отклонения от стандарта игнорирует, а при обнаружении остальных отклонений генерирует предупреждающие сообщения.
-charequ
Переменным числового и CHARACTER типа разрешается быть эквивалентными (объявлять их QUIVALENCE) или разделять одно и тоже пространство в соммон-области. Кроме этого разрешается инициализировать числовые переменные константами типа CHARACTER. Спецификация этой опции эквивалентна включению опции компилятора SCHAREQU в начале каждого файла с исходным текстом.
-с72 Игнорируются все символы в позициях правее 72-й в каждой строке исходного текста. Спецификация этой опции эквивалентна включению опции компилятора $COL72 в начале каждого файла с исходным текстом.
-de Компилируется отладочный код. По умолчанию, каждая строка исходного текста, которая содержит символ MD”1 (строчную или прописную) в 1 позиции, воспринимается, как комментарий. Если же специфицирована данная опция, компилятор компилирует эти строки так, как будто в 1 позиции этих строк стоит пробел.
Остальные параметры воспринимаются как параметры или опции загрузчика, или как объектные модули программ, или как библиотеки совместимых подпрограмм. Эти модули вместе с результатами всех заданных компиляций загружаются (в заданном порядке) и создается выполняемая программа с именем a. out.
Файлы
file.f или file, for исходный файл
file.dbg file.o a. out /usr/lib/fppdoall /usr/lib/fortran /usr/lib/fncode /usr/lib/fjlinker /Hb/libc.a /Hb/libf.a /usr/lib/libpps.a /lib/libf _p.a
файл с информацией для загрузчика pdbx(l) объектный модуль загружаемый выводной файл параллельный препроцессор первая фаза компилятора
генератор кода
преобразователь объектного кода
библиотека поддержки времени выполнения Си библиотека поддержки времени выполнения Фортрана библиотека параллельного программирования библиотека поддержки времени выполнения Паскаля
Смотри также ld(l), ddt(l), pdbx(l)
Balance 8000/21000 Fortran Compiler Users Manual
Диагностика
Диагностика, создаваемая Фортран-компилятором, задумана как самодостаточная. Отдельные сообщения могут выдаваться ассеблером или загрузчиком.
Название
pascal - компилятор с языка Паскаль.
Краткое описание
pascal [ option ] ... file ...
Описание
pascal - это компилятор с языка Паскаль в операционной системе DYNIX. pascal воспринимает параметры нескольких типов.
Параметры, имена которых оканчиваются на “.р“ или на ".pas", воспринимаются как файлы, содержащие исходный текст программ на языке Паскаль; те параметры, имена которых оканчиваются на u.f“ или на “.for" - как файлы с исходным текстом программ на языке Фортран; те параметры, имена которых оканчиваются на ".с" - как файлы с исходным текстом программ на языке Си. Заданные исходные файлы компилируются, и объектные модули помещаются в файлы с именами "х.о“, где "х" - начальная часть имени соответствующего исходного файла. Аналогичным образом параметры с именами, оканчивающимися на ".з", воспринимаются как файлы, содержащие исходный текст программ на языке ассемблера. Они транслируются ассемблером, создающим файлы с окончанием ".о".
Если не специфицирована опция -с, все файлы с окончанием ".о" ком- пануются с библиотеками Фортрана, Паскаля и Си и загружаются, как единое целое. В этом случае все файлы с окончанием “.о" удаляются.
с Подавляется фаза загрузки при компиляции; создается объектный файл, даже если компилируется единственная программа.
е Компиляция в режиме совместимости с языком Си. Параметры будут возникать в месте вызова, а не в месте возврата. Это позволяет ком- пановать си-модули с фортран- и паскаль-модулями. Запись внешних имен (используемых ld(l)) преобразуется в запись строчными буквами. (По умолчанию запись всех внешних имен производится прописными буквами. Имена, специфицированные как cexternal, передаются Id неизменными, вне зависимости от того, специфицирована или нет опция -е.) Дополнительные детали - в Balance 8000/21000 Pascal Compiler Users Manual.
1 listname
Генерируется листинг программы в файле "listname". Если имя файла не описано, то его имя по умолчанию - list.
-prompt
При обнаружении ошибки (и выводе сообщения о ней на экран) производится стандартное приглашение на ввод. Это препятствует “убеганию" сообщения об обнаруженной ошибке с экрана до того, как оно будет прочитано.
p Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты prof. Также профилируются библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль.
pg Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты gprof. Также" профилируются библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль.
р Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты prof. Библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль НЕ профилируются.
PG Компилируется код для профилирования. В загрузочной последовательности указываются библиотеки для утилиты gprof. Библиотеки поддержки времени выполнения систем программирования Си, Фортран и Паскаль НЕ профилируются. Флаг -Pg воспринимается также как и -Рб.
и Файлы с окончанием “.obj“ не удаляются. Для каждого исходного файла компилятором создаются промежуточные файлы с суффиксом “,obj“. Эти файлы необходимы для надежной раздельной компиляции. Дополнительные детали - в Balance 8000/ 21000 Pascal Compiler User's Manual.
g В текущем рабочем справочнике создается файл с отладочной информацией для отладчика dbg(l) или dbx(l). Имя файла будет иметь суффикс ".dbg".
gv В текущем рабочем справочнике создается файл с отладочной информацией для отладчика dbg(l) или dbx(l). Имя файла будет иметь суффикс ".dbg". Для Паскаль-модулей -д и -gv имеют один и тот же эффект. (Для модулей Фортрана информация о всех переменных в COMMON 1-области заносится даже в тех случаях, если на переменную нет ни одной ссылки. Этот флаг увеличивает время инициализации при отладке очень больших программ.)
Остальные параметры воспринимаются как параметры или опции загрузчика, или как объектные модули программ, или как библиотеки совместимых подпрограмм. Эти модули вместе с результатами всех заданных компиляций загружаются (в заданном порядке), и создается выполняемая программа с именем a.out.
Файлы
f11e.p или file.раз исходный файл
file.dbgфайл с информацией для загрузчика dbg(l) или dbx(l)
f11e.oобъектный модуль
a.outзагружаемый выводной файл
/usr/11 b/гсообщения
Смотри также
Balance 8000/21000 Pascal Compiler User's Manual
ld(l), ddt(l), dbg(l), dbx(l)
Диагностика
Диагностика, создаваемая Паскаль-компилятором, задумана как самодостаточная. Отдельные сообщения могут выдаваться загрузчиком.
Название
make - поддержка группы программ.
Краткое описание
make [ -f makefile ] [ option ] ... file ...
Описание
make выполняет команды из makefile для обновления одного или более целевых name. Name - это обычно программа. Если не специфицирована никакая опция -f, то выполняются по очереди "makefile” и "Makefile”. Если makefile задан как "-", берется стандартный ввод. Можно специфицировать более одной опции -f.
make обновляет целевой файл, если он зависит от ранее определенных файлов-реквизитов, которые изменялись после последнего изменения цели, или создает целевой файл, если он не существует.
makefile содержит последовательность описаний, определяющих зависимости. Первая строка описания содержит список целей, разделенных пробелами, затем следует двоеточие, а за ним - список файлов-реквизитов. Текст, следующий за точкой с запятой, и все последующие строки, начинающиеся с табуляции, являются командами shell, которые необходимо выполнить для обновления целевых файлов. Если имя встретилось слева от знака двоеточия более, чем в одной строке, то соответствующий целевой файл зависит от всех имен, которые находятся справа от знака двоеточия в этих строках, но для них может быть определена только одна последовательность команд обновления. Если имя встретилось в строке с двойным двоеточием (“::“), то последовательность команд, следующая за этой строкой, выполняется только тогда, когда целевой файл с этим именем изменялся ранее изменений файлов, чьи имена определены правее двойного двоеточия, но при этом не будут рассматриваться другие строки с двойными двоеточиями, в которых могло встречаться это имя.
Определены две специальные формы имен. Имя, подобное а(Ь), означает файл, называемый Ь, помещенный в архив с именем а. Имя, подобное а((Ь)), означает файл из архива а, у которого есть входная точка с именем Ь.
Комментарии начинаются с и#“» а заканчиваются символом “новая стро- ка“.
В следующем примере makefile говорится, что "pgm” зависит от двух файлов "а.о" и "Ь.о", а они зависят от ".с" файлов и от общего файла "incl".
pgm: а.о Ь.о
сс а.о Ь.о -1m -о pgm а.о: incl а.с
сс -с а.с
Ь.о: incl Ь.о
сс -с Ь.с
Строки makefile вида: stringl ■ string2
являются макроопределениями. Последовательные вхождения $(stringl) или ${stringl} заменяются на string2. Если stringl один символ, то скобки могут опускаться.
Файлам, для которых нет команд конструирования в makefile, make сам выводит файлы-реквизиты. Например, файл ".с" может быть выведен как файл-реквизит для файла ".о", и он будет компилироваться для создания файла ".о". Предыдущий пример может быть написан более кратко:
pgm: а.о Ь.о
сс а.о Ь.о -1m -о pgm а.о Ь.о: incl
Файлы-реквизиты выводятся в соответствии с предписанными суффиксами, перечисленными в списке "prerequisites" для выделенного имени ".SUFFIXES". Если списков больше одного, они соединяются вместе. Пустой список обнуляет все предыдущие. Порядок вхождения в списки учитывается. Первое возможное имя, для которого существуют и файл, и правило, как показано в следующем параграфе, выводится. Список по умолчанию включает в себя:
.SUFFIXES: .out .с .е .г .f .у .1 .s .р
Правило для создания файла с суффиксом s2, который зависит от файла с той же первой частью имени, но с суффиксом si, определяется как вхождение в качестве "цели" ("target") s2sl. В таких вхождениях специальная макроконструкция $* употребляется в качестве имени целевого файла с опущенным суффиксом, $9 - в качестве полного имени целевого файла, $< - в качестве полного списка файлов-реквизитов, а $? - в качестве списка файлов-реквизитов, которых нет в данных. Например, правило для создания оптимизированного файла с суффиксом ".о" из файла с суффиксом ".с" выглядит следующим образом:
.с.о: ; сс -с -0 -о $9 $*.с
Определенные макроконструкции используются* по правилам вывода, заданным по умолчанию, для передачи дополнительных параметров в любую результирующую компиляцию. В частности, "CFLAGS" используеься для задания опций в сс(1), "FFLAGS" - для опций в fortran(l), "PFLAGS" - для опций в pascal(l), a "LFLAGS" и "YFLAGS" - для опций в lex и уасс(1). Кроме того, макроконструкция "MFLAGS" заполняет опции, приложимые к make, в начальной командной строке. Это упрощает организацию иерархии "make-файлов", так что можно вызывать make для "make-файлов" в подсправочниках и передавать им полезные опции, такие как -к.
Другой специальной макроконструкцией является "VPATH". При помощи этой макроконструкции можно задавать список справочников, разделенных двоеточиями. Когда make производит поиск файла как результата отношения зависимости, этот поиск производится сначала в текущем справочнике, а затем в каждом справочнике, определенном в списке макроконструкции "VPATH". Если файл находится, то в дальнейшем вместо имени файла будет употребляться найденный путь. Если макроконструкция "VPATH" не определена, то поиск производится только в текущем справочнике.
Макроконструкция "VPATH" используется в тех случаях, когда имеется несколько отдельных программ, которые компилируются из одних и тех же исходных файлов. В этом случае исходные файлы' помещаются в один справочник, а каждый набор объектных модулей, следуя своему "make-файлу", по-
мещается в отдельный подсправочник. Макроконструкция ”VPATH” будет определять справочник с исходными файлами.
Командные строки выполняются по одной, каждая в своей “оболочке44. Строки печатаются по мере выполнения, если не задана в “make-файле44 специальная цель ".SILENT”, или не задан в строке первый символ ”8”.
Команды, возвращающие ненулевой статус завершения (см. intro(l)), приводят к завершению make, если не задана специальная цель ”. IGNORE” в 44 make-файле44, или командная строка не начинается с символов <табуляция><дефис>.
Прерывание и выход приводят к отбрасыванию цели, если только цель не является справочником или зависит от специального имени ”. PRECIOUS”.
Другие опции:
Р п Разрешает параллельное с ”&” выполнение nl командных последовательностей. Смотри ниже раздел “Поддержка параллельности44. Если в командной строке эта опция встречается несколько раз, то последнее значение п подавляет предыдущие. Если эта опция отсутствует, то make ищет в среде выполнения переменную PARALLEL и использует в качестве п ее числовое значение. Если нет и такой переменной, то по умолчанию как значение п принимается 3.
1 Эквивалентно вхождению специальной цели ".IGNORE:”.
к Когда команда возвращает ненулевой статус выполнения, прекращается работа над текущей целью, но продолжается работа над ветвями, не зависящеми от текущей.
п Выполняется трассировка и печать, но не выполняются команды, необходимые для обновления целей.
t “Касание44 (touch), т.е. обновление измененных данных в целях без выполнения каких-либо команд.
г Эквивалентно вхождению специальной цели ”. SUFFIXES:” без всякого списка.
s Эквивалентно вхождению специальной цели ".SILENT:”.
Поддержка параллельности
Маке включает в себя возможности параллельного выполнения. Если строка, отделяющая цель от ее файлов-реквизитов44, имеет вид ”:&” или ”;;&”, make может выполнять командные последовательности по созданию файлов-реквизитов одновременно. Если два имени разделены амперсантом (&) справа от двоеточия, эти два файла могут создаваться параллельно.
Например, в данном “make-файле44 объекты ”а.о”,”Ь.о” и ”с.о” могут создаваться параллельно:
pgn:& а.о Ь.о с.о
сс -о pgm а.о Ь.о с.о
а.о: а.с defs.h
Ь.о; Ь.с defs.h
с.о:с.с defs.h
В следующем примере только "а.о" и "Ь.о" будут создаваться параллельно, а создание ”с.о” начнется только после того, как создание предыдущих двух файлов будет завершено:
pgn: а.о & Ь.о с.о
сс -о pgm а.о Ь.о с.о
a.o: a.c defs.h b.o: b.c defs.h c.o: c.c defs.h
Опция -P управляет числом параллельных потоков команд. Make, вызванная с параметром -р 1, работает без всякого параллелизма, и это бывает полезно для обнаружения действительного источника ошибок, когда сообщения об ошибках от параллельных командных последовательностей перепутываются. (Полезным методом обнаружения вывода сообщений об ошибках является просмотр командных строк, которые не начинаются с символа табуляции.)
Для выполнения командных последовательностей параллельно, make разветвляет процессы без ожидания и показывает, что процессы разветвлены, путем добавления символа амперсант (&) и ID (идентификатора) процесса при печати команды (аналогично тому, как используется символ & для указания на асинхронную работу в оболочке). Если разветвленная команда возвращает ненулевой статус завершения, make предваряет сообщение об ошибке (или предупреждающее сообщение) идентификатором процесса, что позволяет обнаружить источник ошибки.
Не все командные последовательности можно распараллеливать. Некоторые командные последовательности генерируют файлы в текущем справочнике, имена которых не зависят от имен создаваемых файлов. Например, операции, вызывающие два или более употребления уасс, lex или xstr в одном и том же справочнике, не могут распараллеливаться.
Когда файлы-реквизиты создаются параллельно, только последняя (или единственная) команда в составной командной последовательности может быть ответвлена для параллельного исполнения. Остальные команды выполняются последовательно. Например, в данном “таке-файле“ только "lastcommand- будет запущена параллельно, команды -firstcommand- и -secondcommand" выполняются до того, как будет вызвана любая команда для создания "Ь":
pgm:& а b а:
firstcommand secondcommand lastcommand b:
b-firstcommand b-secondcommand b-lastcommand
Чтобы обойти это ограничение, рекомендуется все команды рассматривать как одну большую команду оболочки, например, так:
pgm:& а b а:
firstcommand; \ secondcommand; \ lastcommand;
b:
b-firstcommand; \
b-secondcommand; \ b-lastcommand
Обратный слэш (\) в конце командной строки приводит к тому, что следующая строка воспринимается как продолжение предыдущей. “Маке-
файл", построенный подобным образом, будет выполнять все команды создания файла "а" параллельно со всеми командами создания "Ь". Заметим, что при этом все три команды создания "а" будут выполняться одной и той же оболочкой (а не тремя раздельными вызовами оболочки). Команды, прямо интерпретируемые оболочкой (например, такие как смена справочника), не будут изолированы от других команд в одной командной строке. Это нужно принимать во внимание при изменении “make-файла" подобным образом.
Файлы
Makefile, Makefile
Смотри также
sh(l), touch(1), fortran(l), pascal(l)
S.I.Feldman, “Make - A Program for Maintaining Computer Programs".
Недостатки
Некоторые команды возвращают ненулевой статус выполнения в неподходящих случаях. Нужно использовать для обхода этой трудности опцию -L Команды, прямо выполняемые оболочкой, особенно cd(l), не действуют на следующие строки в make.
"VPATH" по замыслу должен действовать подобно поддержке "VPATH" в System V, но гарантии того, что их функции полностью идентичны, нет.
Синтаксис ":&" и "::&" не совместим с предыдущими версиями make. Когда требуется переносимость, используйте ":$(р)" или "::$(Р)" и дописывайте р-"&" в командной строке.
Название
pbdx, dbx - параллельный отладчик [отрывок].
Краткое описание
pbdx [-г] [-1] [-и] [-а] [-с file] [-1 dlr] ... \ [objflle [coredump]]
pbdx [-г] [-1] [-и] [-а] [-с file] [-1 dlr] ... \ [-0 objflle]... [-С coredump]...
Описание
Pbdx - средство для отладки на уровне исходного текста и выполнения как последовательных, так и параллельных программ под управлением ОС DYNIX. Pbdx является расширенной версией отладчика bdx UNIX 4.2BSD и может вызываться как pbdx или bdx; оба имени соответствуют одной и той же программе. Когда отлаживается последовательная однопроцессная однопрограммная задача, pbdx работает полностью идентично bdx UNIX 4.2BSD (см. приложение A DYNIX Pdbx Debugger User's Manual, где приведен список отличий).
objfile - это выполняемый объектный файл, созданный компилятором при заданном флаге -д, определяющем создание информации о символах. В настоящее время сс(1), pascal(1) и fortran(l) создают необходимую информацию. На машинном уровне pbdx может производить отладку любой программы.
Объектный файл содержит таблицу символов, в которую включены имена всех исходных файлов, откомпилированных для создания данного объектного файла. Эти файлы доступны для просмотра во время работы отладчика.
Если существует в текущем справочнике файл с именем core или определены один или несколько файлов coredump, pdbx можно использовать для проверки состояния программы в случае ее ошибки.
Первая форма команды pdbx, приведенная выше, используется при отладке прикладной задачи, которая состоит из одного или более процессов в рамках одной программы. Вторая форма используется при отладке прикладной задачи, состоящей из набора программ (например, прикладные задачи типа “пользователь/обслуживающее устройство" или процессы, которые запускаются командой ехес); каждая опция -о определяет отлаживаемую программу.
Если в текущем справочнике существует файл с суффиксом .dbxinit, то выполняются команды отладчика из этого файла. Pdbx проверяет наличие такого файла и в справочнике пользователя, если его нет в текущем справоч- нинке.
Опции командных строк и их значения:
г objfile немедленно выполняется. Если его выполнение завершается успешно, pdbx заканчивает работу. В противном случае, причина завершения будет сообщена, и пользователю будет предоставлена возможность войти в отладчик или оставить программу ошибочной. Если опция -г задана, а стандартный ввод - не терминал, отладчик будет читать из /dev/tty.
1 Заставляет работать pdbx так, как будто стандартный ввод - терминал (т.е. будут выдаваться приглашения для команд).
и Защищает представление идентификаторов в паскаль- и фортран- программах. Компиляторы с Паскаля и Фортрана нечувствительны к форме представления идентификаторов и переводят их в запись только заглавными буквами. Обычно pdbx переводит идентификаторы в запись только строчными буквами. Если же эта опция задана, отладчик оставляет их записанными заглавными буквами.
а Запустить все процессы асинхронно (т.е. как фоновые). Как только pdbx запускает процесс на выполнение, немедленно выдается приглашение на ввод другой команды. Ни один процесс не может читать с терминала.
с file
До начала стандартного ввода выполнить команды pdbx, находящиеся в файле file.
I dir
Добавить dir к списку справочников, которые просматриваются при поиске исходных файлов. Обычно pdbx производит поиск в текущем справочнике и в справочнике, в котором находится objfile. Набор путей поиска справочников можно создавать и при помощи команды use. Если задан более, чем один объектный файл, каждый параметр, задаваемый опцией -I, применим к программе, определяемой следующей опцией -о.
О objfile
Идентифицируется objfile как та программа, которую нужно отлаживать. Если отлаживается только одна программа, то можно идентифицировать программу без опции -о, используя первую форму вызова pdbx, как показано в синопсисе.
С coredump
Идентифицируется файл coreduep, как файл, содержащий образ памяти, используемый при анализе. Опция -с полезна, когда отлаживается более одной программы или когда нужно запоминать более, чем один образ памяти.
Если опция -г не задана, pdbx выдает приглашение на ввод команды и ожидает команду.
Компилирование программ для использования с pdbx
Флаг -д, заданный в командной строке параметров команд сс(1), pascal(l) или fortran(l), вынуждает компилятор создавать символьную информацию для pdbx. Если вызывается команда ld(l) для сборки программы, в ее командную строку также необходимо включить" флаг -1g. Для си-программ символьная информация кодируется в таблице символов, включенной в объектный файл. Для паскаль- и фортран-программ символьная информация помещается в отдельный файл с расширением .dbg. Если pdbx вызывается для отладки объектного файла objfile, он будет считывать символьную информацию как из файла objfile.dbg, так и из таблицы символов в objfile.
Если производилась раздельная компиляция, создается несколько файлов ".dbg"; по одному на каждый исходный текстовый файл. Они должны быть объединены (при помощи команды cat(l)) в один файл objfile.dbg.
Код, скомпилированный при использовании флага -д, нельзя оптимизировать, указывая флаг -о. В противном случае флаг -д не изменяет код, сгенерированный компилятором.
Отладка многопроцессных прикладных задач
Pdbx может управлять совокупностью процессов и их потомков. Когда процесс создается (например, при помощи команды run), ему приписывается номер процесса. У первого процесса будет номер 1, а процесс, созданный pdbx последним, именуется “текущим процессом". Определенные команды pdbx работают с текущим процессом по умолчанию, но могут работать и с указанным процессом (например, с "%2”) или со всеми процессами. Список номеров процессов можно получить с помощью команды ps pdbx, текущий процесс может быть переопределен при помощи команды "%".
Для запуска процесса в качестве фонового необходимо добавить к команде запуска процесса на выполнение (run, cont, step) амперсант (&). Pdbx выдаст приглашение на ввод следующей команды, не дожидаясь окончания процесса.
Выполнение и трассировка команд
run [objfile] [args] [< filename] [> fНепале] [6] rerun [objfile] [args] [< filename] [> filename] [6]
Начать выполнение objfile, передав args как параметры командной строки; символы < или > могут быть использованы для перенаправления входа и выхода обычыным образом. Если команда rerun использу-
ется без параметров, в программу передается предыдущий список параметров; в противном случае она идентична команде run. Обе эти команды завершают все существующие процессы перед созданием процесса для выполнения.
ЗАМЕЧАНИЕ: Параметр objfite должен быть включен, если при вызове pdbx был специфицирован более, чем один объектный файл, в противном случае этот параметр можно опустить.
create [objflle] [args] [< filename] [> filename] [6]
Создает процесс, как и команда run, но при этом не запускает процесс на исполнение и не прекращает ни один из существующих процессов. Созданный процесс может быть запущен на выполнение с помощью команды cont.
terminate [Xn] terminate all
Прекращаются указанные процессы (по умолчанию текущий процесс). Команда ps не включает в список процессов прекращенные процессы, ps [Xn]
Сообщает состояние, ID процесса, приписанный DYNIXom, и родительский процесс процесса п. Если не указан ни один процесс, печатается пронумерованный список всех процессов с указанием состояния каждого. Для образов памяти сообщается тип сигнала, который привел к аварийному завершению программы.
Хп Делает процесс п текущим процессом.
trace [procid] [in procedure/function] [if condition] trace [procid] source-line-number [if condition] trace [procid] procedure/function [in procedure/function] [if condition] trace [procid] expression at source-line-number [if condition] trace [procid] variable [in procedure/function] [if condition]
Когда программа оканчивается, печатается трассировочная информация. Число ассоциируется с командой, которая использовалась для выключения трассировки (см. команду delete).
В мультипроцессных задачах опция procid указывает на процесс, для которого надо создавать трассу (по умолчанию - для всех процессов, ассоциированных с текущим исходным файлом). Procid в форме Хп указывает, что нужно трассировать процесс п. Procid в форме objflle указывает, что нужно трассировать все процессы, созданные из этого объектного файла. Следующий параметр указывает, что именно нужно заносить в трассу. Если этот параметр - source-line-number (номер строки исходного текста), то строка печатается непосредственно перед выполнением. Перед номером строки текста не из текущего исходного файла необходимо поместить имя файла в кавычках и двоеточие, например, "mumble.р":.
Если параметром является имя процедуры или функции, то каждый раз, когда происходит обращение к указанной процедуре, печатается какая подпрограмма обращается, из какой строки исходного текста, И какие параметры передаются при обращении. Кроме того, отмечается и выход из процедуры, а если это функция, которая возвращает значение, то печатается и это значе- ние.
Если этот параметр - xpression (выражение) в предложении, указываемом at, то значение этого выражения печатается каждый раз, когда оно вычисляется, еще до того, как предложение будет выполнено.
Если параметр - variable (переменная), то ее имя и значение будут печататься при всяком изменении этого значения. Если задана эта форма трассировки, то выполнение программы существенно замедляется. Если этот параметр опущен, то все строки исходного текста печатаются перед их выполнением. Если задана эта форма трассировки, то выполнение программы существенно замедляется.
Предложение win procedure/function" (в процедуре/функции) определяет, что трассировочная информация печатается только тогда, когда управление находится внутри этой процедуры /функции.
Condition (условие) - это булево выражение, которое вычисляется перед печатью трассировочной информации; если его значение - ложь, то печать не производится.
stop [procid] if condition
stop [procid] at source-line-number [if condition] stop [procid] in procedure/function [if condition] stop [procid] variable [if condition] stop all
Если достигается указанная строка, или происходит обращение к указанной процедуре или функции, или изменяется указанная переменная, или значение условия становится истиной, то выполнение процесса останавливается. Все остальные формы команды stop устанавливают некоторую форму точки прерывания выполнения.
Если отлаживается мультипроцессная задача, то параметр procid определяет, выполнение какого процесса необходимо прервать. По умолчанию, точка прерывания устанавливается для всех процессов, созданных в текущей программе, а останавливается выполнение только того процесса, который выходит на точку прерывания. Задание prpcid в форме %п определяет применимость точки прерывания только к процессу п. Если procid задана как all (все), все процессы останавливаются, как только один из них достигает точки прерывания. Зада- ние procid в форме objfile определяет применимость точки прерывания ко всем процессам, созданным из этого объектного файла.
Если процесс запускается, команда stop не действует на него, пока он не будет остановлен.
status [>filename]
Вывести на печать текущие активные команды trace и stop, delete comand-number ...
Окончить выполнение команд трассировки или остановки, соответствующих заданным номерам. Номера, ассоциированные с командами трассировки и остановки выводятся командой status.
catch [signal] ignore [signal]
Начать или закончить перехват сигналов до того, как они присылаются в программу. Это бывает полезно при отладке программ, интерпретирующих такие сигналы, как прерывания. Сигнал может бытьопреде- лен номером или именем (например, SIGINT). При работе с именами сигналов не различаются прописные и строчные буквы, а префикс "SIG" является дополнительным. По умолчанию, перехватываются все сигналы кроме SIGCONT, SIGCHILD, SIGALRM и SIGKILL. Если команды заданы без параметров, то catch выводит список перехватываемых сигналов и событий, a ignore - игнорируемых.
catch event ignore event
Перехватывать или игнорировать заданные события, которыми могут быть fork, exec или xit. По умолчанию, перехватываются хес и xit, а разветвления игнорируются. Если отладчик настраивается на перехват разветвления, то дочерний процесс останавливается немедленно после разветвления, а родительский процесс продолжает свое выполнение. Если отладчик настраивается на перехват хес, то процесс останавливается непосредственно перед тем, как новая программа начнет свое выполнение. Если отладчик настраивается на перехват xit, то процесс останавливается непосредственно перед его удалением системой, и он будет удален, каким бы образом не пытались его продолжить.
signal [procid] signal
Послать заданный сигнал заданному процессу (по умолчанию, текущему процессу). Можно задавать все (all) или определенный (%п) процессы.
cont [procid] [signal] [to linenumber] [&]
Продолжить выполнение процесса с того места, где он был остановлен. Для мультипроцессных задач параметр procid определяет, . какие процессы нужно продолжить (по умолчанию, текущий процесс). Можно задавать все (all) или определенный (2%п) процессы.
Если в команде определен сигнал (signal), то процесс продолжается так, как будто он получил этот сигнал, а все сигналы, перехваченные до этого, отбрасываются. Ноль в парамере 2signal показывает, что процесс должен быть продолжен так, как будто он не останавливался. В противном случае он продолжается так, как будто получен указанный сигнал. Если к процессу поступило несколько сигналов, то сигнал, определенный параметром signal, перекрывает все перехваченные. Если задан дополнительный параметр linenumber, выполнение продолжается до тех пор, пока не будет выполнена указанная строка, после чего процесс остановится.
step [procid] [&]
Выполнить одну строку исходного текста. Все сигналы, перехваченные к этому моменту, отбрасываются. Для мультипроцессных задач параметр procid определяет, в каком процесс нужно выполнить строку (по умолчанию, в текущем процессе). Можно задавать все (all) или определенный (%п) процессы.
next [procid] [&]
Выполнить следующую строку исходного текста. Все сигналы, перехваченные к этому моменту, отбрасываются. Можно задавать все (all) или определенный (%п) процессы. Разница между командами next и step заключается в том, что, если в указанной командной строке находится обращение к процедуре (или функции), то команда step остановится до выполнения этой процедуры, а команда next остановится после выполнения этой процедуры.
return [procedure]
Продолжится выполнение до тех пор, пока не будет выполнен возврат из указанной процедуры или из текущей, если этот параметр опущен, call procedure() call procedure (parametres)
Выполнить объектный код, ассоциированный с указанной процедурой или функцией.
window [Xn] on ttyname
Создает ttyname как отладочное окно процесса п. Эта команда может быть использована для облегчения процесса отладки многопроцессной задачи с помощью различных управляющих терминалов для процессов. Указанный процесс (по умолчанию, текущий процесс) будет использоваться как текущий процесс при интерпретации команд, поступивших с указанного терминала. Если процесс прямо указан, то ttyname будет использоваться указанным процессом для определения стандартного ввода, стандартного вывода, вывода сообщений об ошибках и сообщений отладчика. Ttyname может определять окно оболочки BSS/PC-Shells (см. shells(l)) или обычный терминал, с которого запрещен вход в систему. Ttyname может быть как полным именем устройства (например, ”/dev/ttyp9"), так и двусимвольным суффиксом (например, ”р9”).
Печать значений переменных и выражений
Поиск имен проводится сначала в статической области видимости текущей функции, а затем в ее динамической области. Если поиск как в той, так и в другой области не привел к успеху, выбирается произвольный символ и печатается сообщение "[using qualified name]”. Процедура разрешения имени может быть подавлена заданием в качестве квалификатора идентификатора имени блока, например, "module variable”, где module полагается функцией и данными в исходном текстовом файле и именуется именем файла без суффиксов ".с”, ”.f” или ”.р”. Компиляторы с Фортрана и с Паскаля не различают в представлении имен прописные и строчные буквы, поэтому идентификаторы надо задавать только строчными (или только прописными, если при вызове отладчика была задана опция -и) буквами.
Выражения определяются на подходящем общем подмножестве синтаксиса Паскаля и Си. Косвенность указывается при помощи префикса ”*” или постфикса ”*”, индексные выражения массивов записываются в квадратных скобках (”[ ]”). Операция выбора поля (”.") может осуществляться как относительно записи, так и относительно указателя, поэтому ”.” можно употреблять вместо операции Си ”->" (хотя и эта операция поддерживается).
В мультипроцессных задачах выражение в форме п:ехрг вычисляется в контексте процесса п. Перед вычислением выражения процесс должен быть остановлен. Если префикс п: опущен, вычисление проводится в контексте текущего процесса.
* * * (4 страницы опущены) * * *
Файлы
a.out объектный файл;
name, dbg символьная информация, созданная компилятором с Фортрана или Паскаля.
Смотри также
сс(1), fortran(l), pascal(1), ddt(l)
DYNIX Pdbx Debugger Users Manual
Комментарии
Pdbx страдает тем же недостатком (“множественным включением11), что и sdb. Если программа состоит из нескольких объектных файлов, каждый из которых был создан из исходных файлов, включая файлы заголовков, то символьная информация для файлов заголовков дублируется в каждом объектном файле. Поскольку для каждой компоновки выполняется порядка одной инициализации отладчика, использование реорганизации символьной информации компоновщиком (id) не принесет большого выигрыша во времени, хотя уменьшит затраты пространства на магнитном диске.
Проблему можно смягчить искусственным ограничением семантики включений (ftnclude) в Си. Например, файл включений может содержать отдельные вхождения всех описаний для каждого файла, использующего этот файл включений.
Название
шшар - размещение открытого файла в адресном пространстве процесса.
Краткое описание
ftnclude <sys/types.h>
ftnclude <sys/mman.h>
mmap(addr, len, prot, share, fd, pos)
. caddr_t addr; /* starting vtrt-addr */
/* начальный вирт-адрес */
tnt len; /* lengh (bytes) to map */
/* длина (в байтах) размещ. информ. */
tnt prot; /* RO, RW encoding */
/* */
tnt share; /* private/sharing modifications */
/* модификации локальная/разделяемая */
tntfd;/* open file to be mapped */
/* размещаемый открытый файл */ off_tpos;/* where tn file to begin map*/
/* откуда из файла брать размещ. инф. */
Описание
Мшар приводит к тому, что содержимое файла, определенного параметром fd, начиная с байта pos, размером в len байтов помещается в адресном пространстве вызвавшего процесса, начиная с виртуального адреса addr, при использовании защиты, заданной prot. Память, в которой производится размещение, может быть или локальной, или разделяемой, в зависимости от параметра share. Мшаре можно использовать для размещения областей разделяемой памяти, для размещения файлов в памяти и для доступа к специальным областям физического адресного пространства.
Fd должно указывать на открытый обычный (IFREG) или специальный литерный (IFCHR) файл. Драйвер устройства, который реализует специальный файл IFCHR, должен обеспечивать возможность размещения. Обычный файл используется, как правило, для размещения в разделяемой памяти.
Параметр share определяет модификацию размещаемого файла, то есть, будет ли он локальным для обратившегося процесса или разделяемым другими процессами, имеющими доступ к файлу. Если share равно MAP_SHARE, все изменения файла будут общими для всех процессов, которые размещали этот файл параллельно. Если этот параметр равен MAP-PRIVATE, все изменения будут локальными для вызвавшего процесса, но это не препятсвует другим процессам размещать у себя этот файл. Как MAP_SHARED, так и MAP-PRIVATE не имеют отношения к flock(2) и не налагают никаких ограничений на системные вызовы read и write. Если значение параметра равно MAP_ZEROFILL, место в памяти, указанное параметрами addr и len, замещается локальными страницами, которые при обращении воспринимаются как нулевые. При этом значении параметра игнорируются параметры fd и pos, вместо проверки pos на выравнивание по границе слова (рекомендуется задавать fd и pos равными нулю). (Замечание. MAP-PRIVATE в этой версии не реализовано.)
Параметр prot равен PROTJWRITE при задании разрешения записи в размещаемую область, PROT-READ - при задании разрешения чтения, PROT-EXEC - при задании разрешения выборки команд на выполнение. Эти значения могут задаваться с помощью ИЛИ-соединения для задания комбинированного доступа (чтения-записи и т.п.). По программному соглашению PROT-RDWR определено как (PROT-READ|PROT-WRITE). Режим доступа к файлу fd должен позволять запрашиваемый доступ. Параметр prot определяет режим доступа только для вызывающего процесса, все остальные процессы, размещающие в своей памяти данный файл, определяют свои режимы доступа.
Параметры addr, len и pos должны быть целыми числами, кратными размеру страницы памяти, определяемой getpagesize(2). Возможно размещение поверх страниц памяти, в которых ранее производилось размещение. Если параметры addr и len определяют несуществующую часть адресного пространства процесса, то сегмент данных процесса увеличивается для удовлетворения запроса, и “граница (break) “ процесса (см. brk(2)) устанавливается на верхнюю границу размещаемой области. Обращения к “дыркам“ между размещаемой областью и остальной частью сегмента данных приводят к ошибкам сегментации (SIGSEGV). Запрещается размещение в страницы, содержащие команды и стек.
Когда в памяти размещается обычный файл, этот файл представляется частью страниц размещаемой области. Операции read и write над областью размещения файла воздействуют и на соответствующую память. Содержимое памяти копируется в файл, когда процесс замещается в памяти или оканчивается, или когда эта область переразмещается последним процессом, размещавшим ее. Для процессов, которые используют разделяемую память, но не нуждаются в постоянном образе памяти на диске, файл, определяемый параметром fd, может быть удален из справочника (“uni inked “ - см. unlink(2)) даже до того, как будет вызвана imnap: если файл будет удален из справочника, когда область переразмещена, то информация на диске обновляться не будет.
Обычные файлы имеют размеры, округленные до границ блоков системных файлов. Любое неиспользуемое к моменту вызова mmap пространство в файле (например, в разреженном файле) отображается в память, но воспринимается при обращении как обнуленное. Обе эти операции требуют разрешения записи в файл.
В зависимости от типа размещаемого файла могут появляться дополнительные ограничения на pos, offset и другие параметры. Подробности можно найти в тех местах руководства, где описываются соответствующие драйверы устройств (например, ршар(4)).
Закрытие дескриптора файла, ранее использовавшегося при вызове мшар, приводит к отказу от размещения всех страниц, занятых дескриптором файла (см. также munmap(2)). Если дескриптор файла предварительно (до закрытия) был отдублирован с помощью системной функции dup, то отказа от размещения не производится.
Мшар может вызываться многократно с одним и тем же дескриптором файла, что приводит к размещению (возможно с перекрытием) в нескольких областях. У процесса одновременно может быть до 8 областей, причем области, полностью перекрытые последующими размещениями, не учитываются. Размещения, которые используют один и тот же дескриптор файла, и параметры addr и pos которых виртуально выровнены с предыдущими размещениями также не учитываются в общем счете. В простейшем случае размещается дополнительная часть файла в конце предыдущего размещения этого же файла.
Все файлы, размещенные в памяти, остаются после выполнения fork размещенными как в памяти процесса-родителя, так и памяти процесса-потомка. Все разновидности системного вызова exit при успешном выполнении приводят к освобождению всех созданных размещений вызвавшего процесса. При любых размещениях процесса было, vfork ничем не отличается от fork.
Существуют три типа размещения: страничной, физической и нестраничной памяти. Тип размещения определяется типом размещаемого файла. Страничное размещение поддерживает разделяемую память и размещение обычных файлов. Физическое размещение обеспечивает работу с аппаратурой, имеющей ограниченные возможности доступа (например, адресное пространство MULTIBUS, включающее память атомарных замков). Нестраничное размещение памяти обычно используется для специально зарезервированных областей системной памяти и позволяет поддерживать доступ к структурам произвольного размера и расположения, прямой доступ и т.д.
Системные службы (прямой ввод/вывод, чтение/запись, состояние и т.п.) поддерживаются как в страничных, так и в нестраничных размещениях памяти. Попытки использования этих служб в физически размещенной памяти приводят к ошибкам (обычно EFAULT). Образ памяти содержит и копии всех размещенных адресных пространств, однако, физически размещенная память считывается как обнуленная.
Обычные файлы (IFREG) всегда размещаются страничным образом. Специальные литерные файлы (IFCHR) могут поддерживать страничное, физическое и нестраничное размещение, в зависимости от поддерживающей аппаратуры. Физические и нестраничные размещения всегда присутствуют в адресном пространстве процесса, обращения к ним не приводят к ошибкам типа “отсутствие страниц".
Возвращаемое значение
Мшар возвращает значение ноль в случае успешного выполнения. В противном случае возвращается значение -1, а номер ошибки помещается в глобальную переменную errno.
Примеры
Следующий код устанавливает 1-Мбайтовую область разделяемой памяти, начиная с первой страницы, следующей за границей памяти, определяемой границей "break" процесса. Эта область будет разделяться всеми дочерними процессами данного процесса и любыми другими процессами, разместившими у себя в памяти файл "shmem".
pgsz - getpagesizeO;
shm_base - (char *) ( ((int)sbrk(O) + (pgsz-1)) & ~(pgsz-l)); fd - open ("shmem", 0 GREAT | O_RDWR, 0666);
■map (shm_base, 0x100000, PROT_RDWR, MAP_SHARED, fd, 0);l
Следующий код размещает первую страницу памяти атомарных замков в виртуальном адресном пространстве процесса, начиная с адреса 0x200000. Эта область будет разделяться всеми дочерними процессами данного процесса и любыми другими процессами, разместившими у себя в памяти файл "/dev/alm/almOO".
pgsz - getpagesizeO;
fd - open("/dev/alm/alm00", O_RDWR, 0);
map (0x200000, pgsz, PROT_RDWR, MAP_SHARED, fd, 0);
Ошибки
[EINVAL]
Addr, pos или len не кратны размеру страницы памяти.
[EINVAL]
Prot не определяет PROTJWRITE или PR0T_READ, share не определяет MAP_SHARED или MAP_ZEROFILL, или share определяет MAP_ZEROFILL, но prot не содержит PROT_RDWR. (В настоящее время MAP_PRIVATE не поддерживается.) [EINVAL]
Fd не представляет собой ни обычного, ни специального литерного файла.
[EINVAL]
Процесс является дочерним процессом в результате работы vfork. [EINVAL]
Область, определяемая параметрами addr и len, пересекается со страницами, в которых находятся команды или стек процесса.
[ENODEV]
Драйвер устройства, на который указывает параметр fd, не поддерживает размещения.
[ENOMEM]
Нет места в области замены для таблицы страниц размещаемого обычного файла, или попытка создать слишком много процессов.
[EMFILE]
Достигнуто системное предопределенное ограничение на число размещаемых процессом файлов (в настоящее время - 8).
[ENFILE]
Достигнуто общесистемное ограничение на число размещаемых файлов.
Это ограничение определяется значением переменной nmfile в /sys/conf/param.с.
[EACCES]
Fd не разрешает требуемого доступа (чтения или записи), или использован дескриптор файла, открытого только на запись.
[EACCES]
Размещенный обычный файл должен быть расширен до границы блока системы файлов , или для файла нужно выделять пространство, а его дескриптор можно только читать.
[ENOSPC]
Размещенный обычный файл - разреженный, а в файловой системе нет достаточного пространства, чтобы выполнить запрос.
[EFBIG]
Параметры ров и 1еп пытаются создать слишком большой файл.
[other]
Остальные значения ошибок могут быть возвращены которыми драйверами устройств при попытке размещения. Подробности можно найти в описании соответствующих драйверов в руководстве.
Смотри также
fork(2), getpages1ze(2), виптар(2), pmap(2), VB_ctl(2)
Balance Guide to Parallel Programming.
Недостатки
Размещенный файл не может быть усеченным.
Если файл расширен до границ блока системы файлов, его исходные размеры теряются..
Текущие ограничения на то, какая часть адресного пространства может быть переразмещена, должны быть устранены.
Замечания
Из-за ограничений, обусловленных аппаратурой, PROT_WRITE включает в себя и PROT.READ, a PROT_EXEC игнорируется.~
Для снижения накладных расходов размещаемые области следует держать как можно ближе к нижней границе памяти процесса..
Незанятые области в адресном пространстве (“дырки") ниже границы (“break") процесса в файле образа памяти читаются как обнуленные.
Н4. Директивы компилятору
Для распараллеливания циклов DO в Фортране путем разбиения на части обрабатываемых данных переменные, используемые в цикле, должны быть классифицированы, а в программу необходимо внести соответствующие директивы компилятору.
Возможна следующая классификация переменных:
Shared - массив или скаляр, которые только считываются, или массив, к каждому элементу которого происходит обращение только из одной итерации цикла.
Local - скаляр, инициализируемый на каждой итерации цикла до его использования (то есть, переменная может быть переименована на каждой итерации без всякого влияния на результат).
Reduction - массив или скаляр, используемый только в одной ассоциативной коммутативной операции в теле цикла, причем эта операция имеет форму var var op expr.
Shared ordered - массив или скаляр, который не shared, local или reduction; при этом выполнение итераций по одной, но в случайном порядке, может привести к неправильным результатам.
Shared locked - массив или скаляр, не являющийся ни одним из описанных выше (т. е., переменная может считываться и изменяться более, чем в одной итерации цикла, и исполнение итераций цикла по одной в случайном порядке не приводит к неправильному результату).
Доступны следующие директивы компилятору:
должен быть именованным и снабжен разграничительными директивами C$ORDER и C$ENDORDER:
C$ORDER section—name
C$ENDORDER section—name
Параметр section—name может быть любым правильным именем в Фортране. Директива CSORDER помещается в строку, непосредственно предшествующую первому обращению к переменной, а директива C$ENDORDER - в строку, непосредственно следующую за последним обращением к этой переменной. Кроме этого, section—пате должен быть перечислен в списке опции ORDER директивы C$DOACROSS. Слишком длинная директива может быть продолжена в следующей строке, начинающейся с с$.
Захваченные разделяемые переменные должны быть защищены при помощи вызовов подпрограмм «„lock и «„unlock из библиотеки параллельного программирования ОС DYNIX. Вызов подпрограммы «lock помещается в строке, непосредственно предшествующей первому обращению к переменной, а вызов подпрограммы «„unlock - в строку, непосредственно следующую за последним обращением к этой переменной. Директива с$ может использоваться и в этих вызовах (и вообще в любом фортрановском предложении), так как она обрабатывается только при вызове препроцессора.
Опции UNROLL и CHUNK директивы C$DOACROSS описаны в Balance 8000/ 21000 Fortran Compiler User's Manual.
H5. Библиотека параллельного программирования ОС DYNIX
Библиотека параллельного программирования ОС DYNIX содержит программы для создания, синхронизации и завершения параллельных процессов, а также для управления разделяемой памятью.
Цикл, который должен выполняться параллельно, оформляется в виде подпрограммы, вызываемой с помощью функции «„fork. Эта функция порождает набор дочерних процессов, в которые помещает идентичные копии подпрограммы (и всех неразделяемых данных). Эти дочерние процессы выполняются параллельно. Родительский процесс тоже получает копию подпрограммы для выполнения. По умолчанию число дочерних процессов равно половине числа включенных ЦПУ. Число дочерних процессов можно явно указывать при первом обращении к «_set_procs. Если требуется, то число, задаваемое в «_set_procs, может зависеть от количества фактически доступных ЦПУ, которое определяется с помощью функции cpus_onllne.
После выполнения цикла по умолчанию дочерние процессы остаются на ходу до следующего вызова «„fork. Тогда «„fork будет повторно использовать уже существующие процессы, экономя на расходах, необходимых для создания процессов. Можно сделать и по другому: приостановить дочерние процессы при помощи обращения к функции n_park_procs, а затем продолжить их выполнение обращением к m_rele_procs. Это сведет накладные расходы на продолжающуюся работу дочерних процессов к минимуму. После последнего обращения к «„fork все дочерние процессы должны быть уничтожены при помощи n_kill_procs.
Итерации цикла внутри подпрограммы могут определятся статически или динамически. Библиотечными подпрограммами, позволяющими производить распределение, являются: «_get_«ytd, которая возвращает номер, идентифици-
рующий процесс, установленный *_fork, wjget_nuaproc8, которая возвращает общее число дочерних процессов, и n_next, которая увеличивает общий счетчик.
При статическом распределении итераций каждому процессу предписывается выполнение одинакового числа итераций. Этот метод вполне подходит когда объем вычислений в каждой итерации одинаков. Обычным алгоритмом, реализующим это распределение, является получение общего числа п дочерних процессов и выполнение процессом каждой n-й итерации, начиная с итерации, номер которой равен идентифицирующему номеру процесса.
Динамическое распределение итераций позволяет каждому процессу получать одну или несколько итераций цикла, выполнять их и возвращаться в очередь итераций за последующей работой. Этот тип распределения больше подходит к случаям, когда объем вычислений в итерациях различен. Если итерации цикла вычислительно интенсивны, то динамическое распределение выполняется легко и эффективно при использовании общего счетчика для организации очереди итераций. В противном случае очередь может быть организована путем создания разделяемого индекса итераций, защищенного замком. Накладные расходы на работу с замком могут быть уменьшены, если позволить процессу захватывать сразу большое число итераций при каждом обращении к очереди.
В библиотеке параллельного программирования имеются функции работы с замками и организации барьеров для использования при синхронизации. Подпрограммы работы с замком *_1оск и *_unlock обеспечивают работу с одним замком. Подпрограммы s_1nit_lock, s_1ock, s_clock и s_unlock используются, когда необходима работа с несколькими замками. Подпрограмма nsync устанавливает один барьер для всех активных процессов, в то время как s_1n1t barrier и s_wait_barrier можно использовать для органи зации нескольких барьеров для синхронизации подмножеств процессов.
Раздел работы единственного процесса в программе можно установить, используя *_single и *_mu1ti. Mjwlti останавливает работу всех дочечрних процессов, позволяя родительскому процессу выполнить некоторые действия, такие как, например, ввод/вывод. Дочерние процессы продолжат свою работу после обращения к w eulti.
Shea Нос и shfree используются для динамического выделения и освобождения памяти в си-программах, shbrk и shsbrk изменяют размер сегмента разде- ляемой памяти процесса, a brk и sbrk - размер сегмента локальных данных.
Имя
intro - введение в библиотеку параллельного программирования.
Описание
Эти подпрограммы составляют библиотеку параллельного программирования, поддерживающую микро- и макрораспараллеливание программ, разработанных на Си, Фортране и Паскале. (Информацию о микрораспараллеливании и макрораспараллеливании можно найти в Balance Guide to Parallel Programming). Библиотека параллельного программирования не поддерживается System V (средой att).
Описанные здесь подпрограммы находятся в текущей библиотеке параллельного программирования (/usr/11b/l1bpps.a) и в предыдущей версии этой библиотеки (/usr/lib/libpp.a). Более старая версия сохраняется для совместимости с ранними версиями DYNIXa. Подпрограммы из текущей библиотеки компонуются с программой при помощи включения опции -1pps в командную
строку вызова cc или id. Подпрограммы из предыдущей версии библиотеки компонуются при помощи опции -1рр. В одной и той же программе нельзя компоновать подпрограммы из обеих версий библиотеки.
Обзор использования подпрограмм из текущей версии библио- теки параллельного программирования, примеры программ и необ- ходимая информация находятся в Balance Guide to Parallel Programming.
Список функций
Следующие подпрограммы обеспечивают микрораспараллеливание:
Имя
Описано на странице
Описание
«„fork
m-fork(3P)
выполняет подпрограмму распараллелено
m_get_myid
m—get—myid (3P)
возвращает идентификатор процесса
m_get_numprocs
m-get—numprocs (3P)
получает число дочерних процессов
mJdlLprocs
m-kill-procs (3P)
терминирует дочерние процессы
m_lock
m—lock(3P)
инициализирует и блокирует замок
m_nulti
m—single (3P)
заканчивает раздел единственного процесса
m_next
m-next(3P)
увеличивает общий счетчик
mj>ark_procs
m—park—procs (3P)
приостанавливает выполнение дочернего процесса
m„relej>rocs
m—park-procs (3P)
продолжает выполнение дочернего процесса
m_set_procs
m—set-procs(3P)
устанавливает число дочерних процессов
m_single
m—single (3P)
начинает раздел единственного процесса
«„sync
m-sync(3P)
проверка барьера
«„unlock
m-unlock(3P)
снятие блокировки с замка
Следующие подпрограммы обеспечивают макрораспараллеливание:
Имя
Описано на странице
Описание
cpus„online
cpus-online(3P)
возвращает число работающих ЦПУ
sclock
s—lock(3P)
блокирует замок, возвращает, если блокировка не удалась
s„in1t„barrier
s-wait-barrier(3P)
инициализирует барьер
s„init„lock
s-lock(3P)
инициализирует замок
«„lock
s-lock(3P)
блокирует замок
з^ЮСК
s-lock(3P)
блокирует замок (макроконструкция Си)
s_unlock
s-lock(3P)
снимает блокировку с замка
«„UNLOCK
s-lock(3P)
снимает блокировку с замка (макроконструкция Си)
s„wa1t„barrier
s-wait-barrier(3P)
ожидает у барьера
Следующие подпрограммы обеспечивают распределение памяти для параллельного программирования. Подпрограммы brk и sbrk доступны и без загрузки библиотеки параллельного программирования (см. brk(2)>, но их версии в билиотеках необходимы для совместимости с остальной частью библиотеки.
Имя
Описано на странице
Описание
brk
brk(3P)
изменяет размер сегмента локальных данных
sbrk
sbrk(3P)
изменяет размер сегмента локальных данных
shbrk
shbrk(3P)
изменяет размер сегмента разделяемых данных
Следующие подпрограммы находятся в предыдущей версии библиотеки параллельного программирования (/usr/lib/libpp.a) и сохраняются там для обеспечения совместимости с ранними версиями:
Имя p_cpus_online p_finit„barrier pjnit p_init_barrier p.Jnit_lock p_lock p_shmalloc p_unlock p_wait_barrierИмя p_cpus_online p_finit„barrier pjnit p_init_barrier p.Jnit_lock p_lock p_shmalloc p_unlock p_wait_barrierОписано на странице p-cpus-online (ЗР) p-wait-barrier(3P) p-init(3P)p-wait-barrier(3P) p-lock(3P) p-lock(3P) p-shmalloc(3P) p-lock(3P) p-wait-barrier(3P)Описано на странице p-cpus-online (ЗР) p-wait-barrier(3P) p-init(3P)p-wait-barrier(3P) p-lock(3P) p-lock(3P) p-shmalloc(3P) p-lock(3P) p-wait-barrier(3P)Описание возвращает число процессоров в системе инициализирует барьер (Фортран) инициализирует разделяемую память и память атомарных замков инициализирует барьер инициализирует замок блокирует замок выделяет разделяемую память снимает блокировку с замка ожидает у барьера
Имя p_cpus_online p_finit„barrier pjnit p_init_barrier p.Jnit_lock p_lock p_shmalloc p_unlock p_wait_barrier
Имя p_cpus_online p_finit„barrier pjnit p_init_barrier p.Jnit_lock p_lock p_shmalloc p_unlock p_wait_barrier
Описано на странице p-cpus-online (ЗР) p-wait-barrier(3P) p-init(3P)
p-wait-barrier(3P) p-lock(3P) p-lock(3P) p-shmalloc(3P) p-lock(3P) p-wait-barrier(3P)
Описано на странице p-cpus-online (ЗР) p-wait-barrier(3P) p-init(3P)
p-wait-barrier(3P) p-lock(3P) p-lock(3P) p-shmalloc(3P) p-lock(3P) p-wait-barrier(3P)
Следующие подпрограммы сохраняются в старой библиотеке параллельного программирования для совместимости с ранними версиями, но они не описаны в руководстве.
p_exitэквивалентно exit(3)
p_fexitэквивалентно стандартной фортрановскойподпрограмме fhalt
p finit не имеет никакого эффекта.
Имя
brk, sbrk - изменяет размер сегмента локальных данных.
Краткое описание
Синтаксис на Си:
finclude <parallel/parallel.h>
caddar_t brk(addг)
caddar_t addr
caddar_t sbrk(incr) int 1ncr;
Синтаксис на Паскале:
none
Синтаксис на Фортране:
none
Описание
Эти подпрограммы эквивалентны стандартным подпрограммам brk и sbrk (см. Ьгк(2)), за исключением того, что эти подпрограммы проверяют, что сегмент локальных данных не перекрывает сегмент разделяемых данных, который следует за ним. Если вызов этих подпрограмм может привести к перекрытию, они вырабатывают ошибку.
Ошибки
Если обнаруживается ошибка, подпрограмма возвращает значение -1, код ошибки помещается в переменную err no. Brk и sbrk могут возвращать следующий код ошибки:
[ENOMEN]
Запрашиваемый сегмент локальных данных пе- рекрывает сегмент разделяемых данных.
[ENOMEN]
Подпрограмма не может локализовать системное файловое пространство для расширения файла, уже размещенного в сегменте разделяемых данных (см. вмр(2)). Подпрограмма пытается локализовать системное файловое пространство в следу- ющем порядке:
Справочник, на который указывает переменная среды $ТМРРАТН.
Текущий справочник.
Основной справочник пользователя.
Справочник /usr/tmp.
Справочник /tmp.
[ENOMEN]
Превзойден лимит, заданный setrliBtt.
Смотри также
execve(2), getrliBit(2), яа11ос(3), end(3)
Balance Guide to Parallel Programming
Примечания
Промежуток между сегментом локальных данных и сегментом разделяемых данных можно ретулировать при помощи опции -Z компоновщика (см. ld(l)>.
Имя
cpus_onl1ne - возвращает количество работающих ЦПУ.
Краткое описание
Синтаксис на Си:
int cpus_online ();
Синтаксис на Паскале:
function cpus_online : integer;
Синтаксис на Фортране:
integer*4 function cpus_online
Описание
Подпрограмма cpus_online возвращает число процессоров, включенных в конфигурацию и работающих.
Смотри также
tmp_ctl(2), p_cpus_online(3P)
Balance Guide to Parallel Programming
Имя
shbrk, shsbrk - изменяет размер сегмента разделяемых данных.
Краткое описание
Синтаксис на Си:
#include <parallel/parallel.h>
caddr_t shbrk(addr)
caddr_t addr;
caddr_t shsbrk(incr) int incr;
Синтаксис на Паскале:
none
Синтаксис на Фортране:
none
Описание
Для набора параллельных процессов, выполняющих одну прикладную задачу, shbrk устанавливает системную границу памяти сегмента с разделяемыми данными (называемую разделяемой границей) равной addr (округляемой вверх до ближайшего кратного размеру системной страницы памяти). Граница памяти интерпретируется системой как наименьший адрес памяти, не используемой программой. Память с адресом выше addr, но ниже указателя стека или другой области распределяемой памяти (см. mmap(2)> находится вне адресного пространства, и при попытке обращения к ней будет происходить нарушение защиты памяти.
Вторая функция - shsbrk - добавляет к сегменту разделяемых данных 1псг байтов и возвращает указатель на начало новой области.
Когда программа начинает свое выполнение (в результате ехес), разделяемая граница устанавливается по наибольшему адресу, определенному в программе. Поэтому только программы с увеличивающимися областями разделяемых данных нуждаются в использовании shsbrk.
Возвращаемое значение
Ноль возвращается, если разделяемая граница может быть установлена; - 1 - если программа запрашивает больше памяти, чем лимитировано системой. shsbrk возвращает -1, если разделяемая граница не может быть установлена.
Ошибки
Shbrk и shsbrk не выполнятся, и дополнительная память не будет выделена, если будут обнаружены следующие условия возникновения ошибки:
[EINVAL]
Разделяемый граничный адрес понижается.
[ENOMEN] Подпрограмма не может локализовать системное файловое пространство для расширения файла, уже размещенного в сегменте разделяемых данных (см. *ппар(2)>. Подпрограмма пытается локализовать системное файловое пространство в следу- ющем порядке:
Справочник, на который указывает переменная среды $ТМРРАТН.
Текущий справочник.
Основной справочник пользователя.
Справочник /usr/t*p.
Справочник /imp.
[ENOMEN]
Превзойден лимит, заданный setrHwft.
[ENOMEN]
Запрашиваемый сегмент локальных данных пе- рекрывает сегмент разделяемых данных.
Смотри также
ехес(2), getrllMit(2), shmal1oc(3), end(3), имр(2)
Balance Guide to Parallel Programming
Недостатки
В настоящее время размер сегмента разделяемых данных может быть только увеличен.
Разделяемая граница не может быть установлена выше предела сегмента стека любого процесса в программе. (Напоминаем, что у разных процессов могут быть разными размеры сегмента стека.) Если процесс устанавливает разделяемую границу выше дна стека любого процесса, любое обращение к перекрывшейся области этого стека приводит к созданию образа памяти программы и аварийному завершению программы со статусом SIGSEV.
Подпрограммы shbrk и shsbrk используют сигнал SIGSEV и обработку этого сигнала для внутренних целей. Пользователи, объявляющие собственную об-
работку сигнала SIGSEV, не могут ожидать достоверных результатов работы этих подпрограмм.
Если вызов подпрограмм shbrk или shsbrk приводит к перекрытию сегмента разделяемых данных с областью распределяемой памяти, сегмент разделя- емых данных замещает область распределяемой памяти.
Имя
shmalloc, shfree - распределение разделяемой памяти.
Краткое описание
Синтаксис на Си:
char *shmalloc(size)
unsigned size;
shfree(ptг) char *ptr;
Синтаксис на Паскале:
none
Синтаксис на Фортране:
none
Описание
Подрограммы shmalloc и shfree представляют собой простой пакет распределения разделяемой памяти для множества процессов, выполняющих одну прикладную задачу. Для использования этих подпрограмм программа должна быть скомпонована с библиотекой параллельного программирования. Shmalloc возвращает указатель на блок, размера не меньше, чем size байтов, начинающийся с адреса, кратного четырехбайтному слову.
Параметр вызова snfree - указатель на блок, выделенный ранее shmalloc; освобождаемое пространство может быть использовано для следующих распределений памяти, но его содержимое не изменяется.
Понятно, что могут наступить самые тяжелые последствия, если при записи в выделенный блок обратиться за его границы или обратиться к shfree со случайным числом в качестве параметра.
Shmalloc поддерживает набор списков свободных блоков в соответствии с их размерами и выделяет память из подходящего списка. Эта подпрограмма обращается к shsbrk (см. shsbrk(3P)) для получения дополнительной памяти, если запрос не может быть удовлетворен имеющимися свободными блоками. Shmalloc и shfree координируют распределение разделяемой памяти для разных процессов задачи. Они поддерживают правильное состояние списка свободных блоков, даже когда различные процессы запрашивают разделяемую память одновременно. Параллельные запросы на блоки разделяемой памяти выполняются так, что процессы получают уникальные блоки из сегмента разделяемых данных программы.
Диагностика
Подпрограмма shmalloc возвращает нулевой указатель. (0), если нет доступной разделяемой памяти, или если обнаружилось, что используемые в этих подпрограммах данные пострадали в результате записи за границы выделенных блоков. Shmalloc может быть перекомпилирована для того, чтобы она строжайшим образом проверяла места действия всех транзакций. При праве на проверку исходного текста, можно проанализировать исходный текст, чтобы посмотреть, как он это может делать.
Смотри также
shbrk(3P)
Balance Guide to Parallel Programming
Имя
m—fork - параллельное выполнение подпрограммы.
Краткое описание
Синтаксис на Си:
#include <parallel/micro task. h> m—fork(func [,arg,... ]);
void (*func) 0;
sometype args;
Синтаксис на Паскале:
function mfork: integer;
cexternal;
{$E+}
procedure func(arg,...);
(code)
($E-)
m—fork (func [,arg,... ]) ;
args : sometype;
Синтаксис на Фортране:
external func integer*4 m—fork i-m-fork (func [,arg,... ]) subroutine func(arg,...)
Описание
Подпрограмма m_fork определяет подпрограмму в дочерних процессах, которые затем начинают выполнять ее параллельно. Число дочерних процессов, которые будут использоваться при вызове m_fork, можно установить заранее
вызовом m_set_procs. Если обращение к *_set_procs не производилось, то число дочерних процессов устанавливается по умолчанию равным:
(чисао работавших ЦПУ)/2
Если в программе не было дочерних процессов, оставшихся от предыдущих обращений к a_fork, вызов создает эти процессы. Если уже существуют дочерние процессы, созданные при предыдущем вызове, переиспользуются существующие процессы.
Когда B_fork создает дочерние процессы, каждый дочерний процесс снабжается локальной целочисленной переменной, называемой *_ayld, которая идентифицирует данный процесс в наборе созданных дочерних процессов. Идентификатор первого дочернего процесса равен 1, второго - 2 и т.д. При помощи обращения к подпрограмме BjjetByid можно определить идентификационный номер дочернего процесса.
В си-программах файл заголовка /para11el/*1crotask.h содержит объявление внешних переменных m_niytd и «пивргосз. Вторая переменная указывает общее число процессов, выполняющих данную подпрограмму (включая как все дочерние процессы, так и родительский процесс).
Как только дочерние процессы станут доступны, m_fork начинает выполнение подпрограммы func с заданными параметрами. Дочерние процессы выполняют подпрограмму до тех пор, пока все они из нее не выйдут. После этого происходит выход из B_fork, а дочерние процессы продолжают “крутиться", ожидая новой работы. Программа может уничтожить все дочерние процессы при помощи вызова подпрограммы в_к111_ргосз, приостановить их при помощи вызова ш_рагк_ргос8 или оставить их “крутиться до тех пор, пока они не будут переиспользованы при новом вызове *_fork. Приостановка дочерних процессов будут приостановлены до следующего их переиспользования, приводит к более эффективному использованию системы систему, т.к. ЦПУ освобож- дантся от необходимости выполнения “крутящихся" процессов, и снижаются накладные расходы на повторное создание дочерних процессов при следующем обращении к m_fork.
Ошибки
Подпрограмма m fork не выполнится, и ни один дочерний процесс не будет создан, если обнаружатся следующие условия возникновения ошибки:
[EINVAL]
Этот вызов *_fork вложен в предыдущий.
[EAGAIN]
В данном вызове *_fork превышается nprocs, системное ограничение на общее число выполняющихся процессов.
[EAGAIN]
В данном вызове m_fork превышается вахиргс, системное ограничение на число выполняющихся про- цессов у одного пользователя.
Примечания
Каждый вызов *_fork переустанавливает общий счетчик (см. *_next(3P)).
Смотри также
getrl1m1t(2), зЬваПос(З), brk(3P), m_set_procs(3P), n_kill_procs(3P), B_next(3P)
Balance Guide to Parallel Programming
Имя
■_get_*yid - возвращает идентификатор процесса.
Краткое описание
Синтаксис на Си:
#indude <parallel/microtask.h>
int m—get—myidO;
int i;
i-m—get—myid ();
Синтаксис на Паскале:
function m-get—myid : integer ; cexternal ;
Синтаксис на Фортране:
integer*# function m-get—myid
Описание
Подпрограмма Bjgetjnyid возвращает значение переменной m_myld - идентифицирующий номер процесса. Для родительского процесса значение этой переменной равно нулю. Дочерние процессы получают идентифицирующие их номера в порядке создания: первый процесс получает номер 1, второй - 2 и т.д. Файл заголовков Си
/изг /1 пс 1 ude/para 11 е 1 /mi crotask. h
содержит внешнее объявление переменной.
Смотри также
fortran(l), m_fork(3P)
Balance Guide to Parallel Programming
Имя
■jget_numprocs - возвращает число дочерних процессов.
Краткое описание
Синтаксис на Си:
#1nc1ude <paralle1/microtask.h> int m_get_numprocs();
Синтаксис на Паскале:
function m_get_numprocs : integer; cexternal ;
Синтаксис на Фортране:
integer** function mjget_numprocs
Описание
Процедура mjget_numprocs возвращает значение переменной m^numprocs - текущее число процессов в задаче. Это число соответствует числу дочерних процессов плюс один (родительский процесс).
Смотри также
fortran(1), m_set _procs(ЗР)
Balance Guide to Parallel Programming
Имя
m_kill_procs - оканчивает дочерние процессы.
Краткое описание
Синтаксис на Си:
#include <parallel/microtask.h> m_killj)rocs();
Синтаксис на Паскале:
procedure m_kill_procs ; cexternal;
Синтаксис на Фортране:
subroutine m_kill_procs
Описание
Процедура mjdlljprocs оканчивает выполнение всех дочерних процессов, созданных в результате предыдущего вызова процедуры m_fork.
Процедура n_kill_procs не работает, если она вызывается из подпрограммы, для выполнения которой вызывалась процедура m_fork.
Ошибки
Процедура m_kill_procs может окончить свое выполнение по ошибке, если будут обнаружены следующие условия ее возникновения:
[EINVAL] Некоторые процессы-потомки продолжают выполнение в результате вызова m_fork.
Смотри также
M_fork(3P)
Balance Guide to Parallel Programming
Имя
m_lock, m_unlock - устанавливают и снимают блокировку с замка.
Краткое описание
Синтаксис на Си:
finclude <parallel/microtask.h> m_lock();
m_unlock();
Синтаксис на Паскале:
procedure m_lock;
cexternal ;
procedure m_unlock;
cexternal ;
Синтаксис на Фортране:
subroutine m_lock()
subroutine m_unlock()
Описание
Подпрограммы m_lock и m_unlock выполняют функции интерфейса с единственным замком типа slock_t. Если используется один замок, проще пользоваться этими подпрограммами, чем подпрограммами s_1nit_lock, s_lock и s_unlock, так как эти подпрограммы не требуют определения и инициализации замка перед его использованием. Они выполняются быстрее, чем другие подпрограммы, так как им не передается адрес замка как параметр.
М_1оск блокирует замок. м_1оск всегда оканчивается успешно, она выполняется столько времени, сколько нужно для получения доступа к замку.
M_unlock снимает блокировку с замка.
Смотри также
intro(3P), s_lock(3P), m_shmalloc(3P)
Balance Guide to Parallel Programming
Имя
*_next - увеличивает общий счетчик.
Краткое описание
Синтаксис на Си:
fInclude <parallel/m1crotask.h> 1nt n„next();
Синтаксис на Паскале:
function n_next : Integer; cexternal ;
Синтаксис на Фортране:
1nteger*4 function m_next
Описание
Процедура m_next атомарно увеличивает общий счетчик. Программа, первая обратившаяся к этой процедуре, получает в ответ 1, вторая - 2 и т.д. Вызовы подпрограмм m^fork, m_sync или e_single переустанавливают общий счетчик в ноль.
Смотри также
B_fork(3P), m_sync(3P), m_single(3P)
Balance Guide to Parallel Programming
Имя
m_parkj)rocs, m^relejprocs - приостанавливает и запускает дочерние процессы.
Краткое описание
Синтаксис на Си:
#Include <parallel/m1crotask.h> mj>arkj)rocs();
(serial code) (последовательный код) m_rele_procs();
Синтаксис на Паскале:
procedure m_parkjprocs;
cexternal ;
procedure m„relej)rocs;
cexternal ;
Синтаксис на Фортране:
subroutine m_parkjprocs()
subroutine M_relej)rocs()
Описание
Процедура m_parkjprocs приостанавливает выполнение дочерних процессов, созданных в результате работы m_fork. Обычно выполнение дочерних процессов приостанавливают на время, пока родительский процесс интенсивно занимается вводом/выводом или настройкой другой фазы программы. Процедура mjrelejprocs продолжает выполнение приостановленных дочерних процессов, когда они снова нужны.
Нельзя обращаться к процедуре mjparkjprocs, когда выполняется m_fork. Нельзя также обращаться к этой процедуре, если дочерние процессы" уже приостановлены. Для приостановки выполнения дочернего процесса во время работы m_fork следует использовать m_single и m_next.
Ошибки
Эти процедуры могут возвратить следующее сообщение об ошибке:
[EINVAL]
Процедура была вызвана в неподходящем контексте. Например, процедура была вызвана из подпрограммы, выполняющейся в m_fork или процессы уже были приостановлены.
Смотри также
m_single(3P)
Balance Guide to Parallel Programming
Имя
m_set_procs - устанавливает число дочерних процессов.
Краткое описание
Синтаксис на Си:
finclude <parallel/miегоtask.h>
int m_set_procs(nprocs);
int nprocs;
Синтаксис на Паскале:
function m_set_procs (numprocs : longinteger) : longinteger; cexternal ;
Синтаксис на Фортране:
integer** function m_set_procs (nprocs)
integer** nprocs
Описание
Процедура m_set_procs определяет число процессов для параллельного выполнения подпрограмм во время последующих обращений к m_fork. Параметр nprocs определяет общее число процессов, которые могут параллельно выполняться, включая как родительский процесс, так и дочерне процессы. Если этот параметр равен нулю, в программе не будут создаваться дочерние процессы, но все барьеры и ключи будут инициализироваться как будто дочерние процессы создаются.
Процедура m_set_procs инициализирует разделяемую переменную m_numprocs, от которой зависит число процессов, создаваемых в последующих вызовах ■_fork. Файл заголовков Си
/изг/1nc1ude/para11е1/в1сrotask.h
содержит внешнее определение m_numprocs и константу MAXPROCS, определяющую максимальное число процессов, которые система позволяет создавать задаче. Другим ограничивающим фактором является число работающих ЦПУ: ■.numprocs не может быть больше числа работающих ЦПУ минус один.
Обращение к этой программе может и отсутствовать до обращения к ■_fork, в этом случае число процессов принимается по умолчанию равным:
(number of CPU on-line)/2
(число работающих ЦПУ)/2
Программа не должна обращаться к m_set_procs, пока продолжают существовать дрчерние процессы, созданные m_fork (т.е. до того, как они будут уничтожены вызовом m_kill_procs.
Ошибки
Если процедура m_set_procs выполнилась успешно, она возвращает значение ноль. Если же этого не произошло - значение -1, а в переменной err по находится код ошибки.
Вызов m_set_procs не оканчивается успешно, если были обнаружены следующие условия возникновения ошибки:
[EINVAL]
Параметр nprocs больше, чем MAXPROCS, или он больше или равен числу работающих ЦПУ.
[EINVAL]
Дочерние процессы, созданные m_fork, продолжают работать. В этом случае необходимо обратиться сначала к m_kill_procs для уничтожения всех созданных процессов, а только после этого к m_set_procs.
Смотри также
■_fork(3P), m_k1ll_procs(3P)
Balance Guide to Parallel Programming
Имя
n_single, ejnulti - разметка раздела единственного процесса.
Краткое описание
Синтаксис на Си:
#1nc1ude <parallel/microtask.h>
■„singled;
(CODE)
(КОД)
■„■ultid;
Синтаксис на Паскале:
procedure я_зingle;
cexternal ;
procedure ejnulti;
cexternal ;
Синтаксис на Фортране:
subroutine m_single subroutine e_multi
Описание
Процедура я_зingle придерживает все дочерние процессы у барьера до тех пор, пока родительский процесс не выполнит код, следующий за вызовом ■„single и не обратится к я_яиН1. Дочерние процессы после этого продолжают свое выполнение, начиная с кода, следующего за обращением к e_eulti. Обычно эти процедуры используются для того, чтобы позволить родительскому процессу выполнить ввод/вывод или другие последовательные операции во время выполнения m_fork.
Примечания
Обращения к я_з ingle разрешены только во время выполнения mfork. Для приостановки дочерних процессов после выполнения efork надо использовать e_parkj3rocs и m_relejprocs.
Нельзя обращаться к ejnulti, не обратившись сначала к e_single.
Запрещены вложенные вызовы я_з ingle. Нельзя помещать обращения к ■„single и внутри операторных скобок, образуемых обращениями к другим процедурам работы с замками, такими как s_lock и s_unlock.
Ошибки
Эти процедуры могут возвращать следующий код ошибки:
[EINVAL]
Процедура была вызвана в неподходящем контексте. Например, не было ни одного обращения к m_fork, или все процессы уже были приостановлены в результате обращения к процедуре n_park_procs.
Смотри также
m_parkj)rocs(3P)
Balance Guide to Parallel Programming
Имя
m_sync - проверка барьера.
Краткое описание
Синтаксис на Си:
ftnclude <parallel/microtask.h> m_sync();
Синтаксис на Паскале:
procedure m_sync ; cexternal ;
Синтаксис на Фортране:
subroutine m_sync
Описание
Процедура m_sync приводит к тому, что процесс не выполняется дальше ("прокручивается") до тех пор, пока все кооперирующиеся с ним процессы не достигнут этой же точки и не вызовутш_8упс. Программа не должна обращаться к процедуре ш_8упс,до тех пор, пока выполняется множество процессов, т.е. к этой процедуре можно обращаться только тогда, когда программа выполняет подпрограмму при помощи вызова m_fork и не находится между парами операторных скобок, образуемых вызовами m_single/mjmlti, m_lock/m_unlock или s_lock/s_unlock.
Примечания
Обращение к m_sync переустанавливает общий счетчик в ноль (см. m-next(3P)).
Ошибки
Эти процедуры могут возвращать следующий код ошибки:
[EINVAL]
Нет выполняющихся дочерних процессов.
Смотри также
m_set_procs(3P), m_fork(3P), m_s1ngle(ЗР), m_parkj)rocs(3P), m_ki11_procs(3P), m_next(3P)
Balance Guide to Parallel Programming
Имя
s_init_lock, s_lock, s_clock, s_unlock - инициализация, блокировка и снятие блокировки с замка.
Краткое описание
Синтаксис на Си:
finclude <paralie1/paraliel.h>
slock_t *lp;
s_1nit_lock (Ip);
s lock (Ip);
s_LOCK (Ip);
s_clock (Ip);
s_unlock (Ip);
s”UNLXK (Ip);
Синтаксис на Паскале:
procedure s_init_lock(var Ip : integer);
cexternal;
procedure s_lock(var Ip : integer);
cexternal;
procedure s_clock(var Ip : integer);
cexternal;
procedure s_unlock(var Ip : Integer);
cexternal;
Синтаксис на Фортране:
subroutine s_init_lock(lp)
subroutine s_lock(lp)
subroutine s_clock(lp)
subroutine s_unlock(lp)
integer*! Ip
Описание
SJnitJock инициализирует замок, основанный на памяти. После того, как замок инициализирован, можно его блокировать при помощи вызовов процедур s_lock и s_clock и снимать блокировку при помощи обращения к s_unlock. Количество замков практически не ограничено.
В си-программах замок является разделяемой структурой данных типа slock_t, как показано в следующем определяющем предложении языка:
shared slock_t lock;
В паскаль-программах замок является глобальной целочисленной переменной. В фортран-программах замок является переменной типа INTEGER*!. В фортран-программах замок должен быть помещен в разделяемую память или при помощи объявления его в общем блоке и использования опции -F загрузчика или при помощи опции -шр компилятора Фортрана, при использовании которой все переменные помещаются в разделяемую память.
S_Jock и s_clock устанавливают блокировку на замок, адрес которого - 1р. Замок должен быть предварительно проинициализирован при помощи вызова s_1n1t_lock. S_lock успешно завершается всегда, выполняясь столько времени, сколько необходимо для того, чтобы замок стал доступен. S_clock успешно выполняется только тогда, когда замок свободен. Если же замок захвачен другим процессом, s_clock возвращает значение s_FAILED. Эту процедуру можно использовать тогда, когда процессу не нужен именно этот замок (например, тогда, когда можно использовать вместо этого другой замок).
S_unlock снимает блокировку с замка, адрес которого - 1р.
SJ.OCK и SJJNLOCK доступны, как макроверсии процедур s_lock и s_unlock препроцессора Си. Эти макроконструкции находятся в файле заголовков
usr/include/parallel/parallei.h
Программа при использовании макроконструкций выполняется быстрее, чем при использовании процедур, но при этом может увеличиться тело программы. Дополнительную информацию о версиях макроконструкций можно получить, просмотрев исходные тексты в файле
<рага11 е 1/рага11е1.h>
Смотри также
intro(3P), shmalloc(3P), fortran(l), ld(l)
Balance Guide to Parallel Programming
Примечания
Процедуры с именами s_init_lock, s_lock, s_clock и s_unlock используются в программах, написанных на Си, Паскале и Фортране. В си-программах параметр 1р передается как указатель на замок, а в паскаль- и фортран-про- граммах этот параметр является адресом самого замка.
Имя
s_init_barrier, s_wait_barrier - инициализация барьера и ожидание у барьера.
Краткое описание
Синтаксис на Си:
finclude <parallel/parallel.h> s_init_barrier (bp, nprocs); sbarrier_t *bp;
int nprocs;
sjwaitjiarrier (bp);
sbarrier_t *bp;
Синтаксис на Паскале:
procedure s_init_barrier (var barrier : longint;
nprocs : longint);
cexternal;
procedure s_wait_barrier (var barrier : longint);
cexternal;
Синтаксис на Фортране:
integer*4 barrier, nprocs
subroutine s_init_barrier (barrier, nprocs)
subroutine s_wait_barrier (barrier)
Описание
S_init_barrier инициализирует барьер, как точку рандеву ровно nprocs процессов. Этот барьер впоследствии может быть использован в процедурах s_wait_barrier.
В языке Си барьер является разделяемой структурой данных типа
shared sbarrier_t sbarrier;
В Паскале барьер является глобальной целочисленной переменной. В Фортране - переменной типа INTEGER*4. В фортран программах барьер должен быть размещен в разделяемой памяти при помощи объявления его в блоке COMMON и использования опции -F загрузчика или при помощи использования опции -шр компилятора Фортрана, при помощи которой все переменные помещаются в разделяемую память.
Обращение к процедуре s_wait_barrier переводит процесс в состояние активного ожидания того момента, когда ровно nprocs процессов обратятся к этой процедуре. В этой точке все процессы находятся в состоянии активного ожидания одновременно. Барьер необходимо до этого инициализировать с помощью процедуры sjnit_barrier.
Результат будет непредсказуем, если больше чем nprocs процессов обратятся к s_wait_barrier. Барьер можно использовать неограниченное число раз без реинициализации. Барьер нельзя реинициализировать в то время, когда процессы находятся перед ним в состоянии ожидания.
Смотри также
intro(3P), s_shmal1ос(ЗР)
Balance Guide to Parallel Programming
Предметный указатель
Алгоритм параллельный 22
Анализ цикла 33, 115
Архитектура 35-37, 41, 88, 91, 105, 118, 121, 136, 140, 154, 157, 250
Балансировка нагрузки 34, 121, 134, 159, 279
Барьер 14, 16, 28, 29, 30, 110, 157, 159, 286, 293, 348-350, 362-364, 366-367
Блокировка (тупик) 14, 17, 25, 29, 69, 105, 136, 286, 304, 307
Векторизация 20, ПО, 112, 114, 177, 178 Гиперкуб 88-90, 95-97, 102, 104-105, 118, 120, 122-123, 127, 129-130, 135-137, 250- 252, 255-256
Детерминизм 17, 36
Зависимость данных 46, 165-166, 180, 189, 192 - временная
Задача 16, 18, 36, 76-82, 94, 97, 105, 111, 115, 117, 121-123, 128, 134-136, 138, 141- 142, 146, 153-155, 225-244, 286-287, 305, 334-340, 352, 354, 358, 362
Замок (блокировка) 14-17, 48-50, 78-81, 157-158, 162, 165, 214, 279-280, 295, 297, 321, 343-344, 348-350, 359, 363, 365-366 Занятое ожидание 16, 121
Инкапсуляция 31
Каркас программы 37
Коммутатор 57-62
Компилятор 27, 31-34, 37, 42-50, 91, 94, 121, 146, 150, 159, 272, 321-330 - CFT 32, 76, 77, 82, 86, 87, 229 - CIVIC 229
FX/Fortran 40, 41, 44
Transputer Development System 2.2A 95
VS FORTRAN 272
Конвейеризация 42, 159, 289
Конфликт 50, 61, 82, 113, 126, 153, 206, 273, 304
Критическая секция 14, 16, 22-28, 29
Машина параллельная 15, 18, 57
векторная 33, 34
многопроцессорная 16, 33, 37, 38
потоковая 37
сильносвязанная 57
Метод “разделяй и властвуй" 24, 26, 36
Монитор 14
Мультипрограммирование 155
Недетерминизм 15, 43
Операционная система 15, 19, 35, 63, 89, 108, 118, 120-124, 131, 135, 142, 150-151, 155, 157, 165, 303, 306, 307, 321-322, 324,
Berkely UNIX 4.2 41
Chrysalis 63, 64, 71, 205
Concentrix 41
CTSS 73, 76, 77, 85, 87, 224, 225, 226
Dataflo Kernel 138
DYNIX 155, 157, 166, 321
GENIX 138, 142
NX (версия 2.0 и 3.0) 118, 122, 124
TOPSYS B01 88
TSE 254
VB Main Process B01 88
VM 272
VM/SP версия 4 c CMS 107-108
VMS 88
UNIX 63, 65, 316
WSSOS 272
XENIX 119, 303
Оптимизация программы 43, 51, 130-131, 135, 170, 176, 189, 322, 324-327, 331, 336 Отладка программы 29, 36, 105, 110, 117, 124, 136, 160, 165, 305-307, 322-323, 326- 327, 329, 334-341 Память - кэш-память 40, 107, 156, 158 - общая 16, 40, 73, 88-91, 105-108, 118, 120, 137, 139-140, 146, 155, 157, 251, 255- 258 - разделяемая (общая) 108, 111, 155-157, 165, 273, 277, 294, 321, 343, 355, 367 -распределенная 16
Передача сообщений 95-96, 108, 120-130, 134-137, 139-141, 143-144, 146, 153-154, 303-305, 307, 311
Планирование (составление расписания) 112, 114, 116, 121, 138, 141, 279, 283 Поток данных 90, 138, 142-144, 146, 152- 154, 311, 313
Правило прямоугольников 18, 127
Программа параллельная 17-21, 36-37, 43- 44, 66, 89, 95-96, 105, 108, 110-114, 117, 121-122, 126, 135-136, 142, 146, 154-155, 160, 165, 270-271, 288-294, 304 - рекурсивная 44
Программирование параллельное 15-19, 34, 35, 43, 55, 91, 94, 97, 110, 116, 155, 157- 159, 165-166, 272, 321-325
Производительность 36, 46, 51, 54, 70, 82, 84, 86, 88, 104-105, 115-116, 118, 121, 124, 126, 131, 152, 160, 163, 272, 283 Процесс 14-17, 23-31, 47, 49, 53, 91, 95- 96, 99, 108, 111-117, 120-124, 127-129, 136, 138-150, 153-154, 155-163, 165, 252- 253, 271-272, 275-279, 281-296, 304-307, 309, 311-314, 316, 333, 335-367 Разделяемая переменная 23, 48-49, 111-112, 117, 157, 162, 275, 277-280, 290-291, 296, 322-323, 325, 341, 346-348
Разделяемые данные 16, 17, 22, 67, 271- 273, 275-278, 290, 341-346, 351-355 Рекурсия 36, 192 Семафор 14, 16, 76, 157, 235
Сеть обмена 126, 131, 140, 151, 251, 257 - типа “бабочка" 57, 59 - Ethernet 131
Синхронизация 14, 98-99, 108, 110-111, 113, 120-121, 134, 146, 154, 157, 161-162, 165, 252, 277, 279-280, 283, 285, 287, 289- 291, 293-294, 305-306, 347-348
Система многопроцессорная 20-21, 31-32, 35, 37-38, 57, 88, 108, 155
Скорость вычислений 51-54, 142
Сложность
Событие 235-239, 306
Состязания (конкуренция) 15, 17, 19, 124, 131, 133-134
СуперЭВМ 15, 27-28, 33, 38, 40, 73-77, 85-86
типа MIMD 57
Тестирование 23
Трассировка 17, 18, 25
Тупик (блокировка) 14
Ускорение вычислений 17, 42, 71, 84, 121, 163
Утилита 76, 88, 90, 94-95, 131, 144-146, 151, 155, 254, 316, 321, 323, 326, 329 ЭВМ
Alliant FX/8 40, 46, 49, 55
BBN Butterfly 57
CDC Star 35
CRAY-1 27, 87
CRAY-1S 37
CRAY-2 87
CRAY X-MP 22, 29, 30, 33, 37, 73, 85, 86, 87, 221, 226
FPS серии T 88, 250
HEP 14, 15, 17, 22, 27, 28, 37
IBM 3090 107
Intel iPSC 118, 298
Loral Dataflo LDF 100 138, 308
Sequent Balance Series 155, 317
VAX 27, 37, 62
Язык программирования - ассемблера 34, 49, 73 - Си 34, 41, 57, 158, 253-254, 303, 305, 314
с однократным присваиванием
Оккам 88-91, 94-98, 104-105, 251-252
Паскаль 158
Фортран 14, 21-24, 27, 29, 31, 41-44, 82, 108, 110-112, 114, 116, 118, 136, 146, 150, 154, 155, 190, 270, 313, 315
Фортран 77 40, 63
Фортран 8Х 40
функциональный 35, 36, 37
CRAY FORTRAN
DGL 311
FP 35
FX/Fortran 55, 170
KRC 35
SISAL 35, 37
функциональный 35, 36, 37
VM/EPEX FORTRAN 270
Содержание
Предисловие редактораперевода 5
Предисловие 7
Об авторах книги 9
Введение,. 14
Краткая история параллельныхвычислений 15
Терминология в областипараллельнойобработки 16
Вопросы создания программногообеспечения 17
Задача, используемая вкнигевкачестве примера 18
Плантипичной главы книги 18
Библиография 19
Использование многопроцессорных систем: вопросы и возможности 20
Введение 20
Опыт расширений языка Фортран 21
Пример 1. Назначение работ 22
Полученные результаты 23
Причины некорректного поведения программы 23
Выводы 24
Пример 2. Двойственные алгоритмы 24
Результаты наблюдения 25
Причины проблемы 26
Выводы 26
Пример 3. Непреднамеренное совместное использование ресурсов 27
Результаты наблюдений 28
Причины проблемы 28
Выводы 29
Пример 4. Гонки и блокировки 29
Результаты наблюдений 29
Причины некорректного выполнения функцииSYNCAL 30
Выводы 31
Возможные перспективы 31
Языковые расширения 31
Анализ компиляторов 32
Уровни языков для параллельных вычислений 34
Новый язык/Проект компилятора 35
Заключение 37
Благодарности 38
Библиография 38
Alliant FX/8 40
Аппаратные средства 40
Программные средства 41
Разработка программ 42
Механика оптимизации 43
Примеры параллельных программ 44
Программа Pi с автоматическим распараллеливанием 44
Программа Р1 с явным параллелизмом 46
Производительность 51
Первая программа 51
Вторая программа 53
Сравнение временных характеристик первойи второй программ 54
Ловушки и проблемы 54
Выводы 55
Благодарности 56
Библиография 56
Параллельный процессор BBN Butterfly 57
Аппаратные средства 57
Процессорные узлы 59
Коммутатор Butterfly 59
Архитектура ввода-вывода 62
Программное обеспечение 62
Операционная система Chrysalis 63
Система Uniform System (Однороднаясистемная библиотека) 64
Перспективные разработки 65
Измерение времени выполнения программы 66
Пример параллельной программы 66
Производительность 70
Ловушки и проблемы 71
Заключение 72
Благодарности 72
Библиография 72
CRAY Х-МР 73
Аппаратные средства 73
Программное обеспечение 76
Разработка программы 77
Пример параллельной программы 77
Исходный текст основной программы ипояснения к нему 77
Исходный текст подпрограммы TASK ипояснения к нему 80
Производительность 82
Ловушки и проблемы 85
Выводы 87
Благодарности 87
Библиография 87
Параллельный процессор FPS (Floating Point Systems) серии Т 88
Аппаратура 88
Плата системного управления 89
Векторные платы 89
Средства связи векторной платы 90
Память ВП 91
Программное обеспечение 91
Построение программ 94
Программное обеспечение магнитныхдисков для пользователей 94
Система программирования Оккам 94
Транспьютерная система разработкипрограмм 95
Примеры параллельных программ 95
Пример 1. Межузловые связи в системесерии Т 95
Пример 2. Программа вычисления числаПи 97
Производительность 104
Ловушки и проблемы 104
Заключение 105
Благодарности. 105
Литература 106
IBM 3090 107
Аппаратура 107
Программное обеспечение 108
Разработка параллельных программ 110
Пример параллельной программы 110
Ловушки и проблемы ИЗ
Производительность 115
Заключение 116
Литература 117
Параллельная ЭВМ Intel 1PSC 118
Аппаратура 118
Программное обеспечение 120
Разработка программ 121
Обсуждение проектирования 121
Инициализация и загрузка куба 122
Примитивы обмена сообщениями 123
Взаимодействие администратора сузлом 124
Взаимодействие узлов 126
Пример параллельной программы 126
Программа в администраторе - неоптимизированная версия 127
Программа в узле - неоптимизированная версия 129
Изменения с целью оптимизации 130
Производительность 131
Ловушки и проблемы 135
Заключение 136
Благодарности 137
Литература 137
Loral Dataflo LDF 100 138
Аппаратура 138
Программное обеспечение 140
Разработка программ 142
Пример параллельной программы 142
Конструирование графа потока данных программы 142
Запись графа потока данных на языке Data Graph Language 144
Обработка записи на языке DGL 144
Написание прикладных программ дляузловых процессоров 146
Обзор 146
Опыт.... 146
Аннотированный листинг программы 147
Компиляция узловых процессов 150
Сборка откомпилированных программс графом данных 151
Загрузка и выполнение программыузловыми процессорами 151
Производительность 152
Ловушки и проблемы 152
Разработка программ 152
Запись программ процессов 154
Заключение 154
Благодарности 154
Литература 154
Sequent Balance Series 155
Аппаратура 155
Атомарная память блокировки (ALM-Atomic Lock Memory) 157
Программное обеспечение 157
Операционная система DYNIX 157
Разделяемая память 157
Синхронизация 158
Разработка программы 158
Компиляция 159
Производительность 160
Отладка 160
Пример параллельной программы 160
Производительность 163
Ловушки и проблемы 165
Заключение 165
Благодарности 166
Литература 166
Приложение А 167
Приложение В 195
Приложение С 221
Приложение D 245
Приложение Е 259
Приложение F 298
Приложение G 308
Приложение Н 317
Предметный указатель 368
- В Издательстве «Мир»готовится к выпуску
Оптические вычисления: Пер. с англ./ Р. АРР АТУН, Б. БЕРК, С.КАРТРАЙТ и др.- М.: Мир, 1992 (III кв.).- 26 л.: ил. -ISBN 5-03-002321- 6 (В пер.): 5 р. 50 к., 5000 экз.
Optical Computing: Digital and Symbolic. Ed. by Arratoon R. USA, New York: Marcel Dekker Ing., 1989.
Сборник статей специалистов США, ФРГ, Японии посвящен различным аспектам цифровых вычислений, осуществляемых оптическими и оптоэлектронными методами. Рассмотрены вопросы реализации параллельных вычислений оптическими методами, принципы построения оптических систем на основе волоконно-оптических линий, применения оптических методов для проведения символьных вычислений, в том числе для задач искусственного интеллекта. Сделана попытка на высоком уровне ознакомить читателя сразу со всем комплексом проблем, связанных с разработкой оптических цифровых вычислительных систем.
В Издательстве “МИР“готовится к выпуску
ФИЛД А., ХАРРИСОН П. Функциональное программирование: Пер. с англ.-М.Мир, 1991(111 кв.).-37 л.: ил.-ISBN 5-03-001870-0 (В пер.): 4 р.
В книге английских специалистов рассмотрены проблемы аппликативного программирования, существенно повышающего интеллектуальность разрабатываемых программ по сравнению с традиционным программированием. При этом спецификация предметной области существенно упрощает труд программиста. Особое внимание уделяется вопросам реализации функциональных языков, основанной на X-исчислении Черча. В качестве базового языка рассматривается функциональный язык Норе, имеющий простой и ясный синтаксис. Изложение сопровождается многочисленными примерами конкретных программ.
Для программистов как начинающих, так и профессионалов, а также специалистов в области информатики.
В издательстве “МИР“готовится к выпуску
УИНЕР Р Язык Турбо-Си: Пер. с англ.-М.: Мир, 1991 (III кв.).-18л.: ил.-ISBN 5-03-001779-8 (В пер.): 1 р. 60к.
Книга американского специалиста посвящена описанию входного языка компилятора Турбо-Си для персональных ЭВМ типа IBM PC. Подробно рассматриваются синтаксис и семантика языка, анализируются типичные ошибки использования различных его конструкций.
Для программистов и пользователей персональных ЭВМ.
УВАЖАЕМЫЙ ЧИТАТЕЛЬ!
Ваши замечания о содержании книги, ее оформлении, качестве перевода и другие просим присылать по адресу: 129820, Москва, И-110, ГСП, 1-й Рижский пер., 2, издательство “Мир“
Научное издание
Программирование на параллельных вычислительных системах
Под ред. Роберта Бэбба II
Заведующий редакцией д.т.н. АЛ. Щерс
Зам. зав. редакцией Э.Н. Бадиков
Старший научный редактор Т.Н. Шестакова
Художественный редактор О.Н. Адаскина, Н.М. Иванов
Корректор НА. Гиря
ИБ N 7261
Оригинал-макет подготовлен на персональном компьютере и отпечатан на лазерном принтере в издательстве “Мир“
Подписано к печати 13.03.90. Формат 70x100 1/16
Бумага офсетная N 1. Печать офсетная.
Гарнитура Таймс. Объем 11,75. Усл. печ. л. 30,55.
Усл.кр.-отт.61,10. Уч.-изд. л. 25,12. Изд N 6/6999.
Тираж 15000 экз. Зак. 185. Цена 6 р.
Издательство “Мир“
В/О “Совэкспорткнига“
Государственного комитета СССР по печати
129820, ГСП, Москва, И-110, 1-й Рижский пер., 2.
Можайский полиграфкомбинат В/О «Совэкспорткнига»
Государственного комитета СССР по печати
143200, г. Можайск, ул. Мира, 93
Издательство «Мир»
ПРОГРАММИРОВАНИЕ НА ПАРАЛЛЕЛЬНЫХ
ВЫЧИСЛИТЕЛЬНЫХ СИСТЕМАХ
Programming Parallel Processors
Edited by R.G.Babb II
Addison Wesly Publishing Company
Программирование на параллельных вычислительных системах
Под редакцией Р. Бэбба II
Перевод с английского канд. физ.-мат. наук А.С. Косачева, канд. техн, наук Л.В. Шабанова
под редакцией д-ра техн, наук Ю.Г. Дадаева
Москва «Мир» 1991
ББК 32.973-01 П78
УДК 681.3
Авторы: Акселрод Т., Беккерман М., Бэбб Р., Дарема-Роджерс Ф., ДиНуччи Д., Джон Ч., Ларраби А., Мак-Гроу Дж., Миллер Ф., Модал К., Нортон В., Пенник К., Пейс Д., Стерн С., Сторк Л., Стоун Дж., Хелм Р., Хоукинсон С.
Программирование на параллельных вычислительных системах: П78 Пер. с англ./Р. Бэбб, Дж. Мак-Гроу, Т. Акселрод и др.;
под ред. Р. Бэбба И. - М.:Мир, 1991. -376 с., ил.
ISBN 5-03-001527-2
В книге авторов из США рассматриваются вопросы программирования для современных параллельных ЭВМ Alliant FX/8, CRAY Х-МР, FPS Т, IBM 3090, Intel 1PSC и др. Приведены необходимые сведения об архитектуре ЭВМ (векторноконвейерных, потоковых, ЭВМ с сетью обмена типа гиперкуб) и соответствующих средствах программирования. Особое внимание уделено рассмотрению средств и техники программирования для мультизадачного режима. Приемы разработки параллельных программ для ЭВМ разных классов и архитектур иллюстрируются многочисленными примерами.
Для специалистов в области прикладного и системного программного обеспечения, а также студентов соответствующих специальностей вузов.
ББК 32.973-01ББК 32.973-012404090000-418 П 152-91
ББК 32.973-01
ББК 32.973-01
041(00-91
Редакция литературы по информатике и робототехнике
ISBN 5-03-001527-2 (русск.)ISBN 0-201-11721-500 (англ.)ISBN 5-03-001527-2 (русск.)ISBN 0-201-11721-500 (англ.)© 1988 by Addison-Wesly Publishing Company, Inc.
ISBN 5-03-001527-2 (русск.)
ISBN 0-201-11721-500 (англ.)
ISBN 5-03-001527-2 (русск.)
ISBN 0-201-11721-500 (англ.)
© перевод на русский язык,Косачев А.С., Шабанов Л.В.,«Мир», 1991
Предлагаемая русскоязычному читателю книга - не сборник статей, как может показаться на первый взгляд, а коллективная монография, посвященная практическим вопросам программирования для современных параллельных ЭВМ. Появление в последние годы большого числа публикаций по параллельному программированию отражает существующую сейчас тенденцию широкого экспериментального и практического использования различных параллельных вычислительных систем для решения больших задач. Современные параллельные ЭВМ, построенные по разным архитектурным канонам, и средства программирования для них - это большой и пестрый мир, в который входит современный пользователь, вооруженный, как правило, лишь опытом “последовательного" программирования. Авторы монографии как бы приглашают читателя совершить серию путешествий (число их равно числу глав книги, в которых описаны результаты экспериментальных вычислений на ЭВМ с различными архитектурами) в различные “среды параллельного программирования и вычислений". Они рассказывают об интересном программистском “эксперименте", который им удалось провести с целью написания предлагаемой книги.
Монография имеет ряд особенностей. Она написана большим коллективом авторов, которые, несмотря на предписанные планом книги тематические и структурные ограничения, смогли изложить результаты своих программистских экспериментов и выводы из них в весьма непринужденной манере. Еще одна особенность - все рассмотренные параллельные ЭВМ являются коммерчески доступными машинами разной производительности. Продуманный выбор ЭВМ позволит читателям познакомиться с параллельными вычислительными системами, архитектуры и принципы управления которых радикально различаются. Рассмотрение таких ЭВМ, как CRAY Х-МР, FPS Т, BBN Butterfly, Intel iPSC и Loran Dataflo, убеждает в намерении авторов познакомить читателей с различными “параллельными средами" и особенностями программирования в этих средах.
Впечатляют успехи западных фирм в области создания параллельных вычислительных систем разных классов. Однако зарубежный опыт применения таких машин для решения различных научных задач свидетельствует о том, что отношения между программистами и параллельными ЭВМ не всегда носят “дружественный" характер. Программирование для параллельных многопроцессорных систем, как говорит редактор книги Р. Бэбб II, - это нечто особое. Действительно, процесс программирования, отладки и решения задачи на параллельной ЭВМ сопряжен с возможностью возникновения новых для “последовательного" программиста типов ошибок и ситуаций (блокировки и состязания параллельных процессов, недетерминизм выполнения программы и др.).
Читатель познакомится с архитектурами и средствами параллельного программирования для весьма новых, но уже широко известных параллельных ЭВМ. Среди них суперЭВМ CRAY Х-МР (одна из наиболее высокопроизводительных современных коммерчески доступных вычислительных систем), суперЭВМ FPS Т фирмы Floating Point System, которая в области построения суперЭВМ стала играть заметную роль.
Книга знакомит читателя со средствами и особенностями программирования для многопроцессорных ЭВМ с различными схемами связи между процессорами (общая память, специальные шины, сеть обмена типа “бабочка", сеть обмена типа “гиперкуб" и др.) и с различными принципами управления, в частности с управлением потоком данных (ЭВМ Loran Dataflo).
б
Методическая изюминка коллектива авторов, возглавляемого редактором монографии Ричардом Бэббом II, - это использование одной простой тестовой задачи (во всех главах книги, начиная с третьей, рассматривается программирование и решение задачи вычисления числа Пи посредством вычисления определенного интеграла простым численным методом). Использованный метод численного интегрирования легко распараллеливается и сравнительно просто программируется в любой параллельной среде. И тем не менее результаты проведения таких экспериментов по программированию и решению простой задачи дают возможность не только сопоставлять основные свойства используемых сред параллельного программирования, но и служат поучительным примером для тех, кто собирается разрабатывать параллельные программы.
Важна роль приложений, которыми снабжены все главы книги, начиная с третьей. В этих приложених сравнительно подробно описаны средства параллельного программирования, используемые на соответствующих параллельных ЭВМ. В тех случаях, когда приложение является извлечением из руководства для пользователей конкрентной машины, оно носит справочный характер и представляет собой интерес как для прикладных, так и для системных программистов. Некоторые приложения, например А, посвященное описанию средств оптимизации программ для ЭВМ Alliant FX/8, имеют самостоятельное значение.
Представляется, что книга будет встречена с большим интересом программистами, преподавателями вузов, аспирантами и студентами, которым предстоит решать задачи на параллельных ЭВМ. Время освоения параллельных ЭВМ наступило, и вместе с этим возникла потребность в “перековке" “последовательных" программистов в “параллельные". Думается, что предлагаемый перевод поможет массовому советскому пользователю получить адекватные представления о мире практических параллельных вычислений.
Ю.Г. Дадаев
В этой книге отражен опыт программирования на коммерчески доступных параллельных ЭВМ для научного применения. Предполагаемые читатели книга - программисты, администраторы и студенты, изучающие вычислительные науки и другие дисциплины, все те, кто заинтересован в понимании современного состояния средств программирования для нынешнего поколения параллельных ЭВМ. Эта хроника наших “путешествий* должна также служить доказательством интереса большого числа исследователей в области создания программного обеспечения и разработчиков систем, активно работающих в настоящее время в области создания более совершенных параллельных языков и среды программирования для следующего поколения параллельных вычислительных систем.
Эта книга появилась в связи с организацией семинара по проектированию программных средств, который я вел весной 1986 г. в Центре подготовки аспирантов в Орегоне. Большинство участников семинара не имели вообще или имели незначительный опыт реального параллельного программирования, хотя несколько студентов выполняли параллельные программы на факультете вычислительных и технических наук на 32-процессорной ЭВМ iPSC Hypercube фирмы Intel.
Хотя параллельные ЭВМ становятся все более доступными для современных программистов, количество людей (в промышленности или академии - безразлично), которые действительно могли бы прогонять параллельные программы на ЭВМ, все еще мало. Наши эксперименты, вероятно, показывают, что любой программист может получить опыт, когда впервые столкнется с прекрасным новым миром параллельного программирования. Некоторые подробности, приведенные в главах по программированию для различных машин, в действительности являются частностями некоторых более интересных вопросов, которые возникли во время курса наших программных экспериментов, хотя они написаны так, будто все это происходило с автором главы.
Так как по современным данным в настоящее время существует более тридцати компаний с мировой известностью, которые создают параллельные ЭВМ разных типов, мы смогли рассмотреть в нашей книге лишь небольшое подмножество машин. Выбор машин производился по следующим критериям:
машины должны быть коммерчески доступными, а не являться уникальными экспериментальными образцами;
машины должны быть доступны. Вообще, это означает, что нам необходим либо удаленный доступ по телефонным линиям, либо средства поддержки для физического доступа к удаленным ЭВМ. Несколько машин (BBN Butterfly, Loran LDF-100, IBM 3090 и FPS T Series) были добавлены к списку участниками семинара после его завершения;
мы хотели включить машины из широкого диапазона - от ЭВМ класса “супер-мини* (Sequent и Intel) до мини-суперЭВМ (Alliant) и параллельных векторных суперЭВМ (CRAY, IBM);
мы хотели провести эксперименты с различными архитектурами и моделями программирования, включая ЭВМ с общей памятью (Alliant, CRAY, IBM и Sequent) и ЭВМ, управляемые посредством передачи сообщений, (BBN Butterfly, Intel iPSC, FPS T Series и Loran).
Так как несколько машин, на которых мы работали во время написания этой книга, были еще достаточно новыми, “период полураспада* для некоторых из обсужденных деталей реализации оказался достаточно коротким. Действительно, нам было трудно сохранять неизменными главы книга в течение девяти месяцев, за которые эта книга была написана! Однако некоторые
средства параллельного программирования оказались достаточно стабильными и многие наши более общие наблюдения относительно природы параллельного программирования не являются сиюминутными.
Эта книга не может служить справочным пособием по архитектурам параллельных ЭВМ, хотя в ней и рассмариваются аспекты параллельных архитектур, необходимые для обеспечения базиса обсуждения различных вопросов параллельного программирования.
Кроме тех, кто обеспечил техническую помощь при работе над отдельными главами, редактор хотел бы поблагодарить следующих людей, прочитавших и прокомментировавших первые варианты этой книги: Дана Хаммер- строма, Джеймса Гарди, Яна Каплана, Алана Карпа, Ричарда Кибуртца, Кима Корнера и Алана Уорфилда. Я также хотел бы поблагодарить рецензентов этой книги - Джека Донгарра из Аргоннской национальной лаборатории, Роберта Хиромото из Лос-Аламосской национальной лаборатории, Гарри Иордана из Колорадского университета - за их предложения и поддержку этого проекта.
Бивертон, Орегон Роберт Бэбб II
Заявление:
Хотя в книге показан пример, как может быть измерена производительность параллельных вычислений для большинства машин, и описаны различные способы, которыми могут быть интерпретированы данные о производительности для нашей маленькой программы для вычисления числа Пи, эти результаты не должны рассматриваться как определяющие, формальные результаты эталонного тестирования. Было бы серьезным злоупотреблением делать общие выводы об относительной производительности различных параллельных ЭВМ, основываясь на наших ограниченных экспериментах с одной небольшой параллельной программой.
Об авторах книги
Тимоти С. Акселрод, Ливер морская национальная лаборатория им. Лоуренса,
Тимоти Акселрод получил степень бакалавра наук в области физики в Калифорнийском технологическом институте в 1969 г., степень магистра по прикладной физике в Стэнфордском университете в 1971 г. и степень доктора философии по физике в Калифорнийском университете (Санта-Крус) в 1980 г.
В настоящее время руководит исследованиями по параллельным вычислениям в отделе вычислительной физики Ливерморской национальной лаборатории им. Лоуренса. Научные интересы включают: излучательный перенос и численное моделирование сверхновых звезд, а также вопросы оценки производительности машин при параллельных вычислениях. Имеет много публикаций в этих и других областях.
Роберт Бэбб II, Центр подготовки аспирантов, Орегон
Роберт Бэбб - доцецт факультета вычислительных и технических наук Центра подготовки аспирантов в Орегоне. Получил степень бакалавра по астрофизике и математике в университете г. Нью-Мехико в 1969 г. В 1974 г. ему присвоена ученая степень магистра математики (по вычислительным наукам) в университете г. Ватерлоо (Онтарио, Канада) и степень доктора философии (по электротехнике вычислительным наукам) в университете в Нью- Мехико.
С 1974 по 1976 г. был доцентом факультета статистики и вычислительных наук Калифорнийского политехнического государственного университета в Сан Луис Оубиспо. С 1976 по 1978 г. был приглашенным доцентом по вычислительным наукам в Государственном университете в Нью-Мехико. С 1978 г. по 1982 г. работал как инженер-программист в вычислительном центре компании “Боинг44 (Сиэтл), занимался разработкой методов и средств создания крупномасштабных проектов программного обеспечения.
Текущие научные интересы связаны с применением принципа потоковых вычислений для крупноблочной декомпозиции программ и с исследованиями архитектур суперсистем.
Р. Бэбб - член Ассоциации вычислительной техники и Общества специалистов по вычислительной технике при Институте инженеров по электротехнике и радиоэлектронике.
Майкл С. Беккерман, фирма Tektronix
Майкл Беккерман получил степень бакалавра по вычислительным наукам в Портлэндском государственном университете в 1984 г. и работает над диссертацией на степень магистра на факультете вычислительных и технических наук в Центре подготовки аспирантов в Орегоне.
Работает как инженер-программист в фирме Tektronix и является президентом консультирующей фирмы Dialectic Software Technologies.
Научные интересы включают языки программирования, спецификации, программные среды, генерацию кода и крупноблочные потоковые вычисления.
Фредерика Дарема-Роджерс, Исследовательская лаборатория фирмы IBM в Хоторне
Фредерика Дарема-Роджерс получила степень бакалавра по физике и математике в Афинском университете (Греция) в 1969 г., степень магистра в Иллинойсском технологическом институте в 1972 г. и степень доктора фило-
Софии в Калифорнийском университете (г. Дейвис) в 1972 г. (обе ученые степени по теоретической физике).
Была ассистентом в Питсбургском университете и Брукхейвенской национальной лаборатории, а также инженером Центра в Шламбергер-Дол до перехода в фирму IBM в 1982 г. в качестве научного сотрудника.
Научные интересы связаны с исследованием параллельных алгоритмов и средств разработки и анализа эффективности параллельных прикладных программ.
Дэвид Ц. ДиНуччи, Центр подготовки аспирантов, Орегон
Дэвид Ц. ДиНуччи - аспирант факультета вычислительных и технических наук Центра подготовки аспирантов в Орегоне, работает над диссертацией на степень магистра по вычислительным наукам. Ученую степень бакалавра (вычислительные науки) получил в Портлендском государственном университете в 1983 г..
Работал системным программистом в Портлендском государственном университете и координатором информационных систем в средних школах Портленда.
Текущие научные интересы лежат в области крупноблочных потоковых вычислений и формальных спецификаций.
Стюарт У. Хоукинсон, фирма Floating Point System
Стюарт Хоукинсон получил степень бакалавра по химии в Вашингтонском государственном университете в 1965 г. и степень доктора философии по химической физике в Чикагском университете в 1968 г. После защиты докторской диссертации занимался исследованиями в Чикагском университете при обществе NIH и работал в Национальной лаборатории в г. Ок-Ридж.
В настоящее время - штатный научный сотрудник в группе определения продукта фирмы Floating Point System, где активно способствовал разработке параллельных алгоритмов и архитектур. Ранее был руководителем группы программистов, отвечающей за верификацию и улучшение производительности математических библиотек фирмы Floating Point System.
До работы в фирме Floating Point System в течение десяти лет был доцентом на отделении биохимии и биофизики университета в Теннесси (Ноксвилл). Кроме того, работал как приглашенный научный сотрудник в Национальной лаборатории в г. Ок-Ридж в отделе химии.
Текущие профессиональные интресы включают вычислительную математику, паралльные алгоритмы, прикладные научные вычисления.
Ричард К. Хелм, фирма Floating Point System
Кент Хелм - выпускник Государственного колледжа в Эвагрине (Олимпия, Вашингтон), где получил степень бакалавра вычислительных наук в 1982 г.
Работает в фирме Floating Point System как инженер-программист, специализируясь в реализации параллельных операционных систем.
Аллан Р. Ларраби, Вычислительный центр фирмы “Боинг”
Аллан Ларраби получил степень бакалавра по биологии в Бакнеллском университете (Льюисбург, Пенсильвания) в 1957 г. и степень доктора философии по биохимии с уклоном в органическую химию в Массачусетсском технологическом институте в 1962 г..
После двух лет работы в фирме Medical Service Министерства обороны США выполнял научную работу в Национальном институте здоровья (Бисис- да, Мэриленд). В 1966 г. стал доцентом химии в Орегонском университете,
а в 1972 г. - доцентом в Государственном университете г. Мемфиса, шт. Теннесси. С 1978 г. - професор этого университета.
Степень магистра получил в 1986 г. на факультете вычислительных и технических наук в Центре подготовки аспирантов в Орегоне. Его диссертация была посвящена адаптации больших программ из области вычислительной химии на параллельной ЭВМ Intel iPSC. В настоящее время работает как специалист по параллельным вычислениям в Вычислительном центре компании “Боинг" (Беллвью, Вашингтон).
Джеймс Р. Мак-Гроу, Ливерморская национальная лаборатория им. Лоуренса
Джим Мак-Гроу получил степень бакалавра вычислительных наук в университете города Пэрду в 1972 г. и степень доктора философии по вычислительным наукам в Корнеллском университете в 1977 г. Тема его диссертации “Свойства языков для взаимодействия процессов и управления доступом".
Затем доктор Джеймс Мак-Гроу стал доцентом Калифорнийского университета в Дэвисе и исследователем в Ливерморской национальной лаборатории им. Лоуренса. В это время выполнял большую работу по проектированию и использованию функциональных языков для многопроцессорных систем. Один из основных разработчиков языка SISAL, который реализуется на многопроцессорных системах различными исследовательскими группами. В настоящее время работает в вычислительном отделении Ливерморской национальной лаборатории им. Лоуренса. Его административное назначение - организация, оценка и поддержка исследовательских проектов по вычислительным наукам в этой лаборатории.
Текущие научные интересы сконцентрированы на разработке высокопараллельных программ для многопроцессорных ЭВМ и их автоматическом разбиении с целью достижения большой загрузки системы.
Филлип Ц. Миллер, фирма Floating Point System
Фил Миллер - выпускник университета в Пэрду, где получил степень бакалавра наук по электротехнике и электронике в 1978 г. В настоящее время продолжает работать над диссертацией на соискание степени магистра наук по вычислительным и техническим наукам в Центре подготовки аспирантов (Орегон).
Работает в фирме Floating Point System, в качестве старшего инженера по программному обеспечению, специализируется в разработке компиляторов.
Курт Б. Модал, Центр подготовки аспирантов, Орегон
Курт Б. Модал, обучаясь заочно, работает над диссертацией на соискание степени магистра в области вычислительных и технических наук в Центре подготовки аспирантов в Орегоне. Получил степень бакалавра естественных наук в университете шт. Северная Дакота в 1971 г.
С 1972 по 1982 г. был научным сотрудником в Орегонском региональном исследовательском центре. С 1983 по 1984 г. работал консультантом по вычислительной технике в фирме Infbtec Development Inc.
Его исследовательские интересы связаны с параллельной обработкой, визуализацией программ и объектно-ориентированными языками программирования.
В. Алан Нортон, Йорктаунская исследовательская лаборатория фирмы. IBM Алан Нортон получил степень бакалавра искусств в университете шт.
Юта в 1968 г. и степень доктора философии в Принстонском университете в 1976 г. (обе степени по математики).
Был инструктором в университете пгт. Юта с 1976 по 1979 г. и доцентом Гамильтон-колледжа с 1979 по 1980 г. В настоящее время является научным сотрудником фирмы IBM (Йорктаун Хейте, Нью-Йорк), руководит группой исследования параллельных алгоритмов и архитектур исследовательского проекта по параллельной обработке (проект вычислительной системы RP3). Его научные интересы включают анализ производительности и архитектуры параллельных вычислительных систем, параллельные алгоритмы, рекурсивную геометрию и машинную графику.
Даглас М. Пейс, Центр подготовки аспирантов, Орегон
Даг Пейс получил степень бакалавра по вычислительным наукам и математике в Аризонском университете (Флэйгстаф, Аризона). В настоящее время является аспирантом факультета вычислительных и технических наук в Центре подготовки аспирантов в Орегоне.
Проектирует и помогает в разработке компиляторов и параллельных языков в фирме Floating Point System и Центре подготовки аспирантов.
Текущие исследовательские интересы связаны с разработкой параллельных языков проектирования, компиляторов и современных (параллельных) вычислительных архитектур.
Кит Э. Пенник, Вычислительный центр фирмы “Боинг*
Кит Пенник получил степень бакалавра по вычислительным наукам в Вашингтонском государственном университете в 1980 г. В настоящее время работает как системный программист в Вычислительном центре высокоскоростных вычислений компьютерных служб компании “Боинг".
Основные исследовательские интресы связаны с параллельной обработкой, сетевыми системами, разработкой операционных систем и систем баз знаний.
Грегори Ф. Фистер, фирма IBM
Грегори Фистер получил степени бакалавра, магистра и доктора философии по электротехнике в Массачусетсском технологическом институте в 1967, 1969 и 1974 гг. соответственно.
Начал работать в IBM в 1974 г., а с 1975 по 1976 г. работал на факультете электротехники и отделении вычислительных наук Калифорнийского университета в Беркли. В исследовательском отделе фирмы IBM был руководителем работ по программной поддержке разработки Иорктаунской моделирующей машины (YSE), в настоящее время - главный научный сотрудник проекта RP3. Член обществ “Эта Каппа Ню“, “Тву Бета Пи“, “Сигма Хи“ и является старшим членом ИИЭР.
Чарльз Э. Ст. Джон, фирма Floating Point System
Чак Джон - выпускник Йонгстаунского государственного университета (Йонгстаун, Огайо), где получил степень бакалавра по электротехнике в 1977 г. В настоящее время работает над диссертацией на соискание степени магистра по электротехнике в Орегонском государственном университете.
В фирме Floating Point System работает как инженер по разработке аппаратных средств, специализирующийся в проектировании арифметики с плавающей запятой на СБИС. Член ИИЭР и Общества по вычислительной технике (ИИЭР). Его исследовательские интересы - параллельные и высокопроизводительные архитектуры.
Стюарт М. Стерн, Вычислительные службы компании "Боинг*
Стюарт Стерн получил степень бакалавра по математике в Университете Фэрли Дикинсон в 1963 г. Работал в компании “Боинг* с 1964 по 1967 г.,
поддерживая операционную систему. С 1967 по 1972 г. работал в фирме Informatics, Inc. по нескольким контрактам, включающим разработку систем фирмы IBM, прогнозирование президентских выборов, исследования в области машинной графики по проекту Air Force Intelligence.
Мистер Стерн был одним из основателей журналов СР/М Review и UNIX Review. Работал в Вычислительных службах компании “Боинг" с 1972 г. В этот период был одним из основных разработчиков оригинальной системы коммутации сообщений EXCHANGE ATM, занимался созданием многозадачной операционной системы для корпорации MOSLER и поддержкой среды UNIX в Центре искусственного интеллекта. В настоящее время работает как специалист по искусственному интеллекту в Центре высокоскоростных вычислений Вычислительных служб компании “Боинг".
Дженис М. Стоун, фирма IBM
Дженис Стоун получил степень бакалавра по математике в Университете Дюка в 1962 г., дальнейшие исследования по математике вел в Джорджтаунском университете, по логике и философии науки - в Стэнфордском университете.
Работает в фирме IBM с 1984 г., здесь его исследовательские интересы сосредоточены на параллельных алгоритмах и средствах разработки и анализа параллельных программ.
Лиза Сторк, Центр подготовки аспирантов, Орегон
Лиза Сторк аспирант-заочник факультета вычислительных и технических наук Центра подготовки аспирантов в Орегоне, работает над магистерской диссертацией. Получила степень бакалавра по математике в Техасском университете (Остин) в 1977 г. и посещала аспирантскую школу по математике в университете шт. Северная Каролина в Чэпел-Хилл.
В настоящее время работает научным сотрудником в Исследовательской лаборатории медицинских вычислений в госпитале “Эмануэль" и в Центре здоровья в Портлэнде, Орегон, занимаясь экспертными и графическими системами для широкого круга медицинских применений.
Текущие исследовательские интересы связаны с параллельной обработкой, медицинскими экспертными системами и графическим отображением сложных математических объектов.
Введение
Роберт Бэбб II
... требуются для рискованного путешествия. Маленькая заработная плата, жестокий холод, долгие месяцы полной темноты, постоянная опасность, сомнения в успешном возвращении. Слава и признание в случае успеха^.
Эрнест Шэклтон
Программирование для параллельных ЭВМ - это нечто особое. Когда в 1984 г. я пропустил свою первую параллельную программу на тогда новенькой машине НЕР фирмы Denelcor (Denelcor Heterogenous Element Processor) в Лос-Аламосской национальной лаборатории, то быстро понял, что не потерял способности удивляться с тех пор, как в студенческие годы сражался с языком PL/1. .
В Фортране для НЕР реализован очень простой и элегантный способ описания операций синхронизации, основанный на использовании ссылок к специальным переменным, имена которых начинаются со знака “доллар" (например, $1). Такие переменные со знаком доллара размещаются в обычном блоке COMMON и доступны для подпротрамм, которые должны выполняться параллельно. Каждая такая переменная помимо своего обычного для Фортрана значения (целого или вещественного) имеет специальный бит, который указывает, является ли соответствующая переменная свободной или занятой. Попытка присвоить значение занятой переменной должно вызывать задержку процесса до тех пор, пока другой процесс не освободит эту переменную, считав ее значение. Аналогично если некоторый процесс попытается обратиться к значению свободной переменной (обычно такая переменная находится в правой части оператора присваивания), то он автоматически задерживается с помощью эффективного аппаратно реализованного механизма и будет активизирован лишь после того, как другой процесс запишет значение в эту переменную и в результате побочного эффекта переведет ее в состояние “занята". Для выполнения более одной подпрограммы Фортрана используется оператор CREATE (параллельная версия обычного оператора CALL), который позволяет стартовать отдельным параллельным ветвям вычислений.
Видимо эти два безобидных расширения языка Фортран выпустили на волю “джина параллельных вычислений". Теперь он (не где-нибудь, а в надежном старомодном Фортране) “образовывает" семафоры, замки, процессы или задачи, “занятое ожидание", барьеры, критические секции и мониторы. С другой стороны, программисты теперь имеют дело с непредсказуемыми и обычно невоспроизводимыми ситуациями - блокировками (тупиками), зацик-
*) Из газетного объявления об экспедиции в Антарктику.
ливанием при бесполезной передаче сообщений, “гонками" и недетерминиз- мом. Внезапно даже очень простые задачи, программируемые опытными программистами, которые были предназначены для того, чтобы идею параллельного программирования сделать практической реальностью, кажется, неизбежно вели к необнаруживаемым, непредсказуемым и абсолютно мистическим ошибкам. Эти трудности нам, пионерам параллельного программирования, экспериментирующим на ЭВМ НЕР, казались значительно сложнее, чем сложности, которые могли быть объяснимы ошибками в аппаратуре и системных средствах, которые естественны для любой очень новой вычислительной системы.
С тех пор как пришла (и, печально говорить, прошла) ЭВМ НЕР, большое число компаний во всем мире породили целый “зверинец" коммерческих параллельных машин. Некоторые из них были созданы как специальные проекты фирмами по производству вычислительных машин, однако многие были разработаны инициаторами, которые были способны убедить отважных капиталистов, что параллельная обработка была идеей, время которой (а с ним деньги) пришло. .
Эта книга пытается охватить, по крайней мере на уровне руководства, состояние искусства программирования коммерчески доступных параллельных ЭВМ, используемых для решения научных задач. В последнее время также появилось немало параллельных машин, специализированных на решении таких задач, как сопоставление с образцом (распознавание по образцу) [1] и обработка сигналов, но они не рассматриваются в данной книге. Рассматриваемые здесь машины имеют производительность в диапазоне от мини-ЭВМ до суперЭВМ. Их общим свойством является то, что все они представляют собой примеры коммерческих параллельных машин для научных вычислений, которые поддерживают доступные пользователю средства параллельного программирования.