В. В. Воеводин, Вл. В. Воеводин
Параллельные ычислення
Рекомендовано Министерством образования Российской Федерации
в качестве учебного пособия для студентов высших учебных заведений,
обучающихся по направлению 510200 "Прикладная математика и информатика"
Санкт-Петербург
«БХВ-Петербург»
2002
УДК 681.3.06+519.68 ББК 32.973 В63
Воеводин В. В., Воеводин Вл. В.
В63           Параллельные вычисления. — СПб.: БХВ-Петербург, 2002. — 608 с: ил.
ISBN 5-94157-160-7
Книга известных российских ученых посвящена обсуждению ключевых про­блем современных параллельных вычислений. С единых позиций рассматрива­ются архитектуры параллельных вычислительных систем, технологии парал­лельного программирования, численные методы решения задач. Вместе со строгим описанием основных положений теории информационной структуры программ и алгоритмов, книга содержит богатый справочный материал, необхо­димый для организации эффективного решения больших задач на компьютерах с параллельной архитектурой.
Для научных работников, инженеров, преподавателей, аспирантов и студентов естественнонаучных специальностей
УДК 681.3.06+519.68 ББК 32.973
Группа подготовки издания:
Главный редактор                  Екатерина Кондукова
Зам. главного редактора         Анатолий Адаменко
Зав. редакцией                       Анна Кузьмина
Редактор                                Петр Науменко
Компьютерная верстка           Натальи Смирновой
Корректор                              Наталия Першакова
Дизайн обложки                    Игоря Цырульникова
Зав. производством                Николай Тверских
Лицензия ИД № 02429 от 24.07.00. Подписано в печать 23.09.02. Формат 70x1001/i6- Печать офсетная. Усл. печ. л. 49,02. Тираж 3000 экз. Заказ № "БХВ-Петербург", 198005, Санкт-Петербург, Измайловский пр., 29.
Гигиеническое заключение на продукцию, товар № 77.99.02.953.Д.001537.03.02 от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России.
Отпечатано с готовых диапозитивов
в Академической типографии "Наука" РАН
199034, Санкт-Петербург, 9 линия, 12.
ISBN 5-94157-160-7                                                                   © Воеводин В. В., Воеводин Вл. В., 2002
© Оформление, издателвство "БХВ-Петербург", 2002
Содержание
Предисловие.....................................................................................................1
Много ли надо знать о параллельных вычислениях?...........................................1
Часть I. Параллельные вычислительные системы..............................11
Глава 1. Что скрывают "обыкновенные" компьютеры.......................................13
§ 1.1. Немного об устройстве компьютера...........................................................14
Представление информации. Общее устройство компьютера. Операции и операн­ды. Команды. Управление. Арифметико-логическое устройство. Память. Устройст­во ввода/вывода. Центральный процессор. Вопросы и задания.
§ 1.2. Операции с числами.....................................................................................19
Двоичное представление чисел. Разряды. Фиксированная и плавающая запятая. Округление чисел. Ошибка округления. Сравнение представлений чисел. Вопросы и задания.
§ 1.3. Иерархия памяти...........................................................................................25
Различные виды памяти. Время доступа. Виртуальная память. Влияние на время решения задачи. Трудности работы с медленной памятью. Вопросы и задания.
§ 1.4. Языки программирования и программы....................................................31
Языки низкого и высокого уровня. Проблемно-ориентированные языки. Контроль эффективности программ. Компьютерная зависимость. Портабельность программ. Компиляторы и эффективность программ. Необходимость привлечения дополни­тельной информации. Вопросы и задания.
§ 1.5. Узкие места....................................................................................................37
Иллюстративная модель компьютера. Пиковая и реальная производительность. Взаимодействие отдельных узлов компьютера. Эффективность. Узкие места. Во­просы и задания.
Глава 2. Как повышают производительность компьютеров................................42
§ 2.1. Усложнение и наращивание аппаратных средств.....................................43
Уменьшение размеров. Скалярная, конвейерная и параллельная обработка. Иерар­хия памяти. Опережающий просмотр команд. Локальность вычислений и исполь­зования данных. Примеры. Вопросы и задания.
§ 2.2. Повышение интеллектуальности управления компьютером...................60
Закон Мура. Спецпроцессоры. Суперскалярные и VLlW-архитектуры. Коммутацион­ные схемы. Топологии связей процессоров. SMP-компьютеры. Архитектуры NUMA и ccNUMA. Развитие программного обеспечения. Примеры. Вопросы и задания.
IV
Содержание
§ 2.3. Система функциональных устройств..........................................................78
Простые и конвейерные устройства. Стоимость работы. Загруженность. Пиковая и реальная производительность. Эффективность. Различные соотношения. Законы Амдала и Густавсона—Барсиса. Взаимосвязь законов. Вопросы и задания.
Глава 3. Архитектура параллельных вычислительных систем............................94
§ 3.1. Классификация параллельных компьютеров и систем............................96
Классификация Флинна, Хокни, Фенга, Хендлера, Шнайдера, Скилликорна. Взаимосвязь классификаций. Архитектура компьютеров и структура задач. Вопро­сы и задания.
§ 3.2. Векторно-конвейерные компьютеры.........................................................114
Детальное рассмотрение компьютера Cray С90. Структура оперативной памяти. Регистровая структура. Функциональные устройства. Пиковая и реальная произво­дительность. Вопросы и задания.
§ 3.3. Параллельные компьютеры с общей памятью.........................................134
Детальное рассмотрение компьютера HP Superdome. Ячейка компьютера. Локальные и удаленные ячейки. Процессор РА-8700. Работа с памятью. Вопросы и задания.
§ 3.4. Вычислительные системы с распределенной памятью............................142
Детальное рассмотрение компьютеров Cray T3D/T3E. Управляющие и вычисли­тельные узлы. Процессорный элемент. Сетевой интерфейс. Сетевой маршрутиза­тор. Коммуникационная сеть. Память. Кластерные проекты. Вопросы и задания.
§ 3.5. Концепция GRID и метакомпьютинг.......................................................154
Метакомпьютер как огромная распределенная система. Особенности распределе­ния задач и передачи данных. Различные проекты. Концепция GRID. Проблемы пользователей. Вопросы и задания.
§ 3.6. Производительность параллельных компьютеров....................................162
Сравнение вычислительных систем. Пиковая производительность и формат дан­ных. Вычислительные и коммуникационные ядра. Тесты. Вопросы и задания.
Часть П. Параллельное программирование........................................179
Глава 4. Большие задачи и параллельные вычисления.....................................181
§ 4.1. Большие задачи и большие компьютеры..................................................183
Моделирование климатической системы. Обтекание летательных аппаратов. Ма­тематические модели и вычислительная техника. Огромные объемы вычислений и размеры памяти. Вопросы и задания.
§ 4.2. Граф алгоритма и параллельные вычисления...........................................191
Порядок вычислений. Граф алгоритма. Параллельные формы графа. Ярус и высо­та. Инвариантность к ошибкам округления. Граф алгоритма и информационное ядро. Параметризация в графе. Вопросы и задания.
§ 4.3. Концепция неограниченного параллелизма.............................................198
Параллельные алгоритмы. Принцип сдваивания. Примеры алгоритмов малой вы­соты. Ограниченность концепции. Новые алгоритмы — новые свойства. Трудности в проблеме портабельности. Вопросы и задания.
§ 4.4. Внутренний параллелизм............................................................................206
Преимущества внутреннего параллелизма. Примеры. Обнаружение новых свойств. Декомпозиция алгоритмов. Использование медленной памяти. Структура алгорит­мов и программ. Вопросы и задания.
Содержание                                                                                                                           V
Глава 5. Технологии параллельного программирования....................................219
§ 5.1. Использование традиционных последовательных языков......................221
Обилие средств параллельного программирования. Трудности применения. Необ­ходимость дополнительной информации. Системы программирования ОрепМР, DVM, трС. Примеры использования. Вопросы и задания.
§ 5.2. Системы программирования на основе передачи сообщений................268
Системы параллельного программирования Linda, MPI, MPI-2. Интерфейсы для последовательных языков Fortran, С, C++. Примеры использования. Вопросы и задания.
§ 5.3. Другие языки и системы программирования...........................................301
Т-система. Система программирования НОРМА. Приближенность к математиче­ским записям. Примеры использования. Вопросы и задания.
Глава 6. Тонкая информационная структура программ.....................................320
§ 6.1. Графовые модели программ........................................................................324
Информационная структура программы. Операционная и информационная связь. Графы управления, операционно-логической истории, информационный, истории реализации, зависимостей, влияния. Минимальные графы зависимостей. Снова граф алгоритма. Примеры графов. Вопросы и задания.
§ 6.2. Выбор класса программ...............................................................................337
Статический анализ структуры программ. Статистическая значимость отдельных операторов. Линейный класс программ. Вопросы и задания.
§ 6.3. Графы зависимостей и минимальные графы............................................342
Опорные оператор, гнездо циклов, область. Пространства итераций. Лексикографи­ческий порядок. Типы зависимостей. Максимальный и минимальный графы зависи­мостей и их свойства. Теорема об информационном покрытии. Вопросы и задания.
§ 6.4. Простые и элементарные графы................................................................351
Простые и элементарные графы и программы. Расщепление минимальных графов на простые. Погружение минимального графа в объединение элементарных. Све­дение к анализу элементарных программ. Вопросы и задания.
§ 6.5. Лексикографический порядок и i-свойство матриц...............................355
Лексикографический максимум в многограннике, /.-свойство матриц. Критерий лек­сикографического максимума. Дополнительные свойства матриц с /.-свойством. Вопросы и задания.
§ 6.6. Построение минимальных графов зависимостей.....................................363
Основная задача. Конструктивные алгоритмы построения элементарных, простых и минимальных графов зависимостей для программ из линейного класса. Вопросы и задания.
§ 6.7. Циклы ParDO и избыточные вычисления.................................................373
Лексикографически правильные графы. Параллельные множества. Параллельная структура программ. Критерий цикла ParDO. Избыточные вычисления и критерий их обнаружения. Вопросы и задания.
§ 6.8. Примеры.......................................................................................................378
Формализованное построение графов алгоритмов для конкретных программ. Сложнейшие графы алгоритмов для простейших программ. Зависимость парал­лельной структуры от порядка выполнения операций.
VI
Содержание
Глава 7. Эквивалентные преобразования программ..........................................393
§ 7.1. Развертки графа............................................................................................394
Строгая и обобщенная развертки. Свойства разверток. Развертки и параллельные множества. Конструктивный алгоритм построения кусочно-линейных разверток. Выделение в графах строгих и нестрогих зависимостей. Вопросы и задания.
§ 7.2. Макрографы зависимостей.........................................................................404
Макровершины и макродуги. Укрупненное представление зависимостей. Разверт­ки и декомпозиция алгоритмов. Распределенные вычисления. Работа с медленной памятью. Вопросы и задания.
§ 7.3. Эквивалентные программы.........................................................................408
Эквивалентные преобразования программ. Эквивалентные по вычислениям про­граммы. Преобразования, гарантирующие эквивалентность. Вопросы и задания.
§ 7.4. Наиболее распространенные преобразования программ........................415
Перестановка циклов. Слияние циклов. Переупорядочивание операторов. Распре­деление цикла. Скашивание цикла. Расщепление пространства итераций. Выпол­нение итераций цикла в обратном порядке. Треугольные преобразования. Вопросы и задания.
§ 7.5. Две сопутствующие задачи..........................................................................421
Оценивание длины критического пути графа зависимостей. Распределение масси­вов по модулям памяти.
§ 7.6. Примеры.......................................................................................................426
Формализованное построение разверток и макрографов для конкретных программ. Определение циклов ParDO. Распределение массивов по модулям памяти. Эквива­лентное преобразование подпрограммы OLDA из пакета тестов Perfect Club Benchmarks. Самые лучшие результаты. Система V-Ray.
Часть III. Смежные проблемы и применение.....................................441
Глава 8. Вычислительные системы и алгоритмы..............................................443
§ 8.1. Расширение и уточнение линейного класса.............................................448
Прямая подстановка. Вычисляемый цикл go to. Вычисляемые ветвления. Нелиней­ные индексные выражения. Уточнение описания внешних переменных. Подпро­граммы и функции. Функции min и max. Прямое вычисление графов. Вопросы и задания.
§ 8.2. Граф-машина................................................................................................459
Локальность управления. Свойства различных реализаций. Гомоморфная свертка. Граф-машина и граф вычислительной системы. Сохранение временных режимов. Вопросы и задания.
§ 8.3. Регулярные и направленные графы...........................................................471
Итерационные процессы и регулярные графы. Расщепление бесконечного регу­лярного графа. Главный регулярный подграф. Критерий отсутствия контуров. Ли­нейные развертки регулярного графа. Гомоморфная свертка регулярных графов. Вопросы и задания.
§ 8.4. Математические модели систолических массивов...................................482
Систолические массивы как вычислительные системы. Локальность управления. Минимальные коммуникационные связи. Реализация регулярных графов алгорит­мов. Примеры построения систолических массивов для заданных алгоритмов. Во­просы и задания.
Содержание                                                                                                                          VII
§ 8.5. Математическая модель алгебраического вычислителя..........................499
Структура алгебраических задач. Спецпроцессор для алгебраических задач. Ис­пользование систолического массива для матричной операции А + ВС. Вопросы и задания.
§ 8.6. Матрицы и структура алгоритмов..............................................................503
Общие вычислительные процессы. Вариационная матрица алгоритма. Матрицы смежностей и инциденций. Критерий развертки. Уравновешенные графы. Крите­рий уравновешенности. Вопросы и задания.
§ 8.7. Новое применение сведений о структуре.....................................................511
Восстановление линейного функционала. Вычисление градиента. Анализ ошибок округления. Всюду вариационная матрица алгоритма. Вопросы и задания.
§ 8.8. Примеры.......................................................................................................528
Техника ускоренного вычисления градиента функции. Выполнение анализа оши­бок по формальным правилам. Нахождение малых относительных эквивалентных возмущений.
Глава 9. Пользователь в среде параллелизма...................................................533
§ 9.1. Типичные ситуации в вопросах и ответах.................................................534
Конкретные вопросы из практики параллельного программирования. Исследова­ние возникших ситуаций. Возможные идеи и пути решения. Что следует из пере­численных примеров? Вопросы и задания.
§ 9.2. Программный сервис в параллельных вычислениях...............................558
Почему в программе что-то не так? Компиляторы, отладчики, профилировщики, анализаторы, конверторы. Система V-Ray. Статические и динамические характе­ристики параллельных программ. Анализ структуры программ. Графовые структу­ры программ на практике. Вопросы и задания.
§ 9.3. Организационная поддержка пользователя..............................................581
Инфраструктура поддержки работы пользователей. Информационно-аналитический центр по параллельным вычислениям Parallel.ru. Вопросы и задания.
Заключение. Параллельные вычисления: интеграция от А до Я........................585
Список литературы........................................................................................588
Интернет-ресурсы..........................................................................................592
Предметный указатель...................................................................................593
Предисловие
Много ли надо знать
о параллельных вычислениях?
Ничего нельзя сказать о глубине лужи, если не попасть в нее.
Из законов Мерфи
В активе человечества имеется не так много изобретений, которые, едва возникнув, быстро распространяются по всему миру. Одним из них является компьютер. Появившись в середине двадцатого столетия, он через несколь­ко десятков лет повсеместно стал незаменимым инструментом и надежным помощником в обработке самой разнообразной информации: цифровой, текстовой, визуальной, звуковой и др.
Известно, что первопричиной создания компьютеров была настоятельная необходимость быстрого проведения вычислительных работ, связанных с решением больших научно-технических задач в атомной физике, авиастрое­нии, климатологии и т. п. Решение таких задач и сейчас остается главным стимулом совершенствования компьютеров. Однако в настоящее время ос­новная сфера их применения связана с совсем иной деятельностью, в кото­рой вычислительная составляющая либо отсутствует совсем, либо занимает небольшую часть.
Работа биржи, управление производством, офисные приложения, корпора­тивные информационные системы, процессы образования, игры, ведение домашнего хозяйства — это всего лишь отдельные примеры областей невы­числительного использования компьютеров. В подобных областях вполне приемлемо применение персональных компьютеров и рабочих станций для достижения необходимых целей. Относительная простота процесса исполь­зования и комфортная программная среда формируют здесь устойчивое впе­чатление о компьютере как о весьма дружественном помощнике. И мало кто из пользователей такой техники догадывается и, тем более, знает, что все радикально меняется с переходом к решению больших и очень больших задач. Компьютеры становятся весьма сложными, куда-то пропадает друже­ственность интерфейса, программная среда переходит на жесткий команд­ный язык и начинает требовать от пользователей предоставления такой ин­формации, которая не всегда известна, и т. п.
Эта книга о больших и супербольших компьютерах и особенностях их ис­пользования. О тех проблемах, с которыми неизбежно приходится сталки-
2
Предисловие
ваться любому пользователю, вынужденному применять вычислительную технику на пределе ее возможностей.
Вообще говоря, большие задачи сами по себе не являются предметом на­шего обсуждения, тем более, детального. Но иногда мы будем обращаться к ним явно, чтобы проиллюстрировать какие-то положения. Неявно же боль­шие задачи буквально пронизывают всю книгу, т. к. любое обсуждение чаще всего предполагает, что решается именно большая задача. Формально харак­тер обсуждения никак не связан с типом задач, хотя может ощущаться неко­торая ориентация на научно-техническую сферу деятельности. Это объясня­ется всего лишь тем, что основное применение больших и супербольших компьютеров связано именно с данной сферой и потребности данной сферы оказывают наибольшее влияние на развитие компьютеров, по крайней мере, в настоящее время. Тем не менее, обсуждаемые в книге проблемы в одина­ковой мере относятся к большим задачам как научно-техническим, так и любым другим, например, информационным.
Все, что связано с большими компьютерами и большими задачами, сопро­вождается характерным словом "параллельный": параллельные компьютеры, параллельные вычислительные системы, языки параллельного программи­рования, параллельные численные методы и т. п. В широкое употребление этот термин вошел почти сразу после появления первых компьютеров. Точ­нее, почти сразу после осознания того факта, что созданные компьютеры не в состоянии решить за приемлемое время многие задачи. Выход из создав­шегося положения напрашивался сам собой. Если один компьютер не справляется с решением задачи за нужное время, то попробуем взять два, три, десять компьютеров и заставим их одновременно работать над различ­ными частями общей задачи, надеясь получить соответствующее ускорение. Идея показалась плодотворной, и в научных исследованиях конкретное чис­ло объединяемых компьютеров довольно быстро превратилось в произволь­ное и даже сколь угодно большое число.
Объединение компьютеров в единую систему потянуло за собой множество следствий. Чтобы обеспечить отдельные компьютеры работой, необходимо исходную задачу разделить на фрагменты, которые можно выполнять неза­висимо друг от друга. Так стали возникать специальные численные методы, допускающие возможность подобного разделения. Чтобы описать возмож­ность одновременного выполнения разных фрагментов задачи на разных компьютерах, потребовались специальные языки программирования, специ­альные операционные системы и т. д. Постепенно такие слова, как "одно­временный", "независимый" и похожие на них стали заменяться одним сло­вом "параллельный". Всё это синонимы, если иметь в виду описание каких-то процессов, действий, фактов, состояний, не связанных друг с другом. Ни­чего иного слова "параллелизм" и "параллельный" в областях, относящихся к компьютерам, не означают.
Предисловие
3
Далеко не сразу удалось объединить большое число компьютеров. Первые компьютеры были слишком громоздкими, потребляли слишком много энергии, да и многие технологические проблемы комплексирования еще не нашли эффективного решения. Но со временем успехи микроэлектроники привели к тому, что важнейшие элементы компьютеров по многим своим параметрам, включая размеры и объем потребляемой энергии, стали меньше в тысячи и более раз. Идея объединения большого числа компьютеров в единую систему стала главенствовать в повышении общей производительно­сти вычислительной техники. В одной из самых больших современных сис­тем ASCI White объединено 8192 процессора. При этом достигаются весьма впечатляющие суммарные характеристики: пиковая производительность бо­лее 12 Тфлопс, оперативная память 4 Тбайт, дисковый массив 160 Тбайт. Но всем этим богатством нужно еще уметь воспользоваться.
Параллелизм на различных уровнях характерен для всех современных ком­пьютеров от персональных до супербольших: одновременно функционирует множество процессоров, передаются данные по коммуникационной сети, работают устройства ввода/вывода, осуществляются другие действия. Любой параллелизм направлен на повышение эффективности работы компьютера. Некоторые его виды реализованы жестко в "железе" или обслуживающих программах и недоступны для воздействия на него рядовому пользователю. Но с помощью жесткой реализации не удается достичь наибольшей эффек­тивности в большинстве случаев. Поэтому многие виды параллелизма реа­лизуются в компьютерах гибко, и пользователю предоставляется возмож­ность распоряжаться ими по собственному усмотрению.
Под термином "параллельные вычисления" как раз и понимается вся сово­купность вопросов, относящихся к созданию ресурсов параллелизма в про­цессах решения задач и гибкому управлению реализацией этого паралле­лизма с целью достижения наибольшей эффективности использования вычислительной техники.
Вот уже более полувека параллельные вычисления привлекают внимание самых разных специалистов. Три обстоятельства поддерживают к ним по­стоянный интерес. Во-первых, это очень важная сфера деятельности. Зани­маясь параллельными вычислениями, исследователь понимает, что он дела­ет что-то, относящееся к самым большим задачам, самым большим компьютерам и, следовательно, находящееся на передовом фронте науки. Как минимум, близость к передовому фронту науки вдохновляет. Во-вторых, это очень обширная сфера деятельности. Она затрагивает разработ­ку численных методов, изучение структурных свойств алгоритмов, создание новых языков программирования и многое другое, связанное с интерфейсом между пользователем и собственно компьютером. Параллельные вычисле­ния тесно связаны и с самим процессом конструирования вычислительной техники. Структура алгоритмов подсказывает необходимость внесения в компьютер изменений, эффективно поддерживающих реализацию структур-
4
Предисловие
ных особенностей. Инженерные же новшества стимулируют разработку но­вых алгоритмов, эффективно эти новшества использующих. И, наконец, в-третьих. С формальных позиций рассматриваемая сфера деятельности лег­ко доступна для исследований. Достаточно более или менее познакомиться с ее основами на уровне популярной литературы и уже можно делать содер­жательные выводы, возможно, даже никем не опубликованные.
Последнее обстоятельство придает характерную окраску исследованиям в области параллельных вычислений. Легкое освоение основных положений привлекает к параллельным вычислениям большое число специалистов из других областей. Это порождает немало новых задач, особенно на стыке вы­числительной техники и приложений. Некоторые из них оказываются инте­ресными и перспективными. Но, с другой стороны, простота освоения ос­нов порождает массу легковесных результатов, что не делает параллельные вычисления привлекательной областью для серьезных исследований и не позволяет раскрыть все их богатство и многообразие связей.
В подтверждение сказанного приведем некоторые данные на начало 80-х годов прошедшего столетия. Это был период, когда в широкое использова­ние стали поступать самые разнообразные компьютеры и вычислительные системы параллельной архитектуры. К этому времени только по проблемам их освоения было опубликовано более 5000 работ, и поток публикаций имел явную тенденцию к расширению. В данном потоке заметную часть состав­ляли работы, связанные с обсуждением особенностей реализации парал­лельных численных методов, главным образом, по линейной алгебре. Инте­рес к методам линейной алгебры вполне понятен, т. к. они составляют основу процессов решения многих сложных проблем. Согласно данным, взятым из библиографических указателей на начало 80-х годов XX века, в области вычислительных методов линейной алгебры было опубликовано примерно 8000 работ. При этом около 120 ученых мира имели по этой тема­тике более чем по 10 работ. Казалось бы, что именно им и развивать парал­лельные методы линейной алгебры. Но среди них на тот период лишь около 10 человек имели по одной-две публикации, косвенно относящихся к па­раллельным численным методам и их применению на параллельных вычис­лительных системах. И только несколько человек имели публикации, прямо посвященные обсуждению этих проблем.
Интересно отметить, что в определенном смысле история повторяется. Ана­логичной была ситуация на рубеже появления первых вычислительных ма­шин. Тогда математики-алгоритмисты также принимали слабое участие в разработке численных методов и программ для компьютеров. Прошло много лет, прежде чем положение изменилось. Последствия же этого периода ощущаются до сих пор. В настоящее время вычислительное сообщество ак­тивно переходит на использование кластеров, больших многопроцессорных систем, неоднородных систем и систем, распределенных по значительной территории. И опять ведущая роль в освоении новейшей техники в целом
Предисловие
5
принадлежит не профессионалам в создании численных методов, а разра­ботчикам языков программирования, компиляторов, операционных систем и просто лицам, которым необходимо решать задачи. К тому времени, когда математиками будет понято, как надо правильно реализовывать численные методы на новой технике, менять программную среду, скорее всего, будет поздно. Но именно от численных методов во многом зависит, насколько успешно используется вычислительная техника.
Планируя написание этой книги, мы понимали, что заведомо придется ис­кать компромисс между многообразием сторон затрагиваемой проблемы и полнотой их описания. Удачен выбранный компромисс или нет — судить читателю. Мы лишь хотим сначала пояснить свою позицию. Возможно, что после этого будет легче понять как цели появления того или иного материа­ла в книге, так и пути их достижения.
У разных специалистов, связанных с параллельной вычислительной техни­кой, разные взгляды на то, что представляют собой параллельные вычисле­ния, где и какие акценты надо ставить при изложении материала. В этой книге мы попытались отразить позицию пользователей и те проблемы, с которыми им приходится сталкиваться. Говоря о пользователях, мы имеем в виду, прежде всего, тех из них, которые осваивают серийно выпускаемую технику, которые вынуждены изучать особенности этой техники, чтобы ре­шать на ней реальные задачи более эффективно, и которым для достижения эффективности приходится многократно переписывать свои программы.
Это в значительной мере определило представленный в книге материал. Мы сразу отказались от искушения описать многие, в том числе, весьма краси­вые инженерные, программистские и математические идеи, оказавшиеся по каким-либо причинам либо нереализованными, либо апробированными не­достаточно. Но даже при таком ограничении интересных идей и достиже­ний, относящихся к параллельным вычислениям, оказалось немало. Отбор материала и структура его описания подчинены одной из целей книги — показу многообразия и глубины связей различных направлений деятельно­сти в параллельных вычислениях с соседними областями. В первую очередь, с созданием вычислительной техники, разработкой программного обеспече­ния и математическими исследованиями.
С какой бы стороны не рассматривать параллельную вычислительную тех­нику, главным стимулом ее развития было и остается повышение эффек­тивности процессов решения больших и очень больших задач. Эффектив­ность зависит от производительности компьютеров, размеров и структуры их памяти, пропускной способности каналов связи. Но в не меньшей, если не большей, степени она зависит также от уровня развития языков про­граммирования, компиляторов, операционных систем, численных методов и многих сопутствующих математических исследований. Если с этой точки
6
Предисловие
зрения взглянуть на приоритеты пользователей, то они всегда связаны с вы­бором тех средств, которые позволяют решать задачи более эффективно.
Эффективность — понятие многоплановое. Это удобство использования тех­ники и программного обеспечения, наличие необходимого интерфейса, простота доступа и многое другое. Но главное — это достижение близкой к пиковой производительности компьютеров. Данный фактор настолько ва­жен, что всю историю развития вычислительной техники и связанных с ней областей можно описать как историю погони за наивысшей эффективно­стью решения задач.
Конечно, такой взгляд отражает точку зрения пользователей. Но ведь имен­но пользователям приходится "выжимать" все возможное из имеющихся у них средств и приводить в действие все "рычаги", чтобы достичь макси­мальной производительности компьютеров на своих конкретных задачах. Поэтому им нужно знать, где находятся явные и скрытые возможности по­вышения производительности и как наилучшим образом ими воспользо­ваться. Реальная производительность сложным образом зависит от всех составляющих процесса решения задач. Можно иметь высокопроизводи­тельный компьютер. Но если компилятор создает не эффективный код, ре­альная производительность будет малой. Она будет малой и в том случае, если не эффективны используемые алгоритмы. Не эффективно работающая программа — это прямые потери производительности компьютера, средств на его приобретение, усилий на освоение и т. п. Таких потерь хотелось бы избежать или, по крайней мере, их минимизировать.
Проблемы пользователей нам известны не понаслышке. За плечами лежит более двадцати лет научной, производственной и педагогической деятельно­сти в области параллельных вычислений. За это время освоены многие ком­пьютеры и большие вычислительные системы. Было решено немало задач. Но не было ни одного случая, когда одна и та же программа эффективно реализовывалась без существенной переделки при переходе к другой техни­ке. Конечно, хотя и трудно, но все же можно заново переписать программу, удовлетворяя требованиям языка программирования и штатного компилято­ра. Однако новую большую программу суперсложно сделать эффективно работающей.
Технология обнаружения узких мест процесса реализации программы плохо алгоритмизирована. Опыт показывает, что для повышения эффективности приходится рассматривать все этапы действий, начиная от постановки задачи и кончая изучением архитектуры компьютера, через выбор численного метода и алгоритма, тщательно учитывая особенности языка программирования и даже компилятора. Основной способ обнаружения узких мест — это метод проб и ошибок, сопровождающийся большим числом прогонов вариантов программы. Необходимость многих экспериментов объясняется скудностью информации, получаемой от компилятора после каждого прогона. К тому же
Предисловие
7
ни один компилятор не дает никаких гарантий относительно меры эффектив­ности реализуемых им отдельных конструкций языка программирования. С самого начала пользователь ставится в такие условия, когда он не знает, как надо писать эффективные программы. Получить соответствующую информа­цию он может только на основе опыта, изучая почти как черный ящик влия­ние различных форм описания алгоритмов на эффективность.
Наши исследования показывают, что большинство из узких мест может быть объединено в три группы. Первая связана собственно с компьютером. На любом параллельном компьютере не все потоки данных обрабатываются одинаково. Какие-то из них реализуются предельно эффективно, какие-то достаточно плохо. Изучая особенности архитектуры компьютера, очень важ­но понять, что представляет собой те и другие потоки и описать их матема­тически. Вторая определяется структурой связей между операциями про­граммы. Не в каждой программе обязаны существовать фрагменты, реализуемые эффективно на конкретном компьютере. И, наконец, третья зависит от используемой в компиляторе технологии отображения программ в машинный код.
Если технология позволяет разложить программу на фрагменты, по которым почти всегда будет строиться эффективный код, то узких мест в этой группе может не быть. Такие технологии были разработаны для первых параллель­ных компьютеров. Но по мере усложнения вычислительной техники техно­логии компиляции становятся все менее и менее эффективными, и узких мест в третьей группе оказывается все больше и больше. Для больших рас­пределенных систем узкие места компиляции начинают играть решающую роль в потере общей эффективности. Поэтому уже давно наметилось и те­перь почти воплотилось в реальность "компромиссное" разделение труда: наименее алгоритмизированные и сложно реализуемые этапы компиляции, в первую очередь, расщепление программы на параллельные ветви вычисле­ний, распределение данных по модулям памяти и организацию пересылок данных возлагаются на пользователя. Проблем от этого не стало меньше. Просто о том, о чем раньше заботились разработчики компиляторов, теперь должны беспокоиться сами пользователи.
Акцентированное внимание к узким местам процесса решения задач является характерной чертой настоящей книги. Узкие места описываются и изучают­ся практически всюду: в параллельных компьютерах и больших вычисли­тельных системах, процессах работы многих функциональных устройств, конструировании численных методов, языках и системах программирова­ния, различных приложениях и даже в работе пользователей. Особое внима­ние уделяется узким местам, связанным с выявлением параллельных струк­тур алгоритмов и программ и их отображением на архитектуру компьютеров.
Исследуя различные вопросы параллельных вычислений, мы неоднократно убеждались в том, что все они связаны многими незримыми нитями как
8
Предисловие
между собой, так и с совершенно далекими от любого параллелизма облас­тями знаний. Эти связи не лежат на поверхности, и почувствовать их мож­но, лишь поставив исследования на строгую математическую основу. И то­гда начинает возникать ощущение, что параллельные вычисления своими корнями уходят во что-то очень фундаментальное. Возможно, это что-то есть информационная структура алгоритмов. Возможно, оно окажется чем-то иным. Но в любом случае появляется уверенность, что глубокое изучение параллельных вычислений приведет, в конце концов, к пониманию того, как должны быть устроены наши алгоритмы, компьютеры и программное обеспечение. Сейчас параллельные вычисления представляются гигантским айсбергом, верхушка которого исследована весьма основательно. Исследо­вание же остальной его части только начинается. Перспективу этого про­цесса мы также попытались отразить в нашей книге.
Содержание книги и ее структура хорошо видны из подробного содержания. Поэтому сделаем по ней лишь несколько замечаний. В своей основе книга является монографией, поскольку в нее включено много уникального автор­ского материала. Однако содержание систематизировано таким образом, что книга может служить учебным пособием для студентов, аспирантов и спе­циалистов, связанных в своей деятельности с направлением "Прикладная математика и информатика". Для ее чтения нужны минимальные начальные знания. Некоторые разделы доступны даже школьникам, владеющим навы­ками работы с компьютером. Вместе с тем, в книге также приводятся сведе­ния очень высокой степени сложности, достойные внимания самых серьез­ных исследователей. Для облегчения знакомства с книгой каждая глава начинается с небольшого введения, поясняющего цель и особенности изло­женного в ней материала. После ознакомления с новым материалом читате­лю предлагается ответить на вопросы и выполнить задания. Вопросы и за­дания, помеченные одной или двумя звездочками, относятся соответственно к сложным и очень сложным.
Создание учебного пособия широкого профиля по параллельным вычисле­ниям — это, пожалуй, самая главная цель написания настоящей книги. Мы постарались с единых позиций не только рассказать об основах данного на­учного направления, но и показать его проблемы и перспективы развития. Конечно, по параллельным вычислениям имеется немало хороших книг и публикаций. Но все они, на наш взгляд, слишком фрагментарны и не соз­дают цельного впечатления о рассматриваемой сфере деятельности, осуще­ствляемой на стыке многих наук. Структура и форма изложения материала этой книги формировались довольно долго. Научные исследования позво­лили выделить опорные точки параллельных вычислений среди огромного числа общих результатов и наметить их связь между собой. В предваритель­ных публикациях была предпринята попытка развить и систематизировать эти связи на основе строгих математических определений и выкладок. Раз­личные фрагменты книги неоднократно обсуждались на конференциях, се-
Предисловие
9
минарах и в дискуссиях с коллегами. На основе отобранного материала в течение многих лет читаются основные и специальные курсы в Московском государственном университете им. М. В. Ломоносова, физико-техническом институте и международном государственном университете в г. Дубна. Ре­шающим обстоятельством, повлиявшим на ускорение процесса написания книги, стало наше участие в молодежных научных школах. Необходимость создания учебных пособий по параллельным вычислениям там ощущалась особенно остро.
Заниматься параллельными вычислениями довольно сложно. Эту область за короткое время трудно освоить и, тем более, трудно получить хорошие ре­зультаты. Поэтому в коллективах, где проводятся подобные работы, имеет большое значение создание атмосферы поддержки, заинтересованности и терпеливости. Нам приятно выразить признательность академику Г. И. Мар-чуку, который такую атмосферу создал в институте вычислительной матема­тики Российской академии наук и в течение многих лет поддерживал это направление исследований.
Мы благодарны академику В. А. Садовничему за столь же благоприятную атмосферу в Московском университете и поддержку работ научно-исследовательского вычислительного центра МГУ по созданию крупней­шего центра по высокопроизводительным вычислениям в системе вузов России. Многолетнее сотрудничество ИВМ РАН и НИВЦ МГУ в области параллельных вычислений позволило приобрести бесценный опыт как в решении больших задач, так и в правильной расстановке акцентов в про­цессах образования и подготовки высококвалифицированных кадров. Появ­ление этой книги есть одно из следствий данного сотрудничества.
В процессе написания книги нам пришлось контактировать со многими коллегами. Некоторые из них предоставили свои материалы, помогающие лучше осветить отдельные разделы. С другими было просто полезно погово­рить, чтобы сформировать нужную точку зрения. Всем им мы выражаем нашу признательность. Особенно отмечаем участие академика В. П. Дымникова, член-корреспондента А. В. Забродина, докторов физико-математических наук С. М. Абрамова, А. Н. Андрианова, А. Л. Ластовецкого, В. А. Крюкова, А. Н. Томилина, кандидатов физико-математических наук К. Н. Ефимкина, Н. А. Коновалова.
Мы выражаем благодарность Российскому фонду фундаментальных иссле­дований и Министерству промышленности, науки и технологий Российской Федерации. Многие результаты, о которых говорится в книге, были получе­ны в рамках выполнения грантов от этих организаций. Без поддержки этих организаций написать такую книгу было бы гораздо сложнее.
Мы благодарим представительство компании Hewlett-Packard в России за предоставленные материалы, возможность использования ее вычислитель­ной техники, за внимание и постоянный интерес к нашей работе.
10
Предисловие
Мы исключительно благодарны А. И. Караваеву, прекрасно подготовившему все иллюстрации, использованные в данной книге. Наша искренняя благо­дарность сотрудникам лаборатории параллельных информационных техноло­гий НИВЦ МГУ: кандидату физико-математических наук А. С. Антонову, А. Н. Андрееву и С. А. Жуматию за множество ценных замечаний по рукописи.
И, наконец, нашу особую признательность мы хотим выразить Т. С. Гамаюновой и С. Н. Воеводиной. Без их самоотверженной работы по набору текста, его проверке, подготовке оригинал-макета и коррекции мате­риала книга могла бы не появиться в срок.
Параллельные вычисления — перспективная и динамично изменяющаяся область научной и прикладной деятельности. Мы надеемся, что выбрать правильный путь в ней поможет эта книга.
Валентин В. Воеводин и Владимир В. Воеводин
ЧАСТЬ I
Параллельные вычислительные системы
Глава 1
Что скрывают "обыкновенные" компьютеры
Внутри каждой большой задачи сидит маленькая, пытающаяся пробиться наружу.
Из законов Мерфи
В своем развитии персональные компьютеры достигли высокого уровня со­вершенства. Они компактны, обладают большой скоростью выполнения за­даний и, что особенно важно, достаточно просты в обращении. Все эти ка­чества привели к их широкому использованию, в том числе среди лиц, не имеющих специальной компьютерной подготовки. По существу персональ­ный компьютер становится таким же привычным устройством, как пылесос, кухонный комбайн и телевизор. Он остается удобным инструментом до тех пор, пока не приходится решать очень большие задачи. Конечно, в этом случае можно попытаться использовать самый быстрый персональный ком­пьютер с максимально большой памятью. Можно также перейти к исполь­зованию рабочей станции. Однако увеличение возможностей на этом пути все же ограничено. И тогда приходится обращаться к суперкомпьютерам.
Согласно распространенному мнению, суперкомпьютер — это такой ком­пьютер, у которого все самое-самое: супербольшая скорость, супербольшая память, супербольшая цена. Это действительно так. Но, переходя от персо­нального компьютера к суперкомпьютеру, пользователю приходится также сталкиваться с очень большими трудностями в их использовании. Главное, на суперкомпьютерах отсутствует характерная для персональных компьюте­ров пользовательская среда. Оказывается, что использовать суперкомпьютер гораздо сложнее, чем персональный компьютер, а некоторые суперкомпью­теры даже очень сложно. Кроме этого, выясняется, что разных типов супер­компьютеров довольно много, они не совместимы друг с другом, и на раз­ных суперкомпьютерах совсем разные пользовательские среды. Взвесив предстоящие трудности, пользователи нередко отказываются от использова­ния суперкомпьютеров вообще или переходят к более простым суперком­пьютерам. Если же пользователь все же решается использовать суперкомпь­ютер, ему важно понимать причины появления новых трудностей и пути их преодоления.
Современные суперкомпьютеры возникли не вдруг. Они являются продуктом длительного развития "обыкновенных" компьютеров, к которым, кстати, от­носится и типовой персональный компьютер. Эти "обыкновенные" компью-
14
Часть I. Параллельные вычислительные системы
теры имеют ряд принципиальных узких мест, которые не позволяют достичь сколь угодно большой скорости и иметь сколь угодно большую память. Стремление сделать компьютеры более мощными и развязать узкие места привело, в конце концов, к тем изменениям, которые превратили "обык­новенный" компьютер в суперкомпьютер. Узких мест в компьютерах много, много и способов их развязки. Это породило много типов суперкомпьютеров. К сожалению, за возросшие скорости и объемы памяти приходится платить. Для пользователей эта плата связана, в первую очередь, со значительным ус­ложнением и ухудшением пользовательской среды. Многое из того невиди­мого, что делали "обыкновенные" компьютеры, на суперкомпьютерах пользо­вателям надо делать самим. Просто потому, что проблемы эффективного обслуживания пользовательских задач стали настолько сложными, что пока их не научились хорошо решать в автоматическом режиме.
Чтобы лучше понять причины появления новых трудностей и, возможно, на­метить в дальнейшем пути их преодоления, рассмотрим сначала, как устроен "обыкновенный" компьютер. Мы не будем заниматься детальным и, тем бо­лее, инженерным анализом. Лишь рассмотрим те узкие места, развязывание которых, с точки зрения пользователя, и превращает "обыкновенный" компь­ютер в суперкомпьютер с его достоинствами и недостатками.
§1.1. Немного об устройстве компьютера
За очень редким исключением во всех существующих компьютерах, как "обыкновенных", так и супер, реализована одна и та же конструктивная идея. Она состоит в том, что вся исходная и перерабатываемая информация хранится в компьютерах в форме некоторого множества двоичных разрядов или битое, а любое ее преобразование сводится, в конце концов, к преобра­зованию битов с помощью нескольких простых функций.
Сейчас нереально даже представить такую ситуацию, при которой пользова­тель записывает и читает свою информацию в двоичном виде, а также опи­сывает ее преобразование как преобразование множества битов. В действи­тельности он всегда общается с компьютером на каком-нибудь приемлемом для него языке. Часто пользователь даже не знает, какие процессы происхо­дят в конкретном компьютере при решении его задач. Однако для нас очень важно помнить, что, тем не менее, все эти процессы в любом случае осуще­ствляются на битовом уровне. Частично переход к битовому уровню и об­ратно выполняется автоматически с помощью аппаратных средств, частично программным путем. От того, насколько эффективно реализуются эти пере­ходы, в немалой степени зависит успех в решении пользовательских задач.
Не с каждого входного языка разработчики компьютеров и его програм­много окружения могут автоматически организовать эффективные бинарные процессы. И тогда они начинают требовать от пользователя какие-то допол-
Глава 1. Что скрывают "обыкновенные" компьютеры
15
нительные сведения. Чаще всего дополнительные требования формулируются в специфических компьютерных терминах, не всегда очевидным образом связанных с задачей или методом ее решения. Все это усложняет как вход­ной язык, так и всю процедуру общения с компьютером. Такая ситуация особенно характерна для суперкомпьютеров, но не так уж редко она возни­кает и в связи с "обыкновенными" компьютерами.
Основным информационным элементом в компьютере является слово. Каж­дое слово представляет собой упорядоченный набор битов. Слово может де­литься на байты. Байт означает упорядоченный набор из 8 битов. Число битов в слове называется его длиной. В любом конкретном компьютере все слова имеют одну и ту же длину. В разных компьютерах длина слов может быть разной. Например, в персональном компьютере слово состоит из од­ного байта, в компьютере Сгау-1 оно состоит из 64 битов. Если в слове за­писана какая-то информация, то это означает, что в каждом бите слова за­фиксировано одно из двух возможных его состояний 0 или 1. Совокупность состояний всех битов слова определяет содержимое слова. Устройство, в ко­тором хранится все множество слов, обобщенно называется памятью. Оно может быть простым или сложным, однородным по устройству или разно­родным в зависимости от типа или назначения компьютера. Все слова по­именованы. Имя слова называется адресом. Структура адреса в определен­ном смысле отражает структуру памяти. Разные слова имеют разные адреса. Каждый адрес связан с конкретным физическим местом в памяти.
Если память устроена сложно и, к тому же, разнородна по своему строению, времена обращения к отдельным словам могут варьироваться в очень широ­ких пределах, отличаясь друг от друга не только в разы, но и в десятки, сот­ни и даже тысячи раз. Это исключительно важное обстоятельство и оно не­редко решающим образом определяет время решения задач. К различным вопросам эффективной работы с памятью мы будем возвращаться в этой книге неоднократно.
Основная задача любого компьютера состоит в преобразовании хранящейся в памяти информации. Она всегда реализуется как выполнение последова­тельности простых однозначных функций над содержимым отдельных слов. Как правило, все функции имеют не более двух аргументов. Функции могут использовать и/или изменять как слова целиком, так и их части. Вообще говоря, разные компьютеры могут иметь разные наборы исполняемых функций. Однако очень часто эти наборы функционально совпадают во многом или полностью, различаясь лишь техникой реализации. Наиболее распространенными функциями являются простейшие арифметические операции над числами (сложение, вычитание, умножение и т. д.) и логиче­ские операции булевой алгебры над битами слов (конъюнкция, дизъюнкция и т. д.). Обычно в компьютерной терминологии все функции называются операциями, а значение аргумента, иногда сам аргумент и даже адрес слова, где содержится аргумент, — операндами.
16
Часть I. Параллельные вычислительные системы
В компьютере операция реализуется в виде некоторой электронной схемы. Для обозначения реализаций этих схем используется самая различная тер­минология. Они называются чипом, устройством, процессором, сопроцес­сором и т. п. в зависимости от их сложности, связи друг с другом и даже вкуса конструктора. Мы будем использовать термин "устройство", оставляя другие названия для иных целей. Совокупность устройств, реализующих арифметические и логические операции, называется арифметико-логическим устройством (АЛУ).
Кроме операций над содержанием памяти, компьютер должен осуществлять также и все действия, связанные с организацией процесса преобразования информации. Возможные действия компьютера описываются системой машинных команд. Как и любая другая информация, машинная команда за­писывается как содержимое слова. В описании команды задаются код опе­рации и те операнды, над которыми операция будет проводить действия. Некоторые машинные команды выполняют действия над словами, располо­женными в строго определенном месте, например, в фиксированных реги­страх. Такие команды не требуют явного указания операндов. Система команд всегда устроена таким образом, что выполнение каждой команды однозначно определяет следующую команду. Любой процесс преобразова­ния информации описывается конкретной совокупностью машинных команд из числа возможных для данного компьютера. Эта совокупность на­зывается машинным кодом или программой во внутреннем коде. Тем самым подчеркивается, что процесс описан программой на языке машинных команд, а не как-либо иначе.
Структура машинных команд всегда привязана к структуре компьютера и может быть совсем не похожей на структуру тех действий, которые пользо­ватель предполагает выполнять. Как правило, свои действия пользователь описывает программами на языках высокого уровня. Компьютеры их "не по­нимают". Поэтому, чтобы программы могли быть выполнены, они должны быть переведены, прежде всего, в эквивалентный машинный код. Такой пе­ревод осуществляют специальные программные системы, называемые компиляторами. Компиляторы очень сложны и выполняют очень большой объем работы. От того, как именно они преобразуют программы пользова­теля, решающим образом зависит эффективность реализации получаемого машинного кода и, следовательно, эффективность процесса решения задач.
До того как машинный код начнет исполняться, все команды и необходи­мые данные должны быть помещены или, как говорят, загружены в память. Это осуществляется с помощью специальных команд через так называемые устройства ввода. К ним относятся устройства считывания информации с дискеты и лазерных дисков, сканеры, клавиатура и т. п. Результаты работы компьютера выводятся из памяти также с помощью специальных команд через устройства вывода. К ним относятся устройства записи информации на дискеты и лазерные диски, графопостроители, принтеры и т. п. Сейчас
Глава 1. Что скрывают "обыкновенные" компьютеры
17
существует огромное разнообразие устройств ввода/вывода (УВВ). Но неза­висимо от их конструктивных особенностей УВВ остаются самыми медлен­ными устройствами компьютера. Это обстоятельство вызывает много труд­ных проблем, когда объем входной или выходной информации оказывается очень большим.
После загрузки в память машинного кода и данных компьютер может начи­нать свою работу. Руководство ею осуществляет устройство управления (УУ). Оно содержит необходимые регистры, счетчики и другие элементы, обеспе­чивающие управление перемещением информации между памятью, АЛ У, УВВ и другими частями компьютера. УУ должно решать две противоречи­вые задачи. С одной стороны, УУ должно работать достаточно быстро, что­бы не задерживать процесс решения задачи. С другой стороны, УУ должно управлять самыми различными устройствами, в том числе, удаленными друг от друга и работающими одновременно. Поэтому УУ всегда устроено по ие­рархическому и распределенному принципу. Самый быстрый уровень — это те электронные схемы, которые расшифровывают содержание очередной команды, выделяют код операции и адреса операторов, выбирают для ана­лиза одну или несколько последующих команд. Другой уровень УУ — это, например, схемы, управляющие выполнением операций АЛУ. Совокупность устройства управления, арифметико-логического устройства и блока наибо­лее быстрой памяти называется центральным процессором.
Во всех современных компьютерах реализуются два способа управления: схемный и микропрограммный. Схемный используется тогда, когда возни­кает необходимость очень быстрого, но относительно простого управления. Микропрограммный — в случае сложного управления. В целом УУ занимает центральное место в управлении работой компьютера. Но оно обросло многими программными и микропрограммными системами, связанными с решением частных управленческих задач. Некоторые из них очень сложны. Например, система управления файлами по существу представляет собой специализированную вычислительную машину, которая имеет свою память и связана с памятью и магнитными дисками основного компьютера. Еще более сложной является система управления графической информацией и т. п. Совокупность программных систем, обеспечивающих управление рабо­той компьютера, принято называть операционной системой. Для всех совре­менных компьютеров операционная система неотделима от собственно компьютера и является его программным продолжением. Один и тот же компьютер может иметь несколько операционных систем. Например, для персональных компьютеров широко распространены операционные систе­мы MS-DOS, Windows и Unix. Каждая из них имеет свои особенности и ре­шает свои задачи.
Такова общая схема "обыкновенного" компьютера. Она представлена на рис 1.1. Конечно, эта схема идеализирована и лишь иллюстрирует связи меж­ду отдельными частями компьютера. Тем не менее, в ней отражено все то, что
18
Часть I. Параллельные вычислительные системы
мы будем обсуждать более детально, выявляя "узкие места". Отметим, что компьютеры подобного типа кроме названия "обыкновенный" имеют также названия "однопроцессорный", "фон-неймановской архитектуры" и др.
Перейдем теперь к более детальному обсуждению отдельных элементов "обыкновенного" компьютера.
УСТРОЙСТВА ВВОДА/ВЫВОДА
1
АРИФМЕТИКО-ЛОГИЧЕСКОЕ УСТРОЙСТВО
УСТРОЙСТВО УПРАВЛЕНИЯ
КОМПИЛЯТОР
ОПЕРАЦИОННАЯ СИСТЕМА
ПРОГРАММА
Рис. 1.1. Общая схема компьютера
Вопросы и задания
1.   Приведите примеры физических, электрических и механических устройств, имеющих р = 2, 3, 8, 10 устойчивых состояний.
2.   На основе предложенных устройств постройте схемы выполнения арифметиче­ских операций над целыми числами.
3.   Исследуйте любой калькулятор как компьютер. Каковы в нем центральный про­цессор, память, операционная система, форма представления чисел?
4.   Предположим, что компьютер имеет универсальный процессор и память объе­мом в 1 или 2 ячейки. Какие задачи можно решать на таком компьютере?
5.   Знаете ли вы, что первые лекции об автоматизации процесса вычислений прочи­таны английским математиком и экономистом Ч. Бэббиджем в 1840 г.?
6.   Условный переход является одной из важнейших команд управления. Знаете ли вы, что впервые идея использовать условные переходы была высказана ирланд­ским бухгалтером П. Лудгейтом в 1903 г.?
Глава 1. Что скрывают "обыкновенные" компьютеры
19
7.   Рассмотрите любую систему машинных команд. С помощью команд, реализую­щих логические операции и операции безусловного перехода, составьте про­грамму, реализующую операцию условного перехода.
8.   Знаете ли вы, что первая вычислительная машина была построена немецким ученым К. Цузе в 1936 г. и была она механической?
9.   Знаете ли вы, что первая отечественная электронная вычислительная машина МЭСМ была построена коллективом, возглавляемым академиком С. А. Лебедевым, в 1951 г.?
§ 1.2. Операции с числами
Есть одно обстоятельство, общее для всех компьютеров, осуществляющих работу с числами. Это — формы представления чисел. С одной стороны, они диктуются природой самих чисел. С другой — битовым представлением информации.
Известно [5], что любое вещественное число х ф 0 можно единственным об­разом представить в виде бесконечного ряда по степеням числа 2, т. е.
ь
*Хо'-2''-                                       (i-i)
Здесь коэффициенты at принимают одно из значений 0 или 1, число b за­висит от числа х и а/, = 1. При хранении чисел щ в компьютере их можно отождествлять с двоичными разрядами слова. Поэтому и коэффициенты at часто называют двоичными разрядами числа х. При этом коэффициент щ на­зывается /-ым разрядом. Вместо ряда (1.1) число х также записывают про­сто перечислением коэффициентов
x = ±abab-h..ao,a-ia-2...,                                    (1.2)
иногда с указанием знака, иногда без него. Не всегда ставится и запятая, отделяющая коэффициенты при неотрицательных степенях двойки в (1.1) от коэффициентов при отрицательных степенях.
Никакие технические решения не дают возможность хранить все двоичные разряды числа, т. к. в общем случае их бесконечно много. Поэтому числа представляются лишь конечным набором старших разрядов. Другими сло­вами, от числа (1.2) остается только набор коэффициентов аь...а3 для неко­торого s < Ь. Следовательно, любое число, имеющее в своем разложении (1.1) большое количество значащих разрядов, должно заменяться каким-то образом числом, имеющим отведенное количество разрядов. Эта операция замены называется округлением чисел. Разность между округленным и округ­ляемым числами называется ошибкой округления.
20
Часть I. Параллельные вычислительные системы
Теоретически операция округления числа может быть либо однозначно опре­деленной функцией округляемого числа, либо случайной функцией. Как слу­чайная функция она реализуется в компьютерах исключительно редко, т. к. в этом случае невозможно при повторении расчетов получать одни и те же ре­зультаты. Как однозначно определенная функция операция округления может быть реализована самыми разными способами, к чему есть веские причины. Операция округления исключительно важна для пользователя. Чем меньше ошибка округления, тем в общем случае точнее результат. Наилучшее округ­ление очевидно, и реализуется оно обычным "школьным" правилом. В этом случае ошибка округления никогда не превосходит половины последнего раз­ряда в округленном числе. Но реализация данного правила в компьютере тре­бует дополнительно и аппаратуры, и времени. В погоне за скоростью выпол­нения арифметических операций инженеры часто реализуют более простые операции округления. Иногда они делают это очень неудачно. Например, просто плохо была выполнена операция округления в первых моделях знаме­нитого компьютера Сгау-1. Только резкая критика со стороны пользователей заставила изменить операцию округления в последующих моделях.
Конечно, ошибки округления по отношению к самим числам являются ма­лыми поправками. При выполнении различных операций они вносятся в результат почти всегда. И эти малые поправки радикально меняют свойства операций. Если при точном выполнении операции сложения, вычитания, умножения и деления обладают свойствами ассоциативности, коммутатив­ности и дистрибутивности, то эти свойства при выполнении тех же опера­ций с округлением пропадают. Факт, который доставляет очень много хло­пот при конструировании численных методов.
Представление чисел в виде двоичных разложений (1.1), безусловно, продикто­вано двоичным представлением любой информации в компьютерах. К сожале­нию, оно имеет ряд неустранимых изъянов. Доказано, в частности, что при вы­полнении таких арифметических операций, как сложение и вычитание, операция округления, реализованная по любому неслучайному правилу, дает ошибки с ненуле­вым смещением. Это приводит к ненулевым смещениям ошибок и в окончатель­ных результатах, о чем не следует забывать. С точки зрения ошибок округления более привлекательным является разложение чисел по степеням числа 3. Под­робнее с округлением чисел можно познакомиться в книге [5].
Если интересно знать, как реализована операция округления на конкретном компьютере, проведите на нем следующий эксперимент. При точных вы­числениях рекуррентная формула
Уо = 1, У/t = (Ук-\/к)к, к>1
дает ук = 1 для всех к. Не изменяя положения скобок, проведите расчеты по ней на компьютере в течение 10—15 минут и выведите для некоторых к зна­чения ошибки
Ч = \Ук ~ 1|-
Глава 1. Что скрывают "обыкновенные" компьютеры
21
Здесь ук есть реально вычисленная величина. Скорее всего, е^ будет линей­но расти с ростом к. Но ведь некоторые задачи считаются часами, сутками и даже дольше. Теперь задайтесь вопросом о том, можно ли доверять полу­ченному решению. Если возникли сомнения, вы на правильном пути. Тогда полистайте книгу [5] более внимательно.
Требование унифицированного выполнения арифметических операций при­водит к унификации изображения чисел в компьютерах. Пусть для каждого числа отводится х двоичных разрядов памяти. Обычно они составляют слово или несколько слов. Прежде всего должно быть установлено взаимно одно­значное соответствие между отдельными битами и двоичными разрядами числа. В зависимости от того, является ли это соответствие одним и тем же для всех чисел или зависит от числа, различают два основных способа пред­ставления чисел в компьютере, называемых соответственно представлением с фиксированной и плавающей запятой.
Предположим, что т двоичных разрядов памяти служат для изображения х последовательных разрядов чисел, причем положение запятой среди них фиксировано и является одним и тем же для всех чисел. Будем считать, что на изображение разрядов, стоящих слева от запятой, отводится г битов, где г > 0. Такой способ представления чисел называется представлением с фиксированной запятой. С помощью этого способа можно точно запомнить двоичное разложение любого из чисел, имеющих не более г ненулевых раз­рядов слева от запятой и не более \ — г ненулевых разрядов справа от запя­той. Все эти числа лежат в диапазоне
-2г<х< 2Г.
Один из недостатков представления чисел с фиксированной запятой виден сразу. Если число много меньше 2Г по модулю, то большая часть из отве­денных х битов изображает старшие нулевые разряды и фактически не ис­пользуется. Поэтому аппроксимация малых чисел числом с фиксированной запятой связана с большой относительной ошибкой. Однако для чисел, близких к 2Г по модулю, используются все т битов. В этом случае относи­тельная ошибка является минимальной. Абсолютная ошибка представления чисел с фиксированной запятой всегда лежит в одних и тех же пределах не­зависимо от величины чисел. Достоинством представления чисел с фикси­рованной запятой является относительная простота алгоритмов сложения и умножения. По существу они ничем не отличаются от хорошо известных "школьных" алгоритмов выполнения этих операций.
Представление чисел с плавающей запятой заключается в следующем. При­нимая во внимание (1.1), запишем число х в виде
х = а2ь.                                                  (1.3)
22
Часть I. Параллельные вычислительные системы
Тогда будем иметь, что
1/2 < |я| < 1.                                              (1.4)
Число а называется мантиссой числа х, число b — его двоичным порядком. Если х = О, то считается, что а = 0, а число b не определено. Пусть на изо­бражение порядка без знака отводится г битов, на изображение мантиссы без знака % — г битов. Представив порядок и мантиссу как числа с фиксиро­ванной запятой, мы получаем представление числа х с плавающей запятой. При изображении мантиссы и порядка нет отмеченной выше потери отно­сительной точности, т. к. в обоих случаях положение запятой строго опреде­лено. Порядок всегда является целым числом, а первый после запятой дво­ичный разряд мантиссы всегда равен 1. Порядок представляется точно. Мантисса будет точно представлена только для чисел, которые в двоичном разложении имеют не более % — г ненулевых разрядов. Все числа, которые могут быть представлены с плавающей запятой, всегда представляются с высокой относительной точностью. Это числа из диапазона
Tr <\х\<2г~1.
Минимальное положительное число со, которое можно представить с пла­вающей запятой, называется машинным нулем. Ясно, что в нашей трактовке представления чисел
ю = 2-2\ Все числа из диапазона
1' <х< Т1'
изображаются нулем. В разных компьютерах под порядок и мантиссу отво­дятся разные числа разрядов. В частности, в упоминавшемся уже компьюте­ре Сгау-1 на мантиссу со знаком выделено 49 разрядов, на двоичный поря­док — 15 разрядов. Огромный диапазон представляемых чисел и большая их относительная точность, безусловно, определены ориентацией компьютера Cray-1 на научно-технические расчеты.
В современных компьютерах используются оба способа представления чи­сел. Операции над числами с фиксированной запятой выполняются быст­рее, чем над числами с плавающей запятой. Это связано с тем, что при реа­лизации операций с плавающей запятой по существу приходится выполнять все действия с парами чисел с фиксированной запятой. Рассмотрим два числа
х = а2ь, у = c2d
с плавающей запятой. Имеем
ху = {ас)Ъь+<1).
Глава 1. Что скрывают "обыкновенные" компьютеры                                                        23
Если 1/2 < \ас\ < 1, то мы сразу получаем представление числа ху в форме с плавающей запятой. Но если 1/4 < \ас\ < 1/2, то нужное представление чис­ла ху будет другим. Именно,
ху = (2ас)2*+<4
Естественно, что при реализации операции умножения чисел с плавающей запятой обе ситуации должны осуществляться несколько по-разному. По­этому в процессе реализации операции умножения чисел должны быть ветвления.
Еще более сложно осуществляется операция сложения чисел с плавающей запятой. Пусть для определенности b > d. Имеем
(х + у) = (а + с2^-Щ2ь,                                     (1.5)
причем всегда выполняется неравенство
\а+ с2^~ь)\ < 2.
Если
+ c2(rf"*)| = О,
то представление (1.5) сразу дает форму числа х + у с плавающей запятой. Предположим теперь, что
2Г~1 < \а + с2^~ь)\ < 2Г
для какого-нибудь целого числа г. Представим число х + у в следующем виде:
х +у = ((а + с2(('-ь))2-г)2(ь+г).                               (1.6)
Так как выполняются соотношения
1/2 < \{а + c2(rf-*))2-''| < 1,
то (1.6) дает форму с плавающей запятой для числа х + у. Здесь b + г есть порядок числа х + у, выражение
(а + с2^-ь))2~г
задает его мантиссу. Процесс реализации операции сложения также имеет ветвления, причем более сложные, чем у операции умножения.
Представление чисел с плавающей запятой оказывается предпочтительным в тех случаях, когда приходится иметь дело с числами из очень большого диа­пазона. Такая ситуация типична при проведении научно-технических расче­тов. Однако довольно часто встречаются и другие ситуации. Например, в финансовых расчетах, задачах количественного учета заранее известен диа­пазон рассматриваемых чисел. Здесь вполне может использоваться пред­ставление чисел с фиксированной запятой. Необходимость экономии аппа­ратуры также приводит к фиксированной запятой. Умелое ее использование позволяет добиться большей скорости и большей точности решения задачи, чем использование плавающей запятой. Еще большего эффекта можно дос-
24
Часть I. Параллельные вычислительные системы
тичь путем разумного сочетания вычислений обоих типов. Однако мы не будем сколько-нибудь подробно останавливаться здесь на этих вопросах.
Устройства, выполняющие операции сложения/вычитания, называются сумматорами, выполняющие операции умножения — умножителями. Все эти устройства наряду с устройствами, реализующими логические операции, входят в состав АЛУ. Операции с фиксированной запятой выполняются бы­стрее аналогичных операций с плавающей запятой. Операции сложения вы­полняются быстрее операций умножения. Любые логические операции реа­лизуются быстрее операций сложения/вычитания с фиксированной запятой. На каждом конкретном компьютере длительности выполнения стандартных операций отличаются друг от друга не более, чем в несколько раз.
Вопросы и задания
1.   Докажите, что любое вещественное число х можно представить в виде троич­ного разложения
ь
х= Xfl/3''
/ = -оо
где числа at принимают одно из значений 0, 1, — 1.
2.   Докажите, что знак числа х в п. 1 совпадает со знаком числа ab, если ab ф 0.
3.   Обозначим через xs число, получаемое из троичного разложения числа х отбра­сыванием всех разрядов, начиная с (s — 1)-го. Докажите, что для троичного раз­ложения xs > х, если первый из ненулевых отброшенных разрядов отрицатель­ный, и xs < х, если он положительный.
4.   Обладает ли двоичное разложение (1.1) аналогичным свойством?
5.   Докажите, что число (1/2)3* не может быть задано конечной дробью по степе­ням числа 3.
6.   Докажите, что в обозначениях пп. 3, 4 имеют место неулучшаемые оцен­ки \х — xs\ < 2s для двоичного разложения и \х — xs\ < (1/2)3* для троичного раз­ложения.
7.   В какой системе, двоичной или троичной, легче выполнять операцию округле­ния, если стремиться к минимизации абсолютной ошибки?
8.   Знаете ли вы, что первая в мире электронная вычислительная машина "Сетунь", работающая на троичной системе, была сконструирована в Московском универ­ситете к.т.н. Н. П. Брусенцовым?
9.   Предложите какой-либо способ изображения чисел, отличный от способа с фиксированной или плавающей запятой. Насколько сложно при этом выпол­няются операции над числами?
10. Почему числа из интервала ( —со, со), где со есть машинный ноль, заменяются нулем, а не каким-либо другим числом?
Глава 1. Что скрывают "обыкновенные" компьютеры
25
11.   Можно ли в представлении чисел с плавающей запятой вычислить число со !?
12.   При точных вычислениях (1 + V2 )2 = 3 + 2 V2 . Если подставить слева и справа вместо V2 любое его приближенное значение, то результаты всегда будут раз­личными. Какое же из эквивалентных выражений (1 +V2 )2 или 3 + 2V2 надо брать за основу вычислений?
13.   Зависит ли состав множества приближенных решений задачи, определяемого порядком выполнения операций, от выбранного способа округления чисел?
§ 1.3. Иерархия памяти
Предположим, что на компьютере решается какая-нибудь очень большая задача, например, система линейных алгебраических уравнений с матрицей максимального размера. Если используется метод Гаусса, то исключать эле­менты можно в самых различных порядках. При этом общее число выпол­няемых арифметических операций остается одним и тем же или меняется не более чем в 2—3 раза. Однако, пропуская разные варианты метода для од­ной и той же системы, замечаем, что общее время решения задачи меняется в десятки раз и более. Другая реальная ситуация. Пусть зафиксирован ка­кой-либо вариант метода Гаусса, но теперь решаются системы различных размеров. При этом ожидается, что общее время решения задачи будет ме­няться пропорционально общему числу арифметических операций. Но и в этом случае встречаются неожиданности. Иногда для некоторых порядков время решения задачи может оказаться существенно большим.
На общее время решения задачи влияет много факторов. Но главные из них — это время выполнения арифметических операций и время взаимодей­ствия с памятью. В методах типа Гаусса вся совокупность арифметических операций известна заранее. Поэтому, если время решения задачи почему-то неоправданно возрастает, то, скорее всего, есть определенная неаккурат­ность в использовании памяти. А это означает, что имеются причины для более глубокого обсуждения строения памяти и принципов ее использова­ния. Тем более, если возникает необходимость решать большие задачи.
Как бы ни была устроена память в компьютере, каждый бит информации моделируется простейшим техническим элементом, принимающим два со­стояния. Плотность расположения таких элементов исключительно высока. Например, в случае электронного устройства памяти число элементов на одном кристалле может достигать многих миллионов. Однако это не означа­ет, что память можно сделать сколь угодно большой.
Главное требование к памяти — обеспечение малого времени доступа к от­дельным словам. Вообще говоря, оно должно быть намного меньше, чем время выполнения операций над словами, в крайнем случае — соизмери­мым с ним. Ту часть памяти, для которой характерное время доступа замет-
26
Часть /. Параллельные вычислительные системы
но меньше времени выполнения операций, называют быстрой, остальную — медленной. Большая часть образует адресуемую память, меньшая — неадре­су емую. В адресуемой памяти каждое слово имеет адрес. Эта часть памяти доступна пользователю. Работая с ней, можно как записывать информацию, так и считывать. Она также называется оперативной памятью (ОП), а соот­ветствующее техническое устройство — оперативным запоминающим устрой­ством (ОЗУ).
Неадресуемая память недоступна пользователям. В ней различают постоян­ную память и сверхбыструю память. В постоянную память ничего нельзя за­писать, но из нее можно многократно считывать записанную ранее инфор­мацию. Обычно в ней хранятся команды запуска компьютера, различные служебные программы и т. п. Использование оптических технологий откры­вает возможность создания постоянной памяти огромных размеров. В этом случае в ней можно будет хранить, например, весь багаж библиотек про­грамм широкого назначения.
Сверхбыстрая память отличается от оперативной существенно меньшим временем доступа. Чаще всего она имеет 1—2 уровня. Самый быстрый уро­вень — это регистровая память. Она имеет очень небольшой объем, изме­ряемый единицами или десятками слов. В ней хранятся те результаты вы­полнения операции, которые необходимы для реализации команды, непосредственно следующей за исполняемой. По существу регистровая па­мять является неотъемлемой частью АЛУ. Почти такой же быстрой оказыва­ется кэш-память. Она является своеобразным буфером между регистровой памятью и оперативной памятью. В современных компьютерах ее объем до­ходит до миллиона слов. В ней хранятся те результаты выполнения опера­ции, которые могут потребоваться для реализации команд, вскоре следую­щих за исполняемыми. Управляет использованием сверхбыстрой памяти устройство управления. На стадии перевода программ в машинный код компилятор может выстраивать команды таким образом, чтобы эффект от использования сверхбыстрой памяти был максимальным. Но это делается не всегда.
Чтобы добиться малости времени доступа к оперативной памяти, необходи­мо выполнить много условий, в частности, минимизировать время прохож­дения управляющих сигналов к ее элементам. Среди прочего, в существую­щих технологиях это зависит от длин соединений. При большом числе элементов даже теоретически длины соединений нельзя сделать равномерно малыми. При этом неравномерность тем больше, чем больше число элемен­тов. Уже одно это обстоятельство должно приводить к неоднородности па­мяти с точки зрения ее использования. При конструировании оперативного запоминающего устройства используют различные технические решения для уменьшения нежелательного эффекта большого разброса времени доступа. В частности, ОЗУ делают иерархическим, что соответствует делению памяти на кубы, блоки, секции, страницы и т. п. Чем выше уровень иерархии, тем
Глава 1. Что скрывают "обыкновенные" компьютеры
27
меньше разброс времен доступа к отдельным словам одного раздела памяти данного уровня. На самом высоком уровне выделяют группы слов, доступ к которым может быть осуществлен или одновременно, или с минимальной разностью времен. Чаще всего это слова с последовательными физическими адресами или адресами, меняющимися с постоянным шагом.
Таким образом, стремление сделать оперативную память "обыкновенного" компьютера достаточно большой приводит к усложнению ее структуры. В свою очередь, это неизбежно увеличивает разброс времен доступа к от­дельным словам. Наилучший режим работы с оперативной памятью обычно поддерживается как операционными системами, так и компиляторами. Но для программ произвольного вида он не всегда может быть реализован. Что­бы его использовать, составитель программ должен соблюдать вполне опреде­ленные правила, о существовании которых следует узнать заранее.
Если для реализации алгоритма нужно не просто составить какую-нибудь программу, а добиться, чтобы она работала максимально быстро, то соблю­дение этих правил может дать ощутимый эффект. Как уже отмечалось, многие компьютеры особенно быстро осуществляют доступ к словам с по­следовательными физическими адресами. Пусть, например, программа со­ставляется на языке Fortran. С точки зрения этого языка безразлично, про­водить обработку элементов массивов по строкам или столбцам. Безразлично это и с точки зрения сложности получаемой программы. Все различие сводится лишь к тому, что в одном случае мы имеем дело с преоб­разованием элементов каких-то матриц, а в другом — транспонированных к ним. Принимая во внимание специфику доступа к памяти, компиляторы с языка Fortran всегда располагают массивы в физической памяти последова­тельно по адресам, столбец за столбцом, слева направо и сверху вниз. Пред­положим, что реализуется алгоритм решения систем линейных алгебраиче­ских уравнений методом Гаусса. В этом случае время работы программы будет определяться ее трехмерными циклами. В зависимости от того, распо­ложена матрица системы в массиве по столбцам или строкам, внутренние циклы программы будут также осуществлять обработку элементов массивов по столбцам или строкам. Времена реализации обоих вариантов могут раз­личаться весьма заметно, иногда в несколько раз. Факт существенный, если речь идет о разработке оптимальных программ.
Проведенное обсуждение касается, главным образом, того случая, когда при решении задачи удается ограничиться лишь использованием достаточно бы­строй оперативной памяти. Положение становится значительно сложнее, если приходится использовать медленную память.
В большинстве языков программирования отсутствует понятие размера па­мяти. Это связано с желанием не делать программы зависимыми от пара­метров компьютеров. Тем не менее, на каждом конкретном компьютере пользователю может быть предоставлена память лишь ограниченного разме-
28
Часть I. Параллельные вычислительные системы
ра. Стремление максимально увеличить ее размер приводит к необходимо­сти использовать не только быструю оперативную память, но и медленную память. На современных компьютерах медленная память чаще всего реали­зуется на жестких дисках. Ее размеры во много раз больше размеров опера­тивной памяти. Однако во много раз больше у нее и время доступа. При решении предельно больших задач основную часть данных неизбежно при­ходится хранить в медленной памяти. Но в любом компьютере операции выполняются лишь над данными, находящимися в быстрой памяти. Поэто­му в процессе решения больших задач нужно обязательно и, как правило, многократно переносить данные из медленной памяти в быструю и обратно. Это означает, что время решения больших задач определяется двумя состав­ляющими: временем выполнения операций алгоритма и временем осущест­вления обменов между медленной и быстрой памятью. Основная проблема использования медленной памяти состоит в том, что неудачная организация обменов может привести к тому, что вторая составляющая будет превосхо­дить первую в десятки, сотни и даже тысячи раз. Напомним, что неудачная организация работы с оперативной памятью может увеличить время реали­зации алгоритма только в несколько раз.
Квалифицированная организация обменов между медленной и быстрой па­мятью базируется на трех основных принципах: обращаться к медленной памяти как можно реже, переносить за обмен как можно больше данных, при каждом переносе в быструю память обрабатывать данные как можно дольше. Осуществить оптимальный баланс этих принципов — трудная зада­ча. Чтобы не думать о ее решении, пользователю нередко предлагается рабо­тать с виртуальной памятью. Эта память условная. Она имеет вполне опре­деленный размер и вполне определенную систему адресации слов. Виртуальная память может рассматриваться как однородная или иметь не­которую структуру. Для нее может быть разработана система правил пред­почтительного размещения данных. Как конкретно отображается виртуаль­ная память на физическую и как в действительности организуются обмены между медленной и быстрой памятью, зависит от соответствующих алгорит­мов, заложенных в операционную систему. Кое-что об этих алгоритмах можно узнать из документации по компьютеру. Однако следует помнить, что, как правило, они универсальные и поэтому либо не учитывают совсем, либо учитывают очень слабо особенности конкретных программ.
Отсюда следует, что при решении больших задач нельзя безоговорочно дове­рять организацию обменов с медленной памятью операционной системе. Сначала лучше проверить качество ее работы с помощью следующего экс­перимента. На каком-нибудь большом примере, отражающем существо ре­шаемой задачи, нужно замерить в монопольном режиме астрономическое время реализации примера на компьютере и подсчитать время выполнения необходимых операций. Если первое время превышает второе не более чем в несколько раз, работу операционной системы по организации обменов
Глава 1. Что скрывают "обыкновенные" компьютеры
29
можно признать удовлетворительной для данного класса задач. В противном случае их надо организовывать самостоятельно, ориентируясь на вышеизло­женные принципы. При этом стоит помнить, что не для каждой задачи вре­мя обменов с медленной памятью можно довести до низкого уровня.
В качестве подтверждения важности вопросов, касающихся обменов с мед­ленной памятью при решении больших задач, рассмотрим один пример из истории разработки методов и программ для систем линейных алгебраиче­ских уравнений. Несмотря на его сорокалетнюю давность, он и сейчас оста­ется весьма поучительным.
В середине 60-х годов прошлого столетия в нашей стране были широко рас­пространены вычислительные машины типа М-20, построенные коллекти­вом, возглавляемым академиком С. А. Лебедевым. По тем временам это бы­ли вполне современные компьютеры, обладающие производительностью около 20 тыс. операций в секунду. Однако их оперативная память была ма­лой и позволяла решать системы линейных алгебраических уравнений с плотной матрицей порядка всего лишь 50—100. Требования практики за­ставляли искать способы решения систем значительно большего порядка. В качестве медленной памяти на этих машинах использовались магнитные барабаны. Они играли тогда такую же роль, какую сейчас в персональном компьютере играют жесткие диски. Естественно, с поправкой на объем хра­нимой информации. Под этот тип медленной памяти была разработана спе­циальная блочная технология решения больших алгебраических задач [13]. Она позволяла с использованием только 300 слов оперативной памяти ре­шать системы практически любого порядка. Точнее, такого порядка, при котором матрица и правая часть могли целиком разместиться в медленной памяти. При этом системы решались почти столь же быстро, как будто вся информация о них на самом деле была размещена в оперативной памяти. Созданные на основе данной технологии программы были весьма эффек­тивны. В частности, на машинах типа М-20 они позволяли решать системы 200-го порядка всего за 9 минут.
В конце 60-х годов прошлого столетия появилась машина БЭСМ-6. Это бы­ла одна из лучших вычислительных машин в Европе. Она обладала произ­водительностью около одного миллиона операций в секунду, т. е. была бы­стрее машин типа М-20 примерно в 50 раз. Разработана БЭСМ-6 была коллективом под руководством академика С. А. Лебедева, его заместителей В. А. Мельникова и Л. Н. Королева. Среди математического обеспечения машин БЭСМ-6 были и стандартные программы для решения системы ли­нейных алгебраических уравнений большого порядка с использованием медленной памяти. Ожидалось, что с их помощью можно решать большие системы, если и не в 50, то хотя бы в несколько раз быстрее. Однако опыт­ная проверка дала удивительные результаты. Система 200-го порядка реша­лась за 20 минут. Такова цена неаккуратного использования программиста-
30
Часть I. Параллельные вычислительные системы
ми медленной памяти. Конечно, со временем на машине БЭСМ-6 тоже появились хорошие программы. Но при этом пришлось вмешиваться в ра­боту операционной системы.
Заметим, что М-20 и БЭСМ-6, эти пионеры вычислительной техники, отно­сятся к классу "обыкновенных" компьютеров. Но все обсуждаемые выше проблемы являются актуальными и для современных компьютеров того же класса. В этом может легко убедиться каждый. Сравните производитель­ность своего персонального компьютера и машины М-20. Теперь решите на своем компьютере систему линейных алгебраических уравнений 200-го по­рядка методом Гаусса и измерьте время ее решения. Далее сравните отно­шение производительностей и отношение времен решения систем для обоих компьютеров. И убедитесь, что сравнение далеко не в пользу вашего ком­пьютера. Тогда резонно задать вопрос, почему же такое может произойти. Если же вы решите систему большого порядка с использованием жесткого диска в качестве медленной памяти, то результат сравнения с учетом разли­чия в порядке систем окажется еще хуже. И снова задайте себе тот же во­прос. То, что здесь обсуждалось, как раз лежит в русле поиска ответа.
Вопросы и задания
1.   Знаете ли вы, что в качестве внешней памяти первых электронных вычислитель­ных машин использовалась магнитная лента?
2.   Пусть вычисляются матрицы С = АВ и D = ВА как произведение двух квадрат­ных матриц А и В порядка п. Предположим, что матрицы А, В, С, D располага­ются во внешней памяти подряд соответственно по строкам, столбцам, строкам и строкам. Постройте соответствующие алгоритмы, если для их реализации мож­но использовать только О(п) ячеек оперативной памяти.
3.   Что меняется в алгоритме вычисления матрицы D, если она должна быть распо­ложена по столбцам?
4.   Для пп. 2, 3 получите оценку времени работы с внешней памятью при вычислении матриц С, D, если в качестве внешней памяти используется магнитная лента.
5.   Знаете ли вы, что впервые внешняя память на жестких дисках была разработана в 1956 г., каждый диск имел объем всего лишь в несколько мегабайт и размеры с письменный стол?
6.   Для пп. 2, 3 получите оценку времени работы с внешней памятью при вычисле­нии матриц С, D, если в качестве внешней памяти используется жесткий диск.
7.   Знаете ли вы, что впервые концепция виртуальной памяти была реализована в 1962 г. на машине ATLAS?
8.   Если вам доступен аппарат виртуальной памяти, вычислите матрицы С, D из пп. 2, 3 для самых больших значений п. Сравните времена реализации алгорит­мов при одних и тех же значениях п, но разных расположениях матриц. Как вы можете объяснить различие времен?
Глава 1. Что скрывают "обыкновенные" компьютеры
31
§ 1.4. Языки программирования и программы
Мы уже говорили о том, что компьютер способен выполнять лишь про­граммы, написанные в машинных командах. Однако программировать в машинных командах исключительно трудно. Прежде всего, из-за того, что структура команд чаще всего совсем не похожа на структуру тех действий, которыми пользователь предполагает описывать алгоритмы решения своих задач. Если составлять программы в машинных командах, пользователю не­избежно придется моделировать какими-то последовательностями команд каждое из своих действий. Но у этого способа программирования есть одно несомненное достоинство: он позволяет создавать самые эффективные про­граммы. Объясняется это тем, что в данном случае имеется возможность тщательным образом учитывать совместно конкретные особенности как компьютера, так и задачи.
Рядовому пользователю не приходится программировать в машинных коман­дах. У тех же специалистов, которым требуется создавать эффективные программы, такая потребность может появиться. Например, она часто воз­никает у разработчиков библиотек стандартных программ широкого назна­чения. Чтобы в какой-то мере облегчить труд таких специалистов, им пред­лагается программировать в некоторой системе символьного кодирования, называемой автокодом. По существу, это система тех же машинных команд, но с более удобной символикой. Кроме этого, в автокод могут быть введены дополнительные инструкции, моделирующие простейшие и наиболее часто встречающиеся комбинации машинных команд. Конечно, на компьютере имеется компилятор, который переводит программы, написанные на авто­коде, в машинный код.
Автокод — это простейший язык программирования или, как его называют иначе, язык самого низкого уровня. Широко программировали на нем толь­ко на первых компьютерах. Массовое программирование на автокоде пре­кратилось по следующим причинам. Как отмечалось, программирование на нем очень трудоемко. Поэтому над автокодом стали создаваться различные языковые надстройки, называемые языками высокого уровня. Основная за­дача этих языков заключается в облегчении процесса программирования путем замены непонятных инструкций автокода другими инструкциями, более приближенными к пользовательским. Вершиной языков высокого уровня являются проблемно-ориентированные языки. Их инструкции позво­ляют пользователям писать программы просто в профессиональных терми­нах конкретных предметных областей. На каждом компьютере автокод, как правило, один. Языков высокого уровня, особенно проблемно-ориенти­рованных, достаточно много. Компиляторы с них стали сложными иерархи­ческими программными системами. Обратной стороной упрощения языков
32
Часть /. Параллельные вычислительные системы
программирования стало усложнение алгоритмов преобразования программ в машинный код и, как следствие, потеря программистом возможности кон­тролировать эффективность создаваемых им программ. Это исключительно важное обстоятельство, и мы будем возвращаться к нему неоднократно, принимая во внимание нашу нацеленность на решение больших задач.
С точки зрения пользователя программировать на автокоде не только труд­но, но и нецелесообразно из-за его ориентации на конкретный компьютер или, как говорят по другому, компьютерной зависимости. На разных моделях компьютеров автокоды разные. Универсального транслятора, переводящего программу из одного автокода в другой, не существует. Поэтому, переходя от одного типа компьютеров к другому, пользователю приходится перепи­сывать заново все программы, написанные на автокоде. Этот процесс не только сверхсложный, но и крайне дорогой. Следовательно, одной из важ­нейших задач, стоящих перед разработчиками языков программирования высокого уровня, было создание языков, не зависящих от особенностей конкретных компьютеров. Такие языки появились в большом количестве. К ним относятся, например, Algol, Fortran, С и др. Они так и называются — машинно-независимые, что вроде бы подчеркивает их независимость от ком­пьютеров. Еще их называют универсальными, что указывает на возможность описывать очень широкий круг алгоритмов.
Создание машинно-независимых языков программирования привело к по­явлению на рубеже 50—60-х годов прошлого столетия замечательной по своей красоте идеи. Традиционные описания алгоритмов в книгах нельзя без предварительного исследования перекладывать на любой компьютерный язык. Как правило, они неточны, содержат много недомолвок, допускают неоднозначность толкования и т. п. В частности, из-за предполагаемых свойств ассоциативности, коммутативности и дистрибутивности операций над числами в формулах отсутствуют многие скобки, определяющие поря­док выполнения операций. Поэтому в действительности книжные описания содержат целое множество алгоритмов, на котором разброс отдельных свойств может быть очень большим. В результате, как правило, трудоемких исследований из данного множества выбирается какой-то один алгоритм, обладающий вполне приемлемыми характеристиками, и именно он про­граммируется.
В программе любой алгоритм всегда описывается абсолютно точно. В его разработку вкладывается большой исследовательский труд. Чтобы избавить других специалистов от необходимости его повторять, надо этот труд сохра­нять. Программы на машинно-независимых языках высокого уровня как раз удобны для выполнения функций хранения. Идея состояла в том, чтобы на таких языках накапливать багаж хорошо отработанных алгоритмов и про­грамм, а на каждом компьютере иметь компиляторы, переводящие про­граммы с этих языков в машинный код. При этом вроде бы удавалось ре-
Глава 1. Что скрывают "обыкновенные" компьютеры
33
шить сразу две важные проблемы. Во-первых, сокращалось дублирование в программировании. И, во-вторых, автоматически решался вопрос о nopma-бельности программ, т. е. об их переносе с компьютера одного строения на компьютер другого строения. Вопрос, который был и остается весьма акту­альным, т. к. компьютеры существенно меняют свое строение довольно час­то. При реализации данной идеи функции по адаптации программ к осо­бенностям конкретных компьютеров брали на себя компиляторы. Как следствие, пользователь освобождался от необходимости знать устройство и принципы функционирования как компьютеров, так и компиляторов.
В первые годы идея развивалась и реализовывалась очень бурно. Издавались многочисленные коллекции хорошо отработанных алгоритмов и программ из самых разных прикладных областей. Был налажен широкий обмен ими, в том числе, на международном уровне. Программы действительно легко пе­реносились с одних компьютеров на другие. Постепенно и пользователи отходили от детального изучения компьютеров и компиляторов, ограничи­ваясь разработкой программ на машинно-независимых языках высокого уровня. Однако радужные надежды на длительный эффект от реализации обсуждаемой идеи довольно скоро стали меркнуть.
Причина оказалась простой и оказалась связана с несовершенством работы большинства компиляторов. Конечно, речь не идет о том, что они создают неправильные коды, хотя такое тоже случается. Дело в другом — ни один из компиляторов не гарантирует эффективность получаемых кодов. Вопрос об эффективности редко возникает при решении небольших задач. Такие зада­чи реализуются настолько быстро, что пользователь не замечает многие проблемы. Но различные аспекты эффективности программ, в первую оче­редь, скорость их реализации и точность получаемых решений становятся очень актуальными, если на компьютер ставятся большие и, тем более, пре­дельно большие задачи.
Целью производства новых компьютеров является предоставление пользова­телям возможности решать задачи более эффективно, в первую очередь, более быстро. Поэтому, разрабатывая программы на языках высокого уровня, поль­зователь вправе рассчитывать на то, что время их реализации будет умень­шаться примерно так же, как растет производительность компьютеров. Кроме этого, он, конечно, предполагает, что программы на машинно-независимых языках будут действительно не зависеть от особенностей техники. Однако весь опыт развития компьютеров и связанных с ними компиляторов показы­вает, что в полной мере на это надеяться нельзя. Скорость реализации про­грамм как функция производительности компьютеров оказалась на практике не линейной, а значительно более слабой, причем, чем сложнее устроен ком­пьютер, тем она слабее. Программы на машинно-независимых языках показа­ли зависимость от различных особенностей компьютеров. Постоянное же по­явление все новых и новых языков программирования без обеспечения каких-либо гарантий качества компилируемых программ лишь увеличивает сложность
34
Часть I. Параллельные вычислительные системы
решения многих проблем. На практике пользователь не может серьезно вли­ять на разработку языковой и системной среды компьютера. Но именно он ощущает на себе все ее огрехи. Ему и приходится с ней разбираться, хотя это совсем не его цель. Его цель — решать задачу.
Возникла странная ситуация. Пользователь самым прямым образом заинте­ресован в эффективности решения своих задач на компьютере. Для повы­шения эффективности он прилагает огромные усилия: меняет математиче­ские модели, разрабатывает новые численные методы, многократно переписывает программы и т. п. Но пользователь видит компьютер только через программное окружение, в первую очередь, через компилятор и опе­рационную систему. Создавая и исполняя машинные коды, эти компоненты сильно влияют на эффективность реализации пользовательских программ. Однако с их помощью либо трудно, либо просто невозможно понять, где находятся узкие места созданного машинного кода и что необходимо изменить в конкретной программе на языке высокого уровня, чтобы повысить ее эффек­тивность. Действительная информация, которую можно получить от компи­лятора и операционной системы после реализации программы, чаще всего оказывается настолько скудной и трудно воспринимаемой, что ею редко удается воспользоваться при выборе путей модернизации как самой про­граммы, так и реализуемых ею алгоритмов.
Для пользователя вопрос взаимодействия с компьютером с целью поиска путей создания наиболее эффективных программ является очень актуаль­ным. Но его решение почти полностью переложено на плечи самого поль­зователя. Штатное программное окружение компьютера не содержит ника­ких отладчиков, систем анализа структуры программ и т. п. Из описаний компилятора практически невозможно найти даже такую необходимую ин­формацию, как эффективность работы в машинном коде отдельных, наиболее значимых конструкций языка высокого уровня. Есть много оснований упрек­нуть создателей языков, компиляторов и операционных систем в недоста­точном внимании к интересам разработчиков алгоритмов и программ. В та­ком упреке есть доля истины, т. к. интересы пользователей действительно затерялись среди многих других интересов системных программистов и пе­рестали быть доминирующими. Однако надо также признать, что проблемы пользователей оказались совсем непростыми.
Одной из важнейших характеристик вычислительных процессов является точность получаемых результатов. Уже давно известно, что кроме общего описания процессов, на нее влияют форма представления чисел и способ выполнения операции округления. Эти факторы машинно-зависимые. Ин­формация о них не воспринимается языками высокого уровня. Поэтому, строго говоря, такие языки даже по одной этой причине нельзя считать ма­шинно-независимыми, т. к. на разных компьютерах могут допускаться раз­ные представления чисел и разные способы выполнения округлений. В ко­нечном счете, это может приводить, и на практике приводит, к достаточно
Глава 1. Что скрывают "обыкновенные" компьютеры
35
большому разбросу результатов реализаций одной и той же программы. Но тогда возникают многочисленные вопросы: "Какой смысл надо вкладывать в понятие машинно-независимый язык? Как разрабатывать алгоритмы и пи­сать программы, чтобы хотя бы с какими-то оговорками их можно было рассматривать как машинно-независимые? Как трактовать результаты реа­лизации программы с точки зрения точности?" И т. п.
Были предприняты значительные усилия, чтобы получить ответы на все эти вопросы. Унифицированы формы представления чисел. Разработаны стан­дарты на выполнение операции округления. Для многих алгоритмов полу­чены оценки влияния ошибок округления. В целом же успехи невелики. Возможно, что главным достижением предпринятых усилий является осоз­нание того, что при разработке численного программного обеспечения во­просы влияния ошибок округления нельзя игнорировать. Какое-то время казалось, что проблема точности является единственной принципиальной трудностью, связанной с реализацией алгоритмов на компьютерах разных типов. И хотя на легкое решение этой проблемы нельзя было рассчитывать, тем не менее, обозначились определенные перспективы. Постепенно как будто стала формироваться уверенность, что все же удастся создать на алго­ритмических языках эффективное машинно-независимое численное про­граммное обеспечение и, следовательно, довести до практического решения проблему портабельности программ.
Действительность пошатнула и эту уверенность. Было выяснено, что на множестве программ, эквивалентных с точки зрения пользователя по точно­сти, числу арифметических операций и другим интересным для него харак­теристикам, разброс реальных времен реализации различных вариантов на конкретном компьютере остается очень большим. Причем не только для сложных программ, но и для самых простых. Например, программы реше­ния систем линейных алгебраических уравнений с невырожденной тре­угольной матрицей, написанные на языке высокого уровня, могут давать существенно различные времена реализации просто в зависимости от того, осуществляются ли в них внутренние суммирования по возрастанию индек­сов или по их убыванию. Это означает, что пользователь принципиально не может выбрать заранее тот вариант программы на языке высокого уровня, который будет одинаково эффективен на разных компьютерах. Принципи­ально не может хотя бы потому, что такой вариант, скорее всего, не сущест­вует. Но это означает также, что языки программирования высокого уровня можно и по этой причине считать машинно-независимыми лишь условно.
Итак, чтобы получить оптимальный машинный код, необходимо, как ми­нимум, предоставить компилятору описание всего множества вариантов программ, эквивалентных с точки зрения пользователя. Но на традицион­ных алгоритмических языках высокого уровня можно описать только один вариант. Отсюда вытекает два важных вывода. Во-первых, эффективно ре­шить проблему портабельности программ, ориентируясь на использование
36
Часть /. Параллельные вычислительные системы
языков высокого уровня без предоставления дополнительной информации, не­возможно. И, во-вторых, для построения программы, обеспечивающей оп­тимальный машинный код на конкретном компьютере, исключительно важно иметь возможность получения квалифицированной и достаточно пол­ной информации со стороны компилятора и операционной системы на каж­дый прогон программы.
До тех пор пока такая возможность не будет реализована полностью, поль­зователям придется многократно переписывать свои программы в поисках оптимального варианта. Для уменьшения числа переписываний применяются различные приемы. Например, в программах выделяются вычислительные ядра, т. е. участки, на которые приходится основной объем вычислений. Эти ядра пишутся на автокоде. При переходе к новому компьютеру переписы­ваются только они. Тем самым обеспечивается высокая эффективность программ. По такому принципу организованы такие популярные пакеты для решения задач линейной алгебры, как EISPACK, LINPACK, SCALAPACK и др. Вычислительные ядра в них организованы в специальную библиотеку программ BLAS, которая написана на автокоде и переписывается каждый раз при переходе к новому компьютеру. Как показал опыт, этот прием до­вольно дорогой и не всегда применим, особенно при создании больших программных комплексов. К тому же, как выяснилось, при переходе к ком­пьютерам с принципиально иной архитектурой приходится переписывать не только вычислительные ядра, но и их оболочку.
Даже краткое обсуждение показало, что вопросы разработки эффективно реализуемых и долго используемых программ оказались исключительно труд­ными и интересными. Они потребовали не только создания языков высо­кого уровня, но и изучения структуры программ и алгоритмов, разработки новых технологий программирования, определения места и меры использо­вания машинно-ориентированных языковых сред, совершенствования рабо­ты компиляторов и операционных систем и многого другого. Все эти вопро­сы никак не исчезают в связи с усовершенствованием компьютеров, а, наоборот, становятся все более и более актуальными.
Вопросы и задания
1.   Знаете ли вы, что первые в мире программы составила дочь поэта Дж. Байрона графиня А. Лавлейс, и сделала она это для машины Ч. Беббиджа1?
2.   Знаете ли вы, что Fortran, один из первых языков программирования высокого уров­ня, был реализован в 1957 г.2 и активно используется до настоящего времени?
1  Чарльз Бэббидж (1792—1871) работал над созданием механического компьютера, программируемого с помощью перфокарт. Графиня Ада Августа Лавлейс, помимо написания программ, частично финансировала этот проект. — Ред.
2  Fortran был разработан группой сотрудников фирмы IBM под руководством Джона Бэкуса. — Ред.
Глава 1. Что скрывают "обыкновенные" компьютеры
37
3.   Знаете ли вы, что язык Фортран периодически подвергается ожесточенной кри­тике со стороны разработчиков других языков программирования. Тем не ме­нее, до сих пор в сфере научно-технических задач доля программ на языке Фортран и его диалектах превосходит долю программ на всех остальных языках программирования, вместе взятых. Как вы думаете, почему?
Исследуйте на устойчивость различные формы записи алгоритмов на языке высо­кого уровня для следующих задач:
4.   Вычисление cosx через sinx при очень малых значениях х.
5.   Вычисления sinx через tgx при очень больших значениях х.
6.   Вычисление для больших я величины z = (2я)!!/(2я + 1)!!.
7.   Вычисление величины v = х — Р(х)/Р\х) для многочлена Р(х) высокой степени при малых и больших значениях х.
8.   Вычисление среднего арифметического большого количества чисел разных знаков.
9.   Вычисление среднего геометрического большого количества больших положи­тельных чисел.
10.   Решение системы линейных алгебраических уравнений
х + л/2у = 1 л/3х + л/6у = л/3
при различных приближениях чисел л/2 , л/3 , ..., л/6.
11.   Что меняется в заданиях 4—10, если вычисления осуществлять в режимах с фиксированной или плавающей запятой?
12.   Можете ли вы гарантировать в предложенных решениях высокую относитель­ную точность результата и если нет, то почему?
§ 1.5. Узкие места
Компьютеры постоянно совершенствуются. Главный вектор их развития — повышение производительности, т. е. возможности выполнять большее число операций в единицу времени. Данный вектор очень важен. Об этом говорит хотя бы тот факт, что в последние годы производительность компь­ютеров увеличивается на порядок примерно каждые пять лет. За производи­тельность в компьютере отвечает, в первую очередь, арифметико-логическое устройство. Но оно занимает лишь около 20% всего оборудования. Следова­тельно, решая главную задачу — обеспечение пользователя информационно-вычислительными услугами, — компьютер решает и какие-то вспомогатель­ные задачи, видимые или невидимые для пользователя. Меняется состав этих маленьких задач, меняются методы их решения. Вообще говоря, все они развиваются синхронно. Но иногда некоторые из маленьких задач или даже одна из них становится настолько критичной, что на определенном этапе развития компьютера оказывается самой главной, затмевая собой даже
38
Часть /. Параллельные вычислительные системы
компьютер в целом. Обо всем этом мы будем подробно говорить в главе 2. Чтобы лучше представлять, как и почему в процессе развития компьютера возникают различные узкие места, рассмотрим следующую иллюстративную модель.
Будем считать, что компьютер и его программное окружение есть некоторое предприятие широкого профиля по оказанию информационно-вычисли­тельных услуг. Услугой является обработка какой-то совокупности данных. Потребители этих услуг — пользователи. Пользователь представляет пред­приятию согласованное с его требованиями техническое задание на услугу. Это — программа, написанная на том или ином языке. Отдавая заказ на конкретную услугу, пользователь, естественно, не хотел бы ничего знать о том, как предприятие будет ее реализовывать. Требования пользователя про­сты. Услуга должна быть выполнена правильно, быстро и дешево. На мел­ких услугах обычно так и бывает. Но если услуга масштабная, могут проис­ходить сбои. Чаще всего предприятие не может обеспечить выполнение услуг в срок. Конечно, пользователь может обратиться в другое предпри­ятие. Иногда это помогает. Однако нередко возникают и другие ситуации. Например, выполнение заказа в срок может потребовать от пользователя очень больших дополнительных затрат. Или может оказаться, что вообще ни одно из предприятий не берется за выполнение заказа. И тогда пользо­вателю самому приходится разбираться, почему это происходит и что надо делать.
Любое предприятие по обеспечению информационно-вычислительными услугами организовано довольно сложно. Но всегда его ядром является про­изводственный сектор. Это — арифметико-логическое устройство. В его со­ставе имеется некоторый набор оборудования, такого как сумматоры, умно­жители, сопроцессоры и т. п. Все оно выполняет какие-то простейшие операции, вообще говоря, не имеющие прямого отношения к конечной ус­луге. Текущей организацией работы производственного отдела занимается сектор оперативного управления. Это — устройство управления. На предпри­ятии существует склад для хранения исходных материалов, а также проме­жуточной и конечной продукции. Это — память. Структура склада разветв­ленная. Ее сложность объясняется необходимостью хранить самую разную по характеру использования продукцию. При работе производственного сек­тора появляются промежуточные результаты, которые будут востребованы им же или сразу, или почти сразу. Нет никакого смысла отсылать их в даль­ний угол склада, что неизбежно привело бы к общему замедлению процесса. Поэтому отдельные ячейки склада располагаются среди оборудования про­изводственного сектора. Это — регистровая память. Другие рядом с ним. Это — кэш-память. Несколько дальше располагается быстрая оперативная память. И дальше всего — медленная память.
Одним из важнейших является технологический сектор. Именно он на осно­ве технического задания разрабатывает план использования оборудования
Глава 1. Что скрывают "обыкновенные" компьютеры
39
производственного сектора, склада и некоторых других подразделений предприятия для реализации каждой конкретной услуги. Это — компилятор. Осуществление данного плана неизбежно требует перемещения различных объектов из одного места в другое. Обеспечением всех перемещений занима­ется транспортный сектор. Это — различные соединения, шины, каналы и т. п. И наконец, организует реальное выполнение работ в соответствии с разработанным планом организационный сектор. Это — операционная система.
Такова грубая модель устройства и процесса функционирования компьютера. В реальности все сложнее: отдельных подразделений в предприятии гораздо больше, функции между ними разделены не так четко и т. д. Тем не менее, даже на этой иллюстративной модели можно увидеть довольно много.
Вообще говоря, любой универсальный компьютер способен решить любую задачу, если только у него имеется достаточно памяти. Другое дело, что она может решаться настолько долго, что не хватит времени дождаться результа­тов. В рассмотренной модели видно, что время решения задачи определяется многими факторами. Главный из них — мощность оборудования производ­ственного сектора. Но и от других секторов зависит немало. Если оборудо­вание транспортного сектора несовершенно, перемещения объектов из од­ного места в другое будут осуществляться медленно. В этих условиях либо очень трудно, либо даже невозможно добиться высокой производительности всего предприятия. Если технологический сектор составит неэффективный план, требующий, например, больших накладных расходов, — эффект будет похожим. Очень многое зависит от того, как организационный сектор будет реализовывать план, предложенный технологическим сектором. Если из дальних углов склада необходимо часто перемещать большие количества объектов, то нерационально перемещать их малыми порциями. Гораздо эф­фективнее осуществлять перемещение большими группами. Как будет это организовано, зависит от решений, принимаемых организационным секто­ром. А эти решения, в частности, определяются структурой склада. И т. п.
Таким образом, работа нашего модельного предприятия по обеспечению пользователей информационно-вычислительными услугами зависит от дей­ствий всех секторов. Его базовая производительность определяется мощно­стью оборудования производственного сектора. Суммарная производитель­ность всего оборудования этого сектора называется пиковой произво­дительностью. Это — теоретическое понятие. Оно не учитывает никакие временные затраты от деятельности других секторов. Реальная производи­тельность — это производительность предприятия, которую оно реально достигает при выполнении конкретной услуги. Вообще говоря, она может зависеть от вида услуги. Реальная производительность никогда не может превышать пиковую. Степень ее близости к пиковой говорит о степени со­гласованности работы секторов между собой и об эффективности работы предприятия в целом. Отношение реальной производительности к пиковой называется эффективностью работы предприятия. Свою долю в уменьшение
40
Часть I. Параллельные вычислительные системы
эффективности вносят все сектора. Одни больше, другие меньше, какие-то очень много.
Каждый из секторов решает свою маленькую задачу. И только вместе они способны сделать нечто большое. В процессе развития предприятия насту­пает момент, когда эволюционным путем уже нельзя поднять производи­тельность. Тогда приходится проводить глобальную модернизацию. В пер­вую очередь необходимо решить, как радикально увеличить пиковую производительность. Есть два пути, отработанные веками в самых разных отраслях. Грубо говоря, один из них связан с наращиванием однотипного оборудования, другой — с организацией конвейеров. В нашем случае годятся оба и даже их комбинация. Об этом мы также будем подробно говорить в главе 2. На период реконструкции производственного сектора его задача становится главной. Но затем неизбежно приходится модернизировать и все другие сектора. Главными становятся задачи реконструкции склада, транс­портных путей, управления и т. д. Любая малая задача становится главной тогда, когда она оказывается самым узким местом в достижении наивысшей реальной производительности предприятия в целом.
Все сказанное, в том числе касающееся эффективности, имеет прямое от­ношение к любому компьютеру. Если на большинстве программ эффектив­ность работы компьютера находится в пределах 0,5—1, ситуацию можно считать хорошей. Если же эффективность намного меньше, а задачи требу­ется решать как можно быстрее, приходится разбираться, где же теряется производительность и что надо сделать для ее повышения. Другими слова­ми, надо установить узкие места процесса функционирования компьютера и его программного окружения в целом. Согласно рассмотренной модели, в первую очередь они могут определяться:
□  составом, принципом работы и временными характеристиками арифме­тико-логического устройства;
□  составом, размером и временными характеристиками памяти;
□  структурой и пропускной способностью коммуникационной среды;
□  компилятором, создающим неэффективные коды;
□  операционной системой, организующей неэффективную работу с памя­тью, особенно медленной.
Как мы увидим в дальнейшем, постоянное совершенствование именно пе­речисленных составляющих сделало возможным превращение "обык­новенного" компьютера в суперкомпьютер. Хотя и в суперкомпьютерах эти составляющие остаются потенциальными источниками возникновения уз­ких мест.
Глава 1. Что скрывают "обыкновенные" компьютеры
41
Вопросы и задания
1.   Предположим, что один землекоп может за час вырыть яму размером 1x1x1 м3 и способен работать в таком режиме достаточно долго. За какое время бригада из 5, 10, 20 землекопов выроет яму размером 2x2 м2 и глубиной 1 м?
2.   Постройте график времени выполнения работы в зависимости от числа земле­копов в бригаде.
3.   Повторите задания пп. 1, 2 для ямы размером 10x10 м2, глубиной 1 м и бригады из 10, 100 землекопов.
4.   Чем принципиально различаются эти варианты?
5.   Пусть предпринимается попытка увеличить реальную мощность производствен­ного отдела из иллюстративной модели компьютера путем наращивания одно­типного оборудования. Не кажется ли вам, что возникнет ситуация, аналогич­ная рассмотренной в пп. 1—4?
6.   Для создания очень мощного компьютера часто объединяют вместе большое число персональных компьютеров. Пусть их будет п. Для обмена информацией между компьютерами необходимо создать коммуникационную сеть. Теоретиче­ски самый простой способ — соединить каждый компьютер с каждым прямыми связями, т. к. в этом случае информация вроде бы должна передаваться наибо­лее быстро. Всего потребуется п(п — 1) таких связей. Рассмотрите подобные коммуникационные сети для п = 3, 5, 8.
7.   В реальных компьютерах величина п может достигать 104—105. Можете ли вы представить всю совокупность связей в этом случае, если они организованы согласно п. 6?
8.   Пусть снова сверхмощный компьютер создается как объединение п персональ­ных компьютеров. Построим неориентированный граф, в котором вершины символизируют персональные компьютеры, а ребра — прямые связи. Будем считать, что граф связный, т. е. для каждой пары вершин существует связы­вающая их цепь ребер. Такой граф называется коммуникационным. Исследуйте коммуникационный граф из п. 6.
9.   Пусть в коммуникационном графе существуют не все ребра. Будем считать, что передача информации между персональными компьютерами может осуществ­ляться вдоль любой цепи коммуникационного графа, связывающей соответст­вующие вершины. Постройте различные коммуникационные графы, уменьшая общее число ребер.
10. Если вам удалось построить коммуникационный граф, имеющий п вершин, п ребер и такой, что между любыми двумя вершинами существует цепь, состоя­щая не более чем из 21og2« ребер, то вы на правильном пути. Соответствующие коммуникационные сети называются каскадными перестраиваемыми сетями.
Глава 2
Как повышают производительность компьютеров
Независимо от того, куда вы идете, это в гору и против ветра.
Из законов Мерфи
Замечено, что эмоциональное состояние человека, впервые сталкивающегося с суперкомпьютером, проходит несколько стадий. Сначала он испытывает что-то вроде эйфории, начитавшись рекламных данных о компьютере и нахо­дясь в предвкушении быстрого разрешения всех своих вычислительных про­блем. После первого запуска своей программы у него возникает недоумение и подозрение, что что-то он сделал не так, поскольку реально достигнутая про­изводительность слишком сильно отличается от ожидаемой. Он запускает программу повторно, но результат, если и меняется в лучшую сторону, то очень и очень слабо. Он идет к местному компьютерному гуру, и тут-то его поджидает настоящий удар. Сначала ему говорят, что полученные им 5% от пиковой производительности компьютера являются не таким уж и плохим результатом. А затем добавляют, что если он хочет "выжать" из такой вычис­лительной системы максимум, то для него вся работа только начинается. Нужно обойти возможные конфликты в памяти и постараться сбалансировать вычислительную нагрузку между процессорами. Необходимо подумать об оп­тимальном распределении данных, помочь компилятору разобраться со струк­турой программы, заменить последовательные части алгоритма на параллель­ные. Надо сделать еще много чего, что, вообще-то говоря, раньше совершенно не было свойственно работе прикладного программиста. И все прежние проблемы, характерные для последовательных компьютеров и тради­ционного последовательного программирования, разумеется, остаются...
Эту ситуацию заметили давно и предлагали различные способы ее решения. Изменялась архитектура компьютеров, развивалась среда программирова­ния, но оставалось постоянным одно — "головная боль" прикладных про­граммистов, решивших серьезно встать на путь параллельных вычислений. Оговоримся сразу, мы далеки от мысли кого-либо обвинять в такой ситуа­ции. Инженерами и программистами проделана колоссальная работа, без которой и предмет нашего обсуждения не существовал бы. Просто область сложна сама по себе, да и жизнь постоянно вносит свои коррективы. Созда­вая кэш-память, которая прекрасно работает в одном микропроцессоре, ни­кто и не подозревал, какие проблемы она принесет в многопроцессорных
Глава 2. Как повышают производительность компьютеров
43
системах. Только научились анализировать структуру исходного текста программ на Fortran, как появились языки С и C++, привнесшие значи­тельные трудности в статический анализ параллельной структуры программ. Чуть ли не единственным достижением, постоянное развитие которого ни­как не задевает программиста, является увеличение тактовой частоты: про­грамма работает быстрее, а он для этого ничего дополнительного не делает.
В той области, которая является предметом нашего исследования, всегда было и всегда будет очень много нового, непривычного, часто даже парадоксаль­ного. Во все времена для компьютеров, работающих на предельных скоростях, будут требоваться технологии, отличающиеся от "массовых", они всегда будут "на передовом крае", и, следовательно, они всегда будут вызывать трудности в использовании. Но для того и создана данная книга, чтобы показать возмож­ные проблемы и пути их решения. Но это немного позже. В данной главе мы остановимся на базовых понятиях и посмотрим, как проходило развитие и каковы основные особенности современных компьютеров.
§ 2.1. Усложнение и наращивание аппаратных средств
Бесспорно, успехи микроэлектроники впечатляют. Согласно закону Мура, не имеющего строгого доказательства, но подтверждающегося уже не один деся­ток лет, производительность "обычных" компьютеров удваивается каждые полтора года. Однако попытаемся разобраться, только ли этими причинами определяются те рекордные показатели производительности, которыми обла­дают современные компьютеры. Обратимся к известным историческим фак­там и проведем простое сравнение (рис. 2.1). На одном из первых компью­теров мира — EDSAC, появившемся в 1949 году и имевшем время такта 2 микросекунды, можно было выполнить 2« арифметических операций за 18« миллисекунд, т. е. в среднем 100 арифметических операций в секунду. Срав­ним эти данные, например, с характеристиками одного вычислительного узла современного суперкомпьютера Hewlett-Packard Superdome: время такта при­близительно 1,3 не (процессор РА-8700, 750 МГц), а пиковая производитель­ность около 192 миллиардов арифметических операций в секунду.
EDS АС
1949 год
тактовая частота           0,5 МГц
1,5x10
>
HP Superdome
2001 год
770 МГц
производительность           102оп/с            1,9х109 ^> 1,9х1011оп/с
Рис. 2.1. Увеличение производительности ЭВМ — за счет чего?
44
Часть /. Параллельные вычислительные системы
Что же получается? За полвека производительность компьютеров выросла почти в два миллиарда раз. При этом выигрыш в быстродействии, связан­ный с уменьшением времени такта с 2 мкс до 1,3 не, составляет лишь около 1500 раз. С этой цифрой естественно связать развитие микроэлектроники (что верно, конечно же, лишь в некотором приближении). Откуда же взя­лось остальное? Ответ достаточно очевиден — использование новых реше­ний в архитектуре компьютеров, и среди них далеко не последнее место за­нимает принцип параллельной обработки данных, воплощающий идею одновременного выполнения нескольких действий.
Удивительно, но несмотря на все многообразие форм проявления паралле­лизма в архитектуре компьютеров, на обилие сопутствующих проблем, су­ществует лишь два способа параллельной обработки данных, собственно па­раллелизм и конвейерность. Более того, оба способа интуитивно понятны, и нам необходимо сделать лишь небольшие пояснения.
Параллельная обработка
Предположим, что нам нужно найти сумму двух векторов, состоящих из 100 ве­щественных чисел каждый. В нашем распоряжении есть устройство, которое выполняет суммирование пары чисел за пять тактов работы компьютера. Устройство сделано так, что на все время выполнения данной операции оно блокируется, и никакой другой полезной работы выполнять не может. В та­ких условиях вся операция будет выполнена за 500 тактов. Развитие процес­са обработки во времени схематично изображено на рис. 2.2.
Теперь допустим, что у нас есть два точно таких же устройства, которые могут работать одновременно и независимо друг от друга. Для простоты будем рассматривать идеальную ситуацию, когда нет никаких дополнитель­ных накладных расходов, связанных с получением устройствами входных данных и сохранением результатов. Постоянно загружая каждое устройст­во элементами входных массивов, можно получить искомую сумму уже за 250 тактов (рис. 2.3) — ускорение выполнения работы в два раза. В случае 10 подобных устройств время получения результата составит всего 50 тактов, а в общем случае система из N устройств затратит на суммирование время около 500/Ж
Очень много примеров параллельной обработки можно найти и в нашей повседневной жизни: множество кассовых аппаратов в супермаркете, мно­гополосное движение по оживленным автотрассам, бригада землекопов, не­сколько бензиновых колонок на автозаправочных станциях и т. п. Кстати, одним из пионеров в параллельной обработке был академик А. А. Са­марский, выполнявший в начале 50-х годов прошлого столетия расчеты, не­обходимые для моделирования ядерных взрывов. Александр Андреевич ре­шил эту задачу, посадив несколько десятков барышень с арифмометрами за столы. Барышни передавали данные друг другу просто на словах и отклады-
Глава 2. Как повышают производительность компьютеров
45
вали необходимые цифры на арифмометрах. С помощью вот такой "параллельной вычислительной системы", в частности, была рассчитана эво­люция взрывной волны.
0-й такт
. b4b3b2b^
1-й такт
2-й такт
■ b5b4b3b2
9l+6l
3-й такт
■ b5b4b3b2
9l+6l
4-й такт
5-й такт
■ b5b4b3b2
a2+b2
6-й такт
.b5b5b4b3
a2+b2
7-й такт
a2+b2
10-й такт
.b5b5b4b3
11 -й такт
.b7beb5b4
Рис. 2.2. Суммирование векторов С = А + В с помощью последовательного устройства, выполняющего одну операцию за пять тактов
46
Часть I. Параллельные вычислительные системы
+                +
0-й такт
. b5b5b4b3
a2+62
а11
1-й такт
a2+62
a^+bi
2-й такт
a2+b2
a^+bi
3-й такт
.b5b5b4b3
a2+b2
a^+bi
4-й такт
a2+b2
a^+bi
5-й такт
.beb7beb5
a4+64
а3з
6-й такт
.bsb7beb5
a4+64
а3з
7-й такт
.bsb7b5b5
a4+64
а3з
10-й такт
*Wa$aSa7
а6б
а5+Ь5
11-й такт
.b10b9bsb7
Рис. 2.3. Суммирование векторов С = А + В с помощью двух одинаковых последовательных устройств, выполняющих операцию за пять тактов каждое
Конвейерная обработка
В предыдущем примере для выполнения одной операции сложения устрой­ство блокировалось на пять тактов и никакой другой работы не выполняло. Оправдано ли это и можно ли организовать процесс обработки всех элемен-
Глава 2. Как повышают производительность компьютеров
47
тов входных массивов более эффективно? Для ответа на этот вопрос нужно вспомнить материал из предыдущей главы — представление вещественных чисел в компьютере. Сложение каждой пары чисел выполняется в виде по­следовательности микроопераций, таких как сравнение порядков, выравни­вание порядков, сложение мантисс, нормализация и т. п. И что примеча­тельно: в процессе обработки каждой пары входных данных микрооперации задействуются только один раз и всегда в одном и том же порядке: одна за другой. Это, в частности, означает, что если первая микрооперация выпол­нила свою работу и передала результаты второй, то для обработки текущей пары она больше не понадобится, и значит, она вполне может начинать об­работку следующей ждущей на входе устройства пары аргументов.
Исходя их этих рассуждений, сконструируем устройство следующим обра­зом. Каждую микрооперацию выделим в отдельную часть устройства и рас­положим их в порядке выполнения. В первый момент времени входные данные поступают для обработки первой частью. После выполнения первой микрооперации первая часть передает результаты свой работы второй части, а сама берет новую пару. Когда входные аргументы пройдут через все этапы обработки, на выходе устройства появится результат выполнения операции.
Такой способ организации вычислений и носит название конвейерной об­работки. Каждая часть устройства называется ступенью конвейера, а общее число ступеней — длиной конвейера. Предположим, что для выполнения операции сложения вещественных чисел спроектировано конвейерное уст­ройство, состоящее из пяти ступеней, срабатывающих за один такт каждая. Время выполнения одной операции конвейерным устройством равно сумме времен срабатывания всех ступеней конвейера. Это значит, что одна опера­ция сложения двух чисел выполнится за пять тактов, т. е. за столько же времени, за сколько такая же операция выполнялась и на последовательном устройстве в предыдущем случае.
Теперь рассмотрим процесс сложения двух массивов (рис. 2.4). Как и преж­де, через пять тактов будет получен результат сложения первой пары. Но заметим, что одновременно с первой парой прошли частичную обработку и другие элементы. Каждый последующий такт (!) на выходе конвейерного устройства будет появляться сумма очередных элементов. На выполнение всей операции потребуется 104 такта, вместо 500 тактов при использовании последовательного устройства — выигрыш во времени почти в пять раз.
Приблизительно так же будет и в общем случае. Если конвейерное устрой­ство содержит / ступеней, а каждая ступень срабатывает за одну единицу времени, то время обработки п независимых операций этим устройством составит / + п — 1 единиц. Если это же устройство использовать в моно­польном режиме (как последовательное), то время обработки будет равно Ып. В результате для больших значений п получили ускорение почти в / раз за счет использования конвейерной обработки данных.
48                                                                      Часть I. Параллельные вычислительные системы
... а4аъа2аЛ —> ... b4b3b2b^ —►
—► 0-й такт
... а5а4а3а2 > ...Ь5Ь4ЬЪЬ2 —►
аА
—► 1-й такт
■■■аба5а4аз *■ ЪЬ5Ь4Ь3 —►
а2Ь2
аА
—► 2-й такт
■■■a7a6a5a4 *
...b7b6b5b4 —►
азьз
а2Ь2
аА
—► 3-й такт
■■■a8a7a6a5 * -Й8Ь766Ь5 ->•
аА
азьз
з262
ЭА
—► 4-й такт
... аэа8а7ае —> ■■■bgbab7be —>
а5Й5
а4Ь4
аз6з
з262
аА
—► 5-й такт
■■■а10а9а8а7 *
...b10b9bsb7 —►
а6ь6
а5Й5
аА
аз6з
з262
—► с, 6-й такт
■■■ а11а10а9а8 ~~* -611Ь1069Ь8 -*
а7Ь7
аА
Э5Й5
а4Ь4
аз6з
—► с2 с, 7-й такт
■■■ а12а11а10а9 ~~*
аА
а7Ь7
аб66
Э5Й5
а4Ь4
—► с3 с2 с1 8-й такт
Рис. 2.4. Суммирование векторов С= Л + 5 с помощью
конвейерного устройства. Каждая из пяти ступеней
конвейера срабатывает за один такт
Заметим, что использование эпитетов "скалярный", "векторный" и "кон­вейерный" часто вызывает путаницу, поскольку многие из них применимы к близким по смыслу понятиям "обработка", "команда" и "устройство". Ко­манда, у которой все аргументы должны быть только скалярными величи­нами, называется скалярной командой. Если хотя бы один аргумент команды может быть вектором, то такая команда называется векторной командой. На­пример, в системе команд компьютера Cray С90 есть скалярная команда Ascal сложения двух вещественных чисел и Sj с занесением результата в St,: Ascal S]_, S2 -> Sj. Одновременно со скалярной командой предусмотрена и
Глава 2. Как повышают производительность компьютеров
49
команда Avect для сложения двух векторов с занесением результата в третий: jvect yh у2 __>. j/3 g зависимости от кода поступившей команды (Ascal или Avect) процессор интерпретирует операнды либо как адреса скаляров, либо как адреса начала векторов.
Различие между скалярными и конвейерными устройствами мы уже обсудили чуть выше. Иногда в архитектуру процессора включают векторные устрой­ства, ориентированные только на обработку векторов данных. В частности, в компьютере Cray С90 есть и конвейерное устройство UA {™tl, выполняющее только скалярные команды AIscal сложения целых чисел, и векторное уст­ройство UAfa? (также конвейерное), предназначенное для выполнения
только векторных команд целочисленного сложения AIvect. Кстати, в отли­чие от целочисленных команд AIscal и AIvect, обсуждавшиеся ранее команды сложения вещественных чисел Ascal и Avect в этом компьютере выполняются на одном и том же устройстве UAKa\.
Как и прежде, будем считать, что конвейерное устройство состоит из / сту­пеней, срабатывающих за один такт. Два вектора из п элементов можно ли­бо сложить одной векторной командой, либо выполнить подряд п скаляр­ных команд сложения элементов этих векторов. Если п скалярных команд одна за другой исполняются на таком устройстве, то, согласно общему за­кону, они будут обработаны за / + п — 1 тактов. Вместе с тем, на практике выдержать ритм, определяемый этой формулой, довольно сложно: каждый такт нужно обеспечивать новые входные данные, сохранять результат, про­верять необходимость повторной итерации, быть может увеличивать значе­ния индексов и т. д. и т. п. В итоге, необходимость выполнения множества вспомогательных операций приводит к тому, что становится невозможным каждый такт подавать входные данные на вход конвейерного устройства. В конвейере появляются "пузыри", а значит, на выходе уже не каждый такт появляются результаты, и эффективность работы устройства снижается.
При использовании векторных команд в формуле добавляется еще одно слагаемое: а + /+ п — 1, где а — это время, необходимое для инициализа­ции векторной команды. Исполнение векторной команды не требует прак­тически никаких вспомогательных действий, кроме момента ее инициа­лизации или поддержки сегментации (см. § 3.2). Однако присутствие а определяет тот факт, что для небольших значений п соответствующие век­торные команды выгоднее исполнять не в векторном, а в обычном скаляр­ном режиме.
Поскольку ни а, ни / не зависят от значения п, то с увеличением длины вход­ных векторов эффективность конвейерной обработки возрастает. Если под эффективностью обработки понимать реальную производительность Е кон­вейерного устройства, равную отношению числа выполненных операций п к
50
Часть I. Параллельные вычислительные системы
времени их выполнения t, то зависимость производительности от длины входных векторов определяется следующим соотношением:
р _ п _             п             _              1
У ~ Ко + / + я - 1) xj ■ т + + /
п
где т — это время такта работы компьютера.
На рис. 2.5 показан примерный вид этой зависимости. С увеличением числа входных данных реальная производительность конвейерного устройства все больше и больше приближается к его пиковой производительности. Однако у этого факта есть и исключительно важная обратная сторона — пиковая производительность любого конвейерного устройства никогда недостижима на практике.
Е
( п ^
Рис. 2.5. Зависимость производительности конвейерного устройства от длины входного набора данных
Сегодня параллелизмом в архитектуре компьютеров уже мало кого удивишь. Все современные микропроцессоры, будь то ALPHA 21264, Itanium, РА-8700 или Power 4, используют тот или иной вид параллельной обработки, реализо­ванный в миниатюрных рамках одного кристалла. Вместе с тем, сами эти идеи появились очень давно. Изначально они внедрялись в самых передовых, а потому единичных, компьютерах своего времени. Затем после должной от­работки технологии и удешевления производства они начинали использоваться в компьютерах среднего класса, и, наконец, сегодня все это в полном объеме воплощается в рабочих станциях и персональных компьютерах.
Для того чтобы убедиться, что все основные нововведения в архитектуре современных вычислительных систем используются еще со времен, когда ни микропроцессоров, ни понятия суперкомпьютеров еще не было, совершим маленький экскурс в историю, начав практически с момента рождения пер­вых ЭВМ.
Глава 2. Как повышают производительность компьютеров
51
Первые компьютеры (EDSAC, EDVAC, UNIVAC, конец 40-х — начало 50-х годов XX столетия) для реализации принципа хранимой программы исполь­зовали ртутные линии задержки. При таком способе организации памяти разряды слова поступали для последующей обработки последовательно один за другим. Вполне естественно, что точно так же поразрядно выполнялись и арифметические операции, поэтому для сложения двух 32-разрядных чисел требовалось около 32 машинных тактов. Время разрядно-последовашельной памяти и разрядно-последовашельной арифметики в истории вычислительной техники.
С изобретением запоминающих устройств с произвольной выборкой данных стала реализуемой как разрядно-параллельная память, так и разрядно-параллельная арифметика. Все разряды слова одновременно считываются из памяти и участвуют в выполнении операции арифметико-логическим уст­ройством. В 1953 году появилась машина IBM 701 — первая коммерческая ЭВМ, построенная на таком принципе. Однако наиболее удачной машиной этого класса стала выпущенная в 1955 году ЭВМ IBM 704, в которой была впервые применена память на ферритовых сердечниках и аппаратное АУ с плавающей точкой. Удивительный по тем временам коммерческий успех IBM 704 определялся продажей 150 (!) экземпляров этой машины.
Как в машине IBM 704, так и в других компьютерах того времени, все опе­рации ввода/вывода осуществлялись через арифметико-логическое устрой­ство. Внешних устройств было немного, но самое быстрое из них — ленто­протяжное устройство, работало со скоростью около 15 000 символов в секунду, что было во много раз меньше скорости обработки данных процес­сором. Подобная организация вычислительных машин вела к существенно­му снижению производительности во время ввода и вывода информации. Одним из первых решений этой проблемы стало введение специализиро­ванной ЭВМ, называемой каналом ввода/вывода, которая позволяла АЛУ работать параллельно с устройствами ввода/вывода. В 1958 году к машине IBM 704 добавили шесть каналов ввода/вывода, что явилось основой для построения ЭВМ IBM 709.
Очень скоро стало понятно, что значительное повышение производительно­сти компьютеров следует ожидать от использования принципов параллель­ной обработки данных в архитектуре компьютеров. В этом смысле исклю­чительно интересным было выступление академика С. А. Лебедева на сессии Академии наук СССР в 1957 году [33], в котором он высказал массу идей, актуальных и сейчас: "Выполнение арифметических действий в значи­тельной мере может быть совмещено по времени с обращением к памяти. При этом можно отказаться от стандартного цикла выполнения операций, когда вызов следующей команды производится после отсылки результата в запоминающее устройство, и производить выборку команд перед отсылкой результатов в запоминающее устройство...
Возможное ускорение работы машины за счет совмещения вызова команд и чисел по двум независимым каналам также может дать сокращение времени...
52
Часть /. Параллельные вычислительные системы
Помимо быстродействия арифметического устройства, существенным фак­тором, определяющим скорость работы машин, является время обращения к запоминающему устройству. Одним из решений уменьшения времени об­ращения к запоминающему устройству является создание дополнительной «сверхбыстродействующей памяти» сравнительно небольшой емкости. Соз­дание такой «памяти» позволит сократить время для выполнения стандарт­ных вычислений...
Некоторые весьма важные задачи, в особенности многомерные задачи мате­матической физики, не могут успешно решаться при скоростях работы со­временных электронных вычислительных машин, и требуется существенное повышение их быстродействия.
Одним из возможных путей для решения таких задач может явиться парал­лельная работа нескольких машин, объединенных одним общим дополни­тельным устройством управления и с обеспечением возможности передачи кодов чисел с одной машины на другую. Однако может оказаться более целе­сообразным создание ряда параллельно работающих отдельных устройств ма­шины. В такой машине должна иметься общая основная «память» для хране­ния чисел и команд, необходимых для решения задачи. Из этой «памяти» числа и команды, требующиеся для решения того или иного этапа задачи, поступают на ряд сверхбыстродействующих запоминающих устройств сравни­тельно небольшой емкости. Каждое такое сверхбыстродействующее запоми­нающее устройство связано со своим арифметическим устройством. Эти уст­ройства имеют свое индивидуальное управление. Помимо этого, должно быть предусмотрено общее управление всей машиной в целом.
Для более полного использования арифметических устройств требуется, чтобы заполнение «сверхбыстродействующей памяти» из общей «памяти» машины осуществлялось одновременно с выполнением вычислений".
Знакомые идеи, не правда ли? Это удивительно, но факт — идеи, высказан­ные более 40 лет назад на заре развития вычислительной техники, находят свое активное воплощение в жизнь и сейчас.
Одной из первых машин, воплотивших идею повышения производительно­сти за счет совмещения во времени различных этапов выполнения соседних команд, была система IBM STRETCH. Первый из семи построенных ком­пьютеров этой серии был установлен в Лос-Аламосской национальной ла­боратории США в 1961 году. Архитектура системы имела две принципиаль­но важных особенности. Первая особенность — это опережающий просмотр для считывания, декодирования, вычисления адресов, а также предвари­тельная выборка операндов нескольких команд.
Вторая особенность заключалась в разбиении памяти на два независимых банка, способных передавать данные в АЛУ независимо друг от друга (то есть опять-таки идеи параллелизма). Эта особенность, получившая название расслоение памяти, была позднее использована почти во всех больших вы­числительных системах. Основная задача, решаемая в данном случае разра-
Глава 2. Как повышают производительность компьютеров
53
ботчиками компьютеров, состоит в согласовании скорости работы АЛУ и памяти. Это делается за счет разделения всей памяти на к независимых банков (иногда их называют секциями), что позволяет совместить во време­ни различные обращения к памяти. Одновременно вводится чередование адресов, при котором любые к подряд расположенных ячеек памяти с адре­сами Aq, Aq +1, ..., Aq + (к — 1) попадают в разные банки. При такой орга­низации работа с подряд расположенными данными проходит максимально эффективно. На практике этот режим отвечает последовательной обработке элементов векторов или работе со строками/столбцами многомерных масси­вов (выбор между строками и столбцами определяется способом хранения многомерных массивов в каждом конкретном языке программирования, в частности, для двумерных массивов для языка Fortran — это столбцы, а для языка С — строки). Чаще всего число банков равно степени двойки: к = 2т. В этом случае номер необходимого банка просто равен числу, записанному в т младших разрядах адреса.
Одновременно с проектом IBM STRETCH в 1956 году начался и другой из­вестный проект, завершившийся запуском в 1962 году первого компьютера ATLAS. Наверняка, многие слышали о данном проекте, поскольку ATLAS по праву считается значительной вехой в истории развития вычислительной техники. Достаточно сказать, что в нем впервые была реализована мульти­программная операционная система, основанная на виртуальной памяти и системе прерываний. Для нас же этот компьютер интересен, прежде всего, тем, что в нем впервые начали использовать конвейерный принцип обработки команд. Цикл обработки команды разбили на четыре ступени: выборка команды, вычисление адреса операнда, выборка операнда и выполнение операции. Это позволяло, в благоприятной ситуации, сократить среднее время выполнения команды с 6 до 1,6 мкс и увеличить производительность компьютера до 200 тысяч вещественных операций в секунду.
В 1964 году компания Control Data Corporation выпускает компьютер CDC-6600. Генеральным конструктором компьютера, а на то время и вице-президентом компании, был С. Крэй (Seymour Cray), создавший впоследствии множество уникальных суперкомпьютеров. Центральный процессор CDC-6600 содержал десять независимых функциональных устройств — параллелизм в явном виде. Все функциональные устройства могли работать одновременно друг с другом и являлись специализированными (устройства сложения с пла­вающей и фиксированной запятой (по одному), умножения (два устройства), деления, логических операций, сдвига и т. п.). Чтобы лучше почувствовать возможности суперкомпьютера того времени, приведем некоторые характери­стики CDC-6600: время такта 100 не, память разбита на 32 банка по 4096 60-разрядных слов, средняя производительность 2—3 млн. операций в секунду.
В 1969 году Control Data Corporation выпускает компьютер CDC-7600 — усо­вершенствованный вариант модели CDC-6600. Центральный процессор нового компьютера содержит восемь независимых конвейерных функциональных уст­ройств — одновременное использование и параллелизма, и конвейерности.
54
Часть I. Параллельные вычислительные системы
В отличие от предшественника, в CDC-7600 реализована двухуровневая память: вычислительная секция в обычном режиме работает со "сверхоперативной" па­мятью (64К 60-разрядных слов), куда при необходимости подкачиваются дан­ные из основной памяти (512К 60-разрядных слов). Время такта CDC-7600 рав­но 27,5 не, средняя производительность 10—15 млн. операций в секунду.
Следующей важной вехой в развитии идей параллельной обработки данных, безусловно, явилось создание компьютера 1LL1AC IV. Матрица синхронно ра­ботающих процессоров — ключевая идея данного проекта. Архитектура компь­ютера 1LLIAC IV базировалась на концепции системы SOLOMON, описанной еще в 1962 году [61]. По начальному проекту основу 1LL1AC IV составляла матрица из 256 процессорных элементов, сгруппированных в четыре квадран­та по 64 элемента в каждом (рис. 2.6). Планировалось, что вся система будет работать под общим управлением, однако в каждом квадранте находилось собственное устройство управления, выдающее команды для синхронно рабо­тающих процессорных элементов своего квадранта. Была предусмотрена воз­можность объединять два квадранта в один из 128 процессорных элементов или же рассматривать все четыре квадранта как единую матрицу 16x16. Время такта компьютера по проекту равнялось 40 не, а запланированная пиковая производительность 1 миллиард вещественных операций в секунду — фанта­стический по своим масштабам и сложности проект!
СО
ю ю см
1 квадрант на 64 ПЭ
II квадрант на 64 ПЭ
со
со см
устройство управления
| устройство управления
III квадрант на 64 ПЭ
IV квадрант на 64 ПЭ
СО
со см
устройство управления
устройство управления
Рис. 2.6. Проект матричной системы ILLIAC IV
Глава 2. Как повышают производительность компьютеров
55
Однако чудеса в жизни редко случаются... Проект компьютера ILLIAC IV слишком опередил свое время, что и предопределило постоянные задержки и корректировки планов. Работа над системой началась в 1967 году. В 1972 году изготовили один квадрант из 64 процессорных элементов, который был ус­тановлен в научно-исследовательском центре NASA Ames (США). Наладка системы длилась до 1974 года, после чего ее сдали в эксплуатацию, но рабо­ты по доводке продолжались до 1975 года. В реальном использовании сис­тема находилась до 1982 года, после чего этот единственный изготовленный экземпляр нашел свое последнее пристанище в музее. Рассмотрим немного подробнее устройство компьютера.
Центральная часть компьютера состоит из устройства управления и матри­цы из 64-х независимых идентичных процессорных элементов. Роль устрой­ства управления выполняет простая вычислительная система небольшой производительности. Устройство управления выдает поток команд, син­хронно исполняемый процессорными элементами. Каждый процессорный элемент — это арифметико-логическое устройство с собственной памятью объемом 2048 64-разрядных слов. Все процессорные элементы имели доступ только к своей локальной памяти. Для обмена данными в ходе выполнения программы каждый процессорный элемент мог использовать непосредст­венную связь с четырьмя соседями по схеме двумерного тора со сдвигом на единицу по вертикали (рис. 2.7).
I
0
1
2
6
7
8
9
10
15
16
17
'
'
|
|
48
49
55
56
57
58
62
63
I
УСТРОЙСТВО управления
Рис. 2.7. Процессорная матрица компьютера ILLIAC IV
56
Часть I. Параллельные вычислительные системы
Все процессорные элементы матрицы в каждый момент времени синхронно исполняют одну и ту же команду, выдаваемую устройством управления. Вместе с тем, с помощью битовой маски или специальных команд любой процессорный элемент можно перевести в пассивное состояние, запрещая выполнение поступающих команд.
Интересно, что помимо работы с 64-разрядными числами, процессорные элементы могут интерпретировать и обрабатывать данные уменьшенного формата, например, одно слово как два 32-разрядных числа или восемь од­нобайтовых. Этот дополнительный внутренний параллелизм позволяет об­рабатывать 8-разрядные целочисленные данные со скоростью до 10 милли­ардов байтовых операций в секунду.
Что же получилось в результате? Планировали матрицу из четырех квадран­тов и 256 процессорных элементов, а сделали только один, проектное время такта по технологическим соображениям пришлось увеличить до 80 не, стоимость изготовления четвертой части системы оказалась в четыре раза выше, чем предусматривалось сначала, а ее реальная производительность достигала лишь 50 млн. вещественных операций в секунду. Полное фиаско? Ни в коей мере. Данный проект оказал огромное влияние как на архитекту­ру последующих поколений компьютеров, так и на развитие многих компо­нентов программного обеспечения параллельных вычислительных систем. Отработанные в рамках проекта технологии были использованы при созда­нии систем РЕРЕ, BSP, ICL DAP и многих других. Идеи, заложенные в распараллеливающие компиляторы с языка Fortran, в частности, в IVTRAN, послужили основой для создания целого семейства компиляторов и разного рода программных анализаторов для появившихся позже параллельных компьютеров и супер-ЭВМ.
Идея включения векторных операций в систему команд компьютеров давно привлекала исследователей. Во-первых, использование векторных операций позволяет намного проще записать многие вычислительные фрагменты в терминах машинных команд. Если нужно сложить два вектора С = А + В, то отпадает необходимость в таких вспомогательных командах, как увеличение индекса, проверка условия выхода из цикла, перехода на начало тела цикла и многих других — достаточно воспользоваться соответствующей векторной командой. Код становится компактней, а эффективность соответствую­щих фрагментов, как правило, намного выше традиционных вариантов. Во-вторых, и это не менее важно, многие алгоритмы можно выразить в тер­минах векторных операций. И, наконец, в-третьих, подобные операции по­зволяют добиться максимального эффекта от использования конвейерных устройств.
Примеров успешных проектов вычислительных систем с использованием векторных команд немало. Одним из первых примеров явилась отечествен­ная разработка компьютера Стрела, имеющего в системе команд "групповые операции". Более поздние варианты, появившиеся в конце 60-х годов про-
Глава 2. Как повышают производительность компьютеров
57
шлого века, это вычислительные системы ASC производства компании Texas Instruments и STAR 100 компании Control Data Corporation. Однако "классикой жанра", несомненно, является линия векторно-конвейерных су­перкомпьютеров Cray.
В 1972 г. С. Крэй, вице-президент и генеральный конструктор многих ма­шин, покидает компанию Control Data Corporation и основывает новую компанию Cray Research, которая в 1976 г. выпускает свой первый векторно-конвейерньш компьютер Сгау-1. Отличительными особенностями архитекту­ры данного компьютера являются векторные команды, независимые кон­вейерные функциональные устройства и развитая регистровая структура. Конвейерность и независимость функциональных устройств определили высокую теоретическую производительность компьютера (две операции за такт), векторные команды упростили путь к высокой производительности на практике (эффективная загрузка конвейеров), а работа функциональных устройств с регистрами сделала высокую производительность возможной даже при небольшом числе операций (исключительно малая латентность при запуске векторных команд).
Общие характеристики Сгау-1: время такта 12,5 не, 12 конвейерных функцио­нальных устройств, причем все функциональные устройства могут работать одновременно и независимо друг от друга, оперативная память до 1 Мслова, пиковая производительность компьютера 160 млн. операций в секунду. В главе 3 мы будем детально разбирать архитектуру и особенности программирования векторно-конвейерного компьютера Cray С90, имеющего с Сгау-1 много об­щего в архитектуре, но появившегося на пятнадцать лет позже.
Анализируя архитектурные особенности компьютеров, мы неоднократно, хо­тя, быть может, и неявно, затрагивали очень важную проблему согласования скорости работы процессора и времени выборки данных из памяти. В одних компьютерах использовалось расслоение памяти, в других вводилась развитая регистровая структура, многоуровневая память, кэш-память или что-то еще, но в любом случае все подобные особенности организации памяти направле­ны на достижение одной цели — ускорить выборку команд и данных.
Давно было подмечено, что процесс выполнения большинства программ характеризуется двумя свойствами: локальностью вычислений и локальностью использования данных. И в том, и в другом случае следующий необходимый для работы программы объект (команда или операнд) расположен в опера­тивной памяти "недалеко" от предыдущего. Идеальный пример фрагмента программы с высокой локальностью вычислений — это цикл с телом из од­ного оператора и большим числом итераций. На практике многие програм­мы обладают сразу двумя свойствами, и локальностью вычислений, и ло­кальностью использования данных. Но, к сожалению, это ни в коей мере не является законом. Вызовы подпрограмм и функций, косвенная адресация массивов, неудачная работа с многомерными массивами и сложными сгрук-
58
Часть I. Параллельные вычислительные системы
турами данных, использование разного рода условных операторов — это лишь небольшой список причин, по которым свойства локальности в ре­альных программах могут значительно ослабевать.
Основной конструкцией в языках программирования, определяющей как локальность вычислений, так и локальность использования данных, являются циклы. В идеале, все тело цикла лучше поместить в более быструю память, поскольку на следующей итерации, скорее всего, нужно будет выполнить те же самые команды. Похожая ситуация и с данными: все то, что использует­ся часто, лучше хранить на регистрах, у которых времена чтения/записи всегда согласованы со скоростью работы процессора. Однако удовольствие это очень дорогое и сколько-нибудь значительные объемы данных на реги­страх держать невозможно. В такой ситуации будет использоваться следую­щий уровень в иерархии памяти, например, кэш-память. Если объема кэш­памяти не хватит, то можно задействовать следующий уровень и т. д. Ана­логий этому факту очень много в повседневной жизни. Все самое необхо­димое мастер всегда держит под рукой на рабочем столе (регистры), часто используемые предметы хранятся здесь же в ящиках стола (кэш-память), основной набор инструментов лежит в шкафу недалеко от стола (основная память), за чем-то приходится иногда спускаться на склад (дисковая па­мять), а что-то время от времени приходится заказывать даже из другой мас­терской (архив на магнитной ленте).
Примеров конкретных реализаций различных уровней памяти можно привести очень много. Это регистры и регистровые файлы, кэш-память разных уров­ней, основная оперативная память, виртуальные диски, реализованные либо на отдельных участках основной оперативной памяти, либо на другой, более медленной памяти, жесткие диски, ленточные роботы и многое многое дру­гое. А закон формирования иерархии памяти в каждом компьютере один: чем выше уровень в иерархии, тем выше скорость работы с данными, тем дороже (в пересчете на слово или байт) обходится память, поэтому объем каждого последующего уровня становится все меньше и меньше.
Эффективность использования иерархии памяти подметили давно. Так, да­же в описанном выше компьютере ILLIAC IV уже можно выделить, по крайней мере, четыре уровня. Каждый процессорный элемент имел по 6 программно адресуемых регистров и собственную локальную оперативную память на 2048 слов. Для хранения данных использовались два жестких дис­ка на 1 Гбит каждый, а для долговременного хранения предназначалась ла­зерная память с однократной записью емкостью в 1012 бит (луч аргонового лазера выжигал отверстия в тонкой металлической пленке, укрепленной на барабане).
Безусловно, иерархия памяти не имеет прямого отношения к параллелизму. Но она относится к тем особенностям архитектуры компьютеров, которые имеют огромное значение для повышения их производительности, поэтому
Глава 2. Как повышают производительность компьютеров
59
ничего не сказать о ней мы, конечно же, не могли. Это тем более справед­ливо, если учесть, что в настоящее время подобная иерархия поддерживается везде, от суперкомпьютеров до персональных компьютеров.
Вопросы и задания
1.   Каким соотношением связаны между собой время такта и тактовая частота компьютера?
2.   Если время такта компьютера равно 2,5 не, и за каждый такт он может выпол­нять две операции, чему равна пиковая производительность этого компьютера?
3.   Приведите примеры использования конвейерной и параллельной обработки из жизни школы или вуза.
4.   Конвейерное устройство состоит из пяти ступеней. Времена срабатываний сту­пеней равны 1, 1, 2, 1 и 3 такта соответственно. С какой максимальной часто­той на выходе данного устройства будут появляться результаты, если на его вход аргументы поступают без перебоев?
5.   Меняется ли ответ задачи 4 в зависимости от того, в каком порядке идут ступе­ни в таком устройстве?
6.   За какое минимальное число тактов может быть выполнено 70 операций, если в распоряжении есть устройство, описанное в задаче 4?
7.   В компьютере есть 7 параллельно работающих устройств, каждое из которых может выполнить операцию за 7 единиц времени. За какое минимальное время этот компьютер обработает 7 независимых операций?
8.   Конвейерное устройство состоит из к ступеней, срабатывающих за яь п2, , пк тактов соответственно. За какое минимальное число тактов может быть выпол­нено m операций на таком устройстве?
9.   В системе есть два универсальных процессора, выполняющих как операцию сложения, так и операцию умножения за один такт. Предположим, что опера­ции чтения/записи данных происходят мгновенно и всегда есть место для со­хранения промежуточных результатов. За какое минимальное число тактов в та­кой системе может быть выполнен следующий фрагмент программы?
а = с * d + е
Ь = v * t + и
f = х * у + Z
10.   Знаете ли вы, что 100-летие со дня рождения академика С. А. Лебедева, одного из основателей российской школы вычислительной техники, 2 ноября 2002 года?
11.   Знаете ли вы, что далеко не худшей модификацией модулей памяти на магнит­ных сердечниках были модули размером 12x12 см, содержащие тридцать два 32-разрядных слова?
12.   Почему среднее время выполнения команды в компьютере ATLAS после введе­ния в нем четырехступенчатой конвейерной обработки команд сократилось с 6 мке не до 1,5 мке, а до 1,6?
60
Часть I. Параллельные вычислительные системы
13.   Почему пиковой производительности конвейерного компьютера нельзя точно достичь на практике?
14.   Знаете ли вы, что для системы Сгау-1, отличавшейся невероятной по тем време­нам компактностью (диаметр центральной цилиндрической стойки 1,37 м, высота 1,98 м, диаметр в основании на уровне пола 2,74 м), требовалось два компрессора на 25 т хладагента и более 96 км шлейфов для внутренних соединений?
15.   Знаете ли вы, что еще в 1967 году М. А. Карцевым был разработан проект вы­числительного комплекса М-9 с производительностью около 1 миллиарда оп/с?
16.   Какие уровни в иерархии памяти используются в современных компьютерах? Каковы их объемы?
§ 2.2. Повышение интеллектуальности управления компьютером
Итак, основной вывод предыдущего параграфа ясен — "параллелизм в архи­тектуре компьютеров — это не просто надолго, это навсегда". Современные технологии позволяют легко объединять тысячи процессоров в рамках од­ной вычислительной системы, и подобные компьютеры с терафлопной1 производительностью уже не самый интересный предмет для книги рекор­дов Гиннеса. Вместе с тем, весь опыт использования параллельных компью­теров показывает, что ситуация далека от идеальной. На практике почти всегда справедливо утверждение: "повышение степени параллелизма в архи­тектуре компьютера ведет как к росту его пиковой производительности, так одновременно и к увеличению разрыва между производительностью пиковой и реальной". Это и понятно. Не могут все программы одинаково эффективно использовать все 64 процессорных элемента компьютера ILL1AC IV. Если программа не поддается векторизации, она не сможет использовать всех пре­имуществ архитектуры семейства векторно-конвейерных машин Cray.
Иногда структура алгоритма не позволяет в принципе получить эффектив­ную реализацию, но такая ситуация встречается не так часто. Чаще всего реальную производительность программы поднять можно, однако трудо­емкость этого процесса сильно зависит от того, насколько развита поддержка параллелизма в аппаратно-программной среде вычислительной системы. Здесь важно все: операционные системы и компиляторы, технологии параллель­ного программирования и системы времени исполнения программ, под­держка параллелизма в процессоре и особенности работы с памятью. Если что-то не предусмотрено, то для получения эффективной программы поль­зователь должен сам позаботиться об этом: если компилятор плох, то писать на ассемблере, если на аппаратном уровне не решена проблема согласован-
Порядка 1012 (триллион) операций с плавающей запятой. — Ред.
Глава 2. Как повышают производительность компьютеров
61
ности содержимого кэш-памяти разных процессоров, то регулярно в код вставлять функции, вызывающие сброс кэш-памяти, и т. п.
К настоящему моменту накопился значительный опыт поддержки паралле­лизма в программно-аппаратной среде компьютера, чему и будет посвящен данный параграф. Наряду со знакомством с "параллельной" составляющей этой среды, с пониманием того, что сопровождает решение задач на парал­лельном компьютере, не менее важна и обратная сторона вопроса. Если эф­фективность программы оказалось низкой, то в принципе причина может скрываться везде. Не стоит сразу ругать использованный алгоритм или свои способности программирования. Виновником может оказаться и плохой компилятор (или неверное его использование), и блокировки обращения к памяти на уровне аппаратуры, плохо реализованный параллельный ввод/вывод, конфликты при прохождении пакетов по коммуникационной среде — каждая составляющая программно-аппаратной среды компьютера вносит свой вклад в работу программы, а хороший он или плохой, вот с этим и надо разбираться.
Итак, первое, о чем стоит сказать, говоря о повышении эффективности ра­боты программ, — это разработка спецпроцессоров. Перед разработчиками архитектуры компьютеров всегда встает дилемма: реализовывать ту или иную операцию на уровне аппаратуры или возложить эту функцию на про­граммное обеспечение. Поддержка со стороны аппаратуры (специальная система команд процессора, особая структура памяти, разрядность, способы представления данных, топология внутрипроцессорных коммуникаций и т. п.) может дать значительный выигрыш в скорости выполнения опреде­ленного набора операций. Однако, если процессор исключительно эффек­тивно обрабатывает байтовые целые числа, то совершенно не очевидно, что столь же эффективно он будет работать с вещественными числами с пла­вающей точкой или с большими массивами одноразрядных данных. Алго­ритмы разные, а всего в архитектуре заранее предусмотреть невозможно, по­этому и приходится разработчикам искать компромисс между универсальностью и специализированностью. Об этом же заставляют задуматься и соображения относительно размеров вычислительных устройств и их стоимости.
В силу большой специализированное™ спецпроцессоров нужно очень акку­ратно подходить к оценке показателей их производительности. Не редко проскакивают сообщения, которые многие склонны рассматривать как сен­сационные: "разработан процессор, выполняющий X операций в секунду, в то время как лучшие современные образцы работают со скоростью в десят­ки и сотни раз меньшей". Типичная ситуация, в которой удалось эффектно смешать разные понятия и нигде не написать ничего неправильного. Просто "X операций в секунду" воспринимается как общий критерий производи­тельности универсальных процессоров, хотя, как правило, имеются в виду операции специального вида. И сравнение идет с "лучшими современными образцами", что опять-таки ассоциируется с универсальными компьютера-
62
Часть /. Параллельные вычислительные системы
ми, придавая полученным результатам оттенок "значительного шага вперед" по сравнению с существующим уровнем.
Применений у спецпроцессоров много — обработка сигналов, распознава­ние речи, анализ изображений и сейсмологических данных, графические ускорители и т. п. Эффективно используя особенности конкретных алго­ритмов, спецпроцессоры объединяют множество (сотни, тысячи, десятки тысяч) параллельно работающих элементарных функциональных устройств. Большой гибкости нет, зато есть огромная скорость работы. Одними из пер­вых и широко используемыми до сих пор являются спецпроцессоры, по­строенные на основе аппаратной поддержки реализации быстрого преобра­зования Фурье.
Исключительно интересной оказалась идея воспользоваться скрытым от пользователя параллелизмом — параллелизмом на уровне машинных команд (Instruction-Level Parallelism). Выгод — масса, и среди главных стоит назвать отсутствие у пользователя необходимости в специальном параллельном программировании, и то, что проблемы с переносимостью, вообще говоря, остаются на уровне общих проблем переносимости программ в классе по­следовательных машин.
Существует два основных подхода к построению архитектуры процессоров, использующих параллелизм на уровне машинных команд. В обоих случаях предполагается, что процессор содержит несколько функциональных уст­ройств, которые могут работать независимо друг от друга. Устройства могут быть одинаковыми, могут быть разными — в данном случае это неважно.
Суперскалярные процессоры не предполагают, что программа в терминах ма­шинных команд будет включать в себя какую-либо информацию о содер­жащемся в ней параллелизме. Задача обнаружения параллелизма в машин­ном коде возлагается на аппаратуру, она же и строит соответствующую последовательность исполнения команд. В этом смысле код для суперска­лярных процессоров не отражает точно ни природу аппаратного обеспече­ния, на котором он будет реализован, ни точного временного порядка, в котором будут выполняться команды.
VL1W-процессоры (Very Large Instruction Word) работают практически по правилам фоннеймановского компьютера. Разница лишь в том, что коман­да, выдаваемая процессору на каждом цикле, определяет не одну операцию, а сразу несколько. Команда VLlW-процессора состоит из набора полей, ка­ждое из которых отвечает за свою операцию, например, за активизацию функциональных устройств, работу с памятью, операции с регистрами и т. п. Если какая-то часть процессора на данном этапе выполнения про­граммы не востребована, то соответствующее поле команды не задействуется.
Примером компьютера с подобной архитектурой может служить компьютер АР-120В фирмы Floating Point Systems. Его первые поставки начались в 1976 году, а к 1980 году по всему миру было установлено более 1600 экземп-
Глава 2. Как повышают производительность компьютеров
63
ляров. Команда компьютера АР-120В состоит из 64 разрядов и управляет работой всех устройств машины. Каждый такт (167 не) выдается одна команда, что эквивалентно выполнению 6 миллионов команд в секунду. Поскольку каждая команда одновременно управляет многими операциями, то реальная производительность может быть выше. Все 64 разряда команды АР-120В делятся на шесть групп, отвечающих за свой набор операций: опе­рации над 16-разрядными целочисленными данными и регистрами, сложе­ние вещественных чисел, управление вводом/выводом, команды перехода, умножение вещественных чисел и команды работы с основной памятью.
Программа для VLlW-процессора всегда содержит точную информацию о параллелизме. Здесь компилятор всегда сам выявляет параллелизм в про­грамме и явно сообщает аппаратуре, какие операции не зависят друг от друга. Код для VLlW-процессоров содержит точный план того, как процес­сор будет выполнять программу: когда будет выполнена каждая операция, какие функциональные устройства будут работать, какие регистры какие операнды будут содержать и т. п. Компилятор VL1W создает такой план имея полное представление о целевом VLlW-процессоре, чего, вообще гово­ря, нельзя сказать о компиляторах для суперскалярных машин.
Оба подхода имеют свои достоинства и недостатки, и не стоит противопос­тавлять простоту и ограниченные возможности архитектуры VL1W сложно­сти и динамическим возможностям суперскалярных систем. Ясно, что соз­дание плана выполнения операций во время компиляции важно для обеспечения высокой степени распараллеливания и в отношении суперска­лярных систем. Также ясно и то, что во время компиляции существует не­однозначность, которую можно разрешить только во время выполнения программы с помощью динамических механизмов, свойственных суперска­лярной архитектуре.
Большое влияние на развитие идей суперскалярной обработки еще в конце 50-х годов прошлого столетия оказал проект STRETCH фирмы IBM, и сей­час архитектура многих микропроцессоров построена по этому же принци­пу. Яркими представителями VLlW-компьютеров являются компьютеры се­мейств Multiflow и Cydra.
Оба изложенных принципа относятся к увеличению производительности от­дельных процессоров, на основе которых, в свою очередь, могут строиться многопроцессорные конфигурации. Архитектура параллельных компьютеров за время существования компьютерной индустрии развивалась невероятными темпами и в самых разных направлениях. Убедиться в этом совсем не трудно, достаточно просмотреть, например, прекрасный обзор [24]. Однако, если от­бросить детали и выделить общую идею построения большинства современ­ных параллельных вычислительных систем, то останется лишь два класса.
Первый класс — это компьютеры с общей памятью. Системы, построенные по такому принципу, иногда называют мультипроцессорными системами или
64
Часть I. Параллельные вычислительные системы
просто мультипроцессорами. В системе присутствует несколько равноправных процессоров, имеющих одинаковый доступ к единой памяти (рис. 2.8). Все процессоры "разделяют" между собой общую память, отсюда еще одно назва­ние компьютеров этого класса — компьютеры с разделяемой памятью. Все процессоры работают с единым адресным пространством: если один процес­сор записал значение 79 в слово по адресу 1024, то другой процессор, прочи­тав слово, расположенное по адресу 1024, получит значение 79.
Проц Проц ••• Проц
Память
Рис. 2.8. Параллельные компьютеры с общей памятью
Второй класс — это компьютеры с распределенной памятью, которые по ана­логии с предыдущим классом иногда будем называть мультикомпьютерными системами (рис. 2.9). По сути дела, каждый вычислительный узел является полноценным компьютером со своим процессором, памятью, подсистемой ввода/вывода, операционной системой. В такой ситуации, если один про­цессор запишет значение 79 по адресу 1024, то это никак не повлияет на то, что по тому же адресу прочитает другой, поскольку каждый из них работает в своем адресном пространстве.
Проц
Проц
...
Проц
Память
Память
Память
Коммуникационная среда
Рис. 2.9. Параллельные компьютеры с распределенной памятью
К компьютерам с общей памятью относятся все системы класса Symmetric Multi Processors (SMP). В SMP все, кроме нескольких процессоров, в одном экземпляре: одна память, одна операционная система, одна подсистема вво­да/вывода. Слово "симметричный" в названии архитектуры означает, что каждый процессор может делать все то, что и любой другой. Кстати, в на­стоящее время SMP часто рассматривают как альтернативное название для компьютеров с общей памятью, чему дополнительно способствуют два воз­можных варианта расшифровки данной аббревиатуры: Symmetric Multi Proc­essors и Shared Memory Processors.
Глава 2. Как повышают производительность компьютеров
65
Эти два класса, компьютеры с общей и распределенной памятью, появились не случайно. Они отражают две основные задачи параллельных вычислений. Первая задача заключается в построении вычислительных систем с макси­мальной производительностью. Это легко позволяет сделать компьютеры с распределенной памятью. Уже сегодня существуют установки, объединяю­щие тысячи вычислительных узлов в рамках единой коммуникационной среды. Да что говорить, даже Интернет можно рассматривать как самый большой параллельный компьютер с распределенной памятью, объединяю­щий миллионы вычислительных узлов. Но как такие системы эффективно использовать? Как убрать большие накладные расходы, идущие на взаимо­действие параллельно работающих процессоров? Как упростить разработку параллельных программ? Практически единственный способ программиро­вания подобных систем — это использование систем обмена сообщениями, например, PVM или MPI, что не всегда просто. Отсюда возникает вторая задача — поиск методов разработки эффективного программного обеспечения для параллельных вычислительных систем. Данная задача немного проще ре­шается для компьютеров с общей памятью. Накладные расходы на обмен данными между процессорами через общую память минимальны, а техно­логии программирования таких систем, как правило, проще. Проблема здесь в другом. По технологическим причинам не удается объединить большое число процессоров с единой оперативной памятью, а потому очень большую производительность на таких системах сегодня получить нельзя.
Заметим, что в обоих случаях много проблем вызывает система коммутации, связывающая либо процессоры с модулями памяти, либо процессоры между собой. Легко сказать, что 32 процессора имеют равный доступ к единой оперативной памяти или что 1024 процессора могут общаться каждый с ка­ждым, а как это реализовать на практике? Рассмотрим некоторые способы организации коммуникационных систем в компьютерах.
Один из самых простых способов организации мультипроцессорных систем опирается на использование общей шины (рис. 2.10), к которой подключаются как процессоры, так и память. Сама шина состоит из какого-то числа линий, необходимых для передачи адресов, данных и управляющих сигналов между процессорами и памятью. Чтобы предотвратить одновременное обращение нескольких процессоров к памяти, используется та или иная схема арбитража, гарантирующая монопольное владение шиной захватившим ее устройством. Основная проблема таких систем заключается в том, что даже небольшое уве­личение числа устройств на шине (4—5) очень быстро делает ее узким местом, вызывающим значительные задержки при обменах с памятью и катастрофи­ческое падение производительности системы в целом.
Для построения более мощных систем необходимы другие подходы. Одним из них является разделение памяти на независимые модули и обеспечение возможности доступа разных процессоров к различным модулям одновре-
66
Часть I. Параллельные вычислительные системы
менно. Возможных решений может быть много, в частности, использование матричного коммутатора. Процессоры и модули памяти связываются так, как показано на рис. 2.11. На пересечении линий располагаются элементар­ные точечные переключатели, разрешающие или запрещающие передачу информации между процессорами и модулями памяти. Безусловным пре­имуществом такой организации является возможность одновременной рабо­ты процессоров с различными модулями памяти. Естественно, в ситуации, когда два процессора захотят работать с одним модулем памяти, один из них будет заблокирован. Недостатком матричных коммутаторов является боль­шой объем необходимого оборудования, поскольку для связи п процессоров с п модулями памяти требуется п2 элементарных переключателей. Во многих случаях это является слишком дорогим решением, что вынуждает разработ­чиков искать иные пути.
Процессоры Память
С С С
М
Шина
Рис. 2.10. Мультипроцессорная система с общей шиной
Модули памяти
М
М
М
М
0—0—0—0-
о—о—о—о-
0—0—0—0-
Точечные переключатели
Рис. 2.11. Мультипроцессорная система с матричным коммутатором
Альтернативным способом является использование каскадных переключате­лей, например, так, как это сделано в омега-сети. На рис. 2.12 показана сеть из четырех коммутаторов 2x2, организованная в два каскада. Каждый ис­пользованный коммутатор может соединить любой из двух своих входов с любым из двух своих выходов. Это свойство и использованная схема комму-
Глава 2. Как повышают производительность компьютеров
67
тации позволяют любому процессору вычислительной системы, показанной на этом рисунке, обращаться к любому модулю памяти. В общем случае для соединения я процессоров с п модулями памяти потребуется log2« каскадов по я/2 коммутаторов в каждом, т. е. всего («log2«)/2 коммутаторов. Для больших значений п эта величина намного лучше, чем я2, однако появляется проблема другого рода — задержки. Каждый коммутатор не срабатывает мгновенно, т. к. на коммутацию входа с выходом на каждом каскаде требу­ется некоторое время. Опять ищется компромисс между дорогой коммуни­кационной системой с небольшим временем переключения и недорогой системой, но с большими задержками.
с с с с
м м м м
Переключатели 2х2
Рис. 2.12. Мультипроцессорная система с омега-сетью
Мы рассмотрели далеко не все варианты соединений процессоров с моду­лями памяти. Реально используемых схем коммутации процессоров в систе­мах с распределенной памятью намного больше. По существу можно ис­пользовать и все уже рассмотренные варианты, и одновременно массу других. Простейший вариант топологии связи показан на рис. 2.13, а, где все вычислительные узлы объединены в одну линейку. Каждый узел систе­мы, кроме первого и последнего, имеет по одному непосредственному сосе­ду справа и слева. Для построения системы из я узлов требуется я — 1 со­единение. Средняя длина пути между двумя узлами системы равна я/3. Можно уменьшить среднюю длину пути, если преобразовать линейку вы­числительных узлов в кольцо (рис. 2.13, б). Добавив лишь одно дополни­тельное соединение первого узла с последним, мы на самом деле получили два дополнительных полезных свойства у новой топологии. Во-первых, средняя длина пути между двумя узлами сократилась с я/3 до я/6. Во-вторых, за счет того, что передача между любыми узлами уже может идти по двум независимым направлениям, увеличилась отказоустойчивость системы в целом. Пока все связи работоспособны, передача будет идти по кратчай­шему пути. Но если нарушилась какая-либо одна связь, то возможна пере­дача в противоположном направлении.
68
Часть I. Параллельные вычислительные системы
ОЧ!>Чз>Ч±)
Рис. 2.13. Мультикомпьютерные системы с топологиями связи: а — линейка; б — кольцо; в — звезда
Несмотря на явную ограниченность в непосредственных связях даже подоб­ные простые "линейные" топологии хорошо соответствуют многим алгорит­мам, в которых необходима связь лишь соседних процессов между собой. В частности, многие одномерные задачи математической физики (да и многомерные с делением области на одномерные) хорошо решаются подоб­ными методами. Для таких задач никаких других топологий придумывать не надо. Но далеко не все задачи такие. Схему сдваивания или блочные методы линейной алгебры на таких топологиях эффективно реализовать не просто, поскольку неправильное размещение процессов по процессорам приведет к потере большей части времени на коммуникации. В идеальной ситуации пользователь не должен думать об этом, у него других проблем хватает, но на практике чудес не бывает. Сегодня по технологическим причинам нельзя сделать большие мультикомпьютерные системы, в которых каждый процес­сор имел бы непосредственную связь со всеми остальными. А раз так, то и здесь разработчикам вычислительных систем приходится искать компромисс между универсальностью и специализированностью, между сложностью и доступностью. Если класс задач заранее определен, то ситуация сильно об-
Глава 2. Как повышают производительность компьютеров
69
легчается, и результат может быть найден легко. Например, использование схемы распределения работы между параллельными процессами, аналогич­ной схеме клиент-сервер, при которой один головной процесс раздает зада­ния подчиненным процессам (схема мастер/рабочие, или master/slaves), хо­рошо соответствует топологии "звезда" (рис. 2.13, в). Вычислительные узлы, расположенные в лучах звезды, не имеют непосредственной связи между собой. Но это нисколько не мешает эффективному взаимодействию процес­са-мастера с подчиненными процессами при условии, что мастер располо­жен в центральном узле.
Выбор той или иной топологии связи процессоров в конкретной вычисли­тельной системе может быть обусловлен самыми разными причинами. Это могут быть соображения стоимости, технологической реализуемости, про­стоты сборки и программирования, надежности, минимальности средней длины пути между узлами, минимальности максимального расстояния меж­ду узлами и др. Некоторые варианты показаны на рис. 2.14. Топология дву­мерной решетки (рис. 2.14, а) была использована в начале 90-х годов про­шлого века при построении суперкомпьютера Intel Paragon на базе процессоров i860. В соответствии с топологией двумерного тора (рис. 2.14, б) могут быть соединены вычислительные узлы кластеров, использующих сеть SC1, предлагаемую компанией Dolphin Interconnect Solutions. Таким образом, в частности, построен один из кластеров НИВЦ МГУ. В настоящее время эта же компания Dolphin предлагает сетевые комплекты, позволяю­щие объединять узлы в трехмерный тор — чем меньше среднее расстояние между узлами, тем выше надежность. В этом смысле наилучшие показатели имеет топология, в которой каждый процессор имеет непосредственную связь со всеми остальными (рис. 2.14, в). Но о такой роскоши современный уровень технологии даже мечтать не позволяет: я — 1 соединение у каждого узла при общем числе связей я(я 1)/2.
Иногда находятся исключительно интересные варианты, одним из которых является топология двоичного гиперкуба (рис. 2.14, г). В я-мерном простран­стве рассмотрим лишь вершины единичного я-мерного куба, т. е. точки (jq, х2, ..., хп), в которых все координаты X/ могут быть равны либо 0, либо 1. В эти точки мы условно и поместим процессоры системы. Каждый процес­сор соединим с ближайшим непосредственным соседом вдоль каждого из я измерений. В результате получили я-мерный куб для системы из N = 2п процессоров. Двумерный куб соответствует простому квадрату, а четырех­мерный вариант условно изображен на рис. 2.14, г. В гиперкубе каждый процессор связан лишь с log27V непосредственными соседями, а не с N, как в случае полной связности. Заметим, что при всей кажущейся замысловато­сти гиперкуб имеет массу полезных свойств. Например, для каждого про­цессора очень просто определить всех его соседей: они отличаются от не­го лишь значением какой-либо одной координаты xt. Каждая "грань" я-мерного гиперкуба является гиперкубом размерности я — 1. Максималь-
70
Часть I. Параллельные вычислительные системы
ное расстояние между вершинами «-мерного гиперкуба равно п. Гиперкуб симметричен относительно своих узлов: из каждого узла система выглядит одинаковой и не существует узлов, которым необходима специальная обра­ботка. Отметим и то, что многие алгоритмы по своей структуре прекрасно соответствуют такой взаимосвязи между процессорами. В частности, "неудобная" для других топологий схема сдваивания очень хорошо ложится на гиперкуб, используя на каждом шаге алгоритма гиперкуб на единицу меньшей размерности.
1100
1101
юоо
0101
0001
Рис. 2.14. Варианты топологий связи процессоров: а — решетка; б — 2-тор; в — полная связь; г — гиперкуб
Одной из первых реальных многопроцессорных вычислительных систем с ар­хитектурой гиперкуба стал компьютер Cosmic Cube, построенный в 1983 году в Калифорнийском технологическом институте на основе микропроцессо­ров Intel 8086/8087. В 1985 году фирма Intel выпустила первый промышлен­ный гиперкуб. Это был компьютер iPSC (Intel Personal Supercomputer), в котором в качестве узловых процессоров были использованы микропроцес­соры серии 80286/80287. В том же году был объявлен коммерческий гипер­куб NCUBE/ten фирмы NCUBE Corporation, содержащий до 1024 узлов.
В некоторых компьютерах гиперкуб использовался в комбинации с другими типами архитектур. В машинах серии Connection Machine фирмы Thinking
Глава 2. Как повышают производительность компьютеров
71
Machines использовалось до 216 простых узлов. На одном кристалле этого компьютера находилось 16 узлов, имеющих связь каждый с каждым, а 212 таких кристаллов уже объединялись в 12-мерный гиперкуб.
Вернемся к обсуждению особенностей компьютеров с общей и распреде­ленной памятью. Как мы уже видели, оба класса имеют свои достоинства, которые, правда, тут же плавно перетекают в их недостатки. Для компьюте­ров с общей памятью проще создавать параллельные программы, но их мак­симальная производительность сильно ограничивается небольшим числом процессоров. А для компьютеров с распределенной памятью все наоборот. Можно ли объединить достоинства этих двух классов? Одним из возможных направлений является проектирование компьютеров с архитектурой NUMA (Non Uniform Memory Access).
Почему проще писать параллельные программы для компьютеров с общей памятью? Потому что есть единое адресное пространство и пользователю не нужно заниматься организацией пересылок сообщений между процессами для обмена данными. Если создать механизм, который всю совокупную фи­зическую память компьютера позволял бы программам пользователей рас­сматривать как единую адресуемую память, все стало бы намного проще.
По такому пути и пошли разработчики системы Cm*, создавшие еще в кон­це 70-х годов прошлого века первый NUMA-компьютер. Данный компьютер состоит из набора кластеров, соединенных друг с другом через межкластер­ную шину. Каждый кластер объединяет процессор, контроллер памяти, мо­дуль памяти и, быть может, некоторые устройства ввода/вывода, соединен­ные между собой посредством локальной шины (рис. 2.15). Когда процессору нужно выполнить операции чтения или записи, он посылает запрос с нужным адресом своему контроллеру памяти. Контроллер анализи­рует старшие разряды адреса, по которым и определяет, в каком модуле хранятся нужные данные. Если адрес локальный, то запрос выставляется на локальную шину, в противном случае запрос для удаленного кластера от­правляется через межкластерную шину. В таком режиме программа, храня­щаяся в одном модуле памяти, может выполняться любым процессором системы. Единственное различие заключается в скорости выполнения. Все локальные ссылки отрабатываются намного быстрее, чем удаленные. По­этому и процессор того кластера, где хранится программа, выполнит ее на порядок быстрее, чем любой другой.
От этой особенности и происходит название данного класса компьютеров — компьютеры с неоднородным доступом к памяти. В этом смысле иногда го­ворят, что классические SMP-компьютеры обладают архитектурой UMA (Uniform Memory Access), обеспечивая одинаковый доступ любого процессора к любому модулю памяти.
72
Часть I. Параллельные вычислительные системы
Кластер
^
CPU
М
I/O
...Л_______
а
ш
I
S
3
Локальная шин
'\
ш
I
CPU
М
I/O
d
;
5
ь
ш
2
Локальная памят
\
CPU
м
I/O
\
7
к-----
-''
Контроллер памяти
Рис. 2.15. Схема вычислительной системы Cm*
Другим примером NUMA-компьютера являлся компьютер BBN Butterfly, ко­торый в максимальной конфигурации объединял 256 процессоров (рис. 2.16). Каждый вычислительный узел компьютера содержит процессор, локальную память и контроллер памяти, который определяет, является ли запрос к памя­ти локальным или его необходимо передать удаленному узлу через коммутатор Butterfly. С точки зрения программиста память является единой общей памя­тью, удаленные ссылки в которой реализуются немного дольше локальных (приблизительно 6 мкс для удаленных против 2 мкс для локальных).
По пути построения больших NUMA-компьютеров можно было бы смело идти вперед, если бы не одна неожиданная проблема — кэш-память отдель­ных процессоров. Кэш-память, которая помогает значительно ускорить ра­боту отдельных процессоров, для многопроцессорных систем оказывается узким местом. В процессорах первых NUMA-компьютеров кэш-памяти не было, поэтому не было и такой проблемы. Но для современных микропро­цессоров кэш является неотъемлемой составной частью. Причину нашего беспокойства очень легко объяснить. Предположим, что процессор Р\ со­хранил значение х в ячейке q, а затем процессор Pi хочет прочитать содер­жимое той же ячейки q. Что получит процессор Р{! Конечно же, всем бы хотелось, чтобы он получил значение х, но как он его получит, если х попа­ло в кэш процессора Р{> Эта проблема носит название проблемы согласования содержимого кэш-памяти (cache coherence problem, проблема
Глава 2. Как повышают производительность компьютеров
73
когерентности кэшей). Указанная проблема актуальна и для современных SMP-компьютеров, кэш процессоров которых также может вызвать несогла­сованность в использовании данных.
Переключатели
Рис. 2.16. Схема вычислительной системы BBN Butterfly
Для решения данной проблемы разработана специальная модификация NUMA-архитектуры — ccNUMA (cache coherent NUMA). He будем сейчас вдаваться в технические подробности множества протоколов, которые обес­печивают согласованность содержимого всех кэшей. Важно, что эта пробле­ма решается и не ложится на плечи пользователей. Для пользователей важ­нее другой вопрос, насколько "неоднородна" архитектура NUMA? Если обращение к памяти другого узла требует на 5—10% больше времени, чем обращение к своей памяти, то это может и не вызвать никаких вопросов. Большинство пользователей будут относиться к такой системе, как к UMA (SMP), и практически все разработанные для SMP программы будут рабо­тать достаточно хорошо. Однако для современных NUMA систем это не так, и разница времени локального и удаленного доступа лежит в промежутке 200—700%. При такой разнице в скорости доступа для обеспечения должной
74
Часть I. Параллельные вычислительные системы
эффективности выполнения программ следует позаботиться о правильном расположении требуемых данных.
На основе архитектуры ccNUMA в настоящее время выпускается множество реальных систем, расширяющих возможности традиционных компьютеров с общей памятью. При этом, если конфигурации SMP серверов от ведущих производителей содержат 16—32—64 процессора, то их расширения с архи­тектурой ccNUMA уже объединяют до 256 процессоров и больше.
Одновременно с совершенствованием архитектуры проходило развитие и программного обеспечения параллельных компьютеров. Практика показала, что развитие аппаратной и программной составляющих параллельных вы­числительных систем вообще нельзя рассматривать отдельно друг от друга. Новшества одной составляющей влекут изменения в другой. Хорошим при­мером тому может служить аппаратная поддержка барьерной синхронизации процессов в компьютерах семейства Cray T3D/T3E. Этот вид синхронизации часто используется в программах, однако его программная реализация тре­бовала значительных накладных расходов — что нельзя было сделать эффек­тивно на программном уровне, сделали на уровне аппаратуры (см. § 3.4).
Сейчас нас будут интересовать, в первую очередь, изменения в технологиях параллельного программирования. Конечно, в распоряжении современных программистов есть не только ассемблер или Fortran, создано много других систем и языков программирования. Однако в настоящее время проблема разработки эффективного параллельного программного обеспечения оказалась центральной проблемой параллельных вычислений в целом.
Итак, как же заставить несколько процессоров решать одну задачу? Этот вопрос возник одновременно с появлением первых параллельных компью­теров, и к настоящему моменту накоплен целый спектр различных техно­логий программирования. Подробное изложение конкретных технологий будет представлено в главе 5, здесь же мы ограничимся лишь описанием ос­новных подходов.
Начнем с использования в традиционных языках программирования специ­альных комментариев, добавляющих "параллельную" специфику в изначаль­но последовательные программы. Предположим, что вы работаете на век-торно-конвейерном компьютере Cray Т90. Вы знаете, что все итерации некоторого цикла программы независимы и, следовательно, его можно век­торизовать, т. е. очень эффективно исполнить с помощью векторных команд на конвейерных функциональных устройствах. Если цикл простой, то компилятор и сам определит, возможность преобразования последова­тельного кода в параллельный. Если уверенности в высоком интеллекте компилятора нет, то перед заголовком цикла лучше вставить явное указание на отсутствие зависимости и возможность векторизации. В частности, для языка Fortran это выглядит так:
CDIR$ NODEPCHK
Глава 2. Как повышают производительность компьютеров
75
По правилу языка Fortran, буква 'С в первой позиции говорит о том, что вся строка является комментарием, последовательность 'dir$' указывает на то, что это спецкомментарий для компилятора, а часть 'nodepchk1 как раз и говорит об отсутствии информационной зависимости между итерациями последующего цикла.
Следует отметить, что использование спецкомментариев не только добавля­ет возможность параллельного исполнения, но и полностью сохраняет ис­ходный вариант программы. На практике это очень удобно — если компи­лятор ничего не знает о параллелизме, то все спецкомментарии он просто пропустит, взяв за основу последовательную семантику программы.
На использование комментариев опирается и широко распространенный в настоящее время стандарт ОрепМР. Основная ориентация сделана на работу с общей памятью, нитями (threads) и явным описанием параллелизма. В Fortran признаком спецкомментария ОрепМР является префикс !$омр, а в языке С используют директиву "#pragma omp". В настоящее время практи­чески все ведущие производители SMP-компьютеров поддерживают ОрепМР в компиляторах на своих платформах.
Кроме использования комментариев для получения параллельной програм­мы, часто идут на расширение существующих языков программирования. Вво­дятся дополнительные операторы и новые элементы описания переменных, позволяющие пользователю явно задавать параллельную структуру програм­мы и в некоторых случаях управлять исполнением параллельной програм­мы. Так, язык High Performance Fortran (HPF), помимо традиционных опе­раторов Fortran и системы спецкомментариев, содержит новый оператор forall, введенный для описания параллельных циклов программы. Другим примером служит язык трС, разработанный в Институте системного про­граммирования РАН как расширение ANSI С. Основное назначение трС — создание эффективных параллельных программ для неоднородных вычисли­тельных систем.
Если нужно точнее отразить либо специфику архитектуры параллельных систем, либо свойства какого-то класса задач некоторой предметной облас­ти, то используют специальные языки параллельного программирования. Для программирования транспьютерных систем был создан язык Occam, для программирования потоковых машин был спроектирован язык однократ­ного присваивания Sisal. Очень интересной и оригинальной разработкой является декларативный язык НОРМА, созданный под руководством И. Б. Задыхайло в Институте прикладной математики им. М. В. Келдыша РАН для описания решения вычислительных задач сеточными методами. Высокий уровень абстракции языка позволяет описывать задачи в нотации, близкой к исходной постановке проблемы математиком, что условно авторы языка называют программированием без программиста. Язык не содержит традиционных конструкций языков программирования, фиксирующих по-
76
Часть I. Параллельные вычислительные системы
рядок вычисления и тем самым скрывающих естественный параллелизм алгоритма.
С появлением массивно-параллельных компьютеров широкое распростра­нение получили библиотеки и интерфейсы, поддерживающие взаимодействие параллельных процессов. Типичным представителем данного направления яв­ляется интерфейс Message Passing Interface (MPI), реализация которого есть практически на каждой параллельной платформе, начиная от векторно-конвейерных супер-ЭВМ до кластеров и сетей персональных компьютеров. Программист сам явно определяет какие параллельные процессы приложе­ния в каком месте программы и с какими процессами должны либо обме­ниваться данными, либо синхронизировать свою работу. Обычно адресные пространства параллельных процессов различны. В частности, такой идео­логии следуют MPI и PVM. В других технологиях, например Shmem, допус­кается использование как локальных (private) переменных, так и общих (shared) переменных, доступных всем процессам приложения.
Несколько особняком стоит система Linda, добавляющая в любой последо­вательный язык лишь четыре дополнительные функции in, out, read и evai, что и позволяет создавать параллельные программы. К сожалению, простота заложенной идеи оборачивается большими проблемами в реализа­ции, что делает данную красивую технологию скорее объектом академиче­ского интереса, чем практическим инструментом.
Часто на практике прикладные программисты вообще не используют ника­ких явных параллельных конструкций, обращаясь в критических по времени счета фрагментах к подпрограммам и функциям параллельных предметных библиотек. Весь параллелизм и вся оптимизация спрятаны в вызовах, а пользователю остается лишь написать внешнюю часть своей программы и грамотно воспользоваться стандартными блоками. Примерами подобных библиотек являются Lapack, ScaLapack, Cray Scientific Library, HP Mathematical Library, PETSc и многие другие.
И, наконец, последнее направление, о котором стоит сказать, это использо­вание специализированных пакетов и программных комплексов. Как правило, в этом случае пользователю вообще не приходится программировать. Основ­ная задача — это правильно указать все необходимые входные данные и правильно воспользоваться функциональностью пакета. Так, многие химики для выполнения квантово-химических расчетов на параллельных компьюте­рах пользуются пакетом GAMESS, не задумываясь о том, каким образом реализована параллельная обработка данных в самом пакете.
Подводя итог сказанному в двух параграфах этой главы, следует отдать долж­ное тем большим изменениям, которые произошли в аппаратуре и програм­мном обеспечении параллельных компьютеров за последние несколько десят­ков лет. Эти изменения, одни в большей степени, другие в меньшей, способ­ствовали тому, что современные пользователи получили в свое распоряжение
Глава 2. Как повышают производительность компьютеров
77
мощнейшие вычислительные системы, способные решать совершенно не­подъемные ранее вычислительные задачи. Но как показала практика, пробле­мы, характерные для параллельных вычислительных систем в целом, не ис­чезли, а перешли на другой уровень. Если на заре параллелизма стоял вопрос об эффективном использовании нескольких простых компьютеров, то сейчас речь идет об эффективности систем, состоящих из сотен и тысяч процессо­ров, каждый из которых сам по себе является сложной параллельной систе­мой. Вопросы эффективности, пиковой и реальной производительности для таких систем выходят на передний план, поэтому следующий параграф будет посвящен теоретическим аспектам этих понятий.
Вопросы и задания
1.   Две вычислительные системы отличаются только версиями установленных на них операционных систем. Могут ли на таких компьютерах отличаться времена работы одной и той же программы?
2.   Возможна ли такая ситуация: в программе, содержащей 10 000 строк исходного текста, изменили только один символ, а время ее работы выросло в 10 раз?
3.   Возможна ли такая ситуация: в программе, содержащей 10 000 строк исходного текста, изменили только один символ, после чего производительность, с кото­рой ее выполнял компьютер, выросла в 10 раз?
4.   Предположим, что спецпроцессор является матрицей синхронно работающих однобитных процессоров. Какие классы задач можно эффективно решать на та­ких спецпроцессорах?
5.   Какого вида операции должен уметь быстро выполнять спецпроцессор, проек­тируемый для реализации быстрого преобразования Фурье?
6.   Какие еще виды параллелизма на уровне машинных команд, кроме суперска-лярности и идей VLIW-обработки, используются в современных микропроцес­сорах?
7.   Известно, что программа хорошо выполняется на суперскалярном процессоре с некоторым набором независимых функциональных устройств. Означает ли это, что та же самая программа будет хорошо выполняться на VLIW-процессоре, имеющем тот же набор устройств? Верно ли обратное?
8.   Компилятор для какого процессора: суперскалярного или VLIW, должен быть более "интеллектуальным" для генерации эффективных программ?
9.   Почему общую шину не используют для объединения большого числа процес­соров?
10.   Почему средняя длина пути между двумя узлами системы, в которой п процес­соров соединены по топологии "линейка", равна я/3?
11.   Приведите пример алгоритма, структура связей которого хорошо соответствует топологии "кольцо".
12.   Какое минимальное число переключателей 4x4 необходимо для соединения 64 процессоров с помощью омега-сети?
78
Часть I. Параллельные вычислительные системы
13.   Под расстоянием между двумя процессорами будем понимать минимальное число соединений, образующих путь между этими процессорами в данной то­пологии. Чему равно максимальное расстояние между процессорами в тополо­гии "двумерный тор" отхя? Чему равно максимальное расстояние между процес­сорами в топологии "двумерная решетка" отхя?
14.   Сколько непосредственных соседей имеет каждый процессор в топологии "трехмерный тор"? Чему равно максимальное расстояние между процессорами в топологии "трехмерный тор" 4x8x8?
15.   Известно, что алгоритм хорошо отображался на топологию "кольцо". Можно ли гарантировать его хорошее отображение на топологию "гиперкуб"?
16.   Что должен учитывать пользователь при переходе с SMP-компьютера на ком­пьютер с архитектурой NUMA?
17.   Что общего нужно учитывать при создании эффективных программ для компь­ютеров с архитектурой N UMA и компьютеров с распределенной памятью?
18.   Напишите два варианта программы для любого доступного вам компьютера, показывающих, что правильное или неправильное использование кэш-памяти может привести к 10-кратной разнице во времени.
19.   Какой компьютер содержит наибольшее число процессоров в последней редак­ции списка Тор500 самых мощных компьютеров мира?
20.   Какая из технологий программирования, MPI или ОрепМР, лучше соответству­ет компьютерам с распределенной памятью? SMP-компьютерам?
§ 2.3. Система функциональных устройств
Любая вычислительная система есть работающая во времени совокупность функциональных устройств (ФУ). Для оценки качества ее работы вводятся различные характеристики. Рассмотрим их в рамках некоторой модели про­цесса работы устройств, вполне достаточной для практического примене­ния. Мы не будем интересоваться содержательной частью операций, выпол­няемых функциональными устройствами. Они могут означать арифмети­ческие или логические функции, ввод/вывод данных, пересылку данных в память и извлечение данных из нее или что-либо иное. Более того, допуска­ется, что при разных обращениях операции могут означать разные функции. Для нас сейчас важны лишь времена выполнения операций и то, на устрой­ствах, какого типа они реализуются [9].
Пусть введена система отсчета времени и установлена его единица, напри­мер, секунда. Будем считать, что длительность выполнения операций изме­ряется в долях единицы. Все устройства, реальные или гипотетические, ос­новные или вспомогательные, могут иметь любые времена срабатывания. Единственное существенное ограничение состоит в том, что все срабатыва­ния одного и того же ФУ должны быть одинаковыми по длительности. Все­гда мы будем интересоваться работой каких-то конкретных наборов ФУ. По
Глава 2. Как повышают производительность компьютеров
79
умолчанию будем предполагать, что все другие ФУ, необходимые для обес­печения процесса функционирования этих наборов, срабатывают мгновен­но. Поэтому, если не сделаны специальные оговорки, мы не будем в таких случаях принимать во внимание факт их реального существования. Времена срабатываний изучаемых ФУ будем считать ненулевыми.
Назовем функциональное устройство простым, если никакая последующая операция не может начать выполняться раньше, чем закончится предыду­щая. Простое ФУ может выполнять операции одного типа или разные опе­рации. Разные ФУ могут выполнять операции, в том числе одинаковые, за разное время. Примером простого ФУ могут служить не конвейерные сум­маторы или умножители. Эти ФУ реализуют только один тип операции. Простым устройством можно считать многофункциональный процессор, если он не способен выполнять различные операции одновременно, и мы не принимаем во внимание различия во временах реализации операций, предполагая, что они одинаковы. Основная черта простого ФУ только одна: оно монопольно использует свое оборудование для выполнения каждой от­дельной операции.
В отличие от простого ФУ, конвейерное ФУ распределяет свое оборудование для одновременной реализации нескольких операций. Очень часто оно кон­струируется как линейная цепочка простых элементарных ФУ, имеющих одинаковые времена срабатывания. На этих элементарных ФУ последова­тельно реализуются отдельные этапы операций. В случае конвейерного ФУ, выполняющего операцию сложения чисел с плавающей запятой, соответст­вующие элементарные устройства последовательно реализуют такие опера­ции, как сравнение порядков, сдвиг мантиссы, сложение мантисс и т. п. Тем не менее, ничто не мешает, например, считать конвейерным ФУ ли­нейную цепочку универсальных процессоров. Принцип функционирования конвейерного ФУ остается одним и тем же. Сначала на первом элементар­ном ФУ выполняется первый этап первой операции, и результат передается второму элементарному ФУ. Затем на втором элементарном ФУ реализуется второй этап первой операции. Одновременно на освободившемся первом ФУ реализуется первый этап второй операции. После этого результат сраба­тывания второго ФУ передается третьему ФУ, результат срабатывания пер­вого ФУ передается второму ФУ. Освободившееся первое ФУ готово для выполнения первого этапа третьей операции и т. д. После прохождения всех элементарных ФУ в конвейере операция оказывается выполненной. Эле­ментарные ФУ называются ступенями конвейера, число ступеней в конвей­ере — длиной конвейера. Простое ФУ всегда можно считать конвейерным с длиной конвейера, равной 1. Как уже говорилось, конвейерное ФУ часто является линейной цепочкой простых ФУ, но возможны и более сложные конвейерные конструкции.
Поскольку уже конвейерное ФУ само является системой связанных уст­ройств, необходимо установить наиболее общие принципы работы систем
80
Часть I. Параллельные вычислительные системы
ФУ. Будем считать, что любое ФУ не может одновременно выполнять опера­цию и сохранять результат предыдущего срабатывания, т. е. оно не имеет никакой собственной памяти. Однако будем допускать, что результат преды­дущего срабатывания может сохраняться в ФУ до момента начала очеред­ного его срабатывания, включая сам этот момент. После начала очередного срабатывания ФУ результат предыдущего срабатывания пропадает. Предпо­ложим, что все ФУ работают по индивидуальным командам. В момент пода­чи команды на входы конкретного ФУ передаются аргументы выполняемой операции либо как результаты срабатывания других ФУ с их выходов, либо как входные данные, либо как-нибудь иначе. Сейчас нам безразлично, как именно осуществляется их подача. Важно то, что в момент начала очеред­ного срабатывания ФУ входные данные для этого ФУ доступны, а сам про­цесс подачи не приводит к задержке общего процесса. Конечно мы предпо­лагаем, что программы, определяющие моменты начала срабатываний всех ФУ, составлены корректно и не приводят к тупиковым ситуациям.
При определении различных характеристик, связанных с работой ФУ, так или иначе приходится находить число операций, выполняемых за какое-то время. Это число должно быть целым. Если отрезок времени равен Т, а дли­тельность операции есть т, то за время Т можно выполнить либо Т/т, либо — 1 операций, где символ               есть ближайшее к * сверху целое
число. При больших по сравнению с т значениях Т величина          — 1 при-
близительно равна Г/т. Чтобы в дальнейшем не загромождать выкладки и формулы символами целочисленности и различными членами малого поряд­ка, мы будем всюду приводить результаты в главном, что эквивалентно пере­ходу к пределу при Т7-» со. Говоря иначе, все характеристики и соотношения будут носить асимптотический характер, хотя об этом не будет напоминаться специально. Данное обстоятельство нисколько не снижает практическую цен­ность получаемых результатов, но делает их более наглядными.
Назовем стоимостью операции время ее реализации, а стоимостью работы — сумму стоимостей всех выполненных операций. Стоимость работы — это время последовательной реализации всех рассматриваемых операций на простых ФУ с аналогичными временами срабатываний. Загруженностью уст­ройства на данном отрезке времени будем называть отношение стоимости реально выполненной работы к максимально возможной стоимости. Ясно, что загруженность р всегда удовлетворяет условиям 0 < р < 1. Имеет место также очевидное
Утверждение 2.1
Максимальная стоимость работы, которую можно выполнить за время Т, равна Г для простого ФУ и пТ для конвейерного ФУ длины п.
Будем называть реальной производительностью системы устройств количество операций, реально выполненных в среднем за единицу времени. Пиковой
Глава 2. Как повышают производительность компьютеров
81
производительностью будем называть максимальное количество операций, которое может быть выполнено той же системой за единицу времени при отсутствии связей между ФУ. Из определений вытекает, что как реальная, так и пиковая производительности системы суть суммы соответственно ре­альных и пиковых производительностей всех составляющих систему уст­ройств.
Утверждение 2.2
Пусть система состоит из s устройств, в общем случае простых или конвейер­ных. Если устройства имеют пиковые производительности щ, ..., nsv\ работают с загруженностями рь ..., ps, то реальная производительность г системы выра­жается формулой
s r = ^Pini.                                                           (2.1)
Поскольку реальная производительность системы равна сумме реальных производительностей всех ФУ, то достаточно доказать утверждение для од­ного устройства. Пусть для выполнения одной операции ФУ требует время х и за время Г выполнено ^операций. Независимо от того, каков тип устрой­ства, стоимость выполненной работы равна TVx. Если устройство простое, то согласно утверждению 2.1 максимальная стоимость работы равна Т. Поэто­му загруженность устройства равна Ni/T. По определению реальная произ­водительность ФУ есть N/T, а его пиковая производительность — 1/т. И ра­венство (2.1) становится очевидным. Предположим теперь, что устройство конвейерное длины п. Согласно тому же утверждению 2.1 максимальная стоимость работы в данном случае равна пТ. Поэтому загруженность уст­ройства равна Ni/nT. Реальная производительность снова есть N/T, а пико­вая производительность будет равна л/т. И опять равенство (2.1) становится очевидным.
Если г, п, р суть соответственно реальная производительность, пиковая про­изводительность и загруженность одного устройства, то имеет место равен­ство г = рк. Отсюда видно, что для достижения наибольшей реальной про­изводительности устройства нужно обеспечить наибольшую его загру­женность. Для практических целей понятие производительности наиболее важно потому, что именно оно показывает, насколько эффективно устрой­ство выполняет полезную работу. По отношению к производительности по­нятие загруженности является вспомогательным. Тем не менее, оно полезно в силу того, что указывает путь повышения производительности, причем через вполне определенные действия.
Хотелось бы и для системы устройств ввести понятие загруженности, иг­рающее аналогичную роль. Его можно определять по-разному. Например, как и для одного ФУ, можно было бы считать, что загруженность системы ФУ есть отношение стоимости реально выполненной работы к максимально
82
Часть /. Параллельные вычислительные системы
возможной стоимости. Такое определение вполне приемлемо и позволяет сделать ряд полезных выводов. Однако имеются и возражения. В этом опре­делении медленные и быстрые устройства оказываются равноправными и если необходимо повысить загруженность системы, то не сразу видно, за счет какого ФУ это лучше сделать. К тому же, в данном случае не всегда будет иметь место равенство г = рпс соответствующими характеристиками системы.
Правильный путь введения понятия загруженности системы подсказывает соотношение (2.1). Пусть система состоит из s устройств, в общем случае простых или конвейерных. Если устройства имеют пиковые производитель­ности %\ , ..., ns и работают с загруженностями р\, ...,ps, то будем считать по определению, что загруженность системы есть величина
S
P = Yj aiPi' а' = ~Г~ ■                                    (2-2)
7 = 1
Загруженность системы есть взвешенная сумма загруженностей отдельных устройств, т. к. из (2.2) следует, что
s
^а,- =1, а>0, \<i<s.                                    (2.3)
/=i
Поэтому, как и должно быть для загруженности, выполняются неравенства О <р < 1. Из определения (2.2) с учетом (2.3) получаем, что для того, чтобы загруженность системы устройств равнялась 1, необходимо и достаточно, чтобы равнялись 1 загруженности каждого из устройств. Это вполне логич­но. Если система состоит из одного устройства, т. е. s = 1, то из (2.2) выте­кает, что на ней понятия загруженности системы и загруженности устройст­ва совпадают. Данный факт говорит о согласованности только что введенного понятия загруженности системы с ранее введенными понятия­ми. По определению, пиковая производительность л системы устройств рав­на 7ii+ ...+%• Следовательно, согласно (2.1), (2.2) всегда выполняется очень важное равенство
г=рп.                                                   (2.4)
Большое число ФУ, так же как и конвейерные ФУ, используются тогда, когда возникает потребность решить задачу быстрее. Чтобы понять, на­сколько быстрее это удается сделать, нужно ввести понятие "ускорение". Как и в случае загруженности, оно может вводиться различными способами, многообразие которых зависит от того, что с чем и как сравнивается. Не­редко ускорение определяется, например, как отношение времени решения задачи на одном универсальном процессоре к времени решения той же за­дачи на системе из s таких же процессоров. Очевидно, что в наилучшей си-
Глава 2. Как повышают производительность компьютеров
83
туации ускорение может достигать s. Отношение ускорения к s называется эффективностью. Заметим, что подобное определение ускорения применимо только к системам, состоящим из одинаковых устройств, и не распростра­няется на смешанные системы. Понятие же "эффективность" в рассматри­ваемом случае просто совпадает с понятием загруженности. При введении понятия "ускорение" мы поступим иначе.
Пусть алгоритм реализуется за время Т на вычислительной системе из s уст­ройств, в общем случае простых или конвейерных и имеющих пиковые производительности 7ti, ..., 7iy. Предположим, что ni <п2 < ... <ns. При реа­лизации алгоритма система достигает реальной производительности г из (2.1). Будем сравнивать скорость работы системы со скоростью работы ги­потетического простого универсального устройства, имеющего такую же пи­ковую производительность ns, как самое быстрое ФУ системы, и обладаю­щего возможностью выполнять те же операции, что все ФУ системы.
Итак, будем называть отношение R = r/щ ускорением реализации алгоритма на данной вычислительной системе или просто ускорением. Выбор в качест­ве гипотетического простого, а не какого-нибудь другого, например, кон­вейерного ФУ объясняется тем, что одно простое универсальное ФУ может быть полностью загружено на любом алгоритме. Принимая во внимание (2.1), имеем
S
R = ^---------.                                                       (2-5)
тахя,-
Анализ определяющего ускорение выражения (2.5) показывает, что ускоре­ние системы, состоящей из s устройств, никогда не превосходит s и может достигать s в том и только в том случае, когда все устройства системы име­ют одинаковые пиковые производительности и полностью загружены.
Подводя итог проведенным исследованиям, приведем для одного частного случая полезное, хотя и очевидное
Утверждение 2.3
Если система состоит из s устройств одинаковой пиковой производительности простых или конвейерных, то:
•    загруженность системы равна среднему арифметическому загруженностей всех устройств;
•     реальная производительность системы равна сумме реальных производи-тельностей всех устройств;
•     пиковая производительность системы в s раз больше пиковой производи­тельности одного устройства;
•    ускорение системы равно сумме загруженностей всех устройств;
84
Часть I. Параллельные вычислительные системы
• если система состоит только из простых устройств, то ее ускорение равно отношению времени реализации алгоритма на одном универсальном про­стом устройстве с той же пиковой производительностью к времени реализа­ции алгоритма на системе.
Пусть система устройств функционирует и показывает какую-то реальную производительность. Если производительность недостаточна, то в соответст­вии с (2.4) для ее повышения необходимо увеличить загруженность системы. Согласно (2.2) для этого, в свою очередь, нужно повысить загруженность любого устройства, у которого она еще не равна 1. Но остается открытым вопрос, всегда ли это можно сделать. Если устройство загружено не полно­стью, то его загруженность заведомо можно повысить в том случае, когда данное устройство не связано с другими. В случае же связанности устройств ситуация не очевидна.
Снова рассмотрим систему из s устройств. Не ограничивая общности, будем считать все устройства простыми, т. к. любое конвейерное ФУ всегда можно представить как линейную цепочку простых устройств. Допустим, что между устройствами установлены направленные связи, и они не меняются в про­цессе функционирования системы. Построим ориентированный мульти-граф, в котором вершины символизируют устройства, а дуги — связи между ними. Дугу из одной вершины будем проводить в другую в том и только том случае, когда результат каждого срабатывания устройства, соответствующего первой вершине, обязательно передается в качестве аргумента для очеред­ного срабатывания устройству, соответствующему второй вершине. Назовем этот мультиграф графом системы. Предположим, что каким-то образом в систему введены все исходные данные и она начала функционировать со­гласно описанным ранее правилам. Если в процессе функционирования ка­кие-то ФУ будут требовать для своих срабатываний другие исходные дан­ные, то будем предполагать, что они подаются на входы ФУ без задержек. Исследуем максимальную производительность системы, т. е. ее максимально возможную реальную производительность при достаточно большом времени функционирования.
Утверждение 2.4
Пусть система состоит из s простых устройств с пиковыми производительно-стями щ ,..., щ, Если граф системы связный, то максимальная производитель­ность /-тах системы выражается формулой
W =^ПШ17Г(,                                                       (2.6)
l<i<s
Предположим, что дуга графа системы идет из /-го ФУ в у'-ое ФУ. Пусть за достаточно большое время /-ое ФУ выполнило Nj операций, у'-ое ФУ — Nj операций. Каждый результат /-го ФУ обязательно является одним из аргу­ментов очередного срабатывания у'-го ФУ. Поэтому количество операций, реализованныху'-ым ФУ за время Т, не может более, чем на 1, отличаться от
Глава 2. Как повышают производительность компьютеров
85
количества операций, реализованных /-ым ФУ, т. е. N,■ — 1 < Nj< Nt+ 1. Так как граф системы связный, то любые две вершины графа могут быть связаны цепью, составленной из дуг. Допустим, что граф системы содержит q дуг. Ес­ли k-ое ФУ за время Г выполнило Nk операций, а 1-ое ФУ — N/ операций, то из последних неравенств вытекает, что N/— q < N^< N/ + q для любых
к, I, 1 < к, I < s. Пусть устройства перенумерованы так, что tii < л2<. ... < ns. Принимая во внимание эту упорядоченность и полученные для числа вы­полняемых операций соотношения из (2.1), находим, что
с Ni* | Ф ■
Т          Т
Вторые слагаемые в этих неравенствах стремятся к нулю при Т, стремящемся к бесконечности. Для всех к, 1 < к < s, обязаны выполняться неравенства Nk < ilk Т. В силу предполагаемой упорядоченности производительностей пк и того, что все N^ асимптотически равны между собой, число операций, реализуемых каждым ФУ, будет асимптотически максимальным, если вы­полняется равенство TVi = ъ\Т. Это означает, что максимально возможная реальная производительность системы асимптотически будет равна sk\, что совпадает с (2.6).
Следствие
Пусть вычислительная система состоит из s простых устройств с пиковыми производительностями щ , ..., ns. Если граф системы связный, то:
•    асимптотически каждое из устройств выполняет одно и то же число операций;
•    загруженность любого устройства не превосходит загруженности самого не­производительного устройства;
•    если загруженность какого-то устройства равна 1, то это — самое непроиз­водительное устройство;
•    загруженность системы не превосходит
S 1ГШ171/
l<i<s
-г max
s
7 = 1
превосходит
s mini;
-"max
_ l<i<s
max л,-
Ki<s
86
Часть /. Параллельные вычислительные системы
Установление всех приведенных здесь фактов осуществляется почти дослов­ным повторением доказательства утверждения 2.4 и, естественно, использо­ванием формул (2.2), (2.5).
Следствие (1-ый закон Амдала)
Производительность вычислительной системы, состоящей из связанных между собой устройств, в общем случае определяется самым непроизводительным ее устройством.
Заметим, что утверждение 2.4 указывает на одно из узких мест процесса функционирования системы. Некоторые узкие места вычислительных систем, описанные в литературе, так или иначе связываются с именем Амдала, амери­канского специалиста в области вычислительной техники. Чтобы не терять узнаваемость различных фактов, мы не станем нарушать эту традицию и бу­дем оставлять именными соответствующие утверждения, даже если они со­всем простые. Возможно лишь несколько изменим формулировки, приспосо­бив их к текущему изложению материала. Именно по этим причинам мы назвали 1-ым законом Амдала последнее следствие из утверждения 2.4.
Следствие
Пусть система состоит из простых устройств и граф системы связный. Асимптотическая производительность системы будет максимальной, если все устройства имеют одинаковые пиковые производительности.
Когда мы говорим о максимально возможной реальной производительности, то подразумеваем, что функционирование системы обеспечивается таким расписанием подачи команд, которое минимизирует простой устройств. Максимальная производительность может достигаться при разных режимах. В частности, как следует из утверждения 2.4, она достигается при синхрон­ном режиме с тактом, обратно пропорциональным производительности са­мого медленного из ФУ, если, конечно, система состоит из простых уст­ройств и граф системы связный. Пусть система состоит из s простых устройств одинаковой производительности. Тогда как в случае связанной системы, так и в случае не связанной, максимально возможная реальная производительность при больших временах функционирования оказывается одной и той же и равной ^-кратной пиковой производительности одного устройства.
Мы уже неоднократно убеждались в том, что различные характеристики процесса функционирования системы становятся лучше, если система со­стоит из устройств одинаковой производительности. Предположим, что все устройства, к тому же, простые и универсальные, т. е. на них можно выпол­нять различные операции. Пусть на такой системе реализуется некоторый алгоритм, а сама реализация соответствует какой-то его параллельной фор-
Глава 2. Как повышают производительность компьютеров
87
ме. О ней мы будем говорить детально в §4.2. Но здесь удобно привести некоторые факты, имеющие отношение как к параллельной форме, так и к функциональным устройствам. Допустим, что высота параллельной формы равна ш, ширина равна q и всего в алгоритме выполняется N операций.
Утверждение 2.5
В сформулированных условиях максимально возможное ускорение системы равно N/m.
Пусть система состоит из s устройств пиковой производительности п. Пред­положим, что за время Т реализации алгоритма на /-ом ФУ выполняется Nt операций. По определению загруженность /-го ФУ равна Nj/n Т. Согласно (2.5) ускорение системы в данном случае равно
я           пТ
При заданной производительности устройств время реализации одного яру­са параллельной формы равно л-1. Поэтому время Г реализации алгоритма не меньше, чем ш/п, и достигает этой величины, когда все ярусы реализу­ются подряд без пропусков. Следовательно, ускорение системы при любом числе устройств не будет превосходить N/m.
Следствие
Минимальное число устройств системы, при котором может быть достигнуто максимально возможное ускорение, равно ширине алгоритма.
Предположим, что по каким-либо причинам п операций из N мы вынужде­ны выполнять последовательно. Причины могут быть разными. Например, операции могут быть последовательно связаны информационно. И тогда без изменения алгоритма их нельзя реализовать иначе. Но вполне возможно, что мы просто не распознали параллелизм, имеющийся в той части алго­ритма, которая описывается этими операциями. Отношение р = п/ N назо­вем долей последовательных вычислений.
Следствие (2-й закон Амдала)
Пусть система состоит из s одинаковых простых универсальных устройств. Предположим, что при выполнении параллельной части алгоритма все s уст­ройств загружены полностью. Тогда максимально возможное ускорение равно
Я =-----------------.                                                      (2-7)
Р* + (1 - Р)
88
Часть /. Параллельные вычислительные системы
Обозначим через п пиковую производительность отдельного ФУ. Согласно утверждению 2.3:
Если всего выполняется N операций, то среди них pTV операций выполняет­ся последовательно и (1 — p)7V параллельно на s устройствах по (1 — $)N/s операций на каждом. Не ограничивая общности, можно считать, что все последовательные операции выполняются на первом ФУ. Всего алгоритм реализуется за время
т _ W + (1 - ®N/s
7i-------------------------------•
л
На параллельной части алгоритма работают как первое, так и все остальные устройства, тратя на это время
(1 - WA
1 i
для 2 < / < п. Поэтому р! = 1 и
(1 - ют
Р' P7V + - l)N/s ' Следовательно
R = 1 | V (1 " ®N/S =         S
£2$N + (I - p)N/s pj + (1 - p) *
Следствие (3-й закон Амдала)
Пусть система состоит из простых одинаковых универсальных устройств. При любом режиме работы ее ускорение не может превзойти обратной величины доли последовательных вычислений.
По поводу последнего следствия заметим лишь следующее. Если последова­тельно выполняются п операций, то число ярусов любой параллельной формы алгоритма не может быть меньше п. В условиях обозначений утвер­ждения 2.5 это означает, что ш> п.
В проведенных исследованиях нигде не конкретизировалось содержание операций. В общем случае они могут быть как элементарными типа сложе­ния или умножения, так и очень крупными, представляющими алгоритмы решения достаточно сложных задач. Современные вычислительные системы состоят из тысяч и даже десятков тысяч процессоров. Они вполне уклады­ваются в рассмотренные модели. Системы с большим числом процессоров должны быть загружены достаточно полно. В противном случае нет стимула
Глава 2. Как повышают производительность компьютеров
89
их создавать. Исследования говорят о том, что в реализуемых на таких сис­темах алгоритмах доля последовательных операций должна быть порядка десятых и сотых долей процента. О проблемах конструирования подобных алгоритмов мы будем говорить в §4.3.
В заключение отметим следующее. В обширной литературе, посвященной параллельным процессам и параллельным вычислительным системам, мож­но встретить много различных определений и законов, касающихся произ­водительности, ускорения, эффективности и т. п. Как правило, новые опре­деления и законы возникают тогда, когда старые в чем-то не устраивают исследователей. Однако ко всем таким "новациям" следует относиться очень осторожно. Довольно часто в попытке что-то "улучшить" скрываются какие-то узкие места, одни понятия подменяются другими, иногда просто прово­дятся ошибочные рассуждения.
В качестве иллюстрации сказанного рассмотрим примеры 1.5, 1.6 из [57], заменив в них обозначения и терминологию на используемые в данном па­раграфе. В этих примерах обсуждаются две оценки достигаемого ускорения. Обе они получаются в условиях, когда все устройства одинаковые, простые, универсальные. За ускорение берется отношение R = T\/Ts, где Т\ есть вре­мя реализации алгоритма на одном ФУ, Tsна системе из s ФУ. Это согла­суется с последним пунктом утверждения 2.3. В примере 1.5 анализируется оценка ускорения, вытекающая из второго закона Амдала. Именно,
R               S
Л pj + (1 - р)"                                               <2-8)
Здесь RA есть "оценка ускорения по Амдалу", которая полностью совпадает с (2.7). Далее говорится, что при р = 0,5 и s -> оо ускорение по Амдалу всего лишь приближается снизу к 2.
На этом основании делается вывод, что оценка ускорения по Амдалу очень пессимистична. Высказывается и причина этого, сводящаяся к тому, что якобы закон Амдала плохо учитывает "параллелизм по данным". Никаких строгих обоснований не приводится, но дается в подтверждение того, что имеются лучшие оценки, пример 1.6. Как "образец" рассуждений, выдержки из примера интересно привести полностью.
"Оценка Густавсона—Барсиса начинается с того же самого отношения T\/Ts для ускорения, но использует совершенно другие предположения относи­тельно 7\ и Ts. Пусть время реализации задачи с параллелизмом по данным на ^-устройствах нормировано единицей, т. е. Ts = 1. Но каково же Т\1
При реализации программы на одном устройстве нужно вычислить последо­вательную часть за время $TS= р и параллельную часть за время 5(1 — р) Ts = (1 — р)5. Формула для 7\ есть просто сумма: 7\ = р + (1 — р)л. Подстав­ляя 7\ и Ts в выражение для ускорения, мы получаем
ЯГБ = s - (s - 1)р.                                          (2.9)
90
Часть I. Параллельные вычислительные системы
Если р = 0,5, как в предыдущем примере, мы получаем ЛГБ = s — (s — - 1)/2 =(s + 1)/2.
Результат примера 1.6 совсем иной, чем дает пессимистическое предсказа­ние, исходя из закона Амдала".
Далее приводится красивый рис. 1.6, сравнивающий оценки ускорения как функции от р, даваемые формулами (2.8), (2.9), и демонстрирующий, ко­нечно, существенное преимущество оценки (2.9). Объясняется это тем, что алгоритмы с параллелизмом по данным позволяют максимально использо­вать все устройства. По-видимому, столь успешное сравнение и дало осно­вание присвоить оценке (2.9) именной идентификатор ЛГБ — "оценка уско­рения по Густавсону—Барсису".
В действительности, процитированные выдержки вызывают значительно боль­ше вопросов, чем что-то проясняют. Главный из них сводится к правомочности сравнения формул (2.8) и (2.9) при одних и тех же значениях р. Обозначим че­рез p^f величину р в (2.8), через рГБ величину р в (2.9). Напомним, что р^ есть отношение числа операций, выполняемых последовательно, к общему числу опе­раций. Из цитаты "при реализации программы на одном устройстве нужно вы­числить последовательную часть за время РгбТ? = Ргб и параллельную часть за время 5(1 — Ргб№= (1 Ргб)5" следует, что рГБ есть доля времени на выполне­ние последовательных операций при условии, что параллельная часть полно­стью занимает s устройств и общее время на последовательно-параллельное вы­полнение программы равно 1. Но ведь доля последовательно выполняемых операций и доля времени на их выполнение представляют совершенно разные понятия и просто так их нельзя полагать равными. Доля последовательных вы­числений по определению не зависит от числа s используемых процессоров, а доля времени на их выполнение, опять же по определению, — зависит.
Поясним сказанное подробнее. Обозначим через т время выполнения одной операции. Если всего выполняется N операций, то пусть TV] означает число операций в последовательной части, a Nj — число операций в параллельной части. Так как на параллельной части все процессоры загружены полно­стью, то общее время реализации алгоритма равно
T=xNi + х-s
2
Следовательно
N,
}тъ
N,
+ N2
1
•>
Ni
1
N{
+ N2
/s
1 +
1 N2
7tv7
(2.10)
Глава 2. Как повышают производительность компьютеров
91
Поэтому всегда имеем:
Ргб > Рл при s > 1 Ргб ~ Фа пРи больших N2/N\, /?гб ~ Рл ПРИ малых N2/N\. Кроме этого, из (2.10) вытекает, что во всех случаях
о               Фа
1 + С$ - 1)р^
Подставляя это выражение для рГБ в (2.9), получаем, что
l + (s-l)PA фА+(1-рА)
Таким образом, если учесть связь между р^ и Ргб> то оценки ускорения по Амдалу и по Густавсону—Барсису полностью совпадают. В частности, если взять p^ = 0,5, то эквивалентное значение рГБ должно быть равным не 0,5, как это берется в примере 1.6 из [57], a s/(s + 1). И тогда R-A ~ -^гб = 2s/(s + 1), что по обеим оценкам одинаково плохо.
Кроме того, что в [57] неверно сравниваются формулы (2.8) и (2.9), там же имеются и другие неточности. Например, без всяких на то оснований про­тивопоставляются сфера применения формулы Амдала (2.8) и алгоритм с параллелизмом по данным. На самом деле, никаких причин для такого про­тивопоставления нет. Алгоритмы с параллелизмом по данным характерны тем, что в параллельной части они распадаются на одинаковые по выполняе­мым операциям ветви. Это обстоятельство обеспечивает возможность загру­зить одинаковым образом и полностью все ФУ системы при выполнении параллельной части. Но уж если такая возможность имеется, то независимо от причин, приведших к ее появлению, можно с одинаковым успехом при­менять как формулу Амдала, так и формулу Густавсона—Барсиса. Это мы уже показали выше. При правильном использовании параметра (3 никаких различий между обеими формулами нет.
Формулу Амдала следует применять для прогноза возможного ускорения. В данном случае величину р можно подсчитать, не пропуская программу на параллельной вычислительной системе. Формулу же Густавсона—Барсиса можно применять для оценивания достигнутого ускорения, не пропуская программу на однопроцессорном компьютере. Величина р при этом измеря­ется в процессе решения задачи.
С другими понятиями дело обстоит не лучше. Особенно много всяких псев­донаучных изысканий вокруг понятий пиковой и реальной производитель-ностей. Можно понять стремление создателей вычислительной техники ис­пользовать такое определение пиковой производительности, при котором
92
Часть I. Параллельные вычислительные системы
она будет как можно больше. Все-таки пиковая производительность является одной из основных характеристик. К тому же, пользователи интуитивно на­деются, что чем больше пиковая производительность системы, тем задачи на ней решаются быстрее. Если читатель захочет детальнее понять различ­ные подходы к определению пиковой производительности, мы рекомендуем ему предварительно познакомиться с работой [43]. В несколько вольном пе­реводе ее название таково: "12 способов пудрить мозги с помощью пиковой производительности".
Вместо обсуждения понятия реальной производительности приведем сле­дующий курьезный пример. Предположим, что система имеет два простых устройства одинаковой пиковой производительности. Пусть одно устройство есть сумматор, другое — умножитель. Допустим, что все обмены информа­цией осуществляются мгновенно и решается задача вычисления матрицы А = В + С при заданных матрицах В, С. Очевидно, что при естественном выполнении операции сложения матриц реальная производительность будет равна половине пиковой, т. к. умножитель не используется. Спрашивается: "Можно ли каким-то образом на данной задаче повысить реальную произ­водительность?" Ответ: "Можно". Запишем равенство А = В + С в виде А = В + 1хС Умножение элементов матрицы С на 1 позволяет загрузить умножитель и формально реальная производительность увеличивается вдвое и сравнивается с пиковой.
Вам нужно такое увеличение производительности?
Несмотря на вроде бы очевидную курьезность рассмотренного примера, он приоткрывает, тем не менее, очень сложную и очень далекую от сколько-нибудь полного решения проблему. Для любой конкретной формулы или совокупности формул существует бесконечно много эквивалентных с точки зрения точных вычислений записей. Эквивалентные записи могут быть полу­чены путем применения законов ассоциативности, коммутативности и дист­рибутивности, с помощью приведения подобных членов, заменой 0 и 1 со­ответственно разностью и отношением каких-то равных выражений и т. п. На этом множестве порождается бесконечно много алгоритмов, также экви­валентных с точки зрения результатов, получаемых при точных вычислени­ях. Все такие алгоритмы в общем случае имеют разные вычислительные свойства, в частности, по числу операций, точности приближенных вычис­лений и т. д. Вспомните хотя бы алгоритмы вычисления определителя, ос­нованные на использовании прямых формул, следующих из определения, и алгоритмы, основанные на преобразованиях Гаусса. Но все такие алгоритмы имеют и разные реализационные характеристики. Они дают разные загру­женности, ускорения, времена реализации. Так что выбор среди эквива­лентных подходящего алгоритма является сложной задачей, и только изред­ка этот выбор становится курьезом.
Глава 2. Как повышают производительность компьютеров
93
Вопросы и задания
1.   Почему в конвейерных функциональных устройствах длительности срабатываний отдельных ступеней делают одинаковыми?
2.   Пусть граф вычислительной системы есть ориентированное кольцо, все дуги ко­торого направлены в одну сторону, например, по часовой стрелке. Покажите, что существуют различные временные режимы, при которых система будет функ­ционировать.
3.   Докажите, что в условиях п. 2 при одних и тех же входных данных разные вре­менные режимы приводят к одним и тем же результатам.
4.   Докажите, что в условиях п. 2 алгоритм, реализуемый вычислительной системой, расщепляется на независимые между собой ветви вычислений.
5.   На сколько ветвей расщепляется алгоритм в п. 2?
6.   Что имеется общего между отдельными ветвями?
7.   Что меняется в ответах на пп. 2—6, если граф вычислительной системы состоит из двух ориентированных колец разного размера, имеющих одну общую вершину?
8.   Что меняется в ответах на пп. 2—6, если граф вычислительной системы состоит из одного пути?
Глава 3
Архитектура параллельных вычислительных систем
Внутренняя согласованность ценится больше эффективной работы.
Из законов Мерфи
В процессе решения любой задачи на параллельном компьютере можно вы­делить следующие этапы: формулировка задачи, выбор метода ее решения, фиксация алгоритма, выбор технологии программирования, создание про­граммы и, наконец, выполнение ее на том или ином компьютере. Все эти этапы важны и для обычных компьютеров, но при использовании парал­лельных вычислительных систем они приобретают особую значимость. Лю­бой параллельный компьютер — это тщательно сбалансированная система, которая может дать фантастический результат. Такие компьютеры специ­ально проектируются для того, чтобы работать с огромной производитель­ностью. Но параллельные компьютеры не могут работать одинаково произ­водительно на любых программах. Если структура программы не соответствует особенностям их архитектуры, то производительность неиз­бежно падает.
Указанное несоответствие может возникнуть на любом этапе решения зада­чи. Если на каком-либо одном шаге мы не учли особенностей целевого компьютера, то большой производительности на программе заведомо не бу­дет. В самом деле, ориентация на векторно-конвейерный компьютер или вычислительный кластер с распределенной памятью во многом определит метод решения задачи. В одном случае в программе необходимо векторизо­вать внутренние циклы, а в другом надо думать о распараллеливании значи­тельных фрагментов кода. И то, и другое свойство программ определяется свойствами заложенных в них методов. Не обладает выбранный метод таки­ми свойствами, их не будет и в программе, а, значит, и не будет высокой производительности.
Параллельный компьютер стоит в конце всей цепочки, и поэтому его влия­ние прослеживается везде. Чем аккуратнее мы проходим каждый этап, чем больше структура программы соответствует особенностями архитектуры компьютера, тем выше его производительность и тем ближе она к его пико­вым показателям. Все понимают, что достичь пиковой производительности невозможно. Этот показатель в сравнении с реальной производительностью скорее играет роль ориентира, показывая, насколько полно использованы
Глава 3. Архитектура параллельных вычислительных систем
95
возможности компьютера при выполнении той или иной реальной про­граммы. Однако пик производительности вычисляется для случая, когда все работает с максимальной загрузкой, без конфликтов и ожиданий, т. е. в иде­альных условиях. В реальности же все не так. Не так сложно построить компьютер с рекордными показателями пиковой производительности. Го­раздо труднее предложить эффективный способ его использования, по­скольку здесь уже нужно учитывать все предыдущие этапы в указанной вы­ше цепочке.
Параллельные вычислительные системы развиваются очень быстро. С появ­лением вычислительных кластеров параллельные вычисления стали доступ­ны многим. Если раньше параллельные компьютеры стояли в больших центрах, то сейчас кластер может собрать и поддерживать небольшая лабо­ратория. Стоимость кластерных решений значительно ниже стоимости тра­диционных суперкомпьютеров. Для их построения, как правило, использу­ются массовые процессоры, стандартные сетевые технологии и свободно распространяемое программное обеспечение. Если есть желание, минимум средств и знаний, то принципиальных препятствий для построения собст­венной параллельной системы нет.
Интернет. Уникальное явление нашего времени. В рамках единой сети объ­единены миллионы компьютеров. А что, если их использовать для решения одной задачи? Это будет самый мощный параллельный компьютер в мире, намного превосходящий по пиковой производительности все компьютеры из списка Тор500, вместе взятые. Со временем Интернет дойдет до каждой квартиры, предоставляя всем выход в глобальную сеть. И в глобальную вы­числительную сеть. Если вам нужно будет что-то посчитать, то вы подклю­чаетесь к сети, даете задание и получаете результат. При этом совершенно не важно, где именно ваше задание было обработано. Отсюда и рождаются идеи построения метакомпьютера, включающего в себя многочисленные вычислительные ресурсы по всему миру.
Красивых идей при построении параллельных вычислительных систем очень много. В состав компьютеров входит все больше и больше всевозможных вы­числительных узлов. Порой конструкторам приходится разрабатывать уни­кальные решения, чтобы все это множество устройств заставить согласованно работать вместе. Это правильно, на этом держится прогресс вычислительной техники. Не нужно только забывать главного. Погоня за внутренней согласо­ванностью не должна становиться самоцелью. Любой параллельный компью­тер является инструментом для решения реальных задач. В конечном счете, с этой точки зрения и нужно все оценивать. Если внутренняя согласованность в компьютере достигается во имя эффективного решения задач, то это оправда­но. Если нет, то сразу возникает масса вопросов.
А что же мы имеем на практике? Об этом и рассказывает данная глава книги.
96
Часть I. Параллельные вычислительные системы
§ 3.1. Классификация параллельных компьютеров и систем
Уже по первым параграфам данной книги становится ясно, насколько много существует различных способов организации параллельных вычисли­тельных систем. Здесь можно назвать векторно-конвейерные компьютеры, массивно-параллельные и матричные системы, компьютеры с широким командным словом, спецпроцессоры, кластеры, компьютеры с многопоточ­ной архитектурой и т. п. В этот же список входят и те архитектуры, которые мы еще не обсуждали, например, систолические массивы или dataflow-компьютеры. Если же к подобным названиям для полноты описания доба­вить и сведения о таких важных параметрах, как организация памяти, топо­логия связи между процессорами, синхронность работы отдельных уст­ройств или способ исполнения операций, то число различных архитектур станет и вовсе необозримым.
Почему параллельных архитектур так много? Как они взаимосвязаны между собой? Какие основные факторы характеризуют каждую архитектуру? Поиск ответа на такие вопросы так или иначе приводит к необходимости класси­фикации архитектур вычислительных систем [23]. Активные попытки в этом направлении начались после опубликования М. Флинном в конце 60-х го­дов прошлого столетия первого варианта классификации, который, кстати, используется и в настоящее время.
Вообще говоря, при введении классификации можно преследовать самые разные цели. С точки зрения главного инженера организации, где устанав­ливается компьютер, деление компьютеров по мощности потребляемой электроэнергии, конечно же, будет классификацией. Планово-финансовый отдел больше интересует стоимость. Если окажется, что установка двадцати компьютеров в локальной сети обойдется во столько же, во сколько и вось-мипроцессорный сервер с общей памятью, то для него это будут вычисли­тельные системы одного класса. И правильно. Что заложили в основу клас­сификации, такие следствия и получаем.
Ясно, что навести порядок в хаосе очень важно для лучшего понимания ис­следуемой предметной области. В самом деле, вспомним открытый в 1869 го­ду Д. И. Менделеевым периодический закон. Выписав на карточках назва­ния химических элементов и указав их важнейшие свойства, он сумел найти такое расположение, при котором четко прослеживалась закономерность в изменении свойств элементов, расположенных в каждом столбце и каждой строке. Теперь, зная положение элемента в таблице, он мог с большой сте­пенью точности описать его свойства, не проводя с ним никаких непосред­ственных экспериментов. Другим, поистине фантастическим следствием, явилось то, что данный закон указал на несколько "белых пятен" в таблице и позволил предсказать не только существование, но и свойства пока неиз-
Глава 3. Архитектура параллельных вычислительных систем
97
вестных элементов. В 1875 году французский химик Поль Эмиль Ле-кок де Буабодран, изучая спектры минералов, открыл предсказанный Мен­делеевым галлий и впервые подробно описал его свойства. В свою очередь Менделеев, никогда прежде не видевший данного химического элемента, на основании введенной классификации смог и указать на ошибку в определе­нии плотности галлия, и вычислить правильное значение.
Что-то похожее хотелось бы найти и для архитектур параллельных вычисли­тельных систем. Главный вопрос — что заложить в основу классификации, может решаться по-разному. Однако с позиций наших исследований клас­сификация должна давать ключ к пониманию того, как решать задачу эф­фективного отображения алгоритмов на архитектуру вычислительных сис­тем. В некоторых случаях вводят описания классов компьютеров. На основе информации о принадлежности компьютера к конкретному классу пользо­ватель сам принимает решение о способе записи алгоритма в терминах вы­бранной технологии параллельного программирования. Иногда в качестве классификации пытаются ввести формальное описание архитектуры, ис­пользуя специальную нотацию. Такое направление интересно тем, что соз­дается благоприятная основа для построения автоматизированных систем отображения. В самом деле, с одной стороны, есть формальное описание архитектуры целевого компьютера, с другой стороны, есть формальное опи­сание алгоритма. Созданы условия для совместного исследования этих двух объектов и последующего синтеза оптимальной программы. Правда, резуль­тат очень сильно зависит как от качества введенных описаний, так и от методов их совместного исследования. О степени решенности даже упро­щенного варианта данной проблемы читатель может судить по качеству компиляторов, работающих на существующих параллельных компьютерах.
Не стоит сбрасывать со счетов и тот факт, что удачная содержательная клас­сификация может подсказать возможные пути совершенствования компью­теров. Трудно рассчитывать на нахождение нетривиальных "белых пятен", например, в классификации по стоимости или пиковой производительно­сти. Однако размышления о возможной систематике с точки зрения просто­ты и технологичности программирования могут оказаться чрезвычайно по­лезными для определения направлений поиска новых архитектур.
Классификация М. Флинна (М. Flynn). По-видимому, самой ранней и наи­более известной является классификация архитектур вычислительных сис­тем, предложенная в 1966 году М. Флинном. Классификация базируется на понятии потока, под которым понимается последовательность команд или данных, обрабатываемая процессором. На основе числа потоков команд и потоков данных Флинн выделяет четыре класса архитектур.
S1SD (Single Instruction stream/Single Data stream) — одиночный поток команд и одиночный поток данных (рис. 3.1). На рисунках, иллюстрирую­щих классификацию М. Флинна, использованы следующие обозначения:
98
Часть I. Параллельные вычислительные системы
ПР — это один или несколько процессорных элементов, УУ — устройство управления, ПД — память данных. К классу SISD относятся, прежде всего, классические последовательные машины или, иначе, машины фон-неймановского типа, например, PDP-11 или VAX 11/780. В таких машинах есть только один поток команд, все команды обрабатываются последова­тельно друг за другом и каждая команда инициирует одну скалярную опера­цию. Не имеет значения тот факт, что для увеличения скорости обработки команд и скорости выполнения арифметических операций может приме­няться конвейерная обработка: как машина CDC 6600 со скалярными функ­циональными устройствами, так и CDC 7600 с конвейерными попадают в этот класс.
УУ
SISD
ПР
пд
УУ
SIMD
пр ;
. .
пд
' '
' '
Рис. 3.1. Классы SISD и SIMD классификации М. Флинна
SIMD (Single Instruction stream/Multiple Data stream) — одиночный поток команд и множественный поток данных (см. рис. 3.1). В архитектурах по­добного рода сохраняется один поток команд, включающий, в отличие от предыдущего класса, векторные команды. Это позволяет выполнять одну арифметическую операцию сразу над многими данными, например, над элементами вектора. Способ выполнения векторных операций не оговарива­ется, поэтому обработка элементов вектора может производиться либо про­цессорной матрицей, как в 1LL1AC IV, либо с помощью конвейера, как, на­пример, в машине Сгау-1.
M1SD (Multiple Instruction stream/Single Data stream) — множественный поток команд и одиночный поток данных (рис. 3.2). Определение подра­зумевает наличие в архитектуре многих процессоров, обрабатывающих один и тот же поток данных. Однако ни Флинн, ни другие специалисты в области архитектуры компьютеров до сих пор не смогли представить убе­дительный пример реально существующей вычислительной системы, по­строенной на данном принципе. Ряд исследователей относят конвейерные машины к данному классу, однако это не нашло окончательного призна­ния в научном сообществе. Будем считать, что пока данный класс пуст.
M1MD (Multiple Instruction stream/Multiple Data stream) — множественный поток команд и множественный поток данных (см. рис. 3.2). Этот класс
Глава 3. Архитектура параллельных вычислительных систем                                          99
предполагает, что в вычислительной системе есть несколько устройств обра­ботки команд, объединенных в единый комплекс и работающих каждое со своим потоком команд и данных.
УУ
MISD
...
ПР
пд
УУ
MIMD
...
. .
пд
ПР
' '
Рис. 3.2. Классы MISD и MIMD классификации М. Флинна
Итак, что же собой представляет каждый класс? В SISD, как уже говори­лось, входят однопроцессорные последовательные компьютеры типа VAX 11/780. Однако многими критиками подмечено, что в этот класс можно включить и векторно-конвейерные машины, если рассматривать вектор как одно неделимое данное для соответствующей команды. В таком случае в этот класс попадут и такие системы, как Cray-1, CYBER 205, машины се­мейства FACOM VP и многие другие.
Бесспорными представителями класса S1MD считаются матрицы процессо­ров: 1LLIAC IV, 1CL DAP, Goodyear Aerospace МРР, Connection Machine 1 и т. п. В таких системах единое управляющее устройство контролирует множество процессорных элементов. Каждый процессорный элемент полу­чает от устройства управления в каждый фиксированный момент времени одинаковую команду и выполняет ее над своими локальными данными. Для классических процессорных матриц никаких вопросов не возникает. Однако в этот же класс можно включить и векторно-конвейерные машины, напри­мер, Сгау-1. В этом случае каждый элемент вектора надо рассматривать как отдельный элемент потока данных.
Класс M1MD чрезвычайно широк, поскольку включает в себя всевозмож­ные мультипроцессорные системы: Cm*, C.mmp, Cray Y-MP, Denelcor HEP, BBN Butterfly, Intel Paragon, Cray T3D и многие другие. Интересно то, что если конвейерную обработку рассматривать как выполнение последователь­ности различных команд (операций ступеней конвейера) не над одиночным векторным потоком данных, а над множественным скалярным потоком, то все рассмотренные выше векторно-конвейерные компьютеры можно распо­ложить и в данном классе.
Предложенная схема классификации вплоть до настоящего времени является самой применяемой при начальной характеристике того или иного компью­тера. Если говорится, что компьютер принадлежит классу S1MD или M1MD,
100
Часть I. Параллельные вычислительные системы
то сразу становится понятным базовый принцип его работы, и в некоторых случаях этого бывает достаточно. Однако видны и явные недостатки. В ча­стности, некоторые заслуживающие внимания архитектуры, например dataflow и векторно-конвейерные машины, четко не вписываются в дан­ную классификацию. Другой недостаток — это чрезмерная заполненность класса MIMD. Необходимо средство, более избирательно систематизи­рующее архитектуры, которые по Флинну попадают в один класс, но со­вершенно различны по числу процессоров, природе и топологии связи между ними, по способу организации памяти и, конечно же, по техноло­гии программирования.
Классификация Р. Хокни (R. Hockney). Р. Хокни разработал свой подход к классификации для более детальной систематизации компьютеров, попадаю­щих в класс MIMD по систематике М. Флинна. Как отмечалось выше, класс MIMD чрезвычайно широк и объединяет целое множество различных типов архитектур. Пытаясь систематизировать архитектуры внутри этого класса, Р. Хокни получил иерархическую структуру, представленную на рис. 3.3.
MIMD
переключаемые
конвейерные
с общей памятью
регулярные решетки
с распределенной памятью
гиперкубы
иерархические структуры
изменяющие конфигурацию
Рис. 3.3. Дополнительная классификация Р. Хокни класса MIMD
Основная идея классификации состоит в следующем. Множественный по­ток команд может быть обработан двумя способами: либо одним конвейер­ным устройством обработки, работающим в режиме разделения времени для отдельных потоков, либо каждый поток обрабатывается своим собственным устройством. Первая возможность используется в MIMD-компьютерах, ко­торые автор называет конвейерными. Сюда можно отнести, например, про­цессорные модули в Denelcor HEP или компьютеры семейства Тега МТА.
Глава 3. Архитектура параллельных вычислительных систем
101
Архитектуры, использующие вторую возможность, в свою очередь, опять делятся на два класса. В первый класс попадают MIMD-компьютеры, в ко­торых возможна прямая связь каждого процессора с каждым, реализуемая с помощью переключателя. Во втором классе находятся MIMD-компьютеры, в которых прямая связь каждого процессора возможна только с ближайши­ми соседями по сети, а взаимодействие удаленных процессоров поддержива­ется специальной системой маршрутизации.
Среди MIMD-машин с переключателем Хокни выделяет те, в которых вся память распределена среди процессоров как их локальная память (например, PASM, PRINGLE, IBM SP2 без SMP-узлов). В этом случае об­щение самих процессоров реализуется с помощью сложного переключателя, составляющего значительную часть компьютера. Такие машины носят на­звание MIMD-машин с распределенной памятью. Если память это разде­ляемый ресурс, доступный всем процессорам через переключатель, то MIMD-машины являются системами с общей памятью (BBN Butterfly, Cray С90). В соответствии с типом переключателей можно проводить классифи­кацию и далее: простой переключатель, многокаскадный переключатель, общая шина и т. п. Многие современные вычислительные системы имеют как общую разделяемую память, так и распределенную локальную. Такие системы автор рассматривает как гибридные MIMD с переключателем.
При рассмотрении MIMD-машин с сетевой структурой считается, что все они имеют распределенную память, а дальнейшая классификация прово­дится в соответствии с топологией сети: звездообразная сеть (ЮАР), регу­лярные решетки разной размерности (Intel Paragon, Cray T3D), гиперкубы (NCube, Intel iPSC), сети с иерархической структурой, такой как деревья, пирамиды, кластеры (Cm*, CEDAR) и, наконец, сети, изменяющие свою конфигурацию.
Заметим, что если архитектура компьютера спроектирована с использовани­ем нескольких сетей с различной топологией, то, по всей видимости, по аналогии с гибридными MIMD-машинами с переключателями, их стоит на­звать гибридными сетевыми MIMD-машинами, а использующие идеи раз­ных классов — просто гибридными MIMD-машинами. Типичным предста­вителем последней группы, в частности, является компьютер Connection Machine 2, имеющий на внешнем уровне топологию гиперкуба, каждый узел которого является кластером процессоров с полной связью.
Классификация Т. Фенга (Т. Feng). В 1972 году Т. Фенг предложил класси­фицировать вычислительные системы на основе двух простых характери­стик. Первая — число п бит в машинном слове, обрабатываемых параллель­но при выполнении машинных инструкций. Практически во всех современных компьютерах это число совпадает с длиной машинного слова. Вторая характеристика равна числу слов ш, обрабатываемых одновременно данной вычислительной системой. Немного изменив терминологию, функ-
102
Часть I. Параллельные вычислительные системы
ционирование любого компьютера можно представить как параллельную обработку п битовых слоев, на каждом из которых независимо преобразуют­ся m бит. Опираясь на такую интерпретацию, вторую характеристику назы­вают шириной битового слоя.
Каждую вычислительную систему С можно описать парой чисел (я, ш). Произведение Р = пхпг определяет интегральную характеристику потенциала параллельности архитектуры, которую Фенг назвал максимальной степенью параллелизма вычислительной системы. По существу, данное значение есть не что иное, как пиковая производительность, выраженная в других едини­цах. В период появления данной классификации, а это начало 70-х годов прошлого столетия, еще казалось возможным перенести понятие пиковой производительности как универсального средства сравнения и описания по­тенциальных возможностей компьютеров с традиционных последовательных машин на параллельные. Понимание того факта, что пиковая производи­тельность сама по себе не столь важна, пришло позднее, и данный подход отражает, естественно, степень осмысления специфики параллельных вы­числений того времени.
Рассмотрим компьютер Advanced Scientific Computer фирмы Texas Instru­ments (TI ASC). В основном режиме он работает с 64-разрядным словом, причем все разряды обрабатываются параллельно. Арифметико-логическое устройство имеет четыре одновременно работающих конвейера, содержащих по восемь ступеней. При такой организации 4x8 = 32 слова могут обрабаты­ваться одновременно (то есть 32 бита в каждом битовом слое), и значит компьютер TI ASC может быть представлен в виде (64, 32).
На основе введенных понятий все вычислительные системы можно разде­лить на четыре класса.
Разрядно-последовательные, пословно-последовательные (п = т = 1). В ка­ждый момент времени такие компьютеры обрабатывают только один двоич­ный разряд. Представителем данного класса служит давняя система MINIMA с естественным описанием (1, 1).
Разрядно-параллельные, пословно-последовательные (п > 1, т = 1). Боль­шинство классических последовательных компьютеров, так же как и многие вычислительные системы, используемые сейчас, принадлежат к данному классу: IBM 701 с описанием (36, 1), PDP-11 с описанием (16, 1), IBM 360/50 и VAX 11/780 - обе с описанием (32, 1).
Разрядно-последовательные, пословно-параллельные (п = 1, т > 1). Как правило вычислительные системы данного класса состоят из большого чис­ла одноразрядных процессорных элементов, каждый из которых может не­зависимо от остальных обрабатывать свои данные. Типичными примерами служат STARAN (1, 256) и МРР (1, 16384) фирмы Goodyear Aerospace, про­тотип известной системы ILLIAC IV компьютер SOLOMON (1, 1024) и ICL DAP (1, 4096).
Глава 3. Архитектура параллельных вычислительных систем
103
Разрядно-параллельные, пословно-параллельные (я > 1, m > 1). Подавляю­щее большинство параллельных вычислительных систем, обрабатывая одно­временно m х п двоичных разрядов, принадлежит именно к этому классу: ILL1AC IV (64, 64), TI ASC (64, 32), C.mmp (16, 16), CDC 6600 (60, 10), BBN Butterfly GP1000 (32, 256).
Недостатки предложенной классификации достаточно очевидны и связаны со способом вычисления ширины битового слоя ш. По существу Фенг не делает никакого различия между процессорными матрицами, векторно-конвейерными и многопроцессорными системами. Не делается акцент на том, за счет чего компьютер может одновременно обрабатывать более од­ного слова: множественности функциональных устройств, их конвейерности или же какого-то числа независимых процессоров. Если в системе N неза­висимых процессоров имеют каждый по F конвейерных функциональных устройств с длиной конвейера L, то для вычисления ширины битового слоя надо просто найти произведение N х F х L.
Конечно же, опираясь на данную классификацию, достаточно трудно, а иногда и просто невозможно, осознать специфику той или иной вычисли­тельной системы. Достоинством же можно считать введение единой числовой метрики для всех типов компьютеров, позволяющей сравнить любые два компьютера между собой.
Классификация В. Хендлера (W. Handler). В основу классификации В. Хенд-лер закладывает явное описание возможностей параллельной и конвейерной обработки информации вычислительной системой. При этом он намеренно не рассматривает различные способы связи между процессорами и блоками памяти, а считает, что коммуникационная сеть может быть нужным образом сконфигурирована и будет способна выдержать предполагаемую нагрузку.
Предложенная классификация базируется на различии между тремя уровня­ми обработки данных в процессе выполнения программ:
□  уровень выполнения программы; опираясь на счетчик команд и некото­рые другие регистры, устройство управления (УУ) производит выборку и дешифрацию команд программы;
□  уровень выполнения команд; арифметико-логическое устройство компь­ютера (АЛУ) исполняет команду, выданную ему устройством управления;
□  уровень битовой обработки; все элементарные логические схемы процес­сора (ЭЛС) разбиваются на группы, необходимые для выполнения опе­раций над одним двоичным разрядом.
Подобная схема выделения уровней предполагает, что вычислительная сис­тема содержит какое-то число процессоров, каждый со своим устройством управления. Каждое устройство управления связано с несколькими арифме­тико-логическими устройствами, исполняющими одну и ту же операцию в каждый конкретный момент времени. Наконец, каждое АЛУ объединяет
104
Часть I. Параллельные вычислительные системы
несколько групп элементарных логических схем, ассоциированных с обра­боткой одного двоичного разряда (число групп ЭЛС есть не что иное, как длина машинного слова). Если на какое-то время не рассматривать возмож­ность конвейеризации, то число устройств управления к, число арифмети­ко-логических устройств d в каждом устройстве управления и число групп ЭЛС w в каждом АЛУ составят тройку для описания данной вычислитель­ной системы С: t(C) = (к, d, w).
В таких обозначениях описания некоторых хорошо известных вычислитель­ных систем будут выглядеть следующим образом:
/(MINIMA) = (1,1,1);
/(IBM 701) = (1,1,36);
/(SOLOMON) = (1,1024,1);
/(ILLIAC IV) = (1,64,64);
/(STARAN) = (1,8192,1) — в полной конфигурации;
/(C.mmp) = (16,1,16) — основной режим работы;
/(PRIME) = (5,1,16);
/(BBN Butterfly GP1000) = (256,1,32).
Несмотря на то, что перечисленным системам присущ параллелизм разного рода, он без особого труда может быть отнесен к одному из трех выделен­ных уровней.
на каждой ступени слово из w бит
ЦП
АЛУ
ЦП
_
УУ
:
d
АЛУ
ФУ
w' ступеней конвейера
I
ФУ
d' ФУ могут быть сцеплены
к' ЦП из к могут работать в макроконвейере
Рис. 3.4. Уровни параллелизма в классификации В. Хендлера
Теперь можно расширить возможности описания, допустив возможность конвейерной обработки на каждом из уровней (рис. 3.4). В самом деле, кон-вейерность на самом нижнем уровне, т. е. на уровне ЭЛС, это конвейер-
Глава 3. Архитектура параллельных вычислительных систем
105
ность функциональных устройств. Если функциональное устройство обраба­тывает w-разрядные слова на каждой из w' ступеней конвейера, то для ха­рактеристики параллелизма данного уровня естественно рассмотреть произ­ведение w х w'. Знак "х" будем использовать на каждом уровне, чтобы отделить число, представляющее степень параллелизма, от числа ступеней в конвейере. Компьютер TI AS С имеет четыре конвейерных устройства по восемь ступеней в каждом для обработки 64-разрядных слов, следовательно, он может быть описан так:
CTIASC) = (1, 4, 64 х 8).
Следующий уровень конвейерной обработки — это конвейеризация на уровне команд. Предполагается, что в вычислительной системе есть не­сколько функциональных устройств, которые могут работать одновременно в режиме конвейера (для обозначения данной возможности часто использу­ют другой термин — зацепление функциональных устройств). Классическим примером этому служат векторно-конвейерные компьютеры фирмы Cray Research. Другим примером является машина CDC 6600, содержащая десять независимых последовательных функциональных устройств, способных по­давать результат своей работы на вход другим функциональным устройст­вам: ?(CDC 6600) = (1,1 х 10,60) (описан только центральный процессор без учета управляющих и периферийных подсистем).
Наконец, нам осталось рассмотреть конвейеризацию на самом верхнем уровне, известную как макроконвейер. Поток данных, проходя через один процессор, поступает на вход другому. Компьютер РЕРЕ, имея фактически три независимых системы из 288 устройств, описывается так:
?(РЕРЕ) = (1x3, 288, 32).
После расширения трехуровневой модели параллелизма средствами описа­ния потенциальных возможностей конвейеризации каждая тройка
1(C) = (кх к', dx d\ wx w)
интерпретируется так:
П к — число процессоров (каждый со своим УУ), работающих параллельно;
□  к' — глубина макроконвейера из отдельных процессоров;
П d — число АЛУ в каждом процессоре, работающих параллельно;
П d' — глубина конвейера из функциональных устройств АЛУ;
П w — число разрядов в слове, обрабатываемых в АЛУ параллельно;
□   w' — число ступеней в конвейере функциональных устройств АЛУ.
Очевидна связь между классификацией Фенга и классификацией Хендлера: для получения максимальной степени параллелизма в терминах Фенга надо найти произведение всех шести величин в описании Хендлера. Здесь же за-
106
Часть I. Параллельные вычислительные системы
метим, что, заложив в основу своей схемы явное указание на присутствую­щий параллелизм и возможную конвейеризацию, Хендлер сразу снимает некоторые вопросы, характерные для схем Флинна и Фенга, по крайней ме­ре, в плане описания векторно-конвейерных машин.
В дополнение к изложенному способу описания архитектур Хендлер пред­лагает использовать три операции, которые, будучи примененными к трой­кам, позволят описать, во-первых, сложные структуры с подсистемами вво­да/вывода, хост-компьютером или какими-то другими особенностями, и, во-вторых, возможные режимы функционирования вычислительных систем, поддерживаемые для оптимального соответствия структуре программ.
Первая операция (х) в каком-то смысле отражает конвейерный принцип обработки и предполагает последовательное прохождение данных сначала через первый ее аргумент-подсистему, а затем через второй. В частности, описание компьютера CDC 6600 можно уточнить следующим образом:
t(CDC 6600) = (10,1,12) х (1,1 х 10,60),
где первый аргумент отражает существование десяти 12-разрядных перифе­рийных процессоров и тот факт, что любая программа должна сначала быть обработана одним из них и лишь после этого передана центральному про­цессору для исполнения. Аналогично можно получить описание машины РЕРЕ, принимая во внимание, что в качестве хост-компьютера она исполь­зует CDC 7600:
?(РЕРЕ) = t(CDC 7600) х (1 х 3, 288, 32) =
= (15, 1, 12) х (1, 1 х 9, 60) х (1 х 3, 288, 32).
Поток данных последовательно проходит через три подсистемы, что мы и отразили, соединив их знаком "х".
Заметим, что все подсистемы последнего примера достаточно сложны и по данному описанию могут представляться по-разному. Чтобы внести большую ясность, аналогично операции конвейерного исполнения, Хендлер вводит операцию параллельного исполнения (+), фиксирующую возможность неза­висимого использования процессоров разными задачами, например:
/(4, d, w) = [(1, d, w) + (1, d, w) + (1, d, w) + (1, d, w)].
В случае CDC 7600 уточненная запись вида:
(15, 1, 12) х (1, 1 х 9, 60) =
= [(1, 1, 12) + ... + (1, 1, 12)] {15раз} х (1, 1 х 9, 60)
говорит о том, что каждая задача может захватить свой периферийный про­цессор, а затем одна за одной они будут поступать в центральный процессор.
И, наконец, третья операция — операция альтернативы (v), показывает воз­можные альтернативные режимы функционирования вычислительной сие-
Глава 3. Архитектура параллельных вычислительных систем
107
темы. Например, компьютер C.mmp может быть запрограммирован для ис­пользования в трех принципиально разных режимах:
t(C.mmp) = (16, 1, 16) v (1 х 16, 1,16) v (1, 16, 16).
Классификация Л. Шнайдера (L. Snyder). В 1988 году Л. Шнайдер предло­жил выделить этапы выборки и непосредственно исполнения в потоках ко­манд и данных. Именно разделение потоков на адреса и их содержимое по­зволило описать такие ранее "неудобные" для классификации архитектуры, как компьютеры с длинным командным словом, систолические массивы и целый ряд других.
Введем необходимые для дальнейшего изложения понятия и обозначения. Назовем потоком ссылок S некоторой вычислительной системы конечное множество бесконечных последовательностей пар:
S= {(щ </,>) (д2 <t2>), (6, <и,>) 2 2>),
(с, <v,>) (с2 <v2>)},
где первый компонент каждой пары — это неотрицательное целое число, называемое адресом, второй компонент — это набор из п неотрицательных целых чисел, называемых значениями, причем п одинаково для всех наборов всех последовательностей. Например, пара 2 2>) определяет адрес Ь2 и значение и2. Если значения рассматривать как команды, то из потока ссылок получим поток команд I; если же значения интерпретировать как данные, то соответствующий поток — это поток данных В.
Интерпретация введенных понятий очень проста. Элементы каждой после­довательности это адрес и его содержимое, выбираемое из памяти (или за­писываемое в память). Последовательность пар адрес—значение можно рас­сматривать как историю выполнения команд либо перемещения данных между процессором и памятью компьютера во время выполнения програм­мы. Число инструкций, которое данный компьютер может выполнять одно­временно, определяет число последовательностей в потоке команд. Анало­гично, число различных данных, которое компьютер может обработать одновременно, определяет число последовательностей в потоке данных.
Пусть S произвольный поток ссылок. Последовательность адресов потока S, обозначаемая Sa, это последовательность наборов, сформированная из адре­сов каждой последовательности из S:
Sa = <ai bx ci>, <a2 b2 c2>.
Последовательность значений потока S, обозначаемая Sv, это последователь­ность наборов, сформированная из значений каждой последовательности из S:
Sv = <ti ui vi>, <t2 u2 v2>.
Если Sxпоследовательность элементов, где каждый элемент является набором из п чисел, то для обозначения "ширины" последовательности бу­дем пользоваться обозначением: w(Sx) = п.
108
Часть I. Параллельные вычислительные системы
Из определений Sa, Sv и w следует, что если S это поток ссылок со значе­ниями из п чисел, то w{Sa) = \ S\ и w{Sv) = n\S\, где | S\ обозначает мощ­ность множества S.
Каждую пару (I, В) с потоком команд / и потоком данных В будем называть вычислительным шаблоном, а все компьютеры будем разбивать на классы в зависимости от того, какой шаблон они могут исполнить. В самом деле, компьютер может исполнить шаблон (I, В), если он в состоянии:
П выдать w{Ia) адресов команд для одновременной выборки из памяти;
□  декодировать и проинтерпретировать одновременно w(Iv) команд;
□  выдать одновременно w(Ba) адресов операндов;
□  выполнить одновременно w(Bv) операций над различными данными.
Если все эти условия выполнены, то компьютер может быть описан как Iw(ia)w(iv)Dw(Da)w(Dv)- Рассмотрим классическую последовательную машину. Согласно классификации Флинна, она попадает в класс SISD, следователь­но |/| = \В\ = 1 и w(Ia) = w{Ba) = 1. Из-за того, что в подобного рода компьютерах команды декодируются последовательно, следует равенство w(Iv) = 1, а последовательное исполнение команд дает w(Bv) = 1. Поэтому описание однопроцессорной машины с фоннеймановской архитектурой бу­дет выглядеть так: 1\\В\\.
Теперь возьмем две машины из класса SIMD: Goodyear Aerospace МРР и ILLIAC IV, причем не будем принимать во внимание разницу в способах обработки данных отдельными процессорными элементами. Единственный поток команд означает |/| = 1 для обеих машин. Аналогично последова­тельной машине, для потока команд получаем равенство w(Ia) = w(Iv) = 1. Далее, вспомним, что для доступа к операндам устройство управления МРР рассылает один и тот же адрес всем процессорным элементам, поэтому в этой терминологии МРР имеет единственную последовательность в потоке данных, т. е. \В\ = 1. Однако затем выборка данных из памяти и после­дующая обработка осуществляются в каждом процессорном элементе, по­этому w(Bv) = 16 384, а вся система МРР может быть описана /i(iA(i6 384-
В ILLIAC IV устройство управления, так же, как и в МРР, рассылает один и тот же адрес всем процессорным элементам, однако каждый из них может получить свой уникальный адрес, добавляя содержимое локального индекс­ного регистра. Это означает, что \В\ = 64 и в системе присутствуют 64 по­тока адресов данных, определяющих одиночные потоки операндов, т. е. w(Ba) = w(Bv) = 64. Суммируя сказанное, приходим к описанию ILLIAC IV:
Л,1-064,64-
Для более детальной классификации Шнайдер вводит три предопределенных значения, которые могут принимать величины w(Ia), w(Iv), w(Ba) и w(Bv):
П s — значение равно 1;
Глава 3. Архитектура параллельных вычислительных систем
109
П с — значение от 1 до некоторой (небольшой) константы;
П m — значение от 1 до произвольно большого конечного числа.
В частности, в этих обозначениях фоннеймановская машина принадлежит к классу ISSBSS.
Несмотря на то, что и с и m в принципе не имеют определенной верхней гра­ницы, они отражают разные свойства архитектуры компьютера. Описатель с предполагает жесткие ограничения сверху со стороны аппаратуры, и соответ­ствующий параметр не может быть значительно увеличен относительно про­стыми средствами. Примером может служить число инструкций, упакованных в командном слове VLIW-компьютера. С другой стороны, описатель m ис­пользуется тогда, когда обозначаемая величина может быть легко изменена, т. е. другими словами, компьютер по данному параметру масштабируем. На­пример, относительная простота увеличения числа процессорных элементов в системе МРР является основанием для того, чтобы отнести ее к классу IssDsm.
Конечно же, различие между с и т в достаточной мере условное и, как пра­вило, порождает массу вопросов. Например, как описать машину, в которой процессоры связаны через общую шину? С одной стороны, нет никаких принципиальных ограничений на число подключаемых процессоров. Одна­ко каждый дополнительный процессор увеличивает загруженность шины, и при достижении некоторого порога подключение новых процессоров бес­смысленно. С помощью какого символа описать такую систему: с или т? Мы оставляем данный вопрос открытым.
На основе этих обозначений можно выделить следующие классы компьютеров:
□  ISSDSSклассические машины фоннеймановского типа;
□  ISSDSCфоннеймановские машины, в которых заложена возможность выбирать данные, расположенные с разным смещением относительно одного и того же адреса и над которыми будет выполнена одна и та же операция. Примером могут служить компьютеры, имеющие команды ти­па одновременного выполнения двух операций сложения над данными в формате полуслова, расположенными по указанному адресу;
□  IssDsmSIMD-компьютеры без возможности получения уникального адреса для данных в каждом процессорном элементе. Сюда входят, на­пример, МРР, Connection Machine 1 и систолические массивы;
□  IssDmmэто SIMD-компьютеры, имеющие возможность независимой модификации адресов операндов в каждом процессорном элементе, на­пример, ILLIAC IV и Connection Machine 2;
□  ISCDCCвычислительные системы, выбирающие и исполняющие одно­временно несколько команд, для доступа к которым используется один адрес. Типичным примером являются VLIW-компьютеры;
□  ImmDmmк этому классу относятся все компьютеры типа MIMD.
110
Часть I. Параллельные вычислительные системы
Достаточно ясно, что не нужно рассматривать все возможные комбинации описателей s, с и ш, т. к. архитектура реальных компьютеров накладывает ряд вполне разумных ограничений, в частности w(Ia) < w(Iv) и w(Da) < w(Dv).
Подводя итог, можно отметить два положительных момента в классифика­ции Шнайдера: более избирательная систематизация SIMD-компьютеров и возможность описания нетрадиционных архитектур типа систолических массивов или компьютеров с длинным командным словом. Вместе с тем, почти все вычислительные системы типа MIMD опять попали в один и тот же класс ImmDmm. Это означает, что критерий классификации, основанный лишь на потоках команд и данных без учета распределенности памяти и то­пологии межпроцессорной связи, слишком слаб для подобных систем.
Классификация Д. Скилликорна (D. Skillicorn). В 1989 году была сделана очередная попытка расширить классификацию Флинна и тем самым пре­одолеть ее недостатки. Д. Скилликорн разработал подход, пригодный для описания свойств многопроцессорных систем и некоторых нетрадиционных архитектур, в частности, dataflow.
Предлагается рассматривать архитектуру любого компьютера, как абстракт­ную структуру, состоящую из четырех компонентов:
□  процессор команд (IP — Instruction Processor) — функциональное устрой­ство, работающее как интерпретатор команд; в системе, вообще говоря, может отсутствовать;
□  процессор данных (DP — Data Processor) — функциональное устройство, работающее как преобразователь данных в соответствии с арифметиче­скими операциями;
□  иерархия памяти (IM — Instruction Memory, DM — Data Memory) — за­поминающее устройство, в котором хранятся данные и команды, пересы­лаемые между процессорами;
□  переключатель — абстрактное устройство, обеспечивающее связь между процессорами и памятью.
Функции процессора команд во многом схожи с функциями устройств управления последовательных машин и, согласно Д. Скилликорну, сводятся к следующим. На основе своего состояния и полученной от DP информа­ции IP выполняет такие действия: определяет адрес команды, которая будет выполняться следующей; осуществляет доступ к IM для выборки команды; получает и декодирует выбранную команду; сообщает DP команду, которую надо выполнить; определяет адреса операндов и посылает их в DP; получает от DP информацию о результате выполнения команды.
Функции процессора данных делают его во многом похожим на арифмети­ческое устройство традиционных процессоров. DP получает от IP команду, которую надо выполнить; получает от IP адреса операндов; выбирает one-
Глава 3. Архитектура параллельных вычислительных систем
111
ранды из DM; выполняет команду; запоминает результат в DM; возвращает в IP информацию о состоянии после выполнения команды.
команды
DP
IP
информация о состоянии
■ц
п
1=1
т
m
>
о
Ь£
1
Память данных
Память команд
Рис. 3.5. Фоннеймановская архитектура в терминах классификации Д. Скилликорна
Структура традиционной фон-неймановской архитектуры в терминах таким образом определенных основных частей компьютера показана на рис. 3.5. Это один из самых простых видов архитектуры, не содержащих переключа­телей. Для описания сложных параллельных вычислительных систем Д. Скилликорн зафиксировал четыре типа переключателей без какой-либо явной связи с типом устройств, которые они соединяют:
□   1 — 1 — переключатель такого типа связывает пару функциональных уст­ройств;
П п — п — переключатель связывает каждое устройство из одного множест­ва устройств с соответствующим ему устройством из другого множества, т. е. фиксирует попарную связь;
П 1 — п — переключатель соединяет одно выделенное устройство со всеми функциональными устройствами из некоторого набора;
□  п х п — каждое функциональное устройство одного множества может быть связано с любым устройством другого множества, и наоборот.
Примеров подобных переключателей можно привести много. Так, все мат­ричные процессоры имеют переключатель типа 1-я для связи единствен­ного процессора команд со всеми процессорами данных. В компьютерах семейства Connection Machine каждый процессор данных имеет свою ло­кальную память, следовательно, такая связь будет описываться как п — п. В то же время, каждый процессор команд может связаться с любым другим процессором, что отвечает описанию пхп.
112
Часть /. Параллельные вычислительные системы
Классификация Д. Скилликорна строится на основе следующих восьми ха­рактеристик:
□  количество процессоров команд IP;
□  число запоминающих устройств (модулей памяти) команд IM;
□  тип переключателя между IP и IM;
□  количество процессоров данных DP;
□  число запоминающих устройств (модулей памяти) данных DM;
□  тип переключателя между DP и DM;
□  тип переключателя между IP и DP;
□  тип переключателя между DP и DP.
Рассмотрим компьютер Connection Machine 2. В терминах данных характе­ристик его можно описать следующим образом:
(1, 1, 1 — 1, п, п, п — п, 1 — п, п х п).
Условное изображение его архитектуры приведено на рис. 3.6.
п хп SW *
DP
ш
п: 1
IP
SW
Память данных
Память команд
Рис. 3.6. Архитектура Connection Machine 2 в классификации Д. Скилликорна
Для сильно связанных мультипроцессоров типа BBN Butterfly ситуация иная. Такие системы состоят из множества процессоров, соединенных с мо­дулями памяти с помощью переключателя. Задержка при доступе любого процессора к любому модулю памяти примерно одинакова. Связь и синхро­низация между процессорами осуществляется через общие (разделяемые) переменные. Описание таких машин в рамках данной классификации вы­глядит так:
(л, п, п — п, п, п, п х п, п — п, нет).
Глава 3. Архитектура параллельных вычислительных систем
113
Саму архитектуру можно изобразить так, как показано на рис. 3.7.
DP
IP
SW Г
L j
<
.••
-------г-*
-
n
Память данных
Память команд
Рис. 3.7. Архитектура BBN Butterfly в классификации Д. Скилликорна
Используя введенные характеристики и предполагая, что рассмотрение ко­личественных характеристик можно ограничить только тремя возможными вариантами значений: 0, 1 и я (то есть больше единицы), можно получить различные классы архитектур.
Интересно, что среди сформулированных Скилликорном целей, которым должна служить хорошо построенная классификация архитектур, есть и та­кая: "классификация должна показывать, за счет каких структурных особен­ностей достигается увеличение производительности различных вычисли­тельных систем". Эта цель очень созвучна целям наших исследований. Когда пользователь приходит на новую вычислительную систему, то, как правило, ему известна лишь ее пиковая производительность. Однако этого явно не­достаточно. Если ему действительно нужна высокая производительность, то, хочет он этого или нет, ему придется иметь дело с целым спектром смежных вопросов. Практически все они связаны с особенностями архитектуры ком­пьютера. Вот тут-то и пригодилась бы хорошая классификация.
Попадание компьютера в тот или иной класс сразу давало бы пользователю информацию об особенностях его программирования, методах достижения высокой производительности, причинах низкой производительности. Кос­венно такую информацию получить можно, но только в самых общих чер­тах. В частности, все компьютеры в первом приближении можно поделить на компьютеры с общей и распределенной памятью. Для компьютеров с общей памятью пользователю не нужно заботиться о распределении данных, а для программирования можно использовать простую технологию ОрепМР. На компьютерах с распределенной памятью ему, скорее всего, придется ра­ботать в терминах MPI и самому заботиться о распределении и пересылках данных. В общих чертах все верно, но, честно говоря, только в самых об-
114
Часть I. Параллельные вычислительные системы
щих. В таком описании опущено слишком много важных деталей, без кото­рых составление эффективных параллельных программ просто невозможно. Раз эти детали существенны, то они должны быть отражены в классифика­ции. Компьютеров много, деталей еще больше, отсюда и множество клас­сификаций, и интерес к данной проблеме в целом.
Вопросы и задания
1.   Может ли быть полезной на практике классификация компьютеров по их стои­мости (весу, размеру, отказоустойчивости)? Если да, то для какого класса поль­зователей?
2.   Может ли быть полезной на практике классификация компьютеров по их пико­вой производительности? Если да, то для какого класса пользователей?
3.   К какому классу по классификации Флинна можно отнести современные ком­пьютеры с общей памятью, например, HP Superdome?
4.   К каким классам по классификации Флинна можно отнести суперскалярные и VLIW-процессоры?
5.   Приведите примеры современных компьютеров класса SIMD.
6.   Опишите с помощью метрики Фенга первые 10 компьютеров из списка Тор500. Проделайте то же самое с помощью подходов Хендлера, Шнайдера, Скилликорна.
7.   Как соотносятся друг с другом классификации Хендлера и Флинна?
8.   Попробуйте найти взаимосвязь между изложенными в данном параграфе раз­личными классификациями компьютеров.
9.   *Какие параметры архитектуры параллельного компьютера нужно знать пользо­вателю для создания эффективных программ? Предложите классификацию компьютеров, опираясь на выделенные параметры.
10. *В данном параграфе мы говорили о подходах к классификации компьютеров. Предложите классификацию технологий параллельного программирования, раз­работанных к настоящему времени.
§ 3.2. Векторно-конвейерные компьютеры
С появлением в 1976 году компьютера Сгау-1 началась история векторно-конвейерных вычислительных систем. Архитектура оказалась настолько удачной, что компьютер дал начало целому семейству машин, а его название стало нарицательным для обозначения сверхмощной вычислительной тех­ники. Поскольку компьютеры этого семейства по праву считаются класси­ческими представителями мира суперкомпьютеров, с них мы и начнем изу­чение различных классов архитектур.
С середины 90-х годов прошлого века векторные компьютеры стали заметно сдавать позиции, уступая свои места массивно-параллельным компьютерам с распределенной памятью. Это и понятно. Соотношение цена/производи-
Глава 3. Архитектура параллельных вычислительных систем
115
тельность для уникальных систем не может конкурировать с продукцией массового производства, а именно этот параметр для многих является ре­шающим. Поговаривали даже о закате векторного направления в целом. Од­нако в марте 2002 года корпорация NEC объявила о завершении основных работ по созданию параллельной вычислительной системы "the Earth Simu­lator", основу которой составляют векторные процессоры! Система состоит из 640 вычислительных узлов, в каждом узле по 8 векторных процессоров, что дает 5120 процессоров во всей системе в целом. Поскольку пиковая производительность одного процессора составляет 8 Гфлопс, то для всего компьютера эта величина превышает 40 Тфлопс. Производительность на тесте L1NPACK составила 35,6 Тфлопс, т. е. 89% от пика — прекрасный по­казатель! Так что говорить о неперспективности данного направления в раз­витии вычислительной техники явно рано.
Секция межпроцессорного взаимодействия
......................._........._........._........._..............:.....t.....:.............._........
проц0
Регистр
маски
вектора
1|
Векторные регистры
Регистр
ДЛИНЫ
вектора
ч----------------•
Т-регистры
<^
S-регистры
В-регистры
I J
*г+
А-регистры
>
Буферы команд
Блок
управления
вв./выв.
Векторные ФУ
ПРОЦ,
ФУ обработки
вещественных
чисел
Память
Скалярные ФУ
проц14
> Адресные ФУ
проц15
ПРОЦ;
Секция ввода/вывода
X
Внешние устройства Рис. 3.8. Общая схема компьютера Cray С90
В качестве объекта для детального изучения возьмем компьютер Cray С90, в архитектуре которого есть все характерные особенности компьютеров дан­ного класса [18]. Его последователь Cray Т90 имеет такую же структуру, от-
116
Часть I. Параллельные вычислительные системы
личаясь лишь некоторыми количественными характеристиками. Описание компьютера будем вести с той степенью детальности, которая необходима для выделения ключевых особенностей и узких мест архитектуры. Знание именно этих параметров нам понадобится позднее для анализа эффективно­сти функционирования реальных параллельных программ.
Итак, Cray С90 — это векторно-конвейерный компьютер, появившийся на рынке вычислительной техники в самом начале 90-х годов прошлого века. В максимальной конфигурации Cray С90 содержит 16 процессоров, рабо­тающих над общей памятью. Время такта компьютера равно 4,1 не, что со­ответствует тактовой частоте почти 250 МГц. На рис. 3.8 показана общая схема данного компьютера с более детальным представлением структуры одного процессора. Поскольку все процессоры одинаковы, то не имеет зна­чения, какой именно процессор изображать детально.
Все процессоры компьютера Cray С90 не только одинаковы, но и равно­правны по отношению ко всем разделяемым ресурсам: памяти, секции вво­да/вывода и секции межпроцессорного взаимодействия. Рассмотрим кратко их особенности.
Структура оперативной памяти
Оперативная память Cray С90 разделяется всеми процессорами и секцией ввода/вывода. Каждое слово памяти состоит из 80-ти разрядов: 64 разряда для хранения данных и 16 вспомогательных разрядов для коррекции оши­бок. Для увеличения скорости выборки данных вся память разделена на множество банков, которые могут работать одновременно.
Каждый процессор имеет доступ к оперативной памяти через четыре порта с пропускной способностью два слова за один такт каждый. Один из портов всегда связан с секцией ввода/вывода, и, по крайней мере, один из портов всегда выделен под операцию записи. Подобная архитектура хорошо подхо­дит для выполнения векторных операций с не более чем двумя входными векторами.
В максимальной конфигурации реализовано расслоение памяти компьютера на 1024 банка: каждая из 8 секций разделена на 8 подсекций, а каждая под­секция на 16 банков (рис. 3.9). Последовательные адреса идут с чередовани­ем по каждому из данных параметров:
адрес 0 — в 0-й секции, 0-й подсекции, 0-м банке;
адрес 1-е 1-й секции, 0-й подсекции, 0-м банке;
адрес 2-е 2-й секции, 0-й подсекции, 0-м банке;
адрес 8 — в 0-й секции, 1-й подсекции, 0-м банке; адрес 9 — в 1-й секции, 1-й подсекции, 0-м банке;
Глава 3. Архитектура параллельных вычислительных систем
117
адрес 63 — в 7-й секции, 7-й подсекции, 0-м банке; адрес 64 — в 0-й секции, 0-й подсекции, 1-м банке; адрес 65 — в 1-й секции, 0-й подсекции, 1-м банке;
Цо 8 секций
В каждой секции до 8 подсекций
В каждой подсекц до 16 банков
ли
секция 0
секция 3, подсекция 0
секция 3, подсекция 3 -
-банк 0
секция 3, подсекция 3 -
-банк 1
секция 1
секция 3, подсекция 1
секция 3, подсекция 3 -
-банк 2
секция 3, подсекция 2
секция 2
секция 3, подсекция 3
секция 3
секция 3, подсекция 7
секция 7
секция 3, подсекция 3 -
■банк15
Рис. 3.9. Расслоение памяти компьютера Cray С90
При одновременном обращении к одной и той же секции возникает конфликт, который разрешается за 1 такт. В этом случае один из запросов продолжает обрабатываться, а другой просто блокируется на один такт. Если происходит одновременное обращение к одной и той же подсекции одной секции, время на разрешение конфликта уже может достигать 6 тактов. Яс­но, что максимальное число конфликтов будет происходить при постоянном обращении к одной и той же подсекции одной и той же секции. Это заве­домо произойдет при выполнении процессором векторной операции над данными, расположенными с шагом, кратным 64. Этот же пример является иллюстрацией того факта, что даже при выполнении одной программы на одном процессоре Cray С90 конфликты возможны.
Вместе с тем, подобная структура памяти ориентирована на максимально быструю обработку наиболее типичных случаев. Операции чтения/записи последовательно расположенных данных проходят без возникновения кон­фликтов. В частности, все одномерные массивы будут обрабатываться имен­но так. Аналогичная ситуация и при выборке данных с любым нечетным шагом — конфликтов так же не возникнет. В общем случае, чем большей степени двойки кратен шаг выборки данных, тем больше времени требуется на разрешение возникающих конфликтов.
118
Часть I. Параллельные вычислительные системы
Секция ввода/вывода
Этой части компьютера не будем уделять много внимания. Скажем лишь, что он поддерживает три типа каналов для работы с внешними устройства­ми, которые различаются скоростью передачи данных:
□  Low-speed (LOSP) channels — 6 Мбайт/с;
□  High-speed (HISP) channels — 200 Мбайт/с;
□  Very high-speed (VHISP) channels — 1800 Мбайт/с.
Секция межпроцессорного взаимодействия
Основное назначение данной секции заключается в передаче данных и управ­ляющей информации между процессорами для синхронизации их совместной работы организации взаимодействия друг с другом. Секция межпроцессорного взаимодействия содержит разделяемые регистры и семафоры, объединенные в одинаковые группы — кластеры. Каждый кластер состоит из восьми 32-разрядных разделяемых адресных регистров (SB), восьми 64-разрядных разделяемых скалярных регистров (ST) и 32 однобитовых семафоров. Число кластеров в системе определяется конфигурацией компьютера.
Теперь перейдем к описанию структуры отдельного процессора. Все процес­соры имеют одинаковую вычислительную секцию, состоящую из регистров и функциональных устройств (ФУ). Различные регистры и функциональные устройства могут хранить и обрабатывать три класса данных: адреса, скаля­ры и векторы.
Регистровая структура процессора
Каждый процессор имеет набор основных и набор промежуточных регистров. К основным регистрам относятся адресные регистры А, скалярные регистры S и векторные регистры V. Промежуточные регистры В и Т, играющие роль промежуточного хранилища между памятью и основными регистрами, преду­смотрены для регистров А и S соответственно. Все основные регистры связа­ны с памятью и функциональными устройствами, а регистры А и S имеют дополнительную связь с соответствующими промежуточными регистрами. Промежуточные регистры связаны только с памятью и основными регистра­ми, непосредственной связи с функциональными устройствами у них нет. Здесь же заметим, что и традиционной кэш-памяти в данном компьютере нет.
В структуре компьютера предусмотрено 8 адресных регистров в основном наборе А, и 64 регистра в промежуточном наборе В. Адресные регистры предназначены для хранения и вычисления адресов, индексации, указания величины сдвигов, числа итераций циклов и т. д. Все регистры данной группы имеют по 32 разряда.
Как и в случае адресных регистров, в основном наборе скалярных регистров S содержится 8 регистров, и еще 64 регистра в промежуточном наборе Т.
Глава 3. Архитектура параллельных вычислительных систем
119
Эти регистры предназначены для хранения аргументов и результатов ска­лярной арифметики, но могут содержать операнды для векторных команд. Это значит, что скалярные регистры используются для выполнения как ска­лярных, так и векторных команд. Все скалярные регистры 64-разрядные.
Каждый векторный К-регистр может содержать до 128 64-разрядных слов. Всего в процессоре содержится 8 векторных регистров. Промежуточных ре­гистров для набора V нет. Векторные регистры используются только для вы­полнения векторных команд.
Для поддержки выполнения векторных команд предусмотрено два дополни­тельных регистра VL и VM. Регистр длины вектора VL содержит реальную длину векторов, хранящихся в векторных регистрах и участвующих в век­торной операции. Данный регистр содержит 8 разрядов. Регистр маски век­тора VM состоит из 128 разрядов и позволяет выполнять векторную опера­цию не над всеми элементами входных векторов. Если разряд маски равен 1, то операция над соответствующими элементами будет выполнена, в про­тивном случае — нет. Данная возможность исключительно полезна при век­торизации фрагментов, содержащих условные операторы.
Функциональные устройства
Все функциональные устройства у компьютера Cray С90 являются конвей­ерными. Число ступеней у них различно, однако каждая ступень каждого устройства всегда срабатывает за один такт. Это, в частности, означает, что при полной загрузке все устройства могут выдавать результат каждый такт (см. §2.1). Кроме этого, все функциональные устройства независимы и мо­гут работать одновременно друг с другом.
Функциональные устройства данного компьютера делятся на четыре груп­пы: адресные, скалярные, векторные и функциональные устройства для вы­полнения операций над вещественными числами.
Два адресных функциональных устройства предназначены для выполнения сложения/вычитания и умножения 32-разрядных целых чисел. Скалярных устройств в процессоре всегда четыре. Они используются для целочислен­ного сложения/вычитания, логических поразрядных операций, для выпол­нения операций сдвига и нахождения числа нулей до первой единицы в слове. Скалярные устройства оперируют с 64-разрядными данными и пред­назначены для выполнения только скалярных команд.
Число векторных устройств в зависимости от конфигурации компьютера меняется от пяти до семи. В их число могут входить устройства для цело­численного сложения/вычитания, сдвига, логических поразрядных опера­ций, устройство для нахождения числа нулей до первой единицы в слове, для умножения битовых матриц. Некоторые из перечисленных устройств могут быть продублированы. Все векторные функциональные устройства предназначены для выполнения только векторных команд.
120
Часть I. Параллельные вычислительные системы
Три функциональных устройства для вещественной арифметики работают с 64-разрядными числами, представленными в форме с плавающей запятой. Они предназначены для сложения/вычитания, умножения и нахождения обратной величины числа. Отдельного устройства для выполнения явной операции деления вещественных чисел нет. Все устройства данной группы могут выполнять как векторные, так и скалярные команды.
Интересно, что в каждой группе функциональных устройств предусмотрено устройство для выполнения операций сложения. Однако в каждом случае формат операций разный. Адресное функциональное устройство выполняет скалярное сложение 32-разрядых целых чисел. Скалярное устройство пред­назначено для скалярного сложения целых 64-разрядных чисел. Устройство сложения из векторной группы выполняет операции над векторами из 64-разрядных целых чисел. Устройство сложения из последней группы исполь­зуется как для обычного сложения двух вещественных чисел, так и для сло­жения векторов. Каждой из этих разновидностей соответствует своя команда в системе команд компьютера Cray С90, по коду которой аппаратура может "понять", на устройстве какой группы необходимо выполнять ту или иную операцию. Подобная ситуация возникает не только для сложения. Можно сказать, что она характерна для архитектуры компьютера в целом и прояв­ляется во многих других операциях.
Выполнение векторных операций в данном компьютере обладает интересной особенностью. Все конвейеры во всех векторных устройствах и устройствах для вещественной арифметики продублированы (рис. 3.10). Элементы вход­ных векторов с четными номерами всегда поступают на конвейер 0, а элемен­ты входных векторов с нечетными номерами — на конвейер 1. В начальный момент времени нулевые элементы векторных регистров V\ и V2 поступают на первую ступень конвейера 0, и одновременно с этим первые элементы век­торных регистров V\ и Vj поступают на первую ступень конвейера 1. На сле­дующем такте результат первой ступени перемещается на вторую, а на первую ступень конвейеров 0 и 1 поступают вторые и третьи элементы векторных регистров V\ и V2. Аналогично раскладываются результаты в регистре К3: с конвейера 0 они записываются в элементы с четными номерами, а с конвейе­ра 1 — с нечетными. В результате функциональное устройство при макси­мальной загрузке на каждом такте выдает уже не один результат, а два.
В скалярных операциях, использующих функциональные устройства для вещественной арифметики, работает только один конвейер 0.
Архитектура Cray С90 позволяет использовать регистр результатов одной векторной операции в качестве входного регистра для последующей вектор­ной операции. Выход одной операции сразу подается на вход другой, при­чем последняя не обязана ждать завершения первой, а, начиная с некото­рого момента, будет работать одновременно с ней. Подобная ситуация называется зацеплением векторных операций. Вообще говоря, глубина заце­пления может быть любой, например, чтение векторов, выполнение опера­ции сложения, выполнение операции умножения, запись векторов.
Глава 3. Архитектура параллельных вычислительных систем
121
Векторный регистр V.,
Векторный регистр V3
Конвейер О
Конвейер 1
Векторный регистр V2
Рис. 3.10. Выполнение векторных операций в компьютере Cray С90
Основное назначение зацепления состоит опять-таки в увеличении скорости обработки данных. В самом деле, предположим, что нам нужно выполнить операцию вида А/ = Bj + Q х d, где каждый входной вектор содержит по п элементов. Пусть в нашем распоряжении есть функциональные устройства сложения и умножения, содержащие по 1\ и lj ступеней соответственно. Если выполнять исходную операцию традиционным способом, т. е. сначала век­торную операцию умножения, а затем сложения, то вся операция будет реали­зована за 4 + /2 + 2 х п — 2 тактов. Если для той же операции воспользоваться режимом с зацеплением, то по сути получится один конвейер длиной 1\ + /2, а значит и время выполнения всей операции сократится до 4 + I2 + п — 1. При больших значениях п время реализации операции по сравнению с обычным способом уменьшится почти в два раза. Эта ситуация схематично показана на рис. 3.11.
с, —
of —»■
C,xd—>•
В,- —
-»• С,хс/
с,. —»• d —>
-,
X
X
В,. —»■
— Л
+
Рис. 3.11. Зацепление векторных операций
122
Часть I. Параллельные вычислительные системы
Секция управления процессора
Команды выбираются из оперативной памяти блоками и заносятся в буфера команд, откуда затем они выбираются для исполнения. Если необходимой для исполнения команды нет в текущем буфере, она ищется в других буфе­рах. Если требуемой команды в буферах не оказалось, то происходит выбор­ка очередного блока.
Такова основа архитектуры компьютера Cray С90. Нетрудно видеть, что идеи параллельной обработки пронизывают все ее составные части. Среди основных особенностей архитектуры, дающих заметный вклад в ускорение выполнения программ, можно назвать следующие.
□  Конвейеризация выполнения команд. Все основные операции, выполняемые процессором, т. е. обращение в память, обработка команд и выполнение инструкций функциональными устройствами являются конвейерными.
□  Независимость функциональных устройств. Функциональные устройства в Cray С90 являются независимыми, поэтому несколько операций могут выполняться одновременно.
□  Векторная обработка. Векторная обработка увеличивает скорость и эф­фективность обработки за счет того, что обработка целого набора (вектора) данных выполняется одной командой. Скорость выполнения операций в векторном режиме может быть в 10—15 раз выше скорости скалярной обработки.
□  Зацепление функциональных устройств. Возможность выполнения не­скольких векторных операций в режиме "макроконвейера" дает дополни­тельный выигрыш в скорости их обработки.
□  Многопроцессорная обработка. В максимальной конфигурации компьютер может содержать до 16 независимых процессоров. Эти процессоры могут быть использованы по-разному. В частности, они могут выполнять не­сколько независимых программ, но могут и все быть назначены на вы­полнение одной программы.
Пиковая производительность Cray С90
Зная архитектуру компьютера, легко посчитать его пиковую производитель­ность. Поскольку нас, прежде всего, интересует скорость выполнения операций над вещественными числами, то нужно максимально загрузить функциональные устройства для вещественной арифметики. Операция на­хождения обратной величины сама по себе используется редко, а в опера­ции деления ей дополнительно требуется операция умножения. Поэтому для определения пиковой производительности компьютера будем задействовать только устройства умножения и сложения. Для получения максимальной про­изводительности их нужно использовать в режиме с зацеплением. Именно так
Глава 3. Архитектура параллельных вычислительных систем
123
мы уже поступали, когда речь шла о реализации операции вида At = Bt + Q х d. Если дополнительно учесть, что каждое такое устройство для выполнения векторной операции использует два внутренних конвейера, то система из двух устройств будет выдавать результат четырех операций за такт. Время такта компьютера равно 4,1 не, поэтому пиковая производительность одного процессора Cray С90 составит почти 1 Гфлопс или 109 операций в секунду. Если одновременно работают все 16 процессоров компьютера, то пиковая производительность увеличивается до 16 Гфлопс.
Мы разобрали основные особенности архитектуры данного компьютера, из чего стало понятно, почему он считает так быстро. Однако для понимания того, как для него нужно писать эффективные программы, нужно изучить и его другую сторону. Нужно выделить те факторы, которые снижают его производительность на реальных программах. Не сделав этого шага, нам будет трудно понять, что в программе следует изменить, чтобы производи­тельность увеличить. Анализу эффективности выполнения программ на данном компьютере будет посвящена оставшаяся часть параграфа.
Первое, что нам нужно сделать, — это определиться с терминологией. Ком­пьютер обладает векторно-конвейерной архитектурой. Основной выигрыш во времени можно получить за счет использования векторного режима об­работки. Некоторый фрагмент программы может быть обработан в вектор­ном режиме, если для его выполнения могут быть использованы векторные команды из системы команд компьютера. Если весь фрагмент программы удалось заменить векторными командами, то говорят о его полной вектори­зации. В противном случае мы имеем дело с частичной векторизацией или невозможностью векторизации фрагмента вовсе. Процесс поиска подходя­щих фрагментов в программе и их замена векторными командами называется векторизацией программы. Пример векторизуемого фрагмента может выгля­деть так:
DO i = 1, n
C(i) = A(i) + B(i) END DO
Для данного фрагмента компилятор сгенерирует последовательность век­торных команд: загрузка векторов а и в из памяти в векторные регистры, векторная операция сложения, запись содержимого векторного регистра в память.
Однако далеко не любой фрагмент программы можно векторизовать. Для этого необходимо выполнение двух условий. Первое — это наличие векторов-аргументов. Второе условие немного сложнее и заключается в том, что над всеми элементами векторов должны выполняться одинаковые, независимые операции, для которых существуют аналогичные векторные команды в систе­ме команд компьютера. Поясним введенные понятия немного подробнее.
124
Часть /. Параллельные вычислительные системы
Под вектором будем понимать упорядоченный набор однотипных данных, все элементы которого размещены в памяти компьютера с одинаковым смещением друг относительно друга. Простейшим примером векторов в программах служат одномерные массивы. Другим примером могут служить строки и столбцы матриц. Для языка Fortran расстояние между соседними элементами одного столбца матрицы равно единице, а между соседними элементами одной строки — размерности матрицы. Для языка С строки и столбцы меняются местами. Диагональ квадратной матрицы также является примером вектора, поскольку расстояние между всеми ее элементами оди­наково и равно размерности матрицы плюс единица. Наконец, весь много­мерный массив целиком также можно считать одним вектором с длиной, равной произведению всех размерностей массива. Этот список можно про­должать и далее, и в любой программе, использующей регулярные структу­ры данных, можно найти массу различных примеров векторов.
В то же время вся поддиагональная часть двумерной матрицы вектором не является. Связано это с тем, что расстояние между элементами этого набора данных в памяти компьютера не является каким-либо одним постоянным числом.
Кроме векторов, в векторизуемом фрагменте могут использоваться и про­стые переменные. Поскольку в системе команд компьютера Cray С90 есть векторные команды, в которых некоторые аргументы могут быть скалярами, то векторизация следующего фрагмента никаких проблем не вызовет:
DO i = 1, n
B(i) = A(i) + s END DO
Основными кандидатами для векторизации являются самые внутренние циклы всех циклических конструкций программы. Именно они задают пе­ребор "одномерных" наборов данных, которые, в частности, могут быть век­торами. Но работа с векторами еще не является достаточным условием для векторизации. Рассмотрим следующий пример:
DO i = 1, n
A(i) = A(i-l) + B(i) END DO
Вычисление i-го элемента массива а не может начаться, пока не будет вы­числен предыдущий элемент. Но в таком случае теряет всякий смысл исполь­зование конвейерной обработки! Мы не можем загрузить данные на первую ступень конвейера, пока результат не выйдет из устройства. В данном приме­ре существует зависимость между операциями, которая и будет препятствовать векторизации. Именно поэтому мы говорили о возможности векторизации лишь при условии существования одинаковых и независимых операций.
Глава 3. Архитектура параллельных вычислительных систем
125
Рассмотрим еще один пример:
DO i = 1, n
A(i) = Funct( A(i), B(i)) END DO
В явном виде такой фрагмент также не может быть векторизован. Компилятор не знает, какая операция соответствует вызову пользовательской функции Funct, и, следовательно, он не знает, на какие векторные команды можно заменить данный фрагмент. В некоторых случаях компилятор может получить дополнительную информацию, если проведет межпроцедурный анализ, выяс­нит содержание функции Funct и выполнит in-line подстановку.
На этом краткое введение в векторизацию программ мы закончим и перей­дем к анализу узких мест в архитектуре компьютера Cray С90. Основное, что нам предстоит выяснить, какие особенности архитектуры необходимо учи­тывать при написании действительно эффективных программ для данного компьютера.
Анализ факторов, снижающих реальную производительность компьютеров, начнем с обсуждения уже известного нам закона Амдала. Одна из его интер­претаций сводится к тому, что время работы программы определяется ее самой медленной частью. В самом деле, предположим, что одна половина некоторой программы — это сугубо последовательные вычисления, которые невозможно векторизовать. Тогда вне зависимости от свойств другой поло­вины, которая может быть идеально векторизована и, скажем, выполнена мгновенно, ускорения работы всей программы более чем в два раза мы не получим.
Влияние данного фактора надо оценивать с двух сторон. Во-первых, по природе самого алгоритма все множество операций программы Q разбивается на последовательные операции Qi и операции Q2, исполняемые в векторном режиме. Если доля последовательных операций велика, то программист сра­зу должен быть готов к тому, что большого ускорения он никакими средст­вами не получит и, быть может, следует уже на этом этапе подумать об из­менении алгоритма.
Во-вторых, не следует сбрасывать со счетов и качество компилятора. Он вполне может не распознать векторизуемость отдельных конструкций, сге­нерировать для них скалярный код и, тем самым, часть "потенциально хо­роших" операций из Q2 перенести в Qj.
Следующие два фактора, снижающие производительность компьютера на реальных программах, определяют принципиальную невозможность дости­жения пиковой производительности на векторно-конвейерных устройств. Это время разгона векторной команды и секционирование длинных векторов. Для инициализации векторной команды требуется несколько тактов, после чего начинается заполнение конвейера и лишь через некоторое время появ-
126
Часть I. Параллельные вычислительные системы
ляются первые результаты. Только после этого устройство каждый такт бу­дет выдавать результат, и его производительность асимптотически будет приближаться к пиковой. Но лишь приближаться. Точного значения пико­вой производительности никогда получено не будет.
Теперь вспомним устройство векторных регистров. Каждый регистр содер­жит 128 элементов. Для запуска векторной команды длинный входной век­тор необходимо разбить на секции по 128 элементов. Секцию вектора, це­ликом расположенную в регистре, уже можно обрабатывать с помощью векторных команд. Такая технология работы с длинными векторами пред­полагает, что векторизуемый цикл исходной программы должен быть преоб­разован в двумерное гнездо циклов. Внешний цикл последовательно пере­бирает секции, а внутренний работает только с элементами текущей секции. Именно внутренний цикл реально и подлежит векторизации. На практике не стоит пугаться этой процедуры, поскольку она полностью выполняется компилятором.
На переход от обработки одной секции к другой требуется пусть и неболь­шое, но время, а это опять лишняя задержка и опять падение производи­тельности. Если время разгона влияет только при старте, то секционирова­ние немного снижает производительность во всех точках, кратных 128. Это снижение небольшое и с увеличением длины векторов его относительное влияние становится все меньше и меньше, асимптотически приближаясь к малой константе.
Для того чтобы немного почувствовать влияние этих факторов, рассмотрим следующий фрагмент программы.
DO i = 1, n
A(i) = B(i) *s + C(i) END DO
В табл. 3.1 показана производительность компьютера Cray С90 на данном фрагменте в зависимости от длины входных векторов, т. е. от значения п. Необходимо отметить, что приведенные здесь и далее значения производи­тельности на практике могут немного меняться в зависимости от текущей загрузки компьютера и некоторых других факторов. Эти значения нужно рассматривать скорее как ориентир, а не как абсолютный показатель.
Таблица 3.1. Производительность Cray С90 на операции Ai = Btx s + Q
Длина вектора Производительность (Мфлопс)
Длина Производительность вектора (Мфлопс)
1                                   7,0
2                                  14,0
150 413,2 256 548,0
Глава 3. Архитектура параллельных вычислительных систем
127
Таблица 3.1 (окончание)
Длина вектора
Производительность (Мфлопс)
Длина вектора
Производительность (Мфлопс)
4
27,6
257
491,0
16
100,5
512
659,2
32
181,9
1024
720,4
128
433,7
2048
768,0
129
364,3
8192
802,0
И время начального разгона, и секционирование относятся к накладным расходам на организацию векторных операций на конвейерных функцио­нальных устройствах. Показанная зависимость явно стимулирует к работе с длинными векторами данных, т. к. с ростом длины вектора доля накладных расходов в общем времени выполнения операции быстро падает. Одновре­менно заметим, что очень короткие циклы выгоднее выполнять не в век­торном режиме, а в скалярном, поскольку не будет необходимости тратить дополнительное время на инициализацию векторных команд.
Конфликты при обращении в память у компьютеров серии Cray С90 полно­стью определяются аппаратными особенностями организации доступа к оперативной памяти. Наибольшее время на разрешение конфликтов требу­ется при выборке данных с шагом 64, когда постоянно совпадают номера и секций, и подсекций. С другой стороны, выборка с любым нечетным шагом проходит без конфликтов вообще, и в этом смысле она эквивалентна вы­борке с шагом единица. Для оценки влияния конфликтов при доступе в па­мять рассмотрим следующий пример:
DO i = 1, n х k, k
A(i) = B(i) *s + C(i) END DO
В данном примере происходит выполнение векторной операции At = Bj х х s + Q над векторами длины п в режиме с зацеплением. Значение пере­менной к определяет шаг, с которым данные выбираются из памяти. Рас­сматривая выше аналогичный пример для шага, равного единице, мы виде­ли, насколько важным параметром для производительности является длина векторов. Чтобы этот параметр сейчас не мешал, мы сделаем число опера­ций для любого значения к одинаковым, положив верхнюю границу цикла равной п х к. Производительность компьютера Cray С90 на данной опера­ции для «=1000 показана в табл. 3.2.
128
Часть I. Параллельные вычислительные системы
Таблица 3.2. Влияние конфликтов в памяти на производительность Cray С90
Шаг по памяти                                 Производительность (Мфлопс)
1
705,2
2
444,6
4
274,6
8
142,8
16
84,5
32
44,3
64
22,6
128
22,6
Как мы видим, производительность падает катастрофически, достигая ми­нимума с шага 64. А причина этому одна — структура фрагмента плохо со­ответствует особенностям архитектуры компьютера.
Еще одной неприятной стороной конфликтов при обращении к памяти яв­ляется то, что проявляться они могут совершенно по-разному. В предыду­щем примере конфликты возникали при использовании цикла с четным шагом. Но такая ситуация слишком очевидна, и опытного программиста она насторожит сразу. Вместе с тем, казалось бы, не должно быть никаких при­чин для беспокойства при работе фрагмента следующего вида:
DO i = 1, n DO j = 1, n DO k = 1, n
X(i,j,k) = X(i,j,k)+P(k,i)*Y(k,j) END DO END DO END DO
Зависимости между итерациями цикла нет, используются одновременно операции сложения и умножения, внутренний цикл с параметром к легко векторизуется. Однако все не так безобидно, как кажется. Решающее значе­ние имеет то, каким образом описан массив х. Предположим, что описание имеет вид:
DIMENSION Х(40,40,Ю00)
По определению Фортрана массивы хранятся в памяти "по столбцам". Сле­довательно, при изменении последнего индексного выражения на единицу реальное смещение по памяти будет равно произведению размеров массива по предыдущим размерностям. Для нашего примера расстояние в памяти
Глава 3. Архитектура параллельных вычислительных систем
129
между соседними элементами x(i,j,k) и x(i,j,k+i) равно 40 х 40 = 1600. Но, используя другое разложение на множители, число 1600 можно предста­вить произведением 25 х 64! Это число кратно наихудшему шагу для выбор­ки из памяти, поэтому число конфликтов будет максимальным. Можно ли избавиться от конфликтов? Как ни странно, это сделать чрезвычайно легко. Достаточно лишь изменить описание массива, добавив единицу к первым двум размерностям:
DIMENSION Х(41,41Д000)
Расстояние между соседними элементами x(i,j,k) и х(i, j,k+i) становится нечетным числом и конфликты исчезают. Немного увеличив объем массива, мы сделали выборку из памяти максимально эффективной. Точно такой же пример можно привести и для языка С, достаточно строки и столбцы поме­нять местами.
Еще один пример возможного появления конфликтов — это использование косвенной адресации. Рассмотрим следующий фрагмент:
DO i = 1, n
XYZ ( IX(i) ) = XYZ(IX(i))+P(i)*Y(i) END DO
В зависимости от того, к каким элементам массива xyz реально происходит обращение, число конфликтов будет меняться. Их может не быть вовсе, ес­ли, например, ix(i) всегда равно i. Если же предположить, что ix(i) равно некоторому одному и тому же числу для всех i, то число конфликтов будет максимальным (на каждой итерации будет происходить обращение к одному и тому же элементу массива, т. е. будет обращение к одной и той же под­секции одной и той же секции).
Следующие три фактора, снижающие производительность Cray С90, опре­деляются тем, что перед началом выполнения любой операции данные должны быть занесены в регистры. Для этого в архитектуре компьютера предусмотрены три независимых канала передачи данных, два из которых могут работать на чтение из памяти, а третий на запись. Такая структура хорошо подходит для операций, требующих не более двух входных векторов. Примером операции служит, в частности, операция А{= Д х s +Q, на кото­рой компьютер может работать в режиме с зацеплением и, следовательно, показывать хорошие значения производительности.
Однако операции с тремя векторными аргументами, например, At = Bt х Q + Dh уже не могут быть реализованы столь же эффективно. Часть времени будет неизбежно потрачено впустую на ожидание подкачки третьего аргумента для запуска операции с зацеплением. Это является прямым следствием ограни­ченной пропускной способности тракта процессор—память (memory bottleneck). С одной стороны, максимальная производительность достигает­ся на операции с зацеплением, требующей три аргумента, а, с другой сторо-
130
Часть I. Параллельные вычислительные системы
ны, на чтение одновременно могут работать лишь два канала. В табл. 3.3 при­ведена производительность компьютера на указанной выше векторной опера­ции, требующей три входных вектора В, С, D, в зависимости от их длины.
Таблица 3.3. Производительность Cray С90 на операции Д = В, х Q + Ц
Длина вектора                                 Производительность (Мфлопс)
10                                                                    57,0
100                                                                  278,3
1000                                                                 435,3
12801                                                                 445,0
Теперь предположим, что пропускная способность каналов не является уз­ким местом. В этом случае на предварительное занесение данных в регист­ры все равно требуется некоторое дополнительное время. Архитектура ком­пьютера такова, что нам необходимо использовать векторные регистры перед выполнением операций. Как следствие, требуемые для этого операции чте­ния/записи будут неизбежно снижать общую производительность. Довольно часто влияние данного фактора можно заметно ослабить, если повторно ис­пользуемые вектора один раз загрузить в регистры, выполнить все постро­енные на их основе выражения, а уже затем перейти к оставшейся части программы.
Рассмотрим следующий фрагмент программы:
DO j = 1, 120
DO i = 1, n
D(i) = D(i) + s*P(i,j-U + t*P(i,j)
END DO END DO
Для векторизации внутреннего цикла фрагмента нет никаких препятствий. На каждой из 120 итераций по j для выполнения векторной операции тре­буется считать три входных вектора d (i), р (i, j -1) и p (i, j), и записать один выходной d (i). Следовательно, за время работы всего фрагмента будет выполнено 120 х 3 = 360 операций чтения векторов и 120 операций записи.
Сделаем несложное эквивалентное преобразование данного фрагмента. Явно выпишем каждые две последовательные итерации цикла по j, приведя его к следующему виду:
DO j = 1, 120, 2 DO i = 1, n
Глава 3. Архитектура параллельных вычислительных систем
131
D(i) = D(i)+s*P(i,j-l)+t*P(i,j)+s*P(i,j)+t*P(i,j+1) END DO END DO
Теперь на каждой из 60 итераций внешнего цикла потребуется четыре вход­ных вектора D(i), P(i,j-l), P(i,j), P(i,j+l) и, опять же, один выходной D(i). Суммарно, для нового варианта фрагмента будет выполнено 60 х 4 = = 240 операций чтения и 60 операций записи. Выигрыш очевиден. Преобра­зование подобного рода носит название раскрутки цикла. Оно имеет макси­мальный эффект в том случае, когда на соседних итерациях цикла исполь­зуются одни и те же данные. В табл. 3.4 показана производительность компьютера на данном фрагменте в зависимости от глубины раскрутки. Значение п равно 128.
Таблица 3.4. Зависимость производительности Cray С90 от глубины раскрутки
Глубина раскрутки                              Производительность (Мфлопс)
1                                                               612,9
2                                                              731,6
3                                                              780,7
4                                                              807,7
Теоретически, с увеличением глубины раскрутки растет и производитель­ность, приближаясь в пределе к некоторому значению. Однако на практике максимальный эффект достигается где-то на первых шагах, а затем произво­дительность либо остается примерно одинаковой, либо падает. Основная причина данного несоответствия теории и практики заключается в том, что компьютеры Cray С90 имеют сильно ограниченный набор векторных регист­ров: 8 регистров по 128 слов в каждом. Как правило, увеличение глубины раскрутки ведет к увеличению числа входных векторов. Так было и в нашем случае. Фрагмент в исходной форме требовал по три входных вектора на каждой итерации внешнего цикла. Раскрутка глубиной 2 привела к необхо­димости загрузки четырех векторов, для раскрутки на глубину 3 потребуется пять векторов и т. д. Каждый дополнительный вектор предполагает наличие дополнительного регистра, что с увеличением глубины раскрутки станет уз­ким местом.
Теперь вспомним, что значение пиковой производительности вычислялось при условии одновременной работы всех функциональных устройств. Пред­положим, что некоторый алгоритм выполняет одинаковое число операций сложения и умножения. Пусть сначала должны выполниться все операции сложения, и лишь после этого операции умножения. В такой ситуации в каж­дый момент времени в компьютере будут задействованы только устройства
132
Часть /. Параллельные вычислительные системы
одного типа. Более половины от пиковой производительности получить нель­зя. Присутствующая несбалансированность в использовании функциональных уст­ройств является серьезным фактором, сильно снижающим реальную произво­дительность компьютера. Соответствующие данные можно найти в табл. 3.5.
Таблица 3.5. Производительность Cray С90 на различных операциях
Длина вектора
Производительность
на
операции (Мф
лопс)
а,= bj+q
a,= b,l с,
а,= si bj+ t
а,= s/ Ь,х t
10
35,5
24,8
49,7
46,1
100
202,9
88,4
197,4
166,5
1000
343,8
117,2
283,8
215,9
В наборе функциональных устройств нет устройства деления. Для выполне­ния данной операции используется устройство вычисления обратной вели­чины и устройство умножения. Отсюда сразу следует, что производитель­ность фрагмента в терминах операций деления будет очень низкой. Это полностью подтверждает столбец табл. 3.5, соответствующий операции щ= bj/q. Кроме этого, использование деления вместе с операцией сложения немного выгоднее, чем с умножением. Это явно видно из последних двух столбцов таблицы.
Часто структура программы бывает такова, что в ней постоянно происходит передача управления из одной части в другую. Возможными причинами этому могут служить частое обращение к различным небольшим подпро­граммам и функциям, либо просто запутанная структура управления из-за большого числа переходов. Следствием такой структуры станет частая пере­загрузка буферов команд, и, следовательно, возникнут дополнительные на­кладные расходы. Наилучший результат достигается в том случае, если весь фрагмент кода помещается в одном буфере команд. Незначительные потери производительности будут у фрагментов, расположенных в нескольких бу­ферах. Если же перезагрузка частая, т. е. фрагмент или программа обладают малой локальностью вычислений, то производительность может меняться в очень широких пределах.
На этом мы закончим обсуждение негативных факторов и перейдем к выво­дам. Что следует из проведенного анализа архитектуры суперкомпьютера Cray С90? Выводов можно сделать много, но главный из них помогает по­нять причину крайне низкой производительности неоптимизированных программ. Это, в частности, в полной мере относится к программам, пере­несенным с традиционных последовательных компьютеров. Дело в том, что на производительность реальных программ одновременно оказывают влия­ние в той или иной степени ВСЕ перечисленные выше факторы. В самом де-
Глава 3. Архитектура параллельных вычислительных систем
133
ле, программы не бывают векторизуемыми на все 100%. Всегда есть некото­рая последовательная инициализация, ввод/вывод или что-то подобное. Вместе с этим, обязательно будет присутствовать какое-то число конфлик­тов в памяти, быть может легкая несбалансированность в использовании функциональных устройств. Для части операций может не хватать каналов чтения/записи, векторных регистров и т. д. по всем изложенным выше фак­торам. Не стоит сбрасывать со счетов и влияние компилятора, который вполне мог что-то не векторизовать или что-то сделать неоптимально.
Предположим, что влияние каждого отдельного фактора в программе позво­ляет достичь 85% пиковой производительности. Примем самую простую мо­дель, в которой суммарное влияние оценивается произведением коэффици­ентов для каждого фактора. Поскольку мы уже выделили 10 факторов, что дает суммарный эффект 0,8510 или менее, чем до 0,2 от пика! Если мы хо­тим добиться хорошей производительности данного компьютера, то необхо­димо принимать во внимание все указанные выше факторы одновременно, минимизируя их суммарное проявление в программе. Безусловно, это сде­лать можно, но это трудная работа. Работа, которую нельзя недооценивать, и большую часть которой лучше выполнить на этапе планирования алго­ритма и составления программы.
Но не стоит думать, что разобранный нами компьютер столь уж плох. Ни в коей мере. Напротив, линия векторно-конвейерных компьютеров Cray за­нимает первые места по простоте достижения относительно высокой реаль­ной производительности в сравнении с другими архитектурами. Все классы компьютеров обладают целым набором подобных факторов, и мы это уви­дим в последующих параграфах. Главное — это знать не только то, почему компьютер считает быстро, но и то, что в его архитектуре мешает достигать высокой производительности.
Вопросы и задания
1.   Предположим, что слово компьютера состоит из 64 разрядов. Память может ра­ботать с ошибками. Сколько необходимо дополнительных разрядов, чтобы (а) обнаруживать возникшую одиночную ошибку в слове и (б) исправлять одиноч­ную ошибку?
2.   Будет ли являться вектором совокупность данных, расположенных в столбцах матрицы, начиная со второго по пятый включительно? Рассмотреть языки Fortran и С.
3.   Если бы каждый процессор компьютера Cray С90 имел по одному универсаль­ному каналу связи с памятью, могли бы возникнуть конфликты в памяти?
4.   Пропускная способность каждого канала процессора равна двум словам за такт. Почему пропускная способность в одно слово за такт была бы явно недостаточ­ной при существующей архитектуре процессора Cray С90?
134
Часть I. Параллельные вычислительные системы
5.   В состав секции межпроцессорного взаимодействия компьютера Cray С90 вхо­дят семафоры. Что такое семафор? Для чего он служит? Какого рода операции применяют к семафорам?
6.   Почему для векторных регистров не предусмотрено промежуточного набора, аналогичного промежуточным регистрам Т и В1
7.   Зачем нужно зацепление векторных операций?
8.   Для векторизации лучше всего подходят самые внутренние циклы. Какие кон­струкции программы лучше всего подходят для распараллеливания программы между несколькими процессорами компьютера Cray С90?
9.   Что мешает векторизации цикла, содержащего вызов подпрограммы?
10.   Что мешает векторизации цикла типа WHILE?
11.   В программе необходима работа с векторами длиной 129. Производительность компьютера Cray С90 на векторах длиной 256 намного больше, чем на векторах длиной 129. Значит ли это, что для уменьшения времени выполнения програм­мы нужно просто перейти от векторов длины 129 к векторам длины 256?
12.   Может ли некоторый одномерный векторизуемый цикл в скалярном режиме исполняться быстрее, чем в векторном режиме?
13.   Приведите другие возможные формы проявления в различных конструкциях языков программирования конфликтов при обращении к памяти.
§ 3.3. Параллельные компьютеры с общей памятью
К компьютерам с общей памятью у пользователей всегда было неоднознач­ное отношение. С одной стороны, программирование компьютеров этого класса значительно проще, чем, скажем, программирование вычислитель­ных кластеров с распределенной памятью. В самом деле, не нужно думать о распределении массивов, внутренний параллелизм программ описывается просто, процесс отладки идет быстрее. С другой стороны, классические представители этого класса имеют и два недостатка: небольшое число про­цессоров и очень высокую стоимость.
Чтобы увеличить число процессоров, но сохранить возможность работы в рамках единого адресного пространства, предлагались различные решения (см. § 2.2). Одним из распространенных вариантов является решение на ос­нове архитектуры ccNUMA (cache coherent Non Uniform Memory Access). В такой архитектуре память всего компьютера физически распределена, что значительно увеличивает потенциал его масштабируемости. Вместе с тем, память логически остается общей. Это дает возможность использования всех технологий и методов программирования, созданных для SMP-компью-теров. Дополнительно в такой архитектуре содержимое кэш-памяти отдель­ных процессоров на уровне аппаратуры согласуется с содержимым опера-
Глава 3. Архитектура параллельных вычислительных систем
135
тивной памяти (решается проблема когерентности кэшей, cache coherence problem). Значительно увеличивая число процессоров по сравнению с SMP-компьютерами, архитектура ccNUMA привносит дополнительную особен­ность, несвойственную компьютерам с общей памятью. Время обращения к памяти зависит от того, является ли это обращением к локальной или уда­ленной памяти. Процесс написания программ остается прежним, и физиче­ская распределенность памяти программисту не видна. Однако ясно, что эффективность программ напрямую зависит от степени "неоднородности" доступа к памяти.
Проведем исследование архитектуры компьютеров данного класса на при­мере вычислительной системы Hewlett-Packard Superdome. Компьютер поя­вился в 2000 году, а в ноябрьской редакции списка Тор500 2001 года им уже были заняты 147 позиций.
Компьютер HP Superdome в стандартной комплектации может объединять от 2 до 64 процессоров с возможностью последующего расширения систе­мы. Все процессоры имеют доступ к общей памяти, организованной в соот­ветствии с архитектурой ccNUMA. Это означает, что, во-первых, все про­цессы могут работать в едином адресном пространстве, адресуя любой байт памяти посредством обычных операций чтения/записи. Во-вторых, доступ к локальной памяти в системе будет идти немного быстрее, чем доступ к уда­ленной памяти. В-третьих, проблемы возможного несоответствия данных, вызванные кэш-памятью процессоров, решены на уровне аппаратуры.
В максимальной конфигурации Superdome может содержать до 256 Гбайт оперативной памяти. Ближайшие планы компании — реализовать возмож­ность наращивания памяти компьютера до 1 Тбайта.
Архитектура компьютера спроектирована таким образом, что в ней могут использоваться несколько типов микропроцессоров. Это, конечно же, тра­диционные для вычислительных систем Hewlett-Packard процессоры семей­ства РА: РА-8600 и РА-8700. Вместе с тем, система полностью подготовлена и к использованию процессоров следующего поколения с архитектурой 1А-64, разработанной совместно компаниями HP и Intel. При замене суще­ствующих процессоров на процессоры 1А-64 гарантируется двоичная со­вместимость приложений на системном уровне. В дальнейшем, если другого не оговорено, будем рассматривать конфигурации HP Superdome на базе процессора РА-8700.
Основу архитектуры компьютера HP Superdome составляют вычислительные ячейки (cells), связанные иерархической системой переключателей. Каждая ячейка является симметричным мультипроцессором, реализованным на од­ной плате, в котором есть все необходимые компоненты (рис. 3.12):
□  процессоры (до 4-х);
□  оперативная память (до 16 Гбайт);
136
Часть I. Параллельные вычислительные системы
П контроллер ячейки;
□  преобразователи питания;
□  связь с подсистемой ввода/вывода (опционально).
Интересно, что ячейки Superdome во многом похожи на аналогичные архитек­турные элементы других современных ccNUMA компьютеров. В Superdome таким элементом является ячейка, в семействе SGI Origin 3x00 это узел (node), а в компьютерах серии Compaq AlphaServer GS320 — QBB (Quad Building Block). Во всех системах в каждом элементе содержится по четыре процессора.
память
память
1
t
РА-8700
<-►
контроллер ячейки
*
РА-8700
<-►
РА-8700
<-►
РА-8700
<-►
А
контроллер ввода/вывода
к коммутатору Рис. 3.12. Структура ячейки компьютера HP Superdome
Центральное место в архитектуре ячейки Superdome занимает контроллер ячейки. Несмотря на столь обыденное название, контроллер — это слож­нейшее устройство, состоящее из 24 миллионов транзисторов. Для каждого процессора ячейки есть собственный порт в контроллере. Обмен данными между каждым процессором и контроллером идет со скоростью 2 Гбайт/с.
Память ячейки имеет емкость от 2 до 16 Гбайт. Конструктивно она разделе­на на два банка, каждый из которых имеет свой порт в контроллере ячейки. Скорость обмена данными между контроллером и каждым банком составля­ет 2 Гбайт/с, что дает суммарную пропускную способность тракта контрол­лер—память 4 Гбайт/с.
Соединение контроллера ячейки с контроллером устройств ввода/вывода (12 слотов РС1) устанавливается опционально.
Один порт контроллера ячейки всегда связан с внешним коммутатором. Он предназначен для обмена процессоров ячейки с другим процессорами сис­темы. Скорость работы этого порта равна 8 Гбайт/с.
Выполняя интерфейсные функции между процессорами, памятью, другими ячейками и внешним миром, контроллер ячейки отвечает и за когерент­ность кэш-памяти процессоров.
Глава 3. Архитектура параллельных вычислительных систем
137
Ячейка — это базовый четырехпроцессорный блок компьютера. В 64-процессорной конфигурации Superdome состоит из двух стоек, в каждой стойке по 32 процессора (рис. 3.13).
Стойка 1 ячейка—
Стойка 2
О.
о. о
го
2 2
о
-с -с -с ч
ячейка
ГО
ячейка
ячейка
О
ячейка
О.
о. о
го
2 5
о
-с -с -с ч
ячейка
ГО
ячейка
ячейка
о
ячейка
ячейка ячейка
ячейка ячейка
ячейка
ячейка
Рис. 3.13. Общая структура компьютера HP Superdome
Каждая стойка содержит по два восьмипортовых неблокирующих коммута­тора. Все порты коммутаторов работают со скоростью 8 Гбайт/с. К каждому коммутатору подключаются четыре ячейки. Три порта коммутатора задейст­вованы для связи с другими коммутаторами системы (один в этой же стой­ке, и два коммутатора — в другой). Оставшийся порт зарезервирован для связи с другими системами HP Superdome, что дает потенциальную возмож­ность для формирования многоузловой конфигурации компьютера с общим числом процессоров больше 64.
Одним из центральных вопросов любой вычислительной системы с архитек­турой ccNUMA является разница во времени при обращении процессора к локальным и удаленным ячейкам памяти. В идеале хотелось бы, чтобы этой разницы, как в SMP-компьютере, не было вовсе. Однако в таком случае сис­тема заведомо будет плохо масштабируемой. В компьютере HP Superdome возможны три вида задержек при обращении процессора к памяти, являю­щихся своего рода платой за высокую масштабируемость системы в целом:
□  процессор и память располагаются в одной ячейке; в этом случае задерж­ка минимальна;
□  процессор и память располагаются в разных ячейках, но обе эти ячейки подсоединены к одному и тому же коммутатору;
□  процессор и память располагаются в разных ячейках, причем обе эти ячейки подсоединены к разным коммутаторам; в этом случае запрос должен пройти через два коммутатора и задержки будут максимальными.
138
Часть I. Параллельные вычислительные системы
Ясно, что величина задержки зависит не только от взаимного расположения процессора и памяти, но и от числа процессоров. Не менее важным пара­метром является и характер вычислительной нагрузки. Например, задержка может меняться в зависимости от числа одновременно работающих прило­жений. В табл. 3.6 показана зависимость задержки от числа процессоров в двух характерных ситуациях. В первом случае работают однонитевые при­ложения, а во втором — многонитевая программа. В отличие от первого случая, во втором случае появляются дополнительные затраты, необходимые для поддержания когерентности кэш-памяти процессоров. В обоих случаях приводятся усредненные показатели задержки. Предполагается, что запросы к памяти распределены равномерно.
Таблица 3.6. Задержка в зависимости от числа процессоров и загрузки
Число процессоров Однонитевые программы, Многонитевая программа,
не                                        не
4                                           174                                               235
8                                           208                                               266
16                                          228                                               296
32                                          261                                               336
64                                          275                                               360
Для рассмотренных видов нагрузки, при сделанных выше предположениях о распределении запросов к памяти, показанные результаты являются очень неплохими. В самом деле, коэффициент увеличения задержки при переходе от минимальной 4-процессорной конфигурации к 64-процессорной соста­вил лишь 1,6 раза. Это значит, что во многих случаях пользователь вправе надеяться на эффективную реализацию своих программ, созданных им для традиционных SMP-компьютеров.
Компьютер HP Superdome имеет массу интересных особенностей. В частно­сти, программно-аппаратная среда компьютера позволяет его настроить раз­личным образом. Superdome может быть классическим единым компьюте­ром с общей памятью. Однако его можно сконфигурировать и таким образом, что он будет являться совокупностью независимых разделов (nPartitions), работающих под различными операционными системами, в частности, под HP UX, Linux и Windows 2000. Организация эффективной работы с большим числом внешних устройств, возможности "горячей" заме­ны всех основных компонентов аппаратуры, резервирование, мониторинг базовых параметров — все это останется за рамками нашего обсуждения.
Изучив особенности архитектуры HP Superdome в целом, кратко рассмот­рим структуру используемого в нем процессора РА-8700. Если мы хотим
Глава 3. Архитектура параллельных вычислительных систем
139
эффективно использовать всю систему, необходимо, хотя бы в общих чер­тах, иметь представление об особенностях ее базовой компоненты.
Процессор имеет тактовую частоту 750 МГц. Работая с максимальной за­грузкой, он может выполнять четыре арифметические операции за такт. Эти два параметра определяют значение его пиковой производительности — 3 Гфлопс. Отсюда получается и значение пиковой производительности базо­вой 64-процессорной системы HP Superdome — 192 Гфлопс.
Процессор РА-8700 обладает суперскалярной архитектурой. На каждом так­те он выполняет столько операций, сколько (1) позволяет информационная структура кода и (2) сколько в данный момент есть доступных функцио­нальных устройств. Всего процессор РА-8700 содержит 10 функциональных устройств: четыре устройства для целочисленной арифметики и логики, че­тыре устройства для работы с вещественной арифметикой и два устройства для операций чтения/записи. На каждом такте устройство выборки команд может считывать 4 команды из кэш-памяти команд.
Начиная с РА-8500, процессоры данного семейства имеют большую кэш­память первого уровня L1, реализованную прямо на кристалле. В РА-8700 объем кэш-памяти равен 2,25 Мбайт, из которых 1,5 Мбайт отводится под кэш данных, а оставшиеся 0,75 Мбайт — это кэш команд. Вся кэш-память процессора является множественно-ассоциативной с 4 каналами (4-way set-associative cache).
В целом область использования компьютеров HP Superdome исключительно широка. Достаточно сказать, что две крупные инсталляции данного компьютера в России соответствуют различным задачам и областям. Один 64-процессорный компьютер HP Superdome установлен в Межведомственном суперкомпьютер­ном центре для решения широкого спектра научно-технических задач, а 72-процессорная конфигурация работает в Сбербанке России.
В заключение, как и в предыдущем параграфе, выделим те особенности вы­числительных систем с общей памятью, которые снижают их производи­тельность на реальных программах. Не должно складываться впечатление, что каждый параграф данной главы мы хотим закончить на пессимистиче­ской ноте. Однако воспринимать реальность нужно с открытыми глазами, четко представляя не только преимущества использования параллельных компьютеров, но и подводные камни, встречающиеся на этом пути.
Закон Амдала носит универсальный характер, поэтому он упоминается вме­сте со всеми параллельными системами. Не являются исключением и ком­пьютеры с общей памятью. Если в программе 20% всех операций должны выполняться строго последовательно, то ускорения больше 5 получить нельзя вне зависимости от числа использованных процессоров (влияние кэш­памяти сейчас не рассматривается). Это нужно учитывать и перед адаптаци­ей старой последовательной программы к такой архитектуре, и в процессе проектирования нового параллельного кода.
140
Часть I. Параллельные вычислительные системы
Для компьютеров с общей памятью дополнительно следует принять в расчет и такие соображения. Наличие физической общей памяти стимулирует к использованию моделей параллельных программ также с общей памятью. Это вполне естественно и оправданно. Однако в этом случае возникают до­полнительные участки последовательного кода, связанные с синхронизаци­ей доступа к общим данным, например, критические секции. Относительно подобных конструкций в описании соответствующей технологии програм­мирования может и не быть никакого предостережения, однако реально эти фрагменты будут последовательными участками кода.
Работа с памятью является очень тонким местом в системах данного класса. Одну из причин снижения производительности — неоднородность доступа к памяти, мы уже обсуждали. Степень неоднородности на уровне 5—10% серьезных проблем не создаст. Однако разница во времени доступа к ло­кальной и удаленной памяти в несколько раз потребует от пользователя очень аккуратного программирования. В этом случае ему придется решать вопросы, аналогичные распределению данных для систем с распределенной памятью. Другую причину — конфликты при обращении к памяти — мы детально не разбирали, но она также характерна для многих SMP-систем.
Наличие кэш-памяти у каждого процессора тоже привносит свои дополни­тельные особенности. Наиболее существенная из них состоит в необходимо­сти обеспечения согласованности содержимого кэш-памяти. Отсюда появились и первые две буквы в аббревиатуре ccNUMA. Чем реже вовлекается аппара­тура в решение этой проблемы, тем меньше накладных расходов сопровож­дает выполнение программы. По этой же причине во многих системах с общей памятью существует режим выполнения параллельной программы с привязкой процессов к процессорам.
Сбалансированность вычислительной нагрузки также характерна для парал­лельных систем, как и закон Амдала. В случае систем с общей памятью си­туация упрощается тем, что практически всегда системы являются однород­ными. Они содержат одинаковые процессоры, поэтому о сложной стратегии распределения работы речь, как правило, не идет.
Любой современный процессор имеет сложную архитектуру, объединяющую и несколько уровней памяти, и множество функциональных устройств. Реаль­ная производительность отдельного процессора может отличаться от его же пи­ковой в десятки раз. Чем выше степень использования возможностей каждого процессора, тем выше общая производительность вычислительной системы.
И опять, как и в предыдущем параграфе, перечисление особенностей ком­пьютера, влияющих на его производительность, можно продолжать и далее. Здесь они свои, но опять-таки все они в той или иной мере проявляются в каждой программе. Отсюда и проблемы с низкой производительностью, от­сюда и потенциальные проблемы у пользователей. Проблемы решаемые, но их нужно ясно представлять, чтобы выбрать правильный способ решения.
Глава 3. Архитектура параллельных вычислительных систем                                         141
Вопросы и задания
1.   Назовите преимущества и недостатки двух систем: 16-процессорной системы с общей памятью и 16-процессорного вычислительного кластера, построенных на базе одних и тех же процессоров.
2.   Чему равна пиковая производительность 16-процессорного компьютера с общей памятью HP Superdome и 16-процессорного вычислительного кластера, постро­енных на базе процессоров РА-8700 / 750 МГц? Попробуйте оценить стоимость каждого решения. Какие преимущества имеет более дорогое решение?
3.   Проведите исследование методов организации доступа к общей памяти в совре­менных SMP-серверах.
4.   Выделите основные особенности и характерные черты архитектуры IA-64. Какие современные процессоры построены в соответствии с этой архитектурой?
5.   Сколько процессоров содержат современные SMP-компьютеры, имеющие наи­лучшее соотношение цена/производительность? Проведите исследование по разным типам процессоров и систем.
6.   Какое соотношение между объемами памяти на различных уровнях иерархии характерно для современных SMP-компьютеров? Для вычислительных систем с архитектурой ccNUMA?
7.   Какое соотношение между скоростью выполнения арифметических операций и скоростью обмена с основной памятью характерно для современных SMP-компьютеров?
8.   Напишите тестовую программу, которая автоматически определяет соотношение между скоростью выполнения арифметических операций и скоростью обмена с различными уровнями памяти.
9.   Напишите тестовую программу, которая автоматически определяет разницу во времени обращения к локальной и удаленной памяти для вычислительных сис­тем с архитектурой ccNUMA.
10.   Напишите программу, работающую с максимальной производительностью на любой доступной вам вычислительной системе с общей памятью. Постройте за­висимость производительности от числа процессоров.
11.   Добавьте в вычислительное ядро программы из предыдущего вопроса использо­вание массивов (если их там не было). Постройте зависимость производитель­ности вычислительной системы с общей памятью от числа процессоров и раз­меров используемых массивов.
12.   Исследуйте зависимость производительности вычислительной системы с общей памятью, показываемой на тестах из предыдущих двух вопросов, от нагрузки на других процессорах системы.
142
Часть /. Параллельные вычислительные системы
§ 3.4. Вычислительные системы с распределенной памятью
Идея построения вычислительных систем данного класса очень проста. Бе­рется какое-то количество вычислительных узлов, которые объединяются друг с другом некоторой коммуникационной средой. Каждый вычислитель­ный узел имеет один или несколько процессоров и свою собственную ло­кальную память, разделяемую этими процессорами. Распределенность памя­ти означает то, что каждый процессор имеет непосредственный доступ только к локальной памяти своего узла. Доступ к данным, расположенным в памяти других узлов, выполняется дольше и другими, более сложными спо­собами. В последнее время в качестве узлов все чаще и чаще используют полнофункциональные компьютеры, содержащие, например, и собственные внешние устройства. Коммуникационная среда может специально проекти­роваться для данной вычислительной системы либо быть стандартной се­тью, доступной на рынке.
Преимуществ у такой схемы организации параллельных компьютеров много. В частности, покупатель может достаточно точно подобрать конфигурацию в зависимости от имеющегося бюджета и своих потребностей в вычисли­тельной мощности. Соотношение цена/производительность у систем с рас­пределенной памятью ниже, чем у компьютеров других классов. И главное, такая схема дает возможность практически неограниченно наращивать чис­ло процессоров в системе и увеличивать ее производительность. Большое число подключаемых процессоров даже определило специальное название для систем данного класса: компьютеры с массовым параллелизмом или массивно-параллельные компьютеры. Уже сейчас в мире существуют десят­ки компьютеров, в составе которых работает более тысячи процессоров.
Широкое распространение компьютеры с такой архитектурой получили с начала 90-х годов прошлого столетия. Среди них можно назвать Intel Paragon, IBM SP1/SP2, Cray T3D/T3E и ряд других. По большому счету, различие между ними состоит в используемых процессорах и организации коммуникационной среды. Компьютер Intel Paragon был построен на основе процессоров Intel i860, расположенных в узлах прямоугольной двумерной решетки. В вычислительных узлах IBM SP по мере развития данного семей­ства использовалось несколько процессоров, в частности, PowerPC, P2SC и POWER3. Их взаимодействие идет через иерархическую систему высоко­производительных коммутаторов, что дает потенциальную возможность об­щения каждого узла с каждым. Семейство компьютеров Cray T3D/T3E опи­рается на процессоры DEC Alpha и топологию трехмерного тора.
Данный параграф мы начнем с описания архитектуры компьютеров семей­ства Cray T3D/T3E. Являясь представителями обсуждаемого класса вычис-
Глава 3. Архитектура параллельных вычислительных систем
143
лительных систем, эти компьютеры обладают рядом интересных дополни­тельных особенностей, что и предопределило наш выбор.
Итак, компьютеры Cray T3D/T3E — это массивно-параллельные компьюте­ры с распределенной памятью, объединяющие в максимальной конфигура­ции более 2000 процессоров. Как и любые компьютеры данного класса, они содержат два основных компонента: узлы и коммуникационную среду.
Все узлы компьютера делятся на три группы. Когда пользователь подключается к компьютеру, то он попадает на управляющие узлы. Эти узлы работают в многопользовательском режиме, на этих же узлах выполняются однопроцес­сорные программы и работают командные файлы. Узлы операционной системы недоступны пользователям напрямую. Эти узлы поддерживают выполнение многих системных сервисных функций ОС, в частности, работу с файловой системой. Вычислительные узлы компьютера предназначены для выполнения программ пользователя в монопольном режиме. При запуске программе выде­ляется требуемое число узлов, которые за ней закрепляются вплоть до момен­та ее завершения. Гарантируется, что никакая программа не сможет занять вычислительные узлы, на которых уже работает другая программа. Число уз­лов каждого типа зависит от конфигурации системы. В частности, данные, взятые из двух реальных конфигураций Cray ТЗЕ, выглядят так: 24/16/576 или 7/5/260 (управляющие узлы/ узлы ОС/ вычислительные узлы).
Каждый узел последних моделей состоит из процессорного элемента (ПЭ) и сетевого интерфейса. Процессорный элемент содержит один процессор Alpha, локальную память и вспомогательные подсистемы. В модели Cray ТЗЕ-1200Е использовались микропроцессоры DEC Alpha 21164/600 МГц. В модели Cray ТЗЕ-1350 в узлах используются микропроцессоры Alpha 21164А (EV5.6) с тактовой частотой 675 МГц и пиковой производительностью 1,35 Гфлопс. Интересно, что компьютеры серии Cray T3D содержали в каж­дом узле по два независимых процессора DEC Alpha 21064/150 МГц.
Локальная память каждого процессорного элемента является частью физи­чески распределенной, но логически разделяемой памяти всего компьютера. Память физически распределена, т. к. каждый ПЭ содержит свою локаль­ную память. В то же время память разделяется всеми ПЭ, поскольку любой ПЭ через свой сетевой интерфейс может обращаться к памяти любого дру­гого ПЭ, не прерывая его работы.
Сетевой интерфейс узла связан с соответствующим сетевым маршрутизато­ром, который является частью коммуникационной сети. Все маршрутизато­ры расположены в узлах трехмерной целочисленной прямоугольной решет­ки и соединены между собой в соответствии с топологией трехмерного тора (рис. 3.14). Это означает, что каждый узел имеет шесть непосредственных соседей вне зависимости от того, где он расположен: внутри параллелепипе­да, на ребре, на грани или в его вершине.
144
Часть I. Параллельные вычислительные системы
Рис. 3.14. Коммуникационная решетка компьютера Cray ТЗЕ
Подобная организация коммуникационной сети имеет много достоинств, среди которых отметим два. Во-первых, это возможность выбора альтерна­тивного маршрута для обхода поврежденных связей. А во-вторых, быстрая связь граничных узлов и небольшое среднее число перемещений по тору при взаимодействии разных ПЭ.
Каждая элементарная связь между двумя узлами — это два однонаправлен­ных канала передачи данных, что допускает одновременный обмен данными в противоположных направлениях. В модели Cray ТЗЕ-1200Е максимальная скорость передачи данных между узлами равна 480 Мбайт/с, латентность на уровне аппаратуры составляет менее 1 мкс.
Коммуникационная сеть образует трехмерную решетку, соединяя сетевые маршрутизаторы узлов в направлениях X, Y, Z. Выбор маршрута для обмена данными между двумя узлами А к В происходит следующим образом. От­талкиваясь от вершины А, сетевые маршрутизаторы сначала выполняют смещение по измерению X до тех пор, пока координата очередного транзит­ного узла и вершины В по измерению X не станут равными. Затем анало­гичная процедура выполняется по Y, а в конце по Z(cm. рис. 3.14). Так как смещение может быть как положительным, так и отрицательным, то этот механизм помогает минимизировать число перемещений по сети и обойти поврежденные связи. Сетевые маршрутизаторы спроектированы таким обра­зом, чтобы осуществлять параллельный транзит данных по каждому из трех измерений X, Ya Z одновременно.
Безусловно, интересной особенностью архитектуры компьютера является ап­паратная поддержка барьерной синхронизации. Барьер — это точка в программе, при достижении которой каждый процесс должен ждать до тех пор, пока ос­тальные процессы также не дойдут до барьера. Лишь после этого события все процессы могут продолжать работу дальше. Данный вид синхронизации часто
Глава 3. Архитектура параллельных вычислительных систем
145
используется в программах, но его реализация средствами системы програм­мирования сопровождается большими накладными расходами. Поддержка барьеров в аппаратуре позволяет свести эти расходы к минимуму.
Рис. 3.15. Барьерная синхронизация в компьютерах Cray T3D/T3E
В схемах поддержки каждого ПЭ предусмотрено несколько входных и вы­ходных регистров синхронизации. Каждый разряд этих регистров соединен со своей независимой цепью реализации барьера. Все цепи синхронизации одинаковы, а их общее число зависит от конфигурации компьютера. Каждая цепь строится по принципу двоичного дерева на основе двух типов уст­ройств (рис. 3.15). Одни устройства реализуют логическое умножение ("&"), а другие выполняют дублирование входа на два своих выхода ("1—2"). Выход последнего устройства "&" является входом первого устройства дублирова­ния "1—2". Устройства дублирования по своей цепи распространяют полу­ченные значения по всем ПЭ, записывая их в соответствующий разряд вы­ходного регистра.
Рассмотрим работу одной цепи. В исходном состоянии соответствующий разряд входного и выходного регистра каждого ПЭ равен нулю. Появление единицы в выходном разряде ПЭ будет сигнализировать о достижении барь­ера всеми процессами. Как только процесс доходит до барьера, то разряд входного регистра соответствующего ПЭ переопределяется в единицу. На выходе любого устройства "&" единица появится только в том случае, если оба входа содержат единицу. Если какой-либо процесс еще не дошел до барьера, то ноль на входе этого ПЭ пройдет по цепочке устройств логиче­ского умножения и определит ноль на выходе последнего устройства "&". Следовательно, нули будут и в выходных разрядах каждого ПЭ. Как только последний процесс записал единицу в свой входной разряд, единица появ-
146
Часть I. Параллельные вычислительные системы
ляется на выходе последнего устройства "&" и разносится цепью устройств дублирования по выходным разрядам на каждом ПЭ. По значению выход­ного разряда каждый процесс узнает, что все остальные процессы дошли до точки барьерной синхронизации.
Эти же цепи в компьютерах данного семейства используются и по-другому. Если все устройства логического умножения в схеме заменить устройствами логического сложения, то получится цепь для реализации механизма "Эврика". На выходе любого устройства логического сложения единица поя­вится в том случае, если единица есть хотя бы на одном его входе. Это зна­чит, что как только один ПЭ записал единицу во входной регистр, эта еди­ница распространяется всем ПЭ, сигнализируя о некотором событии на исходном ПЭ. Самая очевидная область применения данного механизма — это задачи поиска.
Помимо традиционных суперкомпьютеров типа Cray ТЗЕ или IBM SP, класс компьютеров с распределенной памятью в последнее время активно расши­ряется за счет вычислительных кластеров. Сразу оговоримся, что в компью­терной литературе понятие "кластер" употребляется в различных значениях. В частности, "кластерная" технология используется для повышения скоро­сти работы и надежности серверов баз данных или Web-серверов. Здесь мы будем говорить только о кластерах, ориентированных на решение задач вы­числительного характера.
Классические суперкомпьютеры всегда ассоциировались с чем-то большим: огромные размеры, гигантская производительность, большая память и, ко­нечно же, колоссальная стоимость. Сама по себе высокая стоимость удивле­ния не вызывает. Уникальные решения, да еще и с рекордными характери­стиками, не могут быть дешевыми. Однако прогресс в электронике внес серьезные коррективы. В середине 90-х годов прошлого века на рынке поя­вились недорогие и эффективные микропроцессоры и коммуникационные решения. Появилась реальная возможность создавать установки "суперком­пьютерного" класса из составных частей массового производства. Это и пре­допределило появление кластерных вычислительных систем, являющихся от­дельным направлением развития компьютеров с массовым параллелизмом.
Если говорить кратко, то вычислительный кластер есть совокупность ком­пьютеров, объединенных в рамках некоторой сети для решения одной зада­чи (рис. 3.16). В качестве вычислительных узлов обычно используются дос­тупные на рынке однопроцессорные компьютеры, двух- или четырех-процессорные SMP-серверы. Каждый узел работает под управлением своей копии операционной системы, в качестве которой чаще всего используются стандартные ОС: Linux, Windows NT, Solaris и т. п. Состав и мощность узлов могут меняться даже в рамках одного кластера, что дает возможность созда­вать неоднородные системы. Выбор конкретной коммуникационной среды определяется многими факторами: особенностями класса решаемых задач,
Глава 3. Архитектура параллельных вычислительных систем
147
доступным финансированием, необходимостью последующего расширения кластера и т. п. Возможно включение в конфигурацию кластера специали­зированных компьютеров, например, файл-сервера. Как правило, предос­тавляется возможность удаленного доступа на кластер через Интернет.
Рис. 3.16. Общая схема вычислительного кластера
Ясно, что простор для творчества при проектировании кластеров огромен. Узлы могут не содержать локальных дисков, коммуникационная среда мо­жет одновременно использовать различные сетевые технологии, узлы не обязаны быть одинаковыми и т. д. Рассматривая крайние точки, кластером можно считать как пару ПК, связанных локальной 10-мегабитной Ethernet-сетью, так и вычислительную систему, создаваемую в рамках проекта Cplant в Национальной лаборатории Sandia: 1400 рабочих станций на базе процес­соров Alpha объединены высокоскоростной сетью Myrinet. Чтобы лучше по­чувствовать масштаб и технологию существующих систем, сделаем краткий обзор наиболее интересных современных кластерных установок.
Кластерные проекты
Один из первых проектов, давший имя целому классу параллельных сис­тем — Beowulf-кластеры, возник в центре NASA Goddard Space Flight Center
148
Часть I. Параллельные вычислительные системы
(GSFC). Проект Beowulf стартовал летом 1994 года, и вскоре был собран 16-процессорный кластер на процессорах Intel 486DX4/100 МГц. На каждом узле было установлено по 16 Мбайт оперативной памяти и по 3 сетевых карты для обычной сети Ethernet. Для работы в такой конфигурации были разработаны специальные драйверы, распределяющие трафик между дос­тупными сетевыми картами.
Позже в GSFC был собран кластер TheHlVE — Highly-parallel Integrated Virtual Environment, структура которого показана на рис. 3.17. Этот кластер состоит из четырех подкластеров Е, В, G, и DL, объединяя 332 процессора и два выделенных хост-компьютера. Все узлы данного кластера работают под управлением Red Hat Linux.
QO DO
Fast Ethernet
E
Fast Ethernet G           DL
64 Dual P-Pro
Gigabit Ethernet
66 Dual P-Pro
Gigabit Ethernet
\/
Myrinet
16 Dual 10 Quad Pill Xeon Pill Xeon
Рис. 3.17. Структура кластера TheHlVE
В 1998 году в Лос-Аламосской национальной лаборатории на базе процессо­ров Alpha 21164А с тактовой частотой 533 МГц был создан Linux-кластер Avalon. Первоначально Avalon состоял из 68 процессоров, затем их число было увеличено до 140. В каждом узле установлено по 256 Мбайт оператив­ной памяти, жесткий диск на 3 Гбайт и сетевой адаптер Fast Ethernet. Об­щая стоимость проекта Avalon составила 313 тысяч долларов. Показанная кластером производительность на тесте L1NPACK — 47,7 Гфлопс, позволила ему занять 114 место в 12-й редакции списка Тор500 рядом с 152-процессорной системой IBM RS/6000 SP. В том же 1998 году на самой пре­стижной конференции в области высокопроизводительных вычислений Supercomputing'98 создатели Avalon представили доклад "Avalon: An Alpha/Linux Cluster Achieves 10 Gflops for $150k", получивший первую пре­мию в номинации "Наилучшее соотношение цена/производительность" ("1998 Gordon Bell Price/Performance Prize").
Глава 3. Архитектура параллельных вычислительных систем
149
В апреле 2000 года в рамках проекта АСЗ в Корнелльском университете для проведения биомедицинских исследований был установлен кластер Velocity+. Он состоит из 64 узлов с четырьмя процессорами Intel Pentium III каждый. Узлы работают под управлением Windows 2000 и объединены сетью
cLAN.
Проект LoBoS (Lots of Boxes on Shelfes) реализован в Национальном Инсти­туте здоровья США в апреле 1997 года. Он интересен использованием в ка­честве коммуникационной среды технологии Gigabit Ethernet. Сначала кла­стер состоял из 47 узлов с двумя процессорами Intel Pentium Pro/200 МГц, 128 Мбайт оперативной памяти и диском на 1,2 Гбайт на каждом узле. В 1998 году был реализован следующий этап проекта — LoBoS2, в ходе ко­торого узлы были преобразованы в настольные компьютеры с сохранением объединения в кластер. Сейчас LoBoS2 состоит из 100 вычислительных уз­лов, содержащих по два процессора Pentium П/450 МГц, 256 Мбайт опера­тивной и 9 Гбайт дисковой памяти. Дополнительно к кластеру подключены 4 управляющих компьютера с общим RAID-массивом объемом 1,2 Тбайт.
Интересной разработкой стал в 2000 году кластер KLAT2 (Kentucky Linux Athlon Testbed 2). Система KLAT2 состоит из 64 бездисковых узлов с про­цессорами AMD Athlon/700 МГц и оперативной памятью 128 Мбайт на ка­ждом. Программное обеспечение, компиляторы и математические библио­теки (SCALAPACK, BLACS и ATLAS) были доработаны для эффективного использования технологии 3D-Now! процессоров AMD. Эта работа позволи­ла существенно увеличить производительность системы в целом. Значитель­ный интерес представляет и использованное сетевое решение, названное "Flat Neighbourhood Network" (FNN). В каждом узле установлено четыре сетевых адаптера Fast Ethernet, а узлы соединяются с помощью девяти 32-портовых коммутаторов. При этом для любых двух узлов всегда есть прямое соединение через один из коммутаторов, но нет необходимости в соедине­нии всех узлов через единый коммутатор. Благодаря оптимизации про­граммного обеспечения под архитектуру AMD и топологии FNN удалось добиться рекордного соотношения цена/производительность на тесте LINPACK — 650 долларов за 1 Гфлопс.
Идея разбиения кластера на разделы получила интересное воплощение в проекте Chiba City, реализованном в Аргонской Национальной лаборатории. Главный раздел содержит 256 вычислительных узлов, на каждом из которых установлено по два процессора Pentium Ш/500 МГц, 512 Мбайт оператив­ной памяти и локальный диск объемом 9 Гбайт. Кроме вычислительного раздела в систему входят: раздел визуализации (32 компьютера IBM Intellistation с графическими картами Matrox Millenium G400, 512 Мбайт оперативной памяти и дисками 300 Гбайт), раздел хранения данных (8 сер­веров IBM Netfinity 7000 с процессорами Хеоп/500 МГц и дисками по
150
Часть I. Параллельные вычислительные системы
300 Гбайт) и управляющий раздел (12 компьютеров IBM Netfinity 500). Все они объединены сетью Myrinet, которая используется для поддержки парал­лельных приложений. Для управляющих и служебных целей используются сети Gigabit Ethernet и Fast Ethernet. Все разделы делятся на "города" (towns) по 32 компьютера. Каждый из них имеет своего "мэра", который локально обслуживает свой "город", снижая нагрузку на служебную сеть и обеспечи­вая быстрый доступ к локальным ресурсам.
Сеть Myrinet 2000
128 CPUs
128 CPUs
128 CPUs
128 CPUs
128 CPUs
128 CPUs
>
А V
Сеть Fast Ethernet
Сеть Gigabit Ethernet
Управляющий компьютер
Файл-сервер
Резервный
управляющий
компьютер
Рис. 3.18. Структура суперкомпьютера МВС-1000М
Кластером является и суперкомпьютер МВС-1000М, установленный в Меж­ведомственном суперкомпьютерном центре в Москве (http://www.jscc.ru). Компьютер состоит из шести базовых блоков, содержащих по 64 двухпро­цессорных модуля (рис. 3.18). Каждый модуль имеет два процессора Alpha 21264/667 МГц (кэш-память второго уровня 4 Мбайт), 2 Гбайт оперативной памяти, разделяемой процессорами модуля, жесткий диск. Общее число процессоров в системе равно 768, а пиковая производительность МВС-1000М превышает 1 Тфлопс.
Все модули МВС-1000М связаны двумя независимыми сетями. Сеть Myrinet 2000 используется программами пользователей для обмена данными в процессе вычислений. При использовании МР1 пропускная способность каналов сети достигает значений 110—170 Мбайт/с. Сеть Fast Ethernet ис­пользуется операционной системой для выполнения сервисных функций.
Глава 3. Архитектура параллельных вычислительных систем
151
Коммуникационные технологии построения кластеров
Понятно, что различных вариантов построения кластеров очень много. Од­но из существенных различий лежит в используемой сетевой технологии, выбор которой определяется, прежде всего, классом решаемых задач.
Первоначально Beowulf-кластеры строились на базе обычной 10-мегабитной сети Ethernet. Сегодня часто используется сеть Fast Ethernet, как правило, на базе коммутаторов. Основное достоинство такого решения — это низкая стоимость. Вместе с тем, большие накладные расходы на передачу сообщений в рамках Fast Ethernet приводят к серьезным ограничениям на спектр задач, эффективно решаемых на таких кластерах. Если от кластера требуется большая универсальность, то нужно переходить на другие, более производи­тельные коммуникационные технологии. Исходя из соображений стоимости, производительности и масштабируемости, разработчики кластерных систем делают выбор между Fast Ethernet, Gigabit Ethernet, SCI, Myrinet, cLAN, ServerNet и рядом других сетевых технологий (основные параметры коммуни­кационных технологий можно найти, например, на http://www.Parallel.ru).
Пропускная способность
Узел 1
Узел 2
Рис. 3.19. Латентность и пропускная способность коммуникационной среды
Какими же числовыми характеристиками выражается производительность коммуникационных сетей в кластерных системах? Необходимых пользовате­лю характеристик две: латентность и пропускная способность сети. Латентность — это время начальной задержки при посылке сообщений. Пропускная способность сети определяется скоростью передачи информа­ции по каналам связи (рис. 3.19). Если в программе много маленьких сооб­щений, то сильно скажется латентность. Если сообщения передаются боль­шими порциями, то важна высокая пропускная способность каналов связи. Из-за латентности максимальная скорость передачи по сети не может быть достигнута на сообщениях с небольшой длиной.
152
Часть I. Параллельные вычислительные системы
На практике пользователям не столько важны заявляемые производителем пиковые характеристики, сколько реальные показатели, достигаемые на уровне приложений. После вызова пользователем функции посылки сооб­щения Send() сообщение последовательно проходит через целый набор сло­ев, определяемых особенностями организации программного обеспечения и аппаратуры. Этим, в частности, определяются и множество вариаций на те­му латентности реальных систем. Установили MPI на компьютере плохо, латентность будет большая, купили дешевую сетевую карту от неизвестного производителя, ждите дальнейших сюрпризов.
В заключение параграфа давайте попробуем и для данного класса компью­теров выделить факторы, снижающие производительность вычислительных систем с распределенной памятью на реальных программах.
Начнем с уже упоминавшегося ранее закона Амдала. Для компьютеров дан­ного класса он играет очень большую роль. В самом деле, если предполо­жить, что в программе есть лишь 2% последовательных операций, то рас­считывать на более чем 50-кратное ускорение работы программы не приходится. Теперь попробуйте критически взглянуть на свою программу. Скорее всего, в ней есть инициализация, операции ввода/вывода, какие-то сугубо последовательные участки. Оцените их долю на фоне всей програм­мы и на мгновенье предположите, что вы получили доступ к вычислитель­ной системе из 1000 процессоров. После вычисления верхней границы для ускорения программы на такой системе, думаем, станет ясно, что недооце­нивать влияние закона Амдала никак нельзя.
Поскольку компьютеры данного класса имеют распределенную память, то взаимодействие процессоров между собой осуществляется с помощью пере­дачи сообщений. Отсюда два других замедляющих фактора — латентность и скорость передачи данных по каналам коммуникационной среды. В зави­симости от коммуникационной структуры программы степень влияния этих факторов может сильно меняться.
Если аппаратура или программное обеспечение не поддерживают возмож­ности асинхронной посылки сообщений на фоне вычислений, то возникнут не­избежные накладные расходы, связанные с ожиданием полного завершения взаимодействия параллельных процессов.
Для достижения эффективной параллельной обработки необходимо добиться максимально равномерной загрузки всех процессоров. Если равномерности нет, то часть процессоров неизбежно будет простаивать, ожидая остальных, хотя в это время они вполне могли бы выполнять полезную работу. Данная про­блема решается проще, если вычислительная система однородна. Очень большие трудности возникают при переходе на неоднородные системы, в которых есть значительное различие либо между вычислительными узлами, либо между каналами связи.
Существенный фактор — это реальная производительность одного процессора вычислительной системы. Разные модели микропроцессоров могут поддер­живать несколько уровней кэш-памяти, иметь специализированные функ-
Глава 3. Архитектура параллельных вычислительных систем
153
циональные устройства и т. п. Возьмем хотя бы иерархию памяти компью­тера Cray ТЗЕ: регистры процессора, кэш-память 1-го уровня, кэш-память 2-го уровня, локальная память процессора, удаленная память другого про­цессора. Эффективное использование такой структуры требует особого вни­мания при выборе подхода к решению задачи.
Дополнительно каждый микропроцессор может иметь элементы векторно-конвейерной архитектуры. В этом случае ему будут присущи многие факто­ры, которые мы обсуждали в конце § 3.2.
К сожалению, как и прежде, на работе каждой конкретной программы в той или иной мере сказываются все эти факторы. Однако в отличие от компью­теров других классов, суммарное воздействие изложенных здесь факторов может снизить реальную производительность не в десятки, а в сотни и даже тысячи раз по сравнению с пиковой. Потенциал компьютеров этого класса огромен, добиться на них можно очень многого. Однако могут потребоваться значительные усилия. Все этапы в решении каждой задачи, начиная от вы­бора метода до записи программы, нужно продумывать очень аккуратно.
Крайняя точка — Интернет. Его тоже можно рассматривать как компьютер с распределенной памятью. Причем, как самый мощный в мире компьютер. Попробуйте найти способ решения ваших задач на таком компьютере.
Вопросы и задания
1.   Многие параллельные вычислительные системы с распределенной памятью в качестве узлов используют SMP-компьютеры. Какими свойствами должен обла­дать алгоритм решения задачи, ориентированный на использование компьютера с такой архитектурой?
2.   Есть две системы. У одной быстрые процессоры и медленные каналы связи, а у другой — медленные процессоры и быстрые связи. В чем преимущества и недос­татки каждой системы? На какой системе программы будут иметь лучшую мас­штабируемость?
3.   Приведите пример реальной вычислительной системы с распределенной памятью и коммуникационной сетью, имеющей топологию двумерного тора.
4.   Сколько компьютеров, входящих в последнюю редакцию списка Тор500, имеют в своем составе более 1000 процессоров? Сколько среди них компьютеров с рас­пределенной памятью?
5.   Сколько процессорных элементов объединяет компьютер Cray ТЗЕ, коммуникаци­онная решетка которого имеет размеры 3x4x5? A Cray T3D с такой же решеткой?
6.   Коммуникационная решетка компьютера Cray ТЗЕ имеет размеры 3x4x5. Укажи­те непосредственных соседей узла, расположенного в вершине (на верхней гра­ни, на ребре).
7.   Расстоянием между двумя узлами А и В компьютера Cray ТЗЕ назовем дли­ну минимального пути (по числу ребер), соединяющих А и В. Рассмотрим все возможные конфигурации коммуникационной решетки, состоящей из 64 узлов. Для каждой конфигурации определим максимум среди расстояний по всем парам
154
Часть I. Параллельные вычислительные системы
узлов. Чему равен минимум среди указанных максимальных расстояний? А для решетки из 1024 узлов?
8.   Могут ли в компьютере Cray ТЗЕ маршруты передачи сообщений от вычислитель­ного узла А к узлу В и, наоборот, от В к А быть различными? Одинаковыми?
9.   Предложите способ программной реализации барьерной синхронизации с помо­щью любой доступной технологии параллельного программирования. Сколько времени требуется на выполнение одного барьера на доступном вам компьютере?
10.   В чем вы видите слабые стороны вычислительных кластеров по сравнению с традиционными суперкомпьютерами?
11.   Что понимается под словами "масштабируемость кластера"?
12.   Могут ли в одном кластере вычислительные узлы работать под управлением различных операционных систем?
13.   Есть ли смысл в одном кластере использовать несколько различных сетевых технологий?
14.   Предположим, что в качестве узла кластера используется процессор Intel Pen­tium III (IV). Приведите пример фрагмента программы, разная форма записи которого приводит к различию в производительности процессора в 10 раз (алгоритм изменяться не должен).
15.   Сколько кластерных систем присутствует в текущей редакции списка Тор500?
16.   *Предположим, что необходимо сделать кластер для максимально эффективного выполнения теста LINPACK. Какую архитектуру вы предложите для такого класте­ра? Какова структура одного процессора, коммуникационной среды, каково соот­ношение между скоростью работы процессора и пропускной способностью сети?
17.   Какой должна быть архитектура кластера, чтобы соответствовать структуре зада­чи, решаемой вами в настоящий момент на параллельном компьютере?
§ 3.5. Концепция GRID и метакомпьютинг
В предыдущих параграфах мы рассмотрели несколько классов параллельных вычислительных систем. Это были компьютеры с общей памятью (Cray С90, Hewlett-Packard Superdome), массивно-параллельные компьютеры с распреде­ленной памятью (Cray T3D/T3E), вычислительные кластеры различной кон­фигурации. Если все первые параллельные системы были достоянием отдель­ных мощных фирм и корпораций, то сегодня ситуация резко изменилась. Вычислительный кластер можно собрать практически в любой лаборатории, отталкиваясь от потребностей в вычислительной мощности и доступного бюджета. Для целого класса задач, где не предполагается тесного взаимодей­ствия между параллельными процессами, решение на основе обычных рабо­чих станций и сети Fast Ethernet будет вполне эффективным. Но чем такое решение отличается от обычной локальной сети современного предприятия? С точки зрения прикладного программиста почти ничем. Если у него появится возможность использования подобной сети для решения своих задач, то для него такая конфигурация и будет параллельным компьютером.
Глава 3. Архитектура параллельных вычислительных систем
155
Продолжая идею дальше, любые вычислительные устройства можно считать параллельной вычислительной системой, если они работают одновременно и их можно использовать для решения одной задачи. Под это определение попадают как барышни с арифмометрами, которыми руководил академик А. А. Самарский при расчете первых ядерных взрывов (см. § 2.1), так и ком­пьютеры в сети Интернет. Способ организации параллельных вычислений в каждом случае может быть своим, потенциальные возможности систем мо­гут сильно различаться, но принципиальная возможность параллельного решения задач должна присутствовать.
В этом смысле уникальные возможности дает сеть Интернет, которую мож­но рассматривать как самый большой компьютер в мире. Никакая вычисли­тельная система не может сравниться ни по пиковой производительности, ни по объему оперативной или дисковой памяти с теми суммарными ресур­сами, которыми обладают компьютеры, подключенные к Интернету. Ком­пьютер, состоящий из компьютеров, своего рода метакомпьютер. Отсюда происходит и специальное название для процесса организации вычислений на такой вычислительной системе — мешакомпьюшинг. В принципе, совер­шенно не обязательно рассматривать именно Интернет в качестве коммуни­кационной среды метакомпьютера, эту роль может выполнять любая сетевая технология. В данном случае для нас важен принцип, а возможностей для технической реализации сейчас существует предостаточно. Вместе с тем, к Интернету всегда был и будет особый интерес, поскольку никакая отдельная вычислительная система не сравнится по своей мощности с потенциальны­ми возможностями глобальной сети.
Конструктивные идеи использования распределенных вычислительных ресур­сов для решения сложных задач появились относительно недавно. Первые прототипы реальных систем метакомпьютинга стали доступными с середины 90-х годов прошлого века. Некоторые системы претендовали на универсаль­ность, часть из них была сразу ориентирована на решение конкретных задач, где-то ставка делалась на использование выделенных высокопроизводитель­ных сетей и специальных протоколов, а где-то за основу брались обычные каналы и работа по протоколу HTTP. Вот лишь несколько примеров.
Расширение MPI для поддержки распределенных вычислений PACX-MPI. Поддерживается объединение в единый метакомпьютер нескольких МРР-систем, возможно с различными реализациями MPI. Передача данных меж­ду МРР производится через Интернет с помощью TCP/IP. На конференции Supercomputing в 1998 году продемонстрировано совместное использование средствами PACX-MPI двух 512-процессорных суперкомпьютеров Cray ТЗЕ, находящихся в университете Штутгарта (Германия) и в Питтсбургском су­перкомпьютерном центре (США).
Система Condor распределяет задачи по существующей корпоративной сети рабочих станций, используя то время, когда компьютеры простаивают без
156
Часть I. Параллельные вычислительные системы
нагрузки, например, ночью. Программное обеспечение системы Condor распространяется свободно. В настоящее время поддерживаются все основ­ные платформы: SGI, Solaris, Linux, HP-UX, Digital Unix, Windows NT. Де­тальное описание и необходимые дистрибутивы можно найти на сайте про­екта http://www.cs.wisc.edu/condor/.
Проект SETI@home (Search for Extraterrestrial Intelligence) — поиск внезем­ных цивилизаций с помощью распределенной обработки данных, посту­пающих с радиотелескопа. Присоединиться к проекту может любой желаю­щий, загрузив на свой компьютер программу обработки радиосигналов. Доступны клиентские программы для Windows, Mac, UNIX, OS/2. С момен­та старта проекта в мае 1999 года по май 2002 года для участия в проекте зарегистрировались более 3,7 миллионов человек. Согласно приводимой на сайте статистике суммарная производительность задействованных в проекте компьютеров во много раз превосходит производительность всех компьюте­ров из списка Тор500. Адрес проекта http://setiathome.ssl.berkeley.edu/.
Distributed.net также является одним из самых больших объединений поль­зователей Интернета, предоставляющих свои компьютеры для решения сложных задач в распределенном режиме. Основные проекты связаны с за­дачами определения шифров (RSA Challenges). С момента начала проекта в нем зарегистрировались около 200 тыс. человек. Адрес этого проекта http://www.Distributed.net/.
GIMPS — Great Internet Mersenne Prime Search. Поиск простых чисел Mep-сена, т. е. простых чисел вида 2Р1, где Р является простым числом. В но­ябре 2001 года в рамках данного проекта было найдено максимальное на данное время число Мерсена 213 466 917 — 1. Десятки тысяч компьютеров по всему миру, отдавая часть своих вычислительных ресурсов, работали над этой задачей два с половиной года. Организация Electronic Frontier Foundation предлагает приз в $100 000 за нахождение простого числа Мер­сена, содержащего 10 миллионов цифр. Адрес проекта http://mersenne.org/.
Проект Globus изначально зародился в Аргонской национальной лаборато­рии и сейчас получил широкое признание во всем мире. Целью проекта яв­ляется создание средств для организации глобальной информационно-вычислительной среды. В рамках проекта разработан целый ряд програм­мных средств и систем, в частности, единообразный интерфейс к различ­ным локальным системам распределения нагрузки, системы аутентифика­ции, коммуникационная библиотека Nexus, средства контроля и монито­ринга и др. Разработанные средства распространяются свободно в виде пакета Globus Toolkit вместе с исходными текстами. В настоящее время Globus взят за основу в множестве других масштабных проектов, таких как National Technology Grid, Information Power Grid и European DataGrid. До­полнительную информацию можно найти на сайте http://www.globus.org/.
Глава 3. Архитектура параллельных вычислительных систем                                         157
Прогресс в сетевых технологиях последних лет колоссален. Гигабитные линии связи между компьютерами, разнесенными на сотни километров, становятся обычной реальностью. Объединив различные вычислительные системы в рамках единой сети, можно сформировать специальную вычисли­тельную среду. Какие-то компьютеры могут подключаться или отключаться, но, с точки зрения пользователя, эта виртуальная среда является единым метакомпьютером. Работая в такой среде, пользователь лишь выдает задание на решение задачи, а остальное метакомпьютер делает сам: ищет доступные вычислительные ресурсы, отслеживает их работоспособность, осуществляет передачу данных, если требуется, то выполняет преобразование данных в формат компьютера, на котором будет выполняться задача, и т. п. Пользова­тель может даже и не узнать, ресурсы какого именно компьютера были ему предоставлены. А, по большому счету, часто ли вам это нужно знать? Если потребовались вычислительные мощности для решения задачи, то вы под­ключаетесь к метакомпьютеру, выдаете задание и получаете результат. Все.
Здесь существует почти полная аналогия с электрической сетью. Подключая электрический чайник к розетке, вы не задумываетесь, какая станция про­изводит электроэнергию. Вам нужен ресурс, вы им пользуетесь. Кстати, по аналогии именно с электрической сетью распределенная вычислительная среда в англоязычной литературе получила название Grid или "вычисли­тельная сеть". В дальнейшем, слова Grid и метакомпьютер мы будем исполь­зовать как синонимы.
Продолжая аналогию с электрической сетью, на метакомпьютер хотелось бы перенести не только название, но и такой же простой способ взаимодейст­вия с ним пользователя. Но вот тут-то и возникают основные проблемы, которые определяются сложностью организации самого метакомпьютера. В отличие от традиционного компьютера метакомпьютер имеет целый набор присущих только ему особенностей:
□  метакомпьютер обладает огромными ресурсами, которые несравнимы с ресурсами обычных компьютеров. Это касается практически всех пара­метров: число доступных процессоров, объем памяти, число активных приложений, пользователей и т. п.;
□  метакомпьютер является распределенным по своей природе. Компоненты метакомпьютера могут быть удалены друг от друга на сотни и тысячи ки­лометров, что неизбежно вызовет большую латентность и, следовательно, скажется на оперативности их взаимодействия;
□  метакомпьютер может динамически менять конфигурацию. Какие-то ком­пьютеры к нему подсоединяются и делегируют права на использование своих ресурсов, какие-то отключаются и становятся недоступными. Но для пользователя работа с метакомпьютером должна быть прозрачной. Задача системы поддержки работы метакомпьютера состоит в поиске
158
Часть I. Параллельные вычислительные системы
подходящих ресурсов, проверке их работоспособности, в распределении поступающих задач вне зависимости от текущей конфигурации метаком-пьютера в целом;
□  метакомпьютер неоднороден. При распределении заданий нужно учиты­вать особенности операционных систем, входящих в его состав. Разные системы поддерживают различные системы команд и форматы представ­ления данных. Различные системы в разное время могут иметь различ­ную загрузку, связь с вычислительными системами идет по каналам с различной пропускной способностью. Наконец, в состав метакомпьютера могут входить системы с принципиально различной архитектурой, начи­ная с домашних персональных компьютеров, заканчивая мощнейшими системами из списка Тор500;
□  метакомпьютер объединяет ресурсы различных организаций. Политика дос­тупа и использования конкретных ресурсов может сильно меняться в зависимости от их принадлежности к той или иной организации. Мета­компьютер не принадлежит никому, поэтому политика его администри­рования может быть определена лишь в самых общих чертах. Вместе с тем, согласованность работы огромного числа составных частей метаком­пьютера предполагает обязательную стандартизацию работы всех его служб и сервисов.
Говоря о метакомпьютере, следует четко представлять, что речь идет не только об аппаратной части и не столько об аппаратной части, сколько о его инфраструктуре [49]. В комплексе должны рассматриваться такие вопро­сы, как средства и модели программирования, распределение и диспетчери­зация заданий, технологии организации доступа к метакомпьютеру, интер­фейс с пользователями, безопасность, надежность, политика администри­рования, средства доступа и технологии распределенного хранения данных, мониторинг состояния различных подсистем метакомпьютера и многие другие. Представьте себе, как заставить десятки миллионов различных элек­тронных устройств, составляющих метакомпьютер, работать согласованно над заданиями десятков тысяч пользователей в течение продолжительного времени? Фантастически сложная задача, масштабное решение которой бы­ло невозможным еще десять лет назад.
В настоящее время идет активное обсуждение различных стратегий по­строения метакомпьютера. Однако многие вопросы до сих пор недостаточно проработаны, часть предложенных технологий еще находится на стадии ап­робации, не всегда используется единая терминология. Ситуация в данной области развивается чрезвычайно быстро. В данной книге мы решили ос­новное внимание уделить не детальному описанию каких-либо конкретных систем или технологий метакомпьютинга, а обсуждению базовых принципов и идей вычислительной компоненты сети. Много полезной информации о текущем состоянии работ в данной области можно найти на сайтах http://www.globus.org, http://www.gridforum.org и др.
Глава 3. Архитектура параллельных вычислительных систем
159
Несмотря на кажущуюся вычурность и нереальность создаваемой глобаль­ной вычислительной системы, область применения метакомпьютера обшир­на. В рамках метакомпьютера объединяются колоссальные вычислительные ресурсы. Это делает реальным решение таких задач, к которым раньше нельзя было даже подступиться. Биоинженерия — это одна из таких облас­тей. Для определения пространственной структуры одной макромолекулы на одной современной рабочей станции требуется несколько дней, однако об­щее число необходимых численных экспериментов для различных соедине­ний исчисляется сотнями тысяч. Мощности никакой традиционной вычис­лительной системы для решения такой задачи не хватит, а суммарные ресурсы метакомпьютера могут помочь. Другое направление применения — это обеспечение высокой скорости обработки большого потока слабо свя­занных друг с другом или вовсе независимых задач. Типичными примерами задач данного направления является проведение огромного числа расчетов на завершающем этапе проектирования сложных электронных приборов [49] или же традиционные задачи криптографии. Смежным направлением является проведение вычислений по требованию (on-demand computing). Для многих организаций экономически невыгодно держать у себя высоко­производительные компьютеры, поскольку потребности в проведении больших расчетов возникают лишь эпизодически, а стоимость приобретения и сопровождения такого оборудования велика.
В данном ряду стоит особо отметить задачи распределенного хранения и обработки данных. Реально массивы данных могут быть географически уда­лены друг от друга и распределены по большому числу разного рода храни­лищ и баз данных. Несмотря на разобщенность, все эти данные могут по­требоваться в рамках единого эксперимента, что предъявляет к метаком-пьютеру серьезные требования не только вычислительного, но и коммуни­кационного характера.
Характерной задачей из данной области является создание информационной инфраструктуры для поддержки экспериментов в физике высоких энергий. В 2006—2007 годах в Европейской организации ядерных исследований (CERN) планируется ввести в строй мощный ускоритель — Большой Ад-ронный Коллайдер. Четыре физические установки данного проекта в тече­ние 15—20 лет будут ежегодно собирать данные объемом порядка несколь­ких Пбайт (1015 байт). Во время экспериментов данные будут поступать со скоростью от 100 Мбайт/с до 1 Гбайт/с. Оптимальная схема хранения и по­следующая эффективная обработка собранных данных тысячами участников эксперимента из разных стран мира является исключительно сложной зада­чей. Принято решение создать иерархическую распределенную систему ре­гиональных центров, что позволит снять колоссальную нагрузку на вычис­лительные мощности и системы хранения CERN.
Создаваемая система включает несколько уровней иерархии, причем на возможности каждого уровня накладываются очень жесткие требования.
160
Часть I. Параллельные вычислительные системы
Компьютерные ресурсы каждого центра должны быть адекватны поставлен­ным перед ними задачам. Так, например, основной центр в CERN должен обеспечивать задачи по созданию баз данных по экспериментам. Ресурсы этого центра в 2007 году должны находиться на уровне: мощность вычисли­тельных кластеров 2500К Speclnt95, объем дискового хранилища 2,5 Пбайт, хранилище на лентах объемом 10—15 Пбайт. При этом каждый год ленточ­ное хранилище должно увеличивать свой ресурс на 10—15 Пбайт, а диско­вый массив должен увеличиваться на 20%. Для обеспечения работы физиков всего мира с этими данными создается распределенная система (DataGrid), поэтому сетевые соединения CERN с региональными центрами первого уровня должны быть порядка 1—2 Гбит/с. Суммарные мощности центров первого уровня будут соответствовать мощности основного центра в CERN (то же верно и для суммарной мощности центров второго уровня по отно­шению к центрам первого уровня). Предполагается создать 5—6 центров первого уровня на каждый из 4-х экспериментов (ALICE, ATLAS, CMS и LHCb), и около 25—30 центров второго уровня.
Большая часть центров будет удалена друг от друга на значительное рас­стояние, что требует соответствующей технологии удаленной работы с дан­ными эксперимента. Принято решение в качестве основы для построения инфраструктуры эксперимента использовать Grid-технологии и объединить все центры в рамки единого метакомпьютера. Предполагается разработать специализированное программное обеспечение для эффективного доступа к данным эксперимента, позволяющее кэшировать, тиражировать и переда­вать данные в неоднородной среде метакомпьютера. В качестве технологи­ческой основы выбрана система Globus.
Запланированная работа исключительно масштабна. Она предполагает создание большого числа сложных программных подсистем различного уровня для обеспечения прозрачного доступа к распределенным данным, управления рабочей нагрузкой вычислительных систем, эффективного управления данными, построение систем оперативного мониторинга и т. п. Много интересных деталей данного проекта можно найти на сайте http://www.eu-datagrid.org/.
Удачных примеров использования идей метакомпьютинга для решения ре­альных задач уже сейчас можно привести немало. Но, к сожалению, все эти работы уникальны и настолько трудоемки, что до массового использования еще очень далеко. Легко было сказать, опираясь на аналогию с электриче­ской сетью: "Подключайся и используй". На практике возникает огромное количество действительно серьезных проблем. Часть проблем касается во­просов политического и экономического характера. Без решения и урегули­рования таких вопросов не обойтись и их надо обязательно учитывать, по­скольку масштаб работ по метакомпьютингу быстро выходит на межгосударственный уровень. Большой спектр проблем лежит перед созда­телями самого метакомпьютера и поддерживающих его работу систем. Это и
Глава 3. Архитектура параллельных вычислительных систем
161
понятно: задача сложная, во многом новая и необычная, с небольшим чис­лом готовых наработок и пока явным недостатком опыта.
Однако еще больше проблем появится перед пользователями. Как создавать программы для систем метакомпьютинга на настоящий момент — не ясно. На какую модель программирования ориентироваться пользователю? В рам­ках метакомпьютера могут использоваться все существующие в настоящее время архитектуры. В одних узлах используются системы программирования с явной передачей сообщений, в других ставка делается на многопотоковую обработку, третьи опираются на понятие общей памяти, четвертые исполь­зуют векторную обработку данных и т. д. Как обеспечить правильный под­бор распределенных вычислительных ресурсов в зависимости от свойств конкретной программы или алгоритма? Данная задача представляется ис­ключительно сложной, поскольку затрагивает практически весь спектр во­просов, характерных для параллельных вычислений в целом.
Для небольшого класса многократно используемых программ уже сейчас уда­лось найти подходящее решение. С помощью специально спроектированных средств оформляется Web-интерфейс к программе, которая предварительно подготовлена специалистами к выполнению в рамках некоторой системы ме­такомпьютинга. Пользователь не занимается программированием, а задает свои входные данные и запускает задачу на счет, не заботясь о том, где и как реально программа будет выполнена. Через какое-то время система возвраща­ет ему результат. Сейчас существует несколько систем подобного класса, на­пример, система UNICORE (http://www.fz-juelich.de/unicore/).
Реальная работа по созданию и апробации систем метакомпьютинга активно идет по многим направлениям. Многие ведущие компании взяли Globus Toolkit в качестве стандарта для создания Grid-приложений, создавая про­граммную инфраструктуру для своих платформ. Создаются глобальные полигоны, объединяющие в рамках супервысокоскоростных сетей значи­тельные распределенные вычислительные ресурсы. Проводятся серии экс­периментов, направленных на отработку новых сетевых технологий, методов диспетчеризации и мониторинга в распределенной вычислительной среде, интерфейса с пользователем, моделей и методов программирования. Потен­циал направления, безусловно, огромен, но число нерешенных проблем по­ка перевешивает реальный эффект. Сейчас все находится в стадии станов­ления. Нет сомнений, что в будущем ситуация изменится.
Вопросы и задания
1.   Сколько компьютеров в сети вашей организации? Какова их суммарная произво­дительность? Что нужно сделать, чтобы их можно было бы использовать в режи­ме единого параллельного компьютера?
2.   Чем отличается программирование вычислительного кластера и метакомпьютера с точки зрения прикладного программиста?
162
Часть I. Параллельные вычислительные системы
3.   Как вычислить пиковую производительность метакомпьютера?
4.   Назовите 10 причин, снижающих производительность метакомпьютера на ре­альных программах.
5.   Предположим, что метакомпьютер объединяет только кластеры, состоящие из SMP-узлов. Какие технологии параллельного программирования можно было бы использовать для его программирования?
6.   Попробуйте составить проект вычислительного портала в сети Интернет, предос­тавляющего максимальное число услуг и сервисов вычислительного характера.
7.   Что нужно знать метакомпьютеру для оптимального подбора необходимых вы­числительных ресурсов под конкретную программу или алгоритм?
8.   Какие проблемы появятся при разработке компилятора для метакомпьютера?
9.   Какие вопросы, связанные с обеспечением информационной безопасности, придется решать при создании метакомпьютера?
10.   Как метакомпьютер может решить вопрос о целесообразности передачи про­граммы для проведения вычислений на удаленных ресурсах?
11.   Назовите конкретные задачи (или области) в химии, физике, астрономии и других науках, для которых использование метакомпьютера было бы эффективно.
12.   Сделайте Web-интерфейс к своей параллельной программе с возможностью за­дания входных параметров и запуска программы на различных конфигурациях доступного параллельного компьютера.
13.   Как, в дополнение к предыдущему вопросу, на основе Web-технологий сделать систему online-визуализации динамики исполнения параллельной программы?
§ 3.6. Производительность параллельных компьютеров
Оценивать и сравнивать всегда было делом трудным и неблагодарным. Вы потратите массу сил и времени на выработку критериев и проведение ана­лиза, но обязательно найдется человек, с точки зрения которого результаты должны быть точно противоположными. Сколько людей — столько мнений. Желание получить объективную оценку немедленно наталкивается на мно­жество субъективных факторов: разные постановки задачи, разные критерии сравнения, разные цели оценивания и т. п. Более того, любая оценка не­вольно приводит к сравнению, к явному или неявному ранжированию, вы­делению лучших и самых лучших. В этом вопросе редко удается прийти к единодушному согласию, поскольку убеждают только железные, неоспори­мые факты. Какое яйцо лучше: свежее или "с душком"? Ответ очевиден. Но, видимо, вы также воспитаны на европейской кухне. Любители восточной кухни над вопросом задумаются...
Необходимость оценки производительности и последующего сравнения компьютеров появилась практически одновременно с их рождением. Какой
Глава 3. Архитектура параллельных вычислительных систем
163
компьютер выбрать? Какому компьютеру отдать предпочтение? На каком компьютере задача будет решаться быстрее? Подобные вопросы задавались пользователями всегда и будут задаваться ими еще долго. Казалось бы, что тут сложного, запусти программу и проверь. Но не все так просто. Перенос параллельной программы на новую платформу требует времени, зачастую значительного. Много попыток в таких условиях не сделаешь, да и не всегда есть свободный доступ к новой платформе. А если на новом компьютере должен работать набор программ, как быть в такой ситуации?
Хотелось бы иметь простую единую методику априорного сравнения вычис­лительных систем между собой. В идеальной ситуации вычислять бы для каждой системы по некоторому закону одно число, которое и явилось его обобщенной характеристикой. Со сравнением компьютеров проблем не ста­ло бы, с выбором тоже.
Сопоставить одно число каждому компьютеру можно по-разному. Напри­мер, можно вычислить это значение, опираясь на параметры самого компь­ютера. С этой точки зрения, естественной характеристикой любого компью­тера является его пиковая производительность. Данное значение определяет тот максимум, на который способен компьютер. Вычисляется оно очень просто. Для этого достаточно предположить, что все устройства компьютера работают в максимально производительном режиме. Если в процессоре есть два конвейерных устройства, то рассматривается режим, когда оба конвейе­ра одновременно работают с максимальной нагрузкой. Если в компьютере есть 1000 таких процессоров, то пиковая производительность одного про­цессора просто умножается на 1000.
Иногда пиковую производительность компьютера называют его теоретической производительностью. Этот нюанс в названии лишний раз подчеркивает тот факт, что производительность компьютера на любой реальной программе ни­когда не только не превысит этого порога, но и не достигнет его точно.
Пиковая производительность компьютера вычисляется однозначно, спорить тут не о чем, и уже этим данная характеристика хороша. Более того, подсоз­нательно всегда возникает связь между пиковой производительностью компь­ютера и его возможностями в решении задач. Чем больше пиковая произво­дительность, тем, вроде бы, быстрее пользователь сможет решить свою задачу. В целом данный тезис не лишен некоторого смысла, но лишь некоторого и даже "очень некоторого". Полагаться на него полностью нельзя ни в коем случае. С момента появления первых параллельных компьютеров пользовате­ли убедились, что разброс в значениях реальной производительности может быть огромным. На одних задачах удавалось получать 90% от пиковой произ­водительности, а на других лишь 2%. Если кто-то мог использовать независи­мость и конвейерность всех функциональных устройств компьютера CDC 7600, то производительность получалась высокой. Если в вычислениях были ин­формационные зависимости, то конвейерность не использовалась, и произво-
164
Часть I. Параллельные вычислительные системы
дительность снижалась. Если в алгоритме явно преобладал один тип опера­ций, то часть устройств простаивала, вызывая дальнейшее падение производи­тельности. Об этом мы уже начинали говорить в § 2.3.
Структура программы и архитектура компьютера тесно связаны. Пользова­теля не интересуют потенциальные возможности вычислительной системы. Ему нужно решить его конкретную задачу. Именно с этой точки зрения он и хочет оценить "качество" компьютера. Для обработки данных эксперимен­та в физике высоких энергий не требуется высокоскоростной коммуникаци­онной среды. Главное — это большое число вычислительных узлов. Для та­ких целей вполне подойдет локальная 10-мегабитная сеть организации из ста рабочих станций. Ее можно рассматривать в качестве параллельного компьютера, и ночью целиком отдавать под такие задачи. Теперь попробуй­те на таком компьютере запустить серьезную модель расчета изменения климата. Скорее всего, никакого ускорения решения задачи не будет. Имеем компьютер, с хорошим показателем пиковой производительности. Но для одних задач он подходит идеально, а для других никуда не годится.
Традиционно используются два способа оценки пиковой производительно­сти компьютера. Один из них опирается на число команд, выполняемых ком­пьютером в единицу времени. Единицей измерения, как правило, является MIPS (Million Instructions Per Second). Для определенного класса приложе­ний такой подход является вполне приемлемым, поскольку общее представ­ление о скорости работы компьютера получить можно. Но, опять-таки, только самое общее. Производительность, выраженная в MIPS, говорит о скорости выполнения компьютером своих же инструкций. Но в какое число инструкций отобразится программа пользователя или отдельный ее опера­тор? Заранее не ясно. К тому же, каждая программа обладает своей специ­фикой, число команд той или иной группы от программы к программе мо­жет меняться очень сильно. В этом контексте данная характеристика действительно дает лишь самое общее представление о производительности компьютера.
Интересный эффект в оценке производительности компьютера на основе MIPS наблюдается в компьютерах, в которых для выполнения вещественной арифметики применяются сопроцессоры. В самом деле, операции над чис­лами, представленными в форме с плавающей запятой, выполняются доль­ше простых управляющих инструкций. Если такие операции выполняются без сопроцессора в режиме эмуляции, то срабатывает целое множество не­больших инструкций. Время эмуляции намного больше, чем выполнение операции сопроцессором, но каждая небольшая инструкция срабатывает быстрее, чем команда сопроцессору. Вот и получается, что использование сопроцессора уменьшает время работы программы, зато в режиме эмуляции производительность компьютера, выраженная в MIPS, может оказаться зна­чительно больше.
Глава 3. Архитектура параллельных вычислительных систем
165
При обсуждении производительности компьютера не менее важен и вопрос о формате используемых данных. Если процессор за один такт может выпол­нять операции над 32-разрядными вещественными числами, то его же про­изводительность при работе с 64-разрядной арифметикой может упасть во много раз. Известно, что первый матричный компьютер ILLIAC IV мог вы­полнять до 10 миллиардов операций в секунду. И это в 1974 году! Если брать за основу только цифры, то впечатляет. А если посмотреть вглубь, то окажется, что это верно лишь для простых команд, оперирующих с байтами. Помимо работы с 64-разрядными числами, процессорные элементы 1LL1AC IV могли интерпретировать и обрабатывать данные уменьшенного формата. Например, одно слово они могли рассматривать как два 32-разрядных числа или восемь однобайтовых. Именно этот дополнительный внутренний парал­лелизм и позволял получить столь внушительные характеристики.
Для задач вычислительного характера, в которых важна высокая скорость выполнения операций над вещественными числами, подход к определению производительности также не должен опираться на скорость выполнения машинных инструкций. Во многих случаях операции над вещественными числами выполняются медленнее, чем, скажем, управляющие или регистро­вые операции. За время выполнения одной операции умножения двух чисел в форме с плавающей запятой может выполниться десяток простых инст­рукций. Это послужило причиной введения другого способа измерения пи­ковой производительности: число вещественных операций, выполняемых ком­пьютером в единицу времени. Единицу измерения назвали Flops, что является сокращением от Floating point operations per second. Такой способ, безуслов­но, ближе и понятнее пользователю. Операция а + b в тексте программы всегда соответствует одной операции сложения в коде программы. Пользо­ватель знает вычислительную сложность своей программы, а на основе этой характеристики может получить нижнюю оценку времени ее выполнения.
Да, к сожалению, оценка будет только нижней. В этом и кроется причина недовольства и разочарований. Взяв за ориентир пиковую производитель­ность компьютера, пользователь рассчитывает и на своей программе полу­чить столько же. Совершенно не учитывается тот факт, что пиковая произ­водительность получается при работе компьютера в идеальных условиях. Нет конфликтов при обращении к памяти, все берется с регистров, все уст­ройства постоянно и равномерно загружены и т. п. Но в жизни так бывает очень редко. В каждой программе пользователя есть свои особенности, ко­торые эти идеальные условия нарушают, обнажая узкие места архитектуры. Особенности структуры процессора, система команд, состав функциональ­ных устройств, строение и объем кэш-памяти, архитектура подсистемы дос­тупа в память, реализация ввода/вывода — все это, и не только это, может повлиять на выполнение реальной программы. Причем заметим, мы пере­числили только части аппаратуры, а говорить надо о программно-аппаратной среде выполнения программ в целом. Если для компьютера нет эффектив-
166
Часть I. Параллельные вычислительные системы
ных компиляторов, то пиковая производительность будет вводить в заблуж­дение еще сильнее.
Значит нужно отойти от характеристик аппаратуры и оценить эффектив­ность работы программно-аппаратной среды на фиксированном наборе за­дач. Бессмысленно для каждого компьютера показывать свой набор. Разра­ботчики без труда придумают такие программы, на которых их компьютер достигает производительности, близкой к пиковой. Такие примеры никого не убедят. Набор тестовых программ должен быть зафиксирован. Эти про­граммы будут играть роль эталона, по которому будут судить о возможно­стях вычислительной системы.
Такие попытки неоднократно предпринимались. На основе различных кри­териев формировались тестовые наборы программ или фиксировались от­дельные эталонные программы (такие программы иногда называют бенчмарками, отталкиваясь от английского слова benchmark). Программы за­пускались на различных системах, замерялись те или иные параметры, на основе которых в последствии проводилось сравнение компьютеров между собой. В дальнейшем изложении для подобных программ мы чаще всего будем использовать слово тест, делая акцент не на проверке правильности работы чего-либо, а на тестировании эффективности работы вычислитель­ной системы.
Что имело бы смысл взять в качестве подобного теста? Что-то несложное и известное всем. Таким тестом стал LINPACK. Эта программа предназначена для решения системы линейных алгебраических уравнений с плотной мат­рицей с выбором главного элемента по строке. Простой алгоритм, регуляр­ные структуры данных, значительная вычислительная емкость, возможность получения показателей производительности, близких к пиковым, — все эти черты сделали тест исключительно популярным.
Этот тест рассматривается в трех вариантах. В первом варианте решается система с матрицей размера 100x100. Предоставляется уже готовый исход­ный текст, в который не разрешается вносить никаких изменений. Изна­чально предполагалось, что запрет внесения изменений в программу не по­зволит использовать каких-либо специфических особенностей аппаратуры, и показатели производительности будут сильно занижены. В настоящее время ситуация полностью изменилась. Матрица столь небольшого размера легко помещается целиком в кэш-память современного процессора (нужно лишь 80 Кбайт), что завышает его характеристики.
Во втором варианте теста размер матрицы увеличивается до 1000x1000. Раз­решается как внесение изменений в текст подпрограммы, реализующей за­ложенный авторами метод решения системы, так и изменение самого мето­да. Единственное ограничение — это использование стандартной вызы­вающей части теста, которая выполняет инициализацию матрицы, вызывает подпрограмму решения системы и проверяет корректность результатов.
Глава 3. Архитектура параллельных вычислительных систем
167
В этой же части вычисляется и показанная компьютером производитель­ность, исходя из формулы для числа операций 2«3/3 + 2п2 (п — это размер матрицы), вне зависимости от вычислительной сложности реально исполь­зованного алгоритма.
В данном варианте достигались значения производительности, близкие к пиковой. Этому способствовали и значительный размер матрицы, и воз­можность внесения изменений в программу или алгоритм. Отсюда появи­лось и специальное название данного варианта — LINPACK ТРР, Toward Peak Performance.
С появлением больших массивно-параллельных компьютеров вопрос подбо­ра размера матрицы стал исключительно актуальным. На матрицах 100x100 или 1000x1000 никаких разумных показателей получить не удавалось. Эти задачи были слишком малы. Матрица размера 1000x1000 занимает лишь 0,01—0,001% всей доступной оперативной памяти компьютера. Первые экс­перименты с тестом LINPACK на реальных массивно-параллельных компь­ютерах показали, что и фиксировать какой-либо один размер задачи тоже нельзя. В результате в третьем варианте теста было разрешено использовать изложенную во втором варианте методику для матриц сколь угодно боль­шого размера. Сколько есть оперативной памяти на всех вычислительных узлах системы, столько и можно использовать. Для современных компьюте­ров размер матрицы уже перевалил за миллион, поэтому такой шаг был просто необходим.
Для работы теста LINPACK на вычислительных системах с распределенной памятью была создана специальная версия HPL (High-Performance LINPACK). В отличие от стандартной реализации, в HPL пользователь мо­жет управлять большим числом параметров для достижения высокой произ­водительности на своей установке.
В настоящее время LINPACK используется для формирования списка Тор500 — пятисот самых мощных компьютеров мира (www.top500.org). Кро­ме пиковой производительности Rpeak для каждой системы указывается ве­личина Rmax, равная производительности компьютера на тесте LINPACK с матрицей максимального для данного компьютера размера Nmax. По значе­нию Rmax отсортирован весь список. Как показывает анализ представленных данных, величина Rmax составляет 50—70% от значения Rp^. Интерес для анализа функционирования компьютера представляет и указанное в каждой строке значение N^/2, показывающее, на матрице какого размера достигает­ся половина производительности Rmax.
Что же показывают данные теста LINPACK? Только одно насколько хоро­шо компьютер может решать системы уравнений с плотной матрицей указан­ным методом. Поскольку задача имеет хорошие свойства, то и корреляция производительности на тесте LINPACK с пиковой производительностью компьютера высока. Операции ввода/вывода не затрагиваются, отношение
168
Часть I. Параллельные вычислительные системы
числа выполненных операций к объему используемых данных высокое, ре­гулярность структур данных и вычислений, простая коммуникационная схема, относительно небольшой объем передач между процессорами и дру­гие свойства программы делают данный тест "удобным". Безусловно, по данным этого теста можно получить много полезной информации о вычис­лительной системе и с этим никто не спорит. Но нужно ясно понимать и то, что высокая производительность на LINPACK совершенно не означает того, что и на вашей конкретной программе будет достигнута высокая про­изводительность.
Интересное свойство LINPACK связано с законом Мура. Согласно этому закону производительность вычислительных систем удваивается каждые 18 ме­сяцев. Не имея строгих доказательств, этот закон, тем не менее, подтвержда­ется уже не один десяток лет. Согласно закону, объем памяти увеличивается в четыре раза каждые три года. Это позволит каждые три года удваивать размер используемой матрицы. За эти же три года производительность ком­пьютеров вырастет в четыре раза. Поскольку вычислительная сложность теста LINPACK есть куб от размера матрицы, то время выполнения третьего варианта теста должно удваиваться каждые три года. Получается, что фик­сировать размер матрицы нельзя, а использование матриц максимального размера со временем приведет к очень большим затратам. Желателен ком­промисс, который пока не найден.
В любом случае ясно то, что данных одного теста LINPACK для получения всей картины о возможностях компьютера мало. Неплохим дополнением к тесту LINPACK является набор тестов STREAM. Этот набор содержит четы­ре небольших цикла, работающих с очень длинными векторами. Основное назначение тестов STREAM состоит в оценке сбалансированности скорости работы процессора и скорости доступа к памяти.
Ключевыми в тесте являются следующие четыре операции:
a(i) = b(i) a(i) = q * b(i) a(i) = b(i) + c(i) a(i) = b(i) + q * c(i)
Размеры массивов подбираются таким образом, чтобы ни один из них цели­ком не попадал в кэш-память. Форма записи тестовой программы исключа­ет возможность повторного использования данных, что также могло бы ис­казить реальную картину. Результатом работы тестов являются вычисленные значения реальной скорости передачи данных и производительности.
Первый тест предназначен для определения скорости передачи данных в отсутствии какой-либо арифметики. Во втором тесте добавлена одна допол­нительная операция. Поскольку вторым аргументом является скалярная пе­ременная, то объем передаваемых данных между процессором и памятью
Глава 3. Архитектура параллельных вычислительных систем
169
останется на прежнем уровне. Возможное различие в получаемых результа­тах будет определяться способностью компьютера выполнять арифметиче­ские операции с одновременным доступом к памяти. В третьем тесте появ­ляется второй входной вектор, что увеличивает нагрузку на тракт процес­сор—память. В последнем тесте добавляется еще одна операция. Все тесты работают с 64-разрядными вещественными числами.
Предполагается, что в хорошо сбалансированной архитектуре скорость вы­полнения арифметических операций должна быть согласована со скоростью доступа в память. В современных высокопроизводительных системах это выполняется, но, как правило, только при работе с верхними уровнями ие­рархии памяти. Если попали данные в кэш-первого уровня, то все будет хо­рошо. А что делать в других случаях? Именно по этой причине тесты STREAM работают с очень большими векторами, хранящимися в основной оперативной памяти. Попробуйте на доступной вам системе провести сле­дующий эксперимент. С помощью, например, четвертого теста определите реальную скорость передачи данных между процессором и памятью. Разде­лите пиковую производительность компьютера на только что полученное значение. Чем больше полученное вами число, тем менее сбалансированы в архитектуре скорости обработки и передачи данных (меньше единицы дан­ное значение бывает далеко не всегда).
Характеристики компьютеров, полученные на тех или иных тестах, всегда вызывали и будут вызывать недоверие и критику. Единственная характери­стика, которая по-настоящему интересует пользователя — это насколько эф­фективно компьютер будет выполнять его собственную программу. Каждая программа уникальна. В каждой программе своя смесь команд, задействую-щая определенные компоненты вычислительной системы. Повторить или смоделировать поведение каждой программы тест не может, поэтому и оста­ется у пользователя сомнение в адекватности полученных характеристик.
Это в полной мере касается таких простых тестов, как LINPACK или STREAM. Однако, если задуматься, то возникает замкнутый круг. С одной стороны, тесты должны быть легко переносимыми с платформы на плат­форму, а потому должны быть простыми. Чтобы поместиться в памяти каж­дого компьютера, структуры данных должны быть небольшими. Более того, чтобы получить широкое признание, тест должен иметь понятную форму­лировку решаемой задачи. Перемножение матриц, метод Гаусса решения системы линейных уравнений, быстрое преобразование Фурье, метод Мон­те-Карло — это все доступно, компактно, используется многими, легко пе­реносится. С другой стороны, к подобным программам всегда было отно­шение, как к чему-то несерьезному, как к "игрушкам". Фиксированный размер задачи, характерное вычислительное ядро, отсутствие интенсивного ввода/вывода, упрощенный код и другие свойства тестов дают веские осно­вания для подобного отношения. Доля истины в таких рассуждениях, безус­ловно, есть. Жизнь всегда намного богаче и разнообразнее, чем теория.
170
Часть I. Параллельные вычислительные системы
Точно так же и реальные программы по отношению к тестам. Что же в ре­зультате мы имеем? Средства для оценки производительности компьютеров необходимы. Чтобы использоваться широко, они должны быть простыми. Простые тесты не дают полной картины и в каждом случае нужно исполь­зовать дополнительные средства. Простота необходима, но простота не при­водит к искомому результату.
Одно из возможных направлений выхода из данной ситуации состоит в формировании набора взаимодополняющих тестов. Если одно число не мо­жет адекватно охарактеризовать вычислительную систему, можно попытаться это сделать с помощью набора чисел. Тестовые программы могут браться из разных предметных областей и делать акцент на различных особенностях архитектуры. Часть программ может быть модельными приложениями, а часть может представлять реальные промышленные приложения. Ясно, что принципы формирования таких наборов могут быть самыми разными.
Одним из первых пакетов, построенных на таких принципах, стал набор из 24 Ливерморских циклов (The Livermore Fortran Kernels, LFK) [59]. В его состав вошли вычислительные ядра прикладных программ, используемых в Ливерморской национальной лаборатории США. Все включенные в пакет фрагменты программ являются простыми циклическими конструкциями, разобраться в которых не составляет никакого труда. Часть циклов пред­ставляют собой типичные вычислительные блоки типа скалярного произве­дения, рекурсий или вычисления значения полиномов. Другая часть содер­жит часто используемые конструкции языка Fortran. Некоторые циклы содержат фрагменты, эффективная компиляция которых может быть за­труднена. Примеры трех тестовых конструкций показаны ниже.
С*** KERNEL 1 HYDRO FRAGMENT
cdir$ ivdep
DO 1 k = 1, n 1 X(k) = Q + Y(k) * (R * ZX(k+10) + T * ZX(k+ll))
C*** KERNEL 9 INTEGRATE PREDICTORS
DO 9 k = 1, n
PX(l,k) = DM28*PX(13,k) + DM27*PX(12,k) + DM26*PX(11,k) +
1          DM25*PX(10,k) + DM24*PX( 9,k) + DM23*PX( 8,k) +
2          DM22*PX( 7,k) + C0*(PX( 5,k) + PX(6,k))+ PX(3,k) 9 CONTINUE
C*** KERNEL 20 DISCRETE ORDINATES TRANSPORT: RECURRENCE
Глава 3. Архитектура параллельных вычислительных систем                                         171
dw = 0.200dO cdir$ novector с
DO 20 к = 1, n
di = Y(k) - G(k)/( XX(k)+DK)
dn = dw
IF( di.NE.0.0) dn = MAX( S,MIN( Z(k)/di, T))
X(k) = ((W(k)+V(k)*dn)* XX(k)+U(k))/(VX(k)+V(k)*dn)
XX(k+l) = (X(k) - XX(k) ) *dn+ XX (k) 20 CONTINUE cdir$ vector с
Все тесты LFK очень маленькие, и это характерное свойство пакета в целом. В некоторых тестах содержатся явные указания компилятору. Например, в тесте 1 стоит директива ivdep, которая говорит об отсутствии информаци­онной зависимости в цикле и достаточно большом числе итераций цикла, при котором векторизация заведомо будет иметь смысл. В тесте 20 есть ди­ректива novector, препятствующая использованию векторных команд ком­пилятором во фрагменте вплоть до последующей директивы vector.
Первые версии LFK появились в начале 70-х годов прошлого столетия. Столь раннее возникновение пакета определило его основную направлен­ность — измерение производительности отдельных процессоров. Значительную популярность Ливерморские циклы приобрели на векторно-конвейерных машинах, что, впрочем, легко объяснимо. Формирование набора тестов в виде небольших циклических конструкций хорошо подходило компьютерам этого класса, находившегося в то время на волне высокопроизводительных вычислений.
Интересно, что Ливерморские циклы использовались исследователями в разных целях. Выполнив данный тест на каком-либо перспективном ком­пьютере, можно было приблизительно оценить, насколько хорошо или пло­хо будут работать характерные вычислительные конструкции. Для этого па­кет и создавался. Вместе с тем, Ливерморские циклы широко применялись в качестве теста для компиляторов. Конструкции простые, результат легко оценить и проверить, а для высокопроизводительных компьютеров высокое качество генерируемого кода является исключительно важным требованием.
Пакет тестов PERFECT Club Benchmarks (Performance Evaluation for Cost-effective Transformations) появился в конце 80-х годов прошлого века [45]. Он состоит из тринадцати программ, общим объемом более 50 000 строк на языке Fortran-77. Практически все программы являются реальными прило­жениями, взятыми из различных предметных областей: вычислительная
172
Часть I. Параллельные вычислительные системы
гидродинамика, прогноз погоды, обработка сигналов, моделирование рас­пространения вредных примесей в атмосфере, квантовая механика и др. В этом и состояла основная идея — работать, по возможности, не с модель­ными, а с реальными, живыми программами. Результаты были получены на многих компьютерах и дали богатейший материал для исследований. Про­граммы данного пакета тестировали если и не все, то очень многие пара­метры вычислительных систем.
Однако пакет PERFECT Club Benchmarks не получил широкого распростра­нения. С момента его появления часто высказывалось мнение, что сам про­цесс адаптации программ пакета к новому компьютеру является нетриви­альной задачей. Во многих случаях процесс тестирования выполнялся на порядок быстрее процесса адаптации пакета. С появлением параллельных компьютеров с распределенной памятью ситуация ухудшилась. Потребова­лись соответствующие версии программ этого пакета. Но попробуйте взять уже готовое приложение, изначально не предназначенное для распараллели­вания, состоящее из 5000—10 000 строк, и переписать его, скажем, в терми­нах МР1 или PVM. Переписать "как-нибудь" не сложно, но что такие тесты покажут, какие выводы из их работы можно будет сделать? Если возмож­ность векторизации и адаптация программ для векторно-конвейерных ком­пьютеров еще как-то просматривалась, то перенос программ пакета на ком­пьютеры с распределенной памятью оказался неразрешимой задачей. Кстати, результаты адаптации программы TRFD из пакета PERFECT Club Benchmarks приведены в примере 7.6 из § 7.6. Интересное исследование структуры программы FL052Q из этого же пакета с описанием методики ее распараллеливания можно найти в [3].
Наибольшую известность среди наборов тестов получил пакет NAS Parallel Benchmarks (NPB). В состав пакета входят две группы тестов, отражающих различные стороны реальных программ вычислительной гидродинамики. Пять программ представляют типичные вычислительные ядра, а три про­граммы являются модельными приложениями. Такой состав пакета разумен и вполне оправдан. На вычислительных ядрах можно оценить эффектив­ность выполнения компьютером базовых фрагментов многих реальных программ. Но поскольку никакое ядро само по себе не заменит всей про­граммы целиком, то в состав пакета были включены упрощенные варианты реальных приложений. По сравнению с реальными аналогами эти приложе­ния имеют более регулярную структуру и реализуют упрощенную модель, что явилось результатом компромисса между "реальностью" тестов и слож­ностью их переноса с компьютера на компьютер.
Ключевыми в вычислительных ядрах пакета являются следующие операции.
□ ЕР (Embarrassingly Parallel). Каждым параллельным процессом выполня­ется генерация большого числа псевдослучайных чисел. Взаимодействие процессов требуется только в конце получения очередной порции чисел.
Глава 3. Архитектура параллельных вычислительных систем
173
П IS (Integer Sort). Вариант сортировки большого массива целых чисел.
□  FT (Fourier Transform). Быстрое преобразование Фурье на трехмерной решетке.
□  MG (MultiGrid). Решение трехмерного уравнения Пуассона на основе многосеточного метода.
□  CG (Conjugate Gradient). Реализация метода сопряженных градиентов для нахождения наименьшего собственного значения разреженной симмет­ричной положительно определенной матрицы.
Модельные приложения отражают решение систем дифференциальных уравнений в частных производных на логически структурированных сетках с использованием трех различных неявных методов релаксации.
□  Тест LU включает схему симметричной последовательной верхней релак­сации, отражая решение регулярно разреженных, блочных (5x5), нижних и верхних треугольных систем.
□  Тест SP содержит решение последовательности независимых скалярных пятидиагональных систем, каждая из которых ориентирована вдоль трех взаимно ортогональных направлений в вычислительном пространстве.
□  Тест ВТ близок к тесту SP, отличаясь, в основном, решением блочных (5x5) трехдиагональных систем вместо скалярных пятидиагональных сис­тем. Как в ВТ, так и в SP используются варианты классического неяв­ного метода попеременных направлений.
В процессе своего развития, пакет прошел два этапа. Основное различие эта­пов заключается в правилах написания и адаптации тестов для новых компь­ютеров. Первая версия NPB 1.0 фиксировала только постановку решаемых задач, входные данные и результаты вычислений для проверки корректности расчетов. Для каждой конкретной вычислительной системы исследователи могли выбрать наиболее подходящий метод, структуры данных, способ орга­низации программы. Конечно же, некоторые ограничения все же накладыва­лись. В частности, запрещалось явное программирование на ассемблере, по­скольку это смещало акценты в целях и задачах тестирования. Но в целом предоставленная возможность выбора, с помощью которого можно было учесть особенности архитектуры компьютера, является оправданной.
По большому числу компьютеров были получены интересные результаты, но тестирование в таком виде не прижилось. Основных причин было две. Во-первых, это высокая трудоемкость создания варианта тестов для каждой новой вычислительной платформы. По сути дела, каждый раз тесты писа­лись заинтересованным коллективом заново. Здесь же кроется и вторая причина — как сравнивать данные проведенного тестирования? Какую по­лезную информацию сможет почерпнуть пользователь из такого тестирова­ния? В случае хорошего результата можно было сказать только одно: суще­ствует коллектив, который может эффективно решить поставленную задачу
174
Часть /. Параллельные вычислительные системы
на данном компьютере. Очень часто такой коллектив формировался из чис­ла сотрудников компании, производящей тестируемый компьютер. Сроки создания теста не понятны, трудоемкость процесса не ясна, исходные тек­сты, как правило, не распространялись, для оценки были доступны только конечные цифры. Это скорее напоминало соревнование фирм-произ­водителей, которые по сравнению с обычными пользователями работали в заведомо идеальных условиях. По сути полученные цифры являлись анало­гом пиковой производительности, но уже применительно к конкретной за­даче, поскольку производительности выше этого значения пользователь не получит. Подобные соображения привели к созданию последующей версии пакета NPB.
Начиная с версии NPB 2.0, появившейся в конце 1995 года, фиксировались алгоритмы и исходные тексты для всех тестов. Основная ориентация дела­лась на параллельные компьютеры с большим числом процессоров и рас­пределенной иерархической памятью. В качестве средств программирования использовались Fortran 77 и MPI. Ко всем исходным текстам предоставили свободный доступ, что позволило собрать данные для большого числа вы­числительных систем.
Изменилась и методика тестирования. В отличие от первой версии в NPB 2.0 разрешалось лишь два уровня оптимизации. Первый уровень не допускал никаких изменений исходного текста, а на втором уровне можно было изменить не более 5% кода. Чаще всего на практике рассматривается первый уровень.
Со временем и в тестах NPB столкнулись с проблемой подбора разумного размера задачи для больших вычислительных систем. В описании тестов теперь фигурируют пять различных классов решаемых задач, на практике чаще всего используются три старших класса: А, В и С.
Обе версии тестов NPB в совокупности дают прекрасный материал для ана­лиза эффективности функционирования вычислительных систем. Результа­ты первой версии дают профессионально полученную верхнюю оценку про­изводительности для каждого теста. Эта работа была выполнена экспертами очень высокой квалификации. Данные второй версии пакета NPB показы­вают значения, которые будут характерны для, скажем так, "грамотного" пользователя. Сравнение этих величин дает много полезной информации. В частности, увидев верхнюю оценку, можно сразу понять, стоит ли тратить усилия на дальнейшую оптимизацию кода.
Очень важно и то, что различные тесты пакета NPB позволяют по-разному взглянуть на характеристики программно-аппаратной среды вычислитель­ной системы. В частности, тесты IS и FT требуют не только эффективной реализации глобальных операций, но и высокой пропускной способности сети для передачи больших сообщений. Тест LU характерен передачей большого числа относительно небольших сообщений, что накладывает же-
Глава 3. Архитектура параллельных вычислительных систем
175
сткие ограничения на величину латентности коммуникационной среды. Тест SP отличается малой степенью повторного использования данных, по­этому для него важна большая пропускная способность тракта процессор-память. Напротив, тесты ВТ и LU значительную часть времени тратят на обращение плотных матриц 5x5, поэтому для них важнее эффективная рабо­та кэш-памяти и оптимальное использование регистров.
Этот же набор тестов показал, насколько важной характеристикой является коммуникационный профиль приложений. На небольших параллельных ком­пьютерах коммуникационный профиль программ был не столь интересен. С распространением вычислительных систем, содержащих десятки и сотни процессоров, это понятие вышло на передний план. Длина сообщений, ин­тенсивность передачи сообщений в разные моменты времени работы про­граммы, топология передач сообщений между процессорами, накладные расходы на организацию взаимодействия, масштабируемость коммуникаци­онной структуры программы, структура синхронных и асинхронных посы­лок — эти и ряд других характеристик определяют коммуникационный профиль приложения.
Детальный анализ коммуникационного профиля тестов пакета NPB дает очень много полезной информации. В частности, взаимодействие парал­лельных процессов в тестах FT и IS опирается на коллективные операции, в тестах ВТ и SP используются асинхронные пересылки, а тесты LU и MG построены на основе синхронной передачи сообщений. Для тестов SP и ВТ характерен большой объем данных, пересылаемых с каждого процессора, для LU и MG свойственна небольшая средняя длина сообщений. Для многих тес­тов пакета разница между минимальной и максимальной длиной сообщений составляет несколько порядков. Подобное исследование и описание комму­никационных свойств пакета NPB можно найти, например, в [66].
При всех положительных свойствах пакета NAS Parallel Benchmarks, полной картины о производительности компьютера он все же не дает. Безусловно, NPB является одним из лучших наборов тестов, предназначенных для ана­лиза эффективности параллельных вычислительных систем. Однако у пакета есть и большой недостаток — его поддержкой в настоящее время никто не занимается...
Среди известных тестов, используемых сегодня для оценки производитель­ности компьютеров, можно назвать тесты SPEC. Держателем тестов SPEC является Standard Performance Evaluation Corporation. Это некоммерческий консорциум, в который входят производители вычислительных систем и программного обеспечения, учебные и научные организации. Основная за­дача консорциума состоит в разработке тестов для самых разных програм­мно-аппаратных сред, моделирующих реальные рабочие нагрузки. В состав тестового набора входят тесты вычислительного характера, тесты скорости работы системы в качестве Web-серверов, почтовых серверов, тесты графи-
176
Часть I. Параллельные вычислительные системы
ческих подсистем и многие другие. В их состав включен и пакет SPEChpc96, призванный отразить оценку производительности многопроцессорных вы­числительных систем. Состав тестов часто меняется, что вызвано желанием консорциума оперативно отражать последние достижения в развитии вы­числительной техники.
В середине 2001 года появился пакет программ SPEC ОМРМ2001, предна­значенный для измерения производительности параллельных систем с об­щей памятью, содержащих от 4 до 32 процессоров. Измерение проводится на программах, записанных в терминах ОрепМР. Последователем данного пакета является SPEC OMPL2001, расширяющий число процессоров в тес­тируемой конфигурации компьютера до 512.
Но так уж сложилось, что тесты SPEC, в основном, используются для оценки производительности традиционных систем. Здесь у разработчиков есть боль­шой опыт, задача более обозримая, разработаны методики. Широкого при­знания в мире параллельных компьютеров тесты SPEC пока не получили.
Как мы уже видели, одна из серьезных проблем многих тестов состоит в аккуратном подборе размера задачи. Так было с тестом L1NPACK, для кото­рого рассматривались варианты матриц 100x100, 300x300, 1000x1000 и мат­рицы максимального размера, помещающиеся в память компьютера. Так было с пакетом NPB, включающим пять характерных размеров задач. Одной из первых удачных попыток решить проблему подбора размера задачи стал тест HINT, изменяющий вычислительную нагрузку в процессе своей работы [51]. Данное свойство позволяет тесту HINT оценить эффективность работы компьютера в различных режимах работы с памятью (регистры, кэш, основ­ная память), что абсолютно не было свойственно тестам с фиксированным размером задачи. Эта же особенность дает возможность получать многие характеристики рассмотренных выше тестов SPEC, L1NPACK и NPB на ос­нове результатов работы теста HINT.
Однако что бы ни говорили создатели тестовых пакетов и программ, скеп­сис по поводу получаемых с помощью бенчмарков результатов у пользовате­лей пока остается. Во многом это связано с тем, что тестирование не дает полного представления о работе компьютера в различных режимах. Любой тест приоткрывает лишь часть общей картины. Результат его работы можно условно считать точкой в пространстве, описывающем поведение компью­тера в целом. Если рассматривается набор тестов, например, NPB, то в про­странстве будем иметь несколько точек. Ясно, что кардинально это пробле­му не решает. Объем полученной выборки не может быть сравним со всем многообразием вариантов поведения тестируемого компьютера на реальных программах и наборах данных.
Хорошим вариантом для описания свойств компьютера можно было бы считать возможность выделения в таком пространстве некоторого набора характерных опорных точек. Но что считать характерной точкой и как ин-
Глава 3. Архитектура параллельных вычислительных систем
177
терполировать поведение компьютера на конкретной программе по значе­ниям базовых параметров в выделенных характерных точках? Другой способ описания можно попытаться найти через определение функциональной за­висимости свойств компьютера от параметров его программно-аппаратной среды. Однако в аналитическом виде достаточно точные оценки получить крайне сложно, а на методы вычислительного характера в настоящее время опирается только тест HINT.
Поскольку никакое одно число или даже набор чисел не будут универсаль­ной характеристикой производительности компьютера, то при необходимо­сти получения истинной картины о свойствах компьютера идут по пути комплексного тестирования программно-аппаратной среды в целом. Определя­ют параметры работы вычислительного комплекса на большом наборе про­грамм, имеющих различные вычислительно-коммуникационные характери­стики. Такая работа, как правило, тяжела и трудоемка, но другого пути в настоящий момент нет. В зависимости от целей тестирования выделяют и используют следующие уровни:
□  базовый уровень программного обеспечения: тестирование эффективности работы операционной системы, компиляторов и систем программирова­ния. Этот уровень рассматривается далеко не всегда, но нужно учесть, что все последующие действия будут "видны" через призму данного уров­ня (если компилятор плох, то хороших результатов не будет ни на одном другом уровне);
□  базовый уровень аппаратуры: определение скорости выполнения элемен­тарных операций, скорости обмена между различными уровнями иерар­хии памяти и объемы доступной памяти на каждом уровне (определение объемов важно для последующей интерпретации результатов);
□  уровень операций ввода/вывода: анализ эффективности различных режимов чтения и записи данных при работе с внешними устройствами, опреде­ление скорости выполнения основных операций с файлами и целесооб­разности выполнения асинхронных операций ввода/вывода;
□  базовый коммуникационный уровень: определение параметров среды взаи­модействия параллельных процессов, эффективности выполнения основ­ных коммуникационных процедур и примитивов синхронизации. В на­стоящее время этот уровень чаще всего подразумевает определение латентности и скорости передачи данных по коммуникационной сети в различных режимах, а также тестирование эффективности работы конст­рукций MPI;
□  коммуникационный уровень приложений: исследование эффективности ото­бражения различных логических топологий процессов на коммуникаци­онную среду, получение и анализ коммуникационных профилей харак­терных параллельных программ;
178
Часть I. Параллельные вычислительные системы
П уровень модельных приложений: определение характеристик компьютера при выполнении простых программ различной структуры;
уровень приложений: комплексная проверка характеристик компьютера при выполнении реальных программ.
Ясно, что задача определения производительности параллельных компьюте­ров сложна и на сегодня еще далека от своего решения. Слишком много параметров, зачастую противоречивых, приходится учитывать при конструи­ровании реальных бенчмарков. Состав операций, коммуникационный про­филь, требуемый размер памяти на каждом уровне иерархии, простота пере­носа между вычислительными платформами, время работы, базовые вычислительные конструкции, простота интерпретации результатов — все это, как и многое другое, должно приниматься в расчет авторами тестов.
Ситуация не простая. Но, с другой стороны, задача ясна, необходима и до сих пор еще не решена — так ли это плохо? Цель определена и есть боль­шое поле для деятельности...
Вопросы и задания
1.   Почему цена компьютеров не может служить основой критерия для оценки их производительности?
2.   Сравните пиковую производительность какого-либо современного спецпроцес­сора с пиковой производительностью универсального микропроцессора. С ка­кой производительностью универсальный процессор будет выполнять "наиболее удобные" для спецпроцессора операции?
3.   Возьмите любой современный микропроцессор. Чему равна его производитель­ность, измеренная на пакетах LINPACK, SPEC, STREAM? Как эти данные со­относятся с его пиковой производительностью?
4.   Какие преимущества и недостатки в использовании программы перемножения двух матриц в качестве бенчмарка для параллельных вычислительных систем?
5.   Перечислите, какими свойствами должен обладать хороший бенчмарк для па­раллельных вычислительных систем?
6.   Напишите тестовую программу, определяющую размер кэш-памяти первого и второго уровней и объем доступной оперативной памяти.
7.   Подумайте, какие другие задачи можно было бы использовать в качестве теста, аналогично задаче решения системы уравнений в тесте LINPACK?
8.   Напишите программу, которая показывает, насколько может изменяться произ­водительность процессора в зависимости от расположения данных в его памяти.
9.   Постройте и проанализируйте коммуникационный профиль вашей параллель­ной программы. Определите основные источники затрат на коммуникации.
10. Выделите типичные коммуникационные схемы параллельных программ. Напи­шите тест, реализующий данные схемы и сравните эффективность их работы в различных средах.
ЧАСТЬ II
Параллельное программирование
Глава 4
Большие задачи
и параллельные вычисления
Если у веревки есть один конец, значит у нее должен быть и другой.
Из законов Мерфи
Если создают большие вычислительные системы, значит они для чего-то нужны. Это что-то — большие задачи, которые постоянно возникают в са­мых различных сферах деятельности. Практическая потребность или просто любознательность всегда ставили перед человеком трудные вопросы, на ко­торые нужно было получать ответы. Строится высотное задание в опасной зоне. Выдержит ли оно сильные ветровые нагрузки и колебания почвы? Проектируется новый тип самолета. Как он будет вести себя при различных режимах полета? Уже сейчас зафиксировано потепление климата на нашей планете и отмечены негативные последствия этого явления. А каким будет климат через сто и более лет?
Подобного рода вопросы сотнями и тысячами возникают в каждой области. Уже давно разработаны общие принципы поиска ответов. Первое, что пыта­ются сделать, — это построить математическую модель, адекватно отражаю­щую изучаемое явление или объект. Как правило, математическая модель представляет собой некоторую совокупность дифференциальных, интеграль­ных, алгебраических или каких-то других соотношений, определенных в об­ласти, так или иначе связанной с предметом исследований. Решения этих со­отношений и должны давать ответы на поставленные вопросы.
Как следует из предыдущих глав, существующие компьютеры сами по себе не могут решить ни одной содержательной задачи. Они способны выпол­нять лишь небольшое число очень простых действий. Вся их интеллектуаль­ная сила определяется программами, составленными человеком. Программы также реализуют последовательности простых действий. Но эти действия целенаправленные. Поэтому искусство решать задачи на компьютере есть искусство превращения процесса поиска решения в процесс выполнения последовательности простых действий. Называется такое искусство разра­боткой алгоритмов.
Если математическая модель построена, то следующее, что надо сделать, — это разработать алгоритм решения задачи, описывающей модель. Довольно часто в его основе лежит принцип дискретизации изучаемого объекта и, ес­ли необходимо, окружающей его среды. Исследуемый объект и среда разби-
182
Часть II. Параллельное программирование
ваются на отдельные элементы и на эти элементы переносятся связи, дик­туемые математической моделью. В результате получаются системы уравне­ний с очень большим числом неизвестных. В простейших случаях число не­известных пропорционально числу элементов. Для сложных моделей оно на несколько десятичных порядков больше. Вообще говоря, чем мельче выби­рать элементы разбиения, тем точнее получается результат. В большинстве задач уровень разбиения определяется только возможностью компьютера решить возникающую систему уравнений за разумное время. Но в некото­рых задачах, таких как изучение структуры белковых соединений, расшиф­ровка геномов живых организмов, разбиение по самой сути может доходить до уровня отдельных атомов. И тогда вычислительные проблемы становятся исключительно сложными даже для самых мощных компьютеров, в том числе, параллельной архитектуры.
Во все времена были задачи, решение которых находилось на грани воз­можностей существовавших средств. Вплоть до середины двадцатого столе­тия единственным вычислительным средством был человек, вооруженный в лучшем случае арифмометром или электрической счетной машиной. Ре­шение наиболее крупных задач требовало привлечения до сотни и более расчетчиков. Так как вычисления они проводили одновременно, то по су­ществу такой коллектив своими действиями моделировал работу многопро­цессорной вычислительной системы. Роль отдельного процессора в этой системе выполнял отдельный человек. При подобной организации вычисле­ний никак не могло появиться большое число крупных задач. Их просто некому было считать. Поэтому еще 50—60 лет назад весьма распространен­ным являлось мнение, что несколько мощных компьютеров смогут решить все практически необходимые задачи.
История опровергла эти прогнозы. Компьютеры оказались настолько эф­фективным инструментом, что новые и очень крупные задачи стали возни­кать не только в традиционных для вычислений областях, но даже в таких, где раньше большие вычислительные работы не проводились. Например, в военном деле, управлении, биологии и т. п. К тому же, использование ком­пьютеров освободило человека от нудного и скрупулезного труда, связан­ного с выполнением операций. Это позволило ему направить свой интел­лект на постановку новых задач, построение математических моделей, разработку алгоритмов и при этом не бояться больших объемов вычислений. Подобные обстоятельства и привели к массовой загрузке компьютеров ре­шением самых разных проблем, в том числе, очень крупных. Случилось то, что и должно было случиться: за исключением начального периода развития компьютеров спрос на самые большие вычислительные мощности всегда опережал и опережает предложение таких мощностей. Другое дело, что реа­лизовать как спрос, так и предложение оказалось трудно.
Большие задачи и компьютеры представляют две взаимосвязанные сферы деятельности, два конца одной веревки. Если был один конец — большие
Глава 4. Большие задачи и параллельные вычисления
183
задачи, значит должен был появиться и другой — компьютеры. Потребность в решении больших задач заставляет создавать более совершенные компью­теры. В свою очередь, более совершенные компьютеры позволяют улучшать математические модели и ставить еще большие задачи. В конце концов, обыкновенные компьютеры превратились в параллельные. Задачи от этого параллельными не стали, но начали развиваться алгоритмы с параллельной структурой вычислений. В результате опять появилась возможность увели­чить размеры решаемых задач. Теперь на очереди стоят оптические и кван­товые компьютеры. Конца этому процессу не видно.
§ 4.1. Большие задачи и большие компьютеры
На примере проблем моделирования климатической системы и исследова­ния обтекания летательных аппаратов покажем, как возникают очень боль­шие задачи и что за этим следует дальше.
В современном понимании климатическая система включает в себя атмо­сферу, океан, сушу, криосферу и биоту [26]. Климатом называется ансамбль состояний, который система проходит за достаточно большой промежуток времени. Климатическая модель — это математическая модель, описываю­щая климатическую систему. В основе климатической модели лежат уравне­ния динамики сплошной среды и уравнения равновесной термодинамики. Кроме этого, в модели описываются все энергозначимые физические про­цессы: перенос излучения в атмосфере, фазовые переходы воды, облака и конвенция, перенос малых газовых примесей и их трансформация, мелко­масштабная турбулентная диффузия тепла и диссипация кинетической энергии и многое другое. В целом модель представляет систему трехмерных нелинейных уравнений с частными производными. Решения этой системы должны воспроизводить все важные характеристики ансамбля состояний реальной климатической системы.
Даже без дальнейших уточнений понятно, что климатическая модель ис­ключительно сложна. Работая с ней, приходится принимать во внимание ряд серьезных обстоятельств. В отличие от многих естественных наук, в климате нельзя поставить глобальный натурный целенаправленный экспе­римент. Следовательно, единственный путь изучения климата — это прово­дить численные эксперименты с математической моделью и сравнивать мо­дельные результаты с результатами наблюдений. Однако и здесь не все так просто. Математические модели для разных составляющих климатической системы развиты не одинаково. Исторически первой стала создаваться мо­дель атмосферы. Она и в настоящее время является наиболее развитой. К тому же, за сотни лет наблюдений за ее состоянием накопилось довольно много эмпирических данных. Так что в атмосфере есть с чем сравнивать
184
Часть II. Параллельное программирование
модельные результаты. Для других составляющих климатической системы результатов наблюдений существенно меньше. Слабее развиты и соответст­вующие математические модели. По существу только начинает создаваться модель биоты.
Общая модель климата пока еще далека от своего завершения. Не во всем даже ясно, как могла бы выглядеть совместная идеальная модель. Чтобы приблизить климатическую модель к реальности, приходится проводить очень много численных экспериментов и на основе анализа результатов вносить в нее коррективы. Как правило, пока эксперименты проводятся с моделями для отдельных составляющих климата или для некоторых их ком­бинаций. Наиболее часто рассматривается совместная модель атмосферы и океана. Недостающие данные от других составляющих берутся либо из ре­зультатов наблюдений, либо из каких-то других соображений.
Важнейшей проблемой современности является проблема изменения климата под влиянием изменения концентрации малых газовых составляющих, таких как углекислый газ, озон и др. Как уже отмечалось, климатический прогноз может быть осуществлен только с помощью численных экспериментов над моделью. Поэтому ясно, что для того чтобы научиться предсказывать измене­ние климата в будущем, уже сегодня надо иметь возможность проводить большой объем вычислений. Попробуем хотя бы как-то его оценить.
Рассмотрим модель атмосферы как важнейшей составляющей климата и предположим, что мы интересуемся развитием атмосферных процессов на протяжении, например, 100 лет. При построении алгоритмов нахождения численных решений используется упоминавшийся ранее принцип дискрети­зации. Общее число элементов, на которые разбивается атмосфера в совре­менных моделях, определяется сеткой с шагом в 1° по широте и долготе на всей поверхности земного шара и 40 слоями по высоте. Это дает око­ло 2,6х106 элементов. Каждый элемент описывается примерно 10 компонен­тами. Следовательно, в любой фиксированный момент времени состояние атмосферы на земном шаре характеризуется ансамблем из 2,6х107 чисел. Ус­ловия обработки численных результатов требуют нахождения всех ансамб­лей через каждые 10 минут, т. е. за период 100 лет необходимо определить около 5,3х104 ансамблей. Итого, только за один численный эксперимент приходится вычислять 1,4х1014 значимых результатов промежуточных вычис­лений. Если теперь принять во внимание, что для получения и дальнейшей обработки каждого промежуточного результата нужно выполнить 102— 103 арифметических операций, то это означает, что для проведения одного численного эксперимента с глобальной моделью атмосферы необходимо вы­полнить порядка 1016—1017 арифметических операций с плавающей запятой.
Таким образом, вычислительная система с производительностью 1012 опера­ций в секунду будет осуществлять такой эксперимент при полной своей за­грузке и эффективном программировании в течение нескольких часов. Ис-
Глава 4. Большие задачи и параллельные вычисления
185
пользование полной климатической модели увеличивает это время, как ми­нимум, на порядок. Еще на порядок может увеличиться время за счет не лучшего программирования и накладных расходов при компиляции про­грамм и т. п. А сколько нужно проводить подобных экспериментов! Поэто­му вычислительная система с триллионной скоростью совсем не кажется излишне быстрой с точки зрения потребностей изучения климатических проблем.
Большой объем вычислений в климатической модели и важность связанных с ней выводов для различных сфер деятельности человека являются посто­янными стимулами в деле совершенствования вычислительной техники. Так, на заре ее развития одним из следствий необходимости разработки эф­фективного вычислительного инструмента для решения задач прогноза по­годы стало создание Дж. фон Нейманом самой теории построения "обык­новенного" компьютера. В нашумевших в свое время проектах вычисли­тельных систем пятого поколения и во многих современных амбициозных проектах климатические модели постоянно фигурируют как потребители очень больших скоростей счета. Наконец, существуют проекты построения вычислительных систем огромной производительности, предназначенных специально для решения климатических проблем.
Имеется и обратное влияние. Развитие вычислительной техники позволяет решать задачи все больших размеров. Корректное решение больших задач заставляет развивать новые разделы математики. Как уже говорилось, изу­чение предсказуемости климата требует определения решения системы дифференциальных уравнений на очень большом отрезке времени. Но это имеет смысл делать лишь в том случае, когда решение обладает определен­ной устойчивостью к малым возмущениям. Одна из составляющих климати­ческой системы, а именно атмосфера, относится к классу так называемых открытых (то есть имеющих внешний приток энергии и ее диссипацию) не­линейных систем, траектории которых неустойчивы поточечно. Такие сис­темы имеют совершенно особое свойство. При больших временах их реше­ния оказываются вблизи некоторого многообразия относительно небольшой размерности. Открытие данного свойства послужило сильным толчком к развитию теории нелинейных диссипативных систем методами качествен­ной теории дифференциальных уравнений. В свою очередь, это существен­но усложнило вычислительные задачи, возникающие при климатическом прогнозе, что опять требует больших вычислительных мощностей.
Приведенные доводы, скорее всего, убедили читателя, что для исследования климата действительно нужна очень мощная вычислительная система. Тем более, что климат нельзя изучать, выполняя над ним целенаправленные эксперименты. Однако остается вопрос, насколько много необходимо иметь таких систем. Ведь основная деятельность человека связана с созданием ма­териальных объектов, с чем или над чем можно проводить натурные экспе­рименты. Например, летательные аппараты можно испытывать в аэродина-
186
Часть II. Параллельное программирование
мических трубах, материалы — на стендах, сооружения — на макетах и т. п. Конечно, здесь также нужно проводить какие-то расчеты. Но стоит ли для этих целей использовать вычислительные системы, стоящие не один милли­он долларов? Возможно, для подобных расчетов достаточно ограничиться хорошим персональным компьютером или рабочей станцией?
Ответ зависит от конкретной ситуации. Возьмем для примера такую про­блему, как разработка ядерного оружия. Пока не был установлен мораторий на ядерные испытания, натурные эксперименты давали много информации о путях совершенствования оружия. И хотя при этом приходилось прово­дить очень большой объем вычислений, все же удавалось обходиться вычис­лительной техникой не самого высокого уровня. После вступления морато­рия в силу остался единственный путь совершенствования оружия — это проведение численных экспериментов с математической моделью. По своей сложности соответствующая модель сопоставима с климатической и для ра­боты с ней уже нужна самая мощная вычислительная техника. В аналогич­ном положении находится ядерная энергетика. А такая проблема, как рас­шифровка генома человека. Здесь также невозможны никакие глобальные эксперименты и опять нужна мощная техника. Вообще, оказывается, что проблем, где невозможно или трудно проводить натурные эксперименты, не так уж мало. Это — экономика, экология, астрофизика, медицина и т. д. При этом часто возникают очень большие задачи.
Интересно отметить, что даже там, где натурные эксперименты являются привычным инструментом исследования, большие вычислительные системы начинают активно использоваться. Рассмотрим, например, проблему выбора компоновки летательного аппарата, обладающего минимальным лобовым со­противлением, максимально возможными значениями аэродинамического качества и допустимого коэффициента подъемной силы при благоприятных характеристиках устойчивости и управляемости в эксплуатационных режимах. Для ее решения традиционно использовались продувки отдельных деталей аппарата или его модели в аэродинамических трубах. Но вот несколько цифр [37]. При создании в начале века самолета братьев Райт эксперименты в аэро­динамических трубах обошлись в несколько десятков тысяч долларов, бом­бардировщик 40-х гг. прошлого столетия потребовал миллион, а корабль многоразового использования "Шатл" — 100 млн. долларов. Столь же сильно возрастает время продувки в расчете на одну трубу — почти 10 лет для совре­менного аэробуса. Однако несмотря на огромные денежные и временные за­траты, продувки в аэродинамических трубах не дают полной картины обтека­ния хотя бы просто потому, что обдуваемый образец нельзя окружить датчиками во всех точках. Для преодоления этих трудностей также пришлось обратиться к численным экспериментам с математической моделью [28].
Определять аэродинамические характеристики летательного аппарата нужно уже на стадии проектирования. Необходимо также давать оценку различным вариантам компоновки, оптимизировать геометрические параметры, опре-
Глава 4. Большие задачи и параллельные вычисления
187
делять внешний облик с наилучшими характеристиками. Знание соответст­вующих данных нужно и в процессе летных испытаний при анализе их ре­зультатов для выработки рекомендаций по устранению выявленных недос­татков. Эта задача не может быть решена без информации о характере обтекания элементов аппарата и знания поля течения в целом. Как уже го­ворилось, экспериментальное получение такой информации по мере услож­нения летательных аппаратов становится все более труднодоступным делом.
В практике работы конструкторских организаций используются инженерные методы расчета, основанные на упрощенных математических моделях. Эти методы пригодны на самых ранних этапах проектирования. Они не могут учесть деталей течения, необходимых на углубленных этапах проектирова­ния, и не могут обеспечить требуемую точность расчетов. В большой облас­ти изменения физических параметров упрощенные модели вообще не при­менимы. Для углубленной стадии проектирования летательных аппаратов перспективными являются математические модели, основанные на неста­ционарных пространственных уравнениях динамики невязкого, нетеплопро­водного газа, в том числе, учитывающие уравнения состояния и уравнения вязкого пограничного слоя между поверхностью аппарата и обтекаемой сре­дой. Модели принципиально различаются для случаев сверхзвукового и до­звукового набегающего потока. Вообще говоря, случай дозвукового обтека­ния сложнее, т. к. около летательного аппарата возможно образование мест­ных сверхзвуковых зон. Все части аппарата здесь взаимно влияют друг на друга, и задача должна решаться глобально, т. е. во всей области, окружаю­щей летательный аппарат. Именно для расчета дозвуковых течений необхо­димо использовать вычислительные системы высокой производительности с большим объемом памяти.
Приведем некоторые данные конкретного расчета, взятые из той же работы [28]. Рассчитывались различные варианты дозвукового обтекания летатель­ного аппарата сложной конструкции. Математическая модель требует зада­ния граничных условий на бесконечности. Реально, область исследования берется конечной. Однако из-за обратного влияния границы ее удаление от объекта должно быть значительным по всем направлениям. На практике это составляет десятки длин размера аппарата. Таким образом, область исследо­вания оказывается трехмерной и весьма большой. При построении алгорит­мов нахождения численных решений опять используется принцип дискре­тизации. Из-за сложной конфигурации летательного аппарата разбиение выбирается очень неоднородным. Общее число элементов, на которые раз­бивается область, определяется сеткой с числом шагов порядка 102 по каж­дому измерению, т. е. всего будет порядка 106 элементов. В каждой точке надо знать 5 величин. Следовательно, на одном временном слое число неиз­вестных будет равно 5х106. Для изучения нестационарного режима прихо­дится искать решения в 102—104 слоях по времени. Поэтому одних только значимых результатов промежуточных вычислений необходимо найти около
188
Часть II. Параллельное программирование
109—10п. Для получения каждого из них и дальнейшей его обработки нуж­но выполнить 102—103 арифметических операций. И это только для одного варианта компоновки и режима обтекания. А всего требуется провести рас­четы для десятков вариантов. Приближенные оценки показывают, что об­щее число операций для решения задачи обтекания летательного аппарата в рамках современной модели составляет величину 1015—1016. Для достижения реального времени выполнения таких расчетов быстродействие вычисли­тельной системы должно быть не менее 109—1010 арифметических операций с плавающей запятой в секунду при оперативной памяти не менее 109 слов.
Успехи в численном решении задач обтекания позволили создать комбини­рованную технологию разработки летательных аппаратов. Теперь основные эксперименты, особенно на начальной стадии проектирования, проводятся с математической моделью. Они позволяют достаточно достоверно опреде­лить оптимальную компоновку, проанализировать все стадии процесса об­текания, выявить потенциально опасные режимы полета и многое другое. Все это дает возможность снизить до минимума дорогостоящие натурные эксперименты в аэродинамических трубах и сделать их более целенаправ­ленными. Конечно, численные эксперименты на высокопроизводительных вычислительных системах тоже обходятся недешево. Но все же они значи­тельно дешевле, чем натурные эксперименты. Кроме этого, их стоимость постоянно снижается. Например, по данным США она уменьшается в 10 раз каждые 10 лет. Объединение численных и натурных экспериментов в единый технологический процесс привело к принципиально новому уровню аэродинамического проектирования. В результате значительно возросло ка­чество, уменьшились стоимость, сроки проектирования и время испытаний летательных аппаратов. Совершенствование математических моделей сдела­ет этот процесс еще более эффективным. Как и в примере с климатом, бо­лее совершенные модели потребуют применения более мощных вычисли­тельных систем. В свою очередь, появление новых систем опять даст толчок совершенствованию моделей и т. д. Снова убеждаемся в том, что большие задачи и большие вычислительные системы с точки зрения их развития ока­зывают влияние друг на друга.
В рассмотренной проблеме выбора компоновки летательного аппарата легко увидеть нечто большее, имеющее отношение к самым различным областям деятельности человека. В самом деле, при создании тех или иных изделий, механизмов и сооружений, так же как и при проведении многих научных экспериментов весь процесс от возникновения идеи до ее реализации мож­но грубо разбить на следующие этапы. Сначала каким-то способом разраба­тывается общий проект и готовится технологическая документация. Затем строится опытный образец или его макет. И, наконец, проводится испыта­ние. По его результатам в опытный образец вносятся изменения и снова проводится испытание. Цикл образец—испытание—образец повторяется до тех пор, пока опытный образец не станет действующим, удовлетворяя всем
Глава 4. Большие задачи и параллельные вычисления
189
заложенным в проект требованиям. Проведение каждого испытания и вне­сение очередных изменений в опытный образец почти всегда требует много денег и много времени. Поэтому одна из общих задач заключается в том, чтобы на пути превращения опытного образца в действующий сократить до минимума как число испытаний, так и их стоимость и время проведения. По существу это можно сделать единственным способом — заменить часть натурных экспериментов или большинство из них, а в идеале даже все, экс­периментами с математическими моделями. Значимость численных экспе­риментов в общем процессе зависит от качества модели. Если она хорошо отражает создаваемый или изучаемый объект, натурные эксперименты ока­зываются необходимыми достаточно редко, что, в свою очередь, приводит к большим материальным и временным выгодам. В этой ситуации весь про­цесс во многом превращается в своего рода компьютерную игру, в которой можно посмотреть различные варианты решений, обнаружить и исследовать узкие места, выбрать оптимальный вариант, проанализировать последствия такого выбора и т. д. И лишь изредка отдельные решения придется прове­рять на натурных экспериментах. Это обстоятельство, безусловно, стимули­рует создание самых совершенных математических моделей в различных областях.
Очевидно, что использование математических моделей невозможно без применения вычислительной техники. Но оказывается, что для очень мно­гих случаев нужна не просто какая-нибудь техника, а именно высокопроиз­водительная. В самом деле, изучаем ли мы процесс добычи нефти, или прочность кузова автомобиля, или процессы преобразования электрической энергии в больших трансформаторах и т. п., — исследуемые объекты явля­ются трехмерными. Чтобы получить приемлемую точность численного ре­шения, объект нужно покрыть сеткой не менее чем 100x100x100 узлов. В каждой точке сетки нужно определить 5—20 функций. Если изучается не­стационарное поведение объекта, то состояние всего ансамбля значений функций нужно определить в 102—104 моментах времени. Поэтому только значимых результатов промежуточных вычислений для подобных объектов нужно получить порядка 109—1011. Теперь надо принять во внимание, что на вычисление и обработку каждого из промежуточных результатов, как по­казывает практика, требуется в среднем выполнить 102—103 арифметических операций. И вот мы уже видим, что для проведения только одного варианта численного эксперимента число операций порядка 1011—1014 является впол­не рядовым. А теперь учтем необходимое число вариантов, накладные рас­ходы на время решения задачи, появляющиеся за счет качества программи­рования, компиляции и работы операционной системы. И сразу становится ясно, что скорость вычислительной техники должна измеряться миллиарда­ми операций в секунду. Такая техника стоит недешево. Тем не менее, как говорится, игра стоит свеч.
190
Часть II. Параллельное программирование
Мы неоднократно подчеркивали влияние больших задач на развитие вычис­лительной техники и наоборот. Постепенно в эту сферу втягивается много чего другого. Развитие математического моделирования приводит к более сложным описаниям моделей. Для их осмысления и разработки принципов исследования приходится привлекать новейшие достижения из самых раз­ных областей математики. Дискретизация задач приводит к системам урав­нений с огромным числом неизвестных. Прежние методы их решения не всегда оказываются пригодными по соображениям точности, скорости, тре­буемой памяти, структуре алгоритмов и т. п. Возникают и реализуются но­вые идеи в области вычислительной математики. В конечном счете, для бо­лее совершенных математических моделей создаются новые методы реализации численных экспериментов. Как правило, они требуют больших вычислительных затрат и снова не хватает вычислительных мощностей. А далее опять все идет по кругу, о чем уже не один раз говорилось выше.
Решаясь на регулярное использование численных экспериментов, приходится заботиться о качестве их проведения. Оно определяется многими фактора­ми. Этапы, которые проходит каждый эксперимент, укрупненно отражены на рис. 4.1 Данным рисунком мы хотим подчеркнуть простую мысль: если хотя бы один из этапов на рис. 4.1 окажется неэффективным, то неэффек­тивным будет и весь численный эксперимент.
ИССЛЕДУЕМОЕ ЯВЛЕНИЕ ИЛИ ОБЪЕКТ
1
МАТЕМАТИЧЕСКАЯ МОДЕЛЬ
РЕЗУЛЬТАТ
ЧИСЛЕННЫЙ МЕТОД
АНАЛИЗ
ВЫЧИСЛЕНИИ
1
ПРОГРАММА
КОМПЬЮТЕР
1
КОМПИЛЯТОР
ОПЕРАЦИОННАЯ СИСТЕМА
Рис. 4.1. Этапы численного эксперимента
Следовательно, внимательному изучению должны подвергаться все этапы численного эксперимента. Это важно понять, запомнить и постоянно реали-зовывать на практике. Не правда ли, очень похоже на закон Амдала, о кото­ром упоминалось в связи с производительностью вычислительных систем?
Глава 4. Большие задачи и параллельные вычисления
191
Вопросы и задания
1.   Допустим, что перемножаются две квадратные матрицы. Какие появятся особен­ности в организации вычислительных процессов, если взять матрицы макси­мального размера и стараться решить задачу по возможности быстрее? Рассмот­рите варианты вычислительных систем:
1.1.  С общей памятью и универсальными процессорами.
1.2.  С общей памятью и конвейерными сумматорами, умножителями и делителями.
1.3.  С распределенной памятью и универсальными процессорами.
1.4.  С распределенной памятью и конвейерными сумматорами, умножителями и делителями.
2.   Проделайте то же самое для решения системы линейных алгебраических уравне­ний методом Гаусса.
3.   Что меняется во втором задании, если делители не будут конвейерными?
§ 4.2. Граф алгоритма
и параллельные вычисления
Любая программа для "обыкновенного" компьютера описывает некоторое се­мейство алгоритмов. Выбор конкретного алгоритма при ее реализации опре­деляется тем, как срабатывают условные операторы. Состав и порядок выпол­нения остальных операторов строго задается самой программой. Если в программе отсутствуют условные операторы, то программа изначально описы­вает только один алгоритм. В свою очередь, срабатывания условных операто­ров зависят исключительно от входных данных. Поэтому "обыкновенный" компьютер всегда выполняет какую-то последовательность действий, которая однозначно определена программой и входными данными. Более того, для од­ной и той же программы эта последовательность будет одной и той же на лю­бых моделях "обыкновенного" компьютера. Тем самым заведомо однозначно определяется результат. Все это является, в конечном счете, следствием того, что в любом "обыкновенном" компьютере в любой момент времени может реализоваться только одна операция. Любая другая операция в этот момент может лишь проходить стадию подготовки к реализации.
Иначе обстоит дело на вычислительных системах параллельной архитекту­ры. Теперь в каждый момент времени может выполняться целый ансамбль операций, не зависящих друг от друга. На любой конкретной параллельной системе программа и входные данные однозначно определяют как состав ансамблей, так и их последовательность. Но на разных системах ансамбли и последовательности могут быть разными. Чтобы, тем не менее, гарантировать получение однозначного результата, порядок выполнения всей совокупно­сти операций должен подчиняться некоторому условию.
192
Часть II. Параллельное программирование
Как на "обыкновенном", так и на параллельном компьютере решение задачи находится в результате выполнения множества простых операций. Все опе­рации имеют небольшое число аргументов. Обычно их не более двух. В ка­честве конкретных значений аргументов операции берутся либо входные данные, либо результаты выполнения других операций. Соответствие, какие результаты какими являются аргументами, устанавливается разработчиком программы. Ясно, что любая операция — потребитель аргументов не может начать выполняться раньше, чем закончится выполнение всех операций — поставщиков для нее аргументов. Тем самым на множестве всех операций разработчик программы явно или неявно устанавливает частичный порядок. Для любых двух операций порядок определяет одну из возможностей: либо указывает, какая из операций должна выполняться раньше, либо констати­рует, что обе операции могут выполняться независимо друг от друга. При одном и том же частичном порядке общий временной порядок всего мно­жества операций может быть различным. Несколько позже мы покажем, что любой из этих порядков дает один и тот же результат. Поэтому сохранение частичного порядка, заданного программой, и есть то условие, выполнение которого гарантирует однозначность результата. В рамках одного и того же частичного порядка возможен выбор любой реализации.
Дадим сказанному следующую трактовку. Пусть при фиксированных вход­ных данных программа описывает некоторый алгоритм. Построим ориенти­рованный граф. В качестве вершин возьмем любое множество, например, множество точек арифметического пространства, на которое взаимно­однозначно отображается множество всех операций алгоритма. Возьмем лю­бую пару вершин и, v. Допустим, что согласно описанному выше частично­му порядку операция, соответствующая вершине и, должна поставлять аргу­мент операции, соответствующей вершине v. Тогда проведем дугу из вершины и в вершину v. Если соответствующие операции могут выполняться независимо друг от друга, дугу проводить не будем. В случае, когда аргумен­том операции является начальное данное или результат операции нигде не используется, возможны различные договоренности. Например, можно счи­тать, что соответствующие дуги отсутствуют. Или предполагать, что все входные данные и результаты вводятся и выводятся через специальные уст­ройства ввода/вывода. В этом случае вершины графа, отвечающие таким операциям, и только они не будут иметь соответственно входящие и выхо­дящие дуги. Мы будем поступать в зависимости от обстоятельств. Постро­енный таким образом граф можно было бы назвать графом информацион­ной зависимости реализации алгоритма при фиксированных входных данных. Однако такое название слишком громоздко. Поэтому будем назы­вать его в дальнейшем просто графом алгоритма. Независимо от способа построения ориентированного графа, те его вершины, которые не имеют ни одной входящей или выходящей дуги, будем называть соответственно входными или выходными вершинами графа.
Глава 4. Большие задачи и параллельные вычисления
193
Это понятие требует некоторого пояснения. Граф алгоритма почти всегда за­висит от входных данньгх. Даже если в программе отсутствуют условные опе­раторы, он будет зависеть от размеров массивов, т. к. они определяют общее число выполняемых операций и, следовательно, общее число вершин графа. Так что в действительности граф алгоритма почти всегда есть параме­тризованный граф. От значений параметров зависит, конечно, не только число вершин, но и вся совокупность дуг. Если программа не имеет условных опе­раторов, то как ее саму, так и описанный ею алгоритм будем называть детер­минированными. В противном случае будем называть их недетерминированными. Имеется одно принципиальное отличие графа детерминированного алгоритма от графа недетерминированного алгоритма. Для детерминированного алго­ритма всегда существует взаимно-однозначное соответствие между всеми опе­рациями описывающей его программы и всеми вершинами графа алгоритма. Для недетерминированного алгоритма взаимно-однозначного соответствия при всех значениях параметров, т. е. входных данных, нет. Можно лишь ут­верждать, что в этом случае при каждом наборе значений параметров сущест­вует взаимно-однозначное соответствие между каким-то подмножеством всех операций программы и вершинами графа. Разным значениям параметров мо­гут соответствовать разные подмножества.
В дальнейшем, если не сделаны дополнительные оговорки, мы будем рас­сматривать детерминированные алгоритмы и программы. Причин для введе­ния этого ограничения довольно много. Во-первых, они устроены проще и, естественно, начать исследования именно с них. Во-вторых, класс детермини­рованных алгоритмов и программ весьма широк сам по себе. Далее, многие формально недетерминированные алгоритмы в действительности являются почти детерминированными. Например, в том случае, когда ветвления охва­тывают конечное число операций, не зависящее от значений входных данных. Такие ветвления можно погрузить в более крупные операции, имеющие, тем не менее, конечное число аргументов. И, наконец, если ветвления охватывают большие детерминированные фрагменты, то все равно исследование графа алгоритма сводится к исследованию этих фрагментов.
Введенный в рассмотрение граф алгоритма есть ориентированный ацикли­ческий мультиграф. Его ацикличность следует из того, что в любых про­граммах реализуются только явные вычисления и никакая величина не мо­жет определяться через саму себя. Даже если в программе имеются рекурсивные выражения, то это всего лишь удобная форма описания одно­типных вычислений. При каждом обращении к рекурсии на самом деле реа­лизуются разные операции. В общем случае граф алгоритма есть мульти­граф, т. е. две вершины могут быть связаны несколькими дугами. Это будет тогда, когда в качестве разных аргументов одной и той же операции исполь­зуется одна и та же величина. Будем применять для обозначения графа стандартную символику G= (V, Е), где V— множество вершин, Е— мно­жество дуг графа G.
194
Часть II. Параллельное программирование
Итак, каждое описание алгоритма порождает ориентированный ацикличе­ский мультиграф. Верно и обратное. Если задан ориентированный ацикли­ческий мультиграф, то его всегда можно рассматривать как граф некоторого алгоритма. Для этого каждой вершине нужно поставить в соответствие лю­бую однозначную операцию, имеющую столько аргументов, сколько дуг входит в вершину. Поэтому между алгоритмами и рассматриваемыми гра­фами есть определенное взаимное соответствие.
Утверждение 4.1
Пусть задан ориентированный ациклический граф, имеющий п вершин. Суще­ствует число s < п, для которого все вершины графа можно так пометить одним из индексов 1, 2, ..., s, что если дуга из вершины с индексом / идет в вершину с индексом/ то /</.
Выберем в графе любое число вершин, не имеющих предшествующих, и пометим их индексом 1. Удалим из графа помеченные вершины и инци­дентные им дуги. Оставшийся граф также является ациклическим. Выберем в нем любое число вершин, не имеющих предшествующих и пометим их индексом 2. Продолжая этот процесс, в конце концов, исчерпаем весь граф. Так как при каждом шаге помечается не менее одной вершины, то число различных индексов не превышает числа вершин графа.
Отсюда следует, что никакие две вершины с одним и тем же индексом не связаны дугой. Минимальное число индексов, которым можно пометить все вершины графа, на 1 больше длины его критического пути. И, наконец, для любого целого числа s, не превосходящего общего числа вершин, но боль­шего длины критического пути, существует такая разметка вершин графа, при которой используются все s индексов.
Граф, размеченный в соответствии с утверждением 4.1, называется строгой параллельной формой графа. Если в параллельной форме некоторая вершина помечена индексом к, то это означает, что длины всех путей, оканчиваю­щихся в данной вершине, меньше к. Существует строгая параллельная фор­ма, при которой максимальная из длин путей, оканчивающихся в вершине с индексом к, равна к — 1. Для этой параллельной формы число используемых индексов на 1 больше длины критического пути графа. Среди подобных па­раллельных форм существует такая, в которой все входные вершины нахо­дятся в группе с одним индексом, равным 1. Эта строгая параллельная фор­ма называется канонической. Для заданного графа его каноническая параллельная форма единственна. Группа вершин, имеющих одинаковые индексы, называется ярусом параллельной формы, а число вершин в группе — шириной яруса. Число ярусов в параллельной форме называется высотой па­раллельной формы, а максимальная ширина ярусов — ее шириной. Парал­лельная форма минимальной высоты называется максимальной. Слово "мак­симальная" здесь означает, что в этой параллельной форме в ярусах нахо-
Глава 4. Большие задачи и параллельные вычисления
195
дится в определенном смысле максимальное число вершин. Все канониче­ские параллельные формы являются максимальными.
Заметим, что формулировка утверждения 4.1 остается в силе, если неравен­ство / < j заменить неравенством / < j. Однако ни одно из следствий уже вы­полняться не будет. Граф, размеченный таким образом, называется обобщенной параллельной формой. Соответственно вводятся понятия обоб­щенный ярус, ширина обобщенного яруса и т. п. В исследованиях, связан­ных с параллельными формами, обобщенные параллельные формы играют роль замыкания множества строгих параллельных форм.
Теперь допустим, что алгоритм реализуется на синхронном компьютере, "обыкновенном" или параллельном. Пусть для простоты все операции выполняются за одно и то же время, равное 1. Пренебрежем всеми осталь­ными временными потерями и будем считать, что операции начинают вы­полняться сразу, как только оказываются готовыми их аргументы, без ка­ких-либо простоев. Предположим, что алгоритм начинает реализовываться в нулевой момент времени. Тогда каждой операции можно присвоить индекс, равный моменту окончания ее выполнения. Если эти индексы перенести на соответствующие вершины графа алгоритма, то ясно, что мы получим его строгую параллельную форму. При этом номер яруса означает момент вре­мени, когда заканчивают выполняться соответствующие ему операции. Все операции одного яруса выполняются независимо друг от друга, а число этих операций совпадает с шириной яруса. Высота параллельной формы есть время реализации алгоритма и т. п. Совершенно очевидна связь между раз­личными синхронными реализациями алгоритма и различными строгими параллельными формами его графа. В частности, если алгоритм реализуется на "обыкновенном" компьютере, то этому соответствует параллельная фор­ма, в которой все ярусы имеют ширину, равную 1. В данном случае парал­лельная форма называется линейной и говорят, что граф упорядочен линейно.
Параллельные формы графа алгоритма удобно изображать на листе бумаги, если ось абсцисс считать осью времени, а вершины откладывать по оси ор­динат соответственно времени окончания выполнения операций. Если по­строить аналогичный чертеж для любого реального или гипотетического компьютера, синхронного или асинхронного, то видно, что в проведенных рассуждениях мало что меняется. Лишь ярусы параллельной формы будут размываться во времени. Но как бы они не размывались, их всегда можно привести к синхронной форме с единичным временем выполнения опера­ций, перемещая вершины и соответствующие им дуги параллельно оси абс­цисс. Для этого отметим не только моменты окончания выполнения опера­ций, но и моменты их начала. Ярусы параллельной формы будем строить следующим образом. В первый ярус объединим максимальное число вер­шин, не имеющих входящих дуг и соответствующих операциям с непустым пересечением временных интервалов их выполнения. Эти вершины заведо-
196
Часть II. Параллельное программирование
мо не связаны друг с другом какими-либо дугами. Удалим из графа алго­ритма вершины первого яруса и инцидентные им дуги. Для оставшейся час­ти графа выделим аналогичную группу вершин и будем считать ее вторым ярусом параллельной формы и т. д.
Таким образом, между различными реализациями одного и того же алго­ритма на различных компьютерах и различными параллельными формами графа алгоритма существует определенное взаимное соответствие. Зная граф алгоритма и его параллельные формы, можно понять, каков запас паралле­лизма в алгоритме и как его лучше реализовать на конкретном компьютере параллельной архитектуры. Вот почему мы будем уделять очень много вни­мания как построению графа алгоритма, так и нахождению его параллель­ных форм.
В дальнейшем терминологию, относящуюся к параллельным формам графа алгоритма, будем часто переносить и на сам алгоритм, говоря о параллель­ной форме алгоритма, ярусах алгоритма, высоте параллельной формы алго­ритма. Минимальную высоту параллельных форм алгоритма будем называть высотой алгоритма и т. д. Другими словами, будем сразу считать вершинами графа операции самого алгоритма, а дугами — отношения частичного по­рядка между операциями. Это не дает никаких новых знаний по существу, но позволяет избегать громоздких фраз за счет исключения длинных после­довательностей слов типа "множество операций, соответствующих такому-то множеству вершин графа алгоритма". Обычным же графом алгоритма удоб­нее пользоваться в иллюстративных рисунках и чертежах.
Наконец, покажем, что независимо от того, какая параллельная форма алгорит­ма реализуется на компьютере, результат реализации будет одним и тем же.
Утверждение 4.2
Пусть при выполнении операции ошибки округления определяются только значе­ниями аргументов. Тогда при одних и тех же входных данных все реализации ал­горитма, соответствующие одному и тому же частичному порядку на операциях, дают один и тот же результат, включая всю совокупность ошибок округления.
Рассмотрим каноническую параллельную форму алгоритма. Операции лю­бого яруса не зависят друг от друга. Аргументами операций 1-го яруса явля­ются только входные данные. Они не зависят от времени выполнения опе­раций. Поэтому все операции 1-го яруса дают результаты, не зависящие от того, когда реально выполняются эти операции. Аргументами операций 2-го яруса являются или входные данные, или результаты выполнения операций 1-го яруса. В соответствии со сказанным, все они не зависят от времени вы­полнения операций. Поэтому операции 2-го яруса также дают результаты, не зависящие от того, когда реально выполняются эти операции. Продолжая последовательно по ярусам это доказательство, убеждаемся в справедливо­сти утверждения.
Глава 4. Большие задачи и параллельные вычисления
197
Заметим, что та последовательность выполнения операций, которая дикту­ется программой, записанной на последовательном языке, определяет на множестве операций линейный порядок. Конечно, он сохраняет и частич­ный порядок, о котором говорилось выше. Но в общем случае на множестве операций существуют и другие линейные порядки, сохраняющие тот же частичный порядок. Согласно утверждению 4.2, все они будут давать один и тот же результат. Следовательно, фиксация последовательного порядка вы­полнения операций при задании алгоритма, вообще говоря, является избы­точным действием. Значительно "экономичнее" задавать граф алгоритма или какую-нибудь его строгую параллельную форму, лучше всего — канониче­скую. Тем более, что для реализации алгоритмов на параллельных вычисли­тельных системах нужна именно такая информация. Однако за многие годы существования "обыкновенных" компьютеров накопилось столько много прикладного программного обеспечения на последовательных языках, что не может даже идти речи о его переписывании вручную. Поэтому необхо­димо развивать теорию и строить инструментальные системы, помогающие очищать записи алгоритмов от последовательных "излишеств", что, в конеч­ном счете, и сводится к построению графа алгоритма и нахождению его па­раллельных форм. Как мы увидим в дальнейшем, это очень непростая задача.
По нашему мнению, граф алгоритма представляет самое основное ядро идеи разработчика, содержащейся в записи алгоритма. Разработчик алгоритма мог знать это ядро. Вполне возможно, что он был вынужден замаскировать его из-за того, что язык описания алгоритма не давал возможность эффек­тивно изобразить данное ядро. Но как показывает практика, в подавляющем большинстве случаев это ядро алгоритма для разработчика неизвестно. Бо­лее того, как правило, он даже и не думает о его существовании. Все стано­вится понятным, если принять во внимание, что граф алгоритма есть не просто ядро алгоритма, а является его информационным ядром. До недавнего времени знания о том, как распространяется информация при реализации алгоритмов, не имели практической ценности. Поэтому неудивительно, что не было особого стремления их получать. Появление параллельных вычис­лительных систем сделало эти знания исключительно нужными. Вот они и стали развиваться.
Вопросы и задания
1.   Если на множестве операций меняется частичный порядок, то может ли новый алгоритм быть эквивалентным исходному?
2.   Может ли минимальное значение числа ярусов обобщенной параллельной фор­мы характеризовать структуру алгоритма?
3.   Пусть для одной и той же задачи алгоритмы дают решения с разными ошибками округления. Могут ли такие алгоритмы иметь одни и те же параллельные формы?
198
Часть II. Параллельное программирование
4.   Приведите примеры разных алгоритмов, предназначенных для решения разных задач, но имеющих, тем не менее, одинаковые графы.
5.   Пусть для одной и той же задачи алгоритмы дают решения с одними и теми же ошибками округления при всех входных данных. Могут ли такие алгоритмы иметь разные графы?
6.   Используя унарные и бинарные операции, постройте графы следующих алго­ритмов:
6.1.  Быстрое преобразование Фурье.
6.2.  Решение системы линейных алгебраических уравнений с треугольной матрицей методом обратной подстановки.
6.3.  Решение системы линейных алгебраических уравнений с квадратной матрицей методом Гаусса.
6.4.  Обращение матрицы методом Жордана.
6.5.  Любой метод перемножения двух квадратных матриц порядка п, требующий менее п3 умножений.
§ 4.3. Концепция неограниченного параллелизма
Внедрение первых компьютеров в практику показало, что их мощности не­достаточны для решения многих крупных задач. Технологические возмож­ности того времени не могли обеспечить необходимый уровень производи­тельности. Довольно скоро возникла и стала успешно развиваться новая идея его повышения. Она связывала увеличение производительности с объ­единением в одну систему нескольких компьютеров или процессоров, функционирующих одновременно. Вот несколько характерных примеров, часть из которых уже обсуждалась ранее [35]. В 1958 г. появилась вычисли­тельная система PILOT, объединяющая три независимых компьютера в од­ном комплексе. В 1962 г. вошла в строй система Burroughs D825, включаю­щая до 4 идентичных процессоров, работающих над общей памятью. В 1964 г. была построена система CDC 6600. Она содержала неоднородную совокупность арифметико-логических устройств. Каждое из них выполняло только часть команд из всего набора, но все они могли функционировать одновременно. В 1972 г. появился матричный компьютер 1LL1AC IV, содер­жащий уже 64 элементарных процессора. По существу с этих компьютеров и началась эра параллельных вычислительных систем.
Заметим, что поводом для возникновения самой идеи вычислительных сис­тем параллельной архитектуры послужило не только несовершенство эле­ментной базы и архитектуры первых компьютеров. Это несовершенство бы­ло относительным, но оно не позволяло поднять производительность "обыкновенных" компьютеров до уровня, необходимого для решения наибо­лее крупных задач. А идея была значительно более глубокой. Она наметила
Глава 4. Большие задачи и параллельные вычисления
199
перспективы будущего развития вычислительной техники. Сейчас, спустя несколько десятилетий, параллелизм в вычислительных системах не пере­стал быть актуальным. Несмотря на огромный прогресс в развитии как эле­ментной базы, так и архитектуры компьютеров, "обыкновенные" компьюте­ры снова не позволяют достичь производительности, необходимой для решения наиболее крупных задач, но уже сегодняшнего дня.
Независимо от того, как устроены реальные или гипотетические параллель­ные вычислительные системы, все они базируются на одной и той же методо­логической основе. Именно, каждая такая система имеет какое-то число функциональных устройств, которые могут работать независимо друг от друга и способны, к тому же, выполнять операции алгоритма. Это означает, что для того, чтобы алгоритм мог быть реализуем на параллельной системе, он должен быть представим в виде последовательности ансамблей операций. Все опера­ции одного ансамбля должны быть независимыми и обладать возможностью быть выполненными одновременно на имеющихся в системе функциональ­ных устройствах. Используя терминологию предыдущего параграфа, эту мысль можно выразить иначе: алгоритм должен иметь параллельную форму, ширина ярусов которой в среднем соизмерима с числом функциональных устройств системы. В общем случае, чем больше ярусов, ширина которых равна числу независимых устройств, тем выше реальная производительность параллельной вычислительной системы на данном алгоритме.
Создание параллельных вычислительных систем потребовало разработки математической концепции построения параллельных алгоритмов, т. е. алго­ритмов, приспособленных к реализации на подобных системах. Эта концеп­ция стала развиваться в конце 50-х — начале 60-х годов прошлого столетия. В то время о структуре параллельных вычислительных систем и путях их развития было мало чего известно. Разве только то, что в таких системах одновременно может работать много устройств. Быстрое развитие элемент­ной базы подсказывало, что число устройств вроде бы вскоре может стать очень большим. Концепция получила название концепции неограниченного параллелизма. В ее основе явно или неявно лежит предположение, что алго­ритм реализуется на параллельной вычислительной системе, не наклады­вающей на него никаких ограничений. Считалось, что процессоров может быть сколь угодно много, все они универсальные, работают в синхронном режиме, имеют общую память, любые передачи информации осуществляются мгновенно и без конфликтов. В терминах предыдущего параграфа это озна­чало, что основная цель концепции неограниченного параллелизма своди­лась к построению алгоритмов минимальной высоты, т. к. в такой модели вычислений именно высота определяет время реализации алгоритмов.
Концепция неограниченного параллелизма отражает уровень математиче­ских знаний того времени в области параллельных вычислений. Она имеет как свои недостатки, так и свои достоинства. Рассмотрим некоторые резуль­таты, полученные на ее основе.
200
Часть II. Параллельное программирование
Для решения одной и той же задачи могут применяться алгоритмы, имею­щие различную параллельную сложность. Среди них могут быть и алгорит­мы наименьшей высоты. Рассмотрим важный пример вычисления произве­дения п чисел О], а2, ..., а„, в котором отчетливо видна идея, играющая большую роль в построении алгоритмов малой высоты.
Пусть п = 8. Обычная схема, реализующая процесс последовательного умно­жения, выглядит следующим образом:
Данные Я] а2 «з а4 а5 аб ai а&
Ярус 1 а\й2
Ярус 2 (а]Д2)аз
Ярус 3 (Я]Й2Яз)й4
Ярус 4 (а1й2азй4)й5
Ярус 5 (Я1Й2Язй4й5)й6 Ярус 6 (я]Я2Язй4й5йб)й7 Ярус 7 (Я1Й2Язй4й5й6й7)й8
Высота параллельной формы равна 7, ширина равна 1. Если вычислительная система имеет более одного процессора, то при данной схеме вычислений все они, кроме одного, на всех шагах будут простаивать. Следующая парал­лельная форма другого алгоритма вычисления произведения чисел использу­ет процессоры более эффективно:
Данные а\ а2 «з а4 а5 аб а1 а&
Ярус 1 Я]Д2 аз а4 а5 ав а1 а&
Ярус 2 (Я1Я2)(Я3 й4) («5 «б) («7 «8)
Ярус 3 (й^Яз^) ^fl5 ав а1 а%)
Высота параллельной формы равна 3, ширина равна 4. Существенное сни­жение высоты произошло за счет более полной загруженности процессоров выполнением полезной работы.
Последняя схема очевидным образом распространяется на случай произ­вольного п. Для ее реализации необходимо на каждом ярусе осуществлять максимально возможное число произведений непересекающихся пар чисел, взятых на предыдущем ярусе. В общем случае высота параллельной формы равна Tlog2« 1 , где ГоЛ означает ближайшее к а сверху целое число. Эта па­раллельная форма реализуется на [п/2] процессорах, но в ней загруженность процессоров уменьшается от яруса к ярусу. Процесс построения чисел каж­дого яруса по описанной схеме называется процессом сдваивания.
Очевидно, что с помощью процесса сдваивания можно строить алгоритмы логарифмической высоты не только для многократного применения опера­ции умножения чисел, но и для любой ассоциативной операции, например,
Глава 4. Большие задачи и параллельные вычисления
201
сложения чисел, умножения матриц и т. п. Обратим внимание, что алгоритм последовательного применения операции и алгоритм сдваивания — это принципиально разные алгоритмы, хотя они и требуют для своих реализаций выполнения одного и того же числа операций. Им нужны не только разные числа процессоров, но и разные коммуникационные сети. В них по-разному сказывается влияние ошибок округления и для них по-разному пишутся программы. В общем случае, у них все разное, в том числе, и графы алго­ритмов. На рис. 4.2 для я = 8 вверху представлен граф последовательного применения операции, внизу — граф для принципа сдваивания. Начальные вершины символизируют ввод данных.
Рис. 4.2. Последовательный граф и граф сдваивания
Рассмотренный пример подсказывает один вывод, весьма важный в методо­логическом отношении.
Утверждение 4.3
Пусть функция существенно зависит от п переменных и представлена как су­перпозиция конечного числа операций, имеющих не более р аргументов. Пред­положим, что ее значения вычисляются с помощью какого-либо алгоритма с использованием тех же операций. Если без учета ввода данных высота алго­ритма равна s, то s > logyz.
Рассмотрим каноническую параллельную форму графа алгоритма. Пусть на нулевом ярусе расположены вершины, соответствующие вводу значений входных переменных. На s-ом ярусе находится одна конечная вершина. Так как у всех вершин не более р входящих дуг, то на (s — 1)-ом ярусе имеется не более р вершин, на (s — 2)-ом ярусе не более р2 вершин и т. д. На нуле­вом ярусе будет находиться не более ps вершин. Ясно, что ps> п. Поэтому s > logp п.
Таким образом, если какая-нибудь задача определяется п входными данны­ми, то мы не можем рассчитывать в общем случае на существование алго-
202
Часть II. Параллельное программирование
ритма ее решения с высотой менее log п. Если получен алгоритм высоты порядка loga п, где a > 0, то такой алгоритм мы должны считать эффектив­ным с точки зрения времени его реализации на параллельной вычислитель­ной системе. Если, конечно, не принимать во внимание все другие аспекты реализации.
Пусть вектор у вычисляется как произведение квадратной матрицы А и век­тора х порядка п. Если yh ay, Xj суть элементы соответствующих векторов и матрицы, то для всех / имеем
п
у: =yLauxJ-
Предположим, что вычислительная система имеет п2 процессоров. Тогда на первом шаге можно параллельно вычислить все п2 произведений a^xj, а за­тем, используя схему сдваивания для сложения, за Г log2«l шагов вычислить параллельно все п сумм, определяющих координаты вектора у. Следователь­но, можно построить алгоритм вычисления произведения квадратной мат­рицы и вектора порядка п высоты порядка log2«. При этом ширина алго­ритма равна п2. Процессоры используются неравномерно. Только на первом шаге задействованы все процессоры. Затем число работающих процессоров на каждом шаге уменьшается вдвое. Нельзя согласно утверждению 4.3 по­строить алгоритм, имеющий меньшую высоту. Но существуют алгоритмы, имеющие при логарифмической высоте несколько меньшую ширину.
Задачу вычисления произведения двух матриц порядка п можно рассматри­вать как задачу вычисления п произведений одной матрицы и п независи­мых векторов порядка п, считая векторами столбцы второй матрицы. Если все эти произведения вычислять параллельно по описанному алгоритму, то полученный алгоритм будет иметь высоту порядка log2« и ширину я3. Снова процессоры используются неравномерно, и опять нельзя построить алго­ритм, имеющий меньшую высоту, но существуют алгоритмы, имеющие при логарифмической высоте несколько меньшую ширину.
Обратим внимание на следующее обстоятельство. Если пытаться реализовы-вать описанные алгоритмы по ярусам канонической параллельной формы, то возникает необходимость одновременной рассылки одних и тех же дан­ных по многим процессорам. Такую операцию нельзя осуществить очень быстро. Поэтому в реальных условиях временные затраты на рассылки при­водят к значительному замедлению процессов вычислений.
Построение параллельных алгоритмов наименьшей высоты для матрично-векторных сумм и произведений не вызвало никаких затруднений. Это мо­жет создать впечатление, что такие алгоритмы легко строятся и для других матрично-векторных задач. Подобное впечатление, конечно, не верно. Рас­смотренные алгоритмы скорее являются исключением, чем правилом.
Глава 4. Большие задачи и параллельные вычисления
203
Рассмотрим в соответствии с [39] процесс вычисления векторов xh 1 < / < s, при заданных векторах xq, х~\, , х~г + \ с помощью линейных рекуррентных соотношений следующего вида:
Xj = AftXj _]+...+ AjpCj -r + bj.                                (4.1)
Здесь Ay, bj — заданные матрицы и векторы порядка п, при я = 1 они стано­вятся числами. Предположим, что мы хотим построить при фиксированном s параллельный алгоритм вычисления векторов Х]_, xs по возможности меньшей высоты. Очевидно, что, используя порядка п2г процессоров, правую часть со­отношений (4.1) можно вычислить примерно за log2 nr шагов. Это дает парал­лельный алгоритм высоты порядка s log2 nr. Однако совсем не очевидно, как построить алгоритм с существенно более слабой зависимостью высоты от s. Не очевидно даже, что вообще существуют такие алгоритмы.
Рекуррентные соотношения (4.1) можно записать несколько иначе — через матрицы и векторы большего порядка:
xi-r+l 1
Е
••Air-\Air"i
..0 0 0
0 • 0 .
-Е 0 0 .. Е 0 1
Х,-_2
1
Обозначим матрицу через Qh вектор слева через yt. Тогда будем иметь
У1 = Qiyi - 1 = ... = (Qt Qi -1 Q\) Уо,         l<i<s.
Матрицы Qi и векторы yt имеют порядок nr + 1. Согласно алгоритму сдваи­вания, все произведения (Q,(2i) Уо можно вычислить за Tlog2(5 + 1)1 макро­шагов, используя при этом as + 1щ макропроцессоров, выполняющих в ка­честве макрооперации умножение двух матриц порядка nr + 1. Принимая во внимание построенный параллельный алгоритм для нахождения произведе­ния двух матриц, теперь легко построить параллельный алгоритм для вы­числения всех векторов jq, ..., xs с высотой порядка log2 s х log2nr и шириной порядка (nrfs. Этот алгоритм получил название процесса рекуррентного сдваивания.
Описанный процесс принципиально отличается от алгоритма, основанного на прямом вычислении векторов хг- из (4.1). Оба алгоритма дают одни и те же результаты только в условиях точных вычислений. На основе использо­вания рекуррентных соотношений типа (4.1) построены очень многие чис­ленные методы линейной алгебры, математической физики и анализа. Про­цесс рекуррентного сдваивания указывает путь понимания того, что можно ожидать от этих методов с точки зрения построения алгоритмов малой вы­соты, если использовать большое число процессоров. Предположим, напри-
204                                                                      Часть II. Параллельное программирование
мер, что решается система линейных алгебраических уравнений с невырож­денной треугольной матрицей порядка п. За один параллельный шаг, ис­пользуя около п2/1 процессоров, можно сделать равными 1 все диагональ­ные элементы матрицы, разделив на них коэффициенты соответствующих уравнений. Решая систему с помощью обратной подстановки, сразу получа­ем для неизвестных рекуррентные соотношения типа (4.1), где все матрицы и векторы представляют числа, х0 = 0, г = i, s = п. Следовательно, используя процесс рекуррентного сдваивания, можно решить треугольную систему за 0(log22n) параллельных шагов, задействовав 0(«3) процессоров.
Пусть, наконец, рассматривается задача вычисления обратной матрицы А~] для квадратной матрицы А порядка п. В основе построения быстрого парал­лельного алгоритма лежит следующая идея. Обозначимся.) = X" + с\Хп ~ ] + + ... + сп характеристический многочлен матрицы А. Известно, что ДА) = О согласно теореме Кели-Гамильтона. Поэтому
А~1 =- п-11А"-2+... + сп УЕ).                         (4.2)
Если Xj — корни характеристического многочлена и ^ — следы матриц Ак, то известно, что Xt и % связаны так называемыми соотношениями Ньютона, представляющими относительно с^ систему линейных алгебраических урав­нений с треугольной матрицей. Именно,
1
5Х 2 S2 •?! 3
с1
*1
с2
*2
с3
=
S3
Сп
sn
(4.3)
_sn-lsn-2 ••••$1 п.
Параллельный алгоритм определения матрицы А~] состоит из следующих этапов. Сначала по схеме сдваивания находятся все степени матрицы А от первой до (п — 1)-й. Этот этап имеет log2« макрошагов, где каждый макро-шаг есть вычисление не более я/2 произведений двух матриц порядка п.
Весь первый этап может быть выполнен за 0(log22«) параллельных шагов на 0(п4) процессорах. На втором этапе за 0(log2«) шагов при использовании 0(п2) процессоров вычисляются все следы s^ матриц Ак. На третьем этапе за 0(log22n) шагов на 0(п3) процессорах находятся коэффициенты с^ характе­ристического многочлена путем решения треугольной системы (4.3). На чет­вертом этапе матрица А-1 определяется согласно формуле (4.2) за 0(log2«) параллельных шагов с использованием 0(п3) процессоров. В целом парал­лельный алгоритм вычисления матрицы А~1 имеет высоту 0(log22«) и шири­ну 0(п4). В настоящее время для решения этой задачи неизвестны алгорит­мы существенно меньшей высоты. Ширина же может быть уменьшена.
Глава 4. Большие задачи и параллельные вычисления
205
Если решается система линейных алгебраических уравнений Ах = Ь с квад­ратной невырожденной матрицей А порядка п, то параллельный алгоритм строится очень просто. Представим решение х системы в виде х=А~]Ь. Сначала за 0(log22n) параллельных шагов, используя 0(п4) процессоров, на­ходим матрицу А~]. Затем за 0(log2«) параллельных шагов, используя 0(п2) процессоров, находим решение х как произведение матрицы А~] и вектора Ъ.
В рамках концепции неограниченного параллелизма построено немало ал­горитмов небольшой высоты. С некоторыми из них можно познакомиться в работе [39] и процитированной там литературе. Но мы не будем обсуждать их здесь детально. Причина очень проста: подавляющее большинство этих алгоритмов оказались на практике несостоятельными. Огромное число тре­буемых процессоров, сложные информационные связи между операциями, катастрофическая численная неустойчивость, большое число конфликтов в памяти — вот лишь некоторые из "подводных камней" быстрых параллель­ных алгоритмов. Их практическая несостоятельность видна из анализа ти­пового численного программного обеспечения существующих параллельных систем. По существу, оно почти целиком состоит из программ, реализую­щих те же методы, которые хорошо зарекомендовали себя при реализации на последовательных компьютерах. Реально в какой-то мере используется только принцип сдваивания для вычисления сумм и произведений многих чисел. Подробное обсуждение концепции неограниченного параллелизма можно найти в книге [9].
Несмотря на отмеченные недостатки, мы сочли уместным хотя бы кратко упомянуть об этой концепции в данной книге. Все-таки, она отражает замет­ный период в истории изучения параллелизма в вычислениях. К тому же, концепция оказалась исключительно живучей. Предельная абстрагирован-ность от реалий устройства и использования вычислительной техники сделала ее привлекательной для проведения исследований, главным образом, чисто математических. Однако на сегодняшний день все достижения в рамках кон­цепции неограниченного параллелизма скорее представляют набор отдельных изобретений в области численных методов, чем систематически развиваю­щуюся область математики. Тем не менее, вполне возможно, что здесь еще не сказано последнее слово, и к построению быстрых параллельных алгоритмов все же будет разработан более систематизированный подход.
Вопросы и задания
1. Используя унарные и бинарные операции, постройте параллельные алгоритмы малой высоты для следующих задач:
1.1.  Вычисление значения многочлена в точке.
1.2.  Решение системы линейных алгебраических уравнений с трехдиагональной мат­рицей.
206
Часть II. Параллельное программирование
1.3.  Решение системы линейных алгебраических уравнений с двухдиагональной мат­рицей.
1.4.  Обращение двухдиагональной матрицы.
1.5.  Решение системы линейных алгебраических уравнений с блочной двухдиаго­нальной матрицей блочного порядка m и порядком блоков, равным п.
2.   **Пусть дан любой детерминированный алгоритм, использующий лишь опера­ции сложения, умножения и деления чисел. Опишите класс математически экви­валентных алгоритмов, полученных из данного путем применения законов ассо­циативности, коммутативности и дистрибутивности, а также приведения подобных членов и замены подобными членами чисел 0 и 1. На этом классе по­пытайтесь установить алгоритмы минимальной и максимальной сложности как по отдельным операциям, так и по операциям в целом, в том числе параллель­ную сложность.
3.   В условиях п. 2 для следующих задач с матрицами порядка п постройте алгорит­мы, использующие О(па), 0 < а < 3, операций умножения, и исследуйте структу­ру соответствующих графов:
3.1.  *Перемножение двух квадратных матриц.
3.2.  *Решение системы линейных алгебраических уравнений с квадратной матрицей.
3.3.  *Если в пп. 3.1, 3.2 вам удалось построить алгоритмы, в которых а = log27, вы на правильном пути. Соответствующие алгоритмы называются алгоритмами Штрассена.
3.4.  Сравните графы алгоритмов Штрассена с графом алгоритма быстрого преобра­зования Фурье.
§ 4.4. Внутренний параллелизм
В процессе длительного использования последовательных компьютеров был накоплен и тщательно отработан огромный багаж численных методов и программ. Появление параллельных компьютеров вроде бы должно было привести к созданию новых методов. Но этого не произошло. Попытка раз­работать специальные параллельные методы, в частности, методы малой вы­соты, оказалась на практике несостоятельной. Естественно, возникает во­прос, как же тогда решать задачи на параллельных компьютерах и можно ли на этих компьютерах использовать старый алгоритмический и программный багаж? Как это часто бывает, ответ был найден простым по форме и совсем не простым по реализации. В терминах § 4.2 он сводится к следующему. Возьмем любой подходящий алгоритм, записанный в виде математических соотношений, последовательных программ или каким-либо иным способом. Допустим, что по этой записи для него удалось построить граф алгоритма. Предположим, что для этого графа обнаружена параллельная форма с доста­точной шириной ярусов. Тогда рассматриваемый алгоритм, по крайней ме­ре, принципиально можно реализовать на параллельном компьютере. Очень важно, что согласно утверждению 4.2 параллельная реализация алгоритма
Глава 4. Большие задачи и параллельные вычисления
207
будет иметь такие же вычислительные свойства, как и любая другая. В част­ности, если исходный алгоритм был численно устойчив, то он останется таким же и в параллельной форме. Подобный параллелизм в алгоритмах стали называть внутренним.
Оказалось, что в старых, давно используемых и хорошо отработанных алго­ритмах довольно часто удается обнаружить хороший запас внутреннего па­раллелизма. Использование внутреннего параллелизма имеет очевидное дос­тоинство, т. к. не нужно тратить дополнительные усилия на изучение вычислительных свойств вновь создаваемых алгоритмов. Недостатки также очевидны из-за необходимости определять и исследовать графы алгоритмов. В тех случаях, когда внутреннего параллелизма какого-либо алгоритма не­достаточно для эффективного использования конкретного параллельного компьютера, приходится заменять его другим алгоритмом, имеющим луч­шие свойства параллелизма. К счастью, для очень многих задач уже разра­ботано большое количество различных алгоритмов. Поэтому подобрать под­ходящий алгоритм почти всегда удается. Правда, осуществить этот выбор не так легко, т. к. нужно знать параллельную структуру алгоритмов. А она-то как раз неизвестна. Поэтому понятно, насколько актуальными являются сведения о параллельных свойствах алгоритмов, а также знания, позволяю­щие эти сведения получать.
Рассмотрим некоторые примеры. Пусть решается классическая задача вы­числения произведения А = ВС двух квадратных матриц В, С порядка п. Бу­дем считать элементы матриц А, В, С числами и обозначим их ay, bik, сщ. Согласно определению операции умножения матриц имеем
п
a>j = X b>k ckj, /, У = 1, 2, ..., п.                                       (4.4)
к = 1
Эти формулы довольно часто используются для непосредственного вычис­ления элементов матрицы А. Сами по себе они не определяют алгоритм од­нозначно, т. к. не определен порядок суммирования произведений bikckj под знаком суммы. Однако заметим, что явно виден параллелизм вычислений. Выражается он отсутствием указания о соблюдении какого-либо порядка перебора индексов /, j.
Если операции сложения и умножения чисел выполняются точно, то все порядки суммирования в (4.4) эквивалентны и приводят к одному и тому же результату. Пусть из каких-то соображений выбран следующий алгоритм реализации формул (4.4):
а/)=0, а.Ш = афк -1) + bikCkh и у; к = i; 2, ..., п,                        (4.5)
аЧ = ачп)-
208
Часть II. Параллельное программирование
Снова явно указан параллелизм перебора индексов /, j. Однако по индексу к параллелизма нет, т. к. этот индекс должен перебираться последовательно от 1 до п, что следует из средней формулы (4.5).
Построим теперь граф алгоритма (4.5). Будем считать, что вершины графа соответствуют операциям вида a + be, где a, b, с элементы того же кольца, которому принадлежат элементы матриц А, В, С. Для наглядности их можно рассматривать как числа, хотя ничто не мешает им быть, например, квад­ратными матрицами одного порядка. При построении графа природу эле­ментов а, Ь, с учитывать не будем. Не будем также сначала показывать в графе вершины и инцидентные им дуги, связанные с вводом элементов мат­риц В, С и с присвоением элементам матрицы А вычисленных значений «/"Л Чтобы не вносить в исследование графа алгоритма неоправданные до­полнительные трудности, вершины графа нельзя располагать произвольно. Приемлемый способ их расположения подсказывает сама форма записи (4.5). Рассмотрим прямоугольную решетку в трехмерном пространстве с ко­ординатами /, j, к. Во все целочисленные узлы решетки для 1 < /, j, к < п по­местим вершины графа. Анализируя запись (4.5), нетрудно убедиться в том, что в вершину с координатами /, j, к для к > 1 будет передаваться результат выполнения операции, соответствующей вершине с координатами i,j, к — 1.
к
i
<
i
i
i
i
i
i
i i
i
i
Рис. 4.3. Графы перемножения матриц
Граф алгоритма устроен достаточно просто. Он распадается на п2 не связан­ных между собой подграфов. Каждый подграф содержит п вершин и пред­ставляет один путь, расположенный параллельно оси к. Как и следовало ожидать, в графе отражен тот же параллелизм, который был явно указан в записях (4.4), (4.5) Для л = 2 граф алгоритма представлен на рис 4.3, а. В полном графе имеется множественная рассылка данных. Элемент Ь^ рас­сылается по всем вершинам, имеющим те же самые значения координат /, к.
Глава 4. Большие задачи и параллельные вычисления
209
Элемент сщ рассылается по всем вершинам, имеющим те же самые коорди­наты к, j. Для я = 2 эти рассылки показаны на рис. 4.3, б. Все вершины со­ответствуют операциям вида a + be. Однако при к = 1 они выполняются не­сколько иначе, чем при к ф 1. Именно, в этих случаях аргумент а не является результатом какой-либо операции, а всегда полагается равным 0. Ничто не мешает 0 считать входным данным алгоритма и рассылать его по всем вершинам при к = 1.
Этот пример хорошо иллюстрирует, насколько важно правильно выбрать способ расположения вершин графа алгоритма. Если, например, расположить вершины на прямой линии, да еще без какой-либо связи с индексами /, j, к из (4.5), то вряд ли в таком графе удалось бы разглядеть параллелизм. При выбранном рас­положении вершин параллелизм очевиден. Более того, легко описать любой ярус любой параллельной формы. Это есть множество вершин, не содержащее при фиксированных /, j более одной вершины. Ярусы канонической параллель­ной формы представляют множество вершин, лежащих на гиперплоскости к = const. Поэтому вполне естественно ожидать, что нам придется связывать способ расположения вершин с системой индексов, используемых в записи ал­горитма. Данный пример такую зависимость уже наметил.
Заметим, что если суммирование в (4.4) выполнять по схеме сдваивания, то граф алгоритма станет совсем другим. Именно, каждый путь в графе на рис. 4.3, а должен быть заменен на нижний граф на рис. 4.2. Новый граф уже труднее разместить в трехмерном пространстве. Но ведь и записать суммиро­вание по принципу сдваивания, используя один индекс, значительно сложнее.
Пусть теперь решается система линейных алгебраических уравнений Ах = b с невырожденной треугольной матрицей А порядка п методом обратной подстановки. Обозначим через ay, bh xt элементы матрицы, правой части и вектор-решения. Предположим для определенности, что матрица А левая треугольная с диагональными элементами, равными 1. Тогда имеем
/'-1 xi = b\, Xj = bj — 2^ayXj, 2 < i < n.                            (4.6)
7 = 1
Эта запись также не определяет алгоритм однозначно, т. к. не определен порядок суммирования. Рассмотрим, например, последовательное суммиро­вание по возрастанию индекса j. Соответствующий алгоритм можно запи­сать следующим образом
xfj) = xij - О - aijXjJ - 1), / = i, 2, ..., n, j = 1, 2, ..., / - 1,           (4.7)
xt=xf'-l\
Основная операция алгоритма имеет вид a — be. Выполняется она для всех допустимых значений индексов /, j. Все остальные операции осуществляют присваивание. Опять неэффективна простая перенумерация операций при
210
Часть II. Параллельное программирование
построении графа алгоритма. В декартовой системе координат с осями /, j построим прямоугольную координатную сетку с шагом 1 и поместим в узлы сетки при 2 < i < п, 1 <j<i — 1 вершины графа, соответствующие операциям a — be. Анализируя связи между операциями, построим граф алгоритма, включив в него также вершины, символизирующие ввод входных данных ay и bj. Этот граф для случая я = 5 представлен на рис. 4.4, а. Верхняя угловая вершина находится в точке с координатами /= 1, j= 0. На этом рисунке показана одна из максимальных параллельных форм. Ее ярусы отмечены пунктирными линиями. Параллельная форма становится канонической, ес­ли вершины, соответствующие вводу элементов ау, поместить в первый ярус. Общее число ярусов, содержащих операции типа а — be, равно п — 1. Опе­рации ввода элементов вектора b расположены в первом ярусе.
Рис. 4.4. Графы для треугольных систем
Если при вычислении суммы в (4.6) мы останавливаемся по каким-то сооб­ражениям на последовательном способе суммирования, то выбор суммиро­вания именно по возрастанию индекса j был сделан, вообще говоря, слу­чайно. Так как в этом выборе пока не видно каких-либо преимуществ, то можно построить алгоритм обратной подстановки с суммированием по убы­ванию индекса/ Соответствующий алгоритм таков:
xfb = bh
xff> = дс//+1> - ayxff), i = 1, 2, ..., я, j = i~ 1, / - 2, ..., 1,                 (4.8)
xt=xP.
Его граф для случая n = 5 представлен на рис. 4.4, б. Теперь верхняя угловая вершина находится в точке с координатами / = 1,у = 1.
Глава 4. Большие задачи и параллельные вычисления
211
Пытаясь размещать вершины, соответствующие операциям типа a — be, по ярусам хотя бы какой-нибудь параллельной формы, мы обнаруживаем, что теперь в каждом ярусе всегда может находиться только одна вершина. Объ­ясняется это тем, что все вершины графа на рис. 4.4, # лежат на одном пути. Этот путь показан пунктирными стрелками. Поэтому общее число ярусов в графе алгоритма (4.8), содержащих операции вида a — dc, всегда равно (п2 — — п + 2)/2, что намного больше, чем я — 1 ярус в графе алгоритма (4.7).
Полученный результат трудно было ожидать. Действительно, оба алгоритма (4.7) и (4.8), предназначены для решения одной и той же задачи. Они по­строены на одних и тех же формулах (4.6) и в отношении точных вычисле­ний эквивалентны. Оба алгоритма совершенно одинаковы с точки зрения их реализации на однопроцессорных компьютерах, т. к. требуют выполнения одинакового числа операций умножения и вычитания и одинакового объема памяти. На классе треугольных систем оба алгоритма даже эквивалентны с точки зрения влияния ошибок округления.
Тем не менее, графы обоих алгоритмов принципиально отличаются друг от друга. Если эти алгоритмы реализовывать на параллельной вычислительной системе, имеющей п универсальных процессоров, то алгоритм (4.7) можно реализовать за время, пропорциональное п, а алгоритм (4.8) — только за время, пропорциональное п2. В первом случае средняя загруженность про­цессоров близка к 0,5, во втором случае она близка к 0.
Таким образом, алгоритмы, совершенно одинаковые с точки зрения реали­зации на "обыкновенных" компьютерах, могут быть принципиально различ­ными с точки зрения реализации на параллельных компьютерах.
Воспользовавшись данным примером, подчеркнем, что в этом факте, собст­венно говоря, и заключается основная трудность математического освоения параллельных компьютеров. Специалисты различного профиля, работающие на "обыкновенных" компьютерах в течение многих лет, привыкли оценивать алгоритмы, главным образом, через три их характеристики: число операций, объем требуемой памяти и точность. На этих характеристиках было построено практически все: основные параметры вычислительной техники, процесс обу­чения вычислительному делу, создание численных методов и алгоритмов, оценка эффективности, разработка языков и трансляторов и многое другое.
Создание параллельных вычислительных систем дополнительно потребовало от алгоритмов принципиально других свойств и характеристик, знание ко­торых для последовательных компьютеров было просто не нужно. Нет ни­каких оснований предполагать, что складывавшиеся годами и десятилетия­ми стереотипы отношения к вычислительной технике и конструированию алгоритмов изменятся достаточно быстро. Инерция в этом процессе исклю­чительно велика. Вот почему важно не только исследовать алгоритмы более глубоко и основательно. Особенно необходимо создавать конструктивную методологию для выявления и изучения параллельных свойств алгоритмов.
212
Часть II. Параллельное программирование
Среди многих задач линейной алгебры, возникающих при решении уравне­ний математической физики сеточными методами, часто встречается задача решения систем линейных алгебраических уравнений с блочно-двухдиа-гональными матрицами. При этом внедиагональные блоки представляют собой диагональные матрицы, диагональные блоки — двухдиагональные матрицы. Будем считать для определенности, что матрица системы является левой треугольной. Пусть она имеет блочный порядок m и порядок блоков, равный п. Итак, рассмотрим систему линейных алгебраических уравнений Аи = /следующего вида:
А А А
А А
о
ГА]
РЧ
А
А
А
=
А
_А,_
F
_ m _
О
Аи -
где
А
ъ
eu Ь
0
е2к
hk
-1, к Ь„к _
•>
А =
0
е„-
~«А
f\k
А =
и
, А =
flk
Unk_
fnk _
Чк
О
О
1Ък
(4.9)
1пк
Решение блочно-двухдиагональной системы (4.9) определяется рекуррентно: £4 = ДГЧА-А-1А-1), 1<к<т,                       (4.10)
если положить Щ = 0, Bq = 0. Макрооперация X = В _1(F — BY) вычисляет вектор X по векторам F, Yu матрицам В, В. Построим граф алгоритма (4.9), считая, что каждая из его вершин соответствует данной макрооперации. Очевидно, он будет таким, как показано на рис. 4.5. Большие размеры вер­шин и дуг на рисунке подчеркивают, что операции являются сложными и передается сложная информация.
Глава 4. Большие задачи и параллельные вычисления
213
е, D0=0 В2         D,                    Вт Dm_,
Рис. 4.5. Макрограф для блочно-двухдиагональной системы
Из строения графа сразу видно, что если алгоритм (4.10) рассматривать как последовательность матрично-векторных операций, то он является строго последовательным и не распараллеливается. Практически не распараллели­вается и каждая макрооперация в отдельности. Она также представляет ре­шение двухдиагональной системы. Поэтому распараллеливаться может толь­ко процесс вычисления правой части системы, если, конечно, данную систему снова решать аналогично (4.10).
Из этих фактов можно было бы сделать вывод о невозможности хорошего распараллеливания алгоритма решения рассматриваемой системы (4.9). Од­нако такой вывод был бы преждевременным.
Исследуем поэлементную запись алгоритма решения блочно-двухдиаго­нальной системы. С учетом введенных ранее обозначений элементов матриц и векторов имеем:
uik = (fik~ ei - 1, kui - 1, к ~ Ф, к - Iм/, к - \)^ik >                    (4 11)
к = 1,2, ..., т, i = 1, 2, ..., п.
При этом предполагается, что м/0 = щк = е0/( = d^ = 0 для всех /, к. В записи (4.11) основной и, по существу, единственной является скалярная операция
u=b~l(f- ex- dy),                                      (4.12)
которая вычисляет величину и по величинам f, е, х, у, b, d. Опять неэффек­тивна простая перенумерация операций. Для построения графа алгоритма рассмотрим прямоугольную решетку, узлы которой имеют целочисленные координаты /, к. Во все узлы решетки для 1 < / < я, \ < к < т поместим вер­шины графа и будем считать, что они соответствуют операции (4.12). Не будем указывать вершины, поставляющие входные данные и нулевые значе­ния некоторых аргументов. Анализируя запись (4.11), нетрудно убедиться в том, что в вершину с координатами /, к будут передаваться результаты вы­полнения операций, соответствующих вершинам с координатами / — 1, к и /, к — 1. Вся остальная информация, необходимая для реализации операции с координатами /, к, является входной и нужна для реализации только этой операции. В графе данного алгоритма нет множественной рассылки входных данных типа той, которая имела место в алгоритме (4.5).
214
Часть II. Параллельное программирование
Для случая п = 5, m = 9 граф алгоритма представлен на рис. 4.6. Из него сле­дует, что вопреки возможным ожиданиям граф алгоритма прекрасно распа­раллеливается. Пунктирными линиями отмечены ярусы максимальной парал­лельной формы. Понятно, что высота алгоритма без учета ввода входных данных равна m + п +1, ширина алгоритма равна min(m, п). На рис. 4.7 в этом же графе пунктирными линиями обведены группы вершин. Каждая из таких групп соответствует одной вершине графа, представленного на рис. 4.5. Они же являются ярусами обобщенной параллельной формы. Из этих рисун­ков видно, каким образом хорошо распараллеливаемый алгоритм может пре­вратиться в нераспараллеливаемый при неудачном укрупнении операций.
Рис. 4.6. Граф для блочно-двухдиагональной системы
Рис. 4.7. Ярусы обобщенной параллельной формы
Глава 4. Большие задачи и параллельные вычисления
215
Рассмотрим, наконец, решение краевой задачи для одномерного уравнения теплопроводности. Пусть требуется найти решение и {у, z),
где
Эй Э2и „         
ду dz2 и(0, Z) = cp(z), и(у, 0) = и0(у), и(у, 1) = и{(у). Построим равномерную сетку с шагом h по z и шагом т по у. Предположим, что по тем или иным причинам выбрана явная схема
uf - «у_1) _ «<!_-/> - Ц~^ + ит ~                    а2                     '
Пусть алгоритм реализуется в соответствии с формулой
uf = „y-D +^(w('-/) -2_1) +"5+~i1)>-                        (4ЛЗ)
Для построения графа алгоритма введем прямоугольную систему координат с осями /, j. Переменные у, z связаны с переменными /, j соотношениями
у = т/, z = hj.
Поместим в каждый узел целочисленной решетки вершину графа и будем считать ее соответствующей скалярной операции
<й = а(1-Ц-) + Аг(Ь + с),                                     (4.14)
А2 А2
выполняемой для разных значений аргументов а, Ь, с. Для случая h = 1/8, х = 7/6 граф алгоритма представлен на обоих рис. 4.8, а, б. На границе об­ласти расположены вершины, символизирующие ввод начальных данных и граничных значений.
На данном примере мы хотим затронуть очень важный вопрос, касающийся реализации алгоритмов на любых компьютерах. До сих пор мы связывали ее с выполнением операций по ярусам параллельных форм. Конечно, опера­ции одного яруса не зависят друг от друга и их действительно можно реали-зовывать на разных устройствах одновременно. Но эффективность такой организации параллельных вычислений может оказаться очень низкой. На рис. 4.8, а пунктирными линиями показаны все ярусы максимальной парал­лельной формы алгоритма (4.13). Предположим, что операции реализуются по этим ярусам. Каждая операция вида (4.14) одного яруса требует трех ар­гументов. Они являются результатами выполнения операций на предыдущем ярусе. Если данные, полученные на одном ярусе, могут быстро извлекаться из памяти, то никаких серьезных проблем с реализацией алгоритма по яру­сам параллельной формы не возникает. Однако для больших многомерных
216
Часть II. Параллельное программирование
задач ярусы оказываются столь масштабными, что информация о них может не поместиться в быстрой памяти. Тогда для ее размещения приходится ис­пользовать медленную память. Для этой памяти время выборки одного чис­ла существенно превышает время выполнения базовой операции (4.14). Это означает, что при переходе к очередному ярусу время, затраченное на вы­полнение операций, окажется значительно меньше времени взаимодействия с памятью. Чем меньше отношение времени выполнения операций ко вре­мени доступа к памяти, тем меньше эффективность реализации алгоритма (4.13) по ярусам параллельной формы. В этом случае большая часть времени работы параллельного компьютера будет тратиться на осуществление обме­нов с медленной памятью, а не на собственно счет.
Рис. 4.8. Микро- и макропараллелизм в графе
Можно ли каким-либо способом повысить эффективность использования медленной памяти в алгоритме (4.13) и, если можно, то как? Ответ на дан­ный вопрос дает более детальное изучение графа алгоритма. Заметим, что в графе алгоритма (4.13), кроме строгой параллельной формы с горизонталь­ными ярусами на рис. 4.8, а, существуют параллельные формы, строгие и обобщенные, с наклонными ярусами. Например, обобщенные параллельные формы можно получить, сгруппировав в ярусы вершины на прямых / + j = const и / — j = const. На рис. 4.8, б пунктирными линиями показаны некоторые из этих ярусов. Отмеченные ярусы разбивают область задания графа на многогранники. Алгоритм (4.13) теперь можно реализовывать по полученным многогранникам, в том числе, параллельно. При этом одному процессору всегда поручается выполнение всех операций, относящихся к одному многограннику. При параллельной реализации сначала выполняются
Глава 4. Большие задачи и параллельные вычисления
217
операции, соответствующие нижним заштрихованным многогранникам на рис. 4.8, б. Затем параллельно выполняются операции, соответствующие со­седним незаштрихованным многогранникам, и т. д.
В новом процессе операции одного многогранника становятся макроопера­цией. Время выполнения макрооперации определяется числом вершин в многограннике, что примерно пропорционально его площади. Информаци­онную связь между собой многогранники осуществляют через вершины, лежащие около границ. Следовательно, время на извлечение из памяти ин­формации, необходимой для реализации макрооперации, определяется дли­ной границы многогранника. Размеры многогранников можно выбирать произвольно. Они зависят только от того, какие ярусы обобщенных парал­лельных форм формируют их границы. Всегда можно выбрать такое разбие­ние области задания графа, при котором для большинства многогранников отношение длин границ к их площадям будет сколь угодно малым. При реа­лизации таких макроопераций влияние времени доступа к медленной памя­ти будет снижено очень сильно.
До сих пор параллельные формы алгоритмов применялись только для выяв­ления множеств независимых операций. Как показывает данный пример, они могут также быть полезными для исследования возможности эффектив­ного использования медленной памяти. Подчеркнем, что проведенные выше рассуждения одинаково пригодны как для параллельных компьютеров, так и для "обыкновенных".
Рассмотренные выше примеры убедительно показали, что во многих алго­ритмах действительно имеется хороший запас параллелизма. Ключевым мо­ментом в его выявлении или установлении факта, что он отсутствует, явля­ется знание графов алгоритмов и их параллельных форм. Более того, оказалось, что графы алгоритмов и их параллельные формы можно успешно применять для решения и других вопросов, связанных с реализацией алго­ритмов на компьютерах, причем необязательно параллельных. Однако все рассмотренные примеры были относительно простыми, а графы алгоритмов почти очевидными. Тем не менее, на этих примерах удалось нащупать неко­торые принципы проведения исследований структуры алгоритмов. Чтобы теперь перейти к существенно более сложным алгоритмам, необходимо на­меченную методологию поставить на фундаментальную теоретическую ос­нову. Но об этом мы будем говорить в следующих главах.
Вопросы и задания
Исследуйте внутренний параллелизм в следующих алгоритмах:
1.   Схема Горнера вычисления значения многочлена в точке.
2.   Быстрое параллельное вычисление значения многочлена в точке.
218
Часть II. Параллельное программирование
3.   Решение системы линейных алгебраических уравнений с двухдиагональной мат­рицей методом обратной подстановки.
4.   Быстрое параллельное решение системы линейных алгебраических уравнений с двухдиагональной матрицей.
5.   Вычисление произведения АТ(В + В7)А, где А, В — квадратные матрицы.
6.   Рекуррентное вычисление значений Ujjk = F(Uj _ ^к, u-ц _ iд, ицк _ j) для произ­вольной функции F(x, у, z)-
7.   Решение системы линейных алгебраических уравнений методом Гаусса.
8.   Решение системы линейных алгебраических уравнений методом Жордана.
9.   Быстрое преобразование Фурье.
10. Перемножение матриц методом Штрассена.
Глава 5
Технологии параллельного программирования
Все, что начинается хорошо, кончается плохо. Все, что начинается плохо, кончается еще хуже.
Из законов Мерфи
Итак, вы приступаете к созданию параллельной программы. Желание есть, задача ясна, метод выбран, целевой компьютер, скорее всего, тоже опреде­лен. Осталось только все мысли выразить в той или иной форме, понятной для этого компьютера. Чем руководствоваться, если собственного опыта по­ка мало, а априорной информации о доступных технологиях параллельного программирования явно недостаточно? Наводящих соображений может быть много, но в результате вы все равно будете вынуждены пойти на ком­промисс, делая выбор между временем разработки программы, ее эффек­тивностью и переносимостью, интенсивностью последующего использова­ния программы, необходимостью ее дальнейшего развития. Не вдаваясь в детали выбора, попробуйте для начала оценить, насколько важны для вас следующие три характеристики.
Основное назначение параллельных компьютеров — это быстро решать за­дачи. Если технология программирования по разным причинам не позволя­ет в полной мере использовать весь потенциал вычислительной системы, то нужно ли тратить усилия на ее освоение? Не будем сейчас обсуждать при­чины. Ясно то, что возможность создания эффективных программ является серьезным аргументом в выборе средств программирования.
Технология может давать пользователю полный контроль над использовани­ем ресурсов вычислительной системы и ходом выполнения его программы. Для этого ему предлагается набор из нескольких сотен конструкций и функций, предназначенных "на все случаи жизни". Он может создать дейст­вительно эффективную программу, если правильно воспользуется предло­женными средствами. Но захочет ли он это делать? Не стоит забывать, что он должен решать свою задачу из своей предметной области, где и своих проблем хватает. Маловероятно, что физик, химик, геолог или эколог с большой радостью захочет осваивать новую специальность, связанную с па­раллельным программированием. Возможность быстрого создания параллель­ных программ должна приниматься в расчет наравне с другими факторами.
Вычислительная техника меняется очень быстро. Предположим, что была найдена технология, позволяющая быстро создавать эффективные парал-
220
Часть II. Параллельное программирование
дельные программы. Что произойдет через пару лет, когда появится новое поколение компьютеров? Возможных вариантов развития событий два. Пер­вый вариант — разработанные прежде программы были "одноразовыми" и сейчас ими уже никто не интересуется. Бывает и так. Однако, как правило, в параллельные программы вкладывается слишком много средств (времени, усилий, финансовых затрат), чтобы просто так об этом забыть и начать раз­работку заново. Хочется перенести накопленный багаж на новую компью­терную платформу. Скорее всего, на новом компьютере старая программа рано или поздно работать будет, и даже будет давать правильный результат. Но дает ли выбранная технология гарантии сохранения эффективности па­раллельной программы при ее переносе с одного компьютера на другой? Ско­рее всего, нет. Программу для новой платформы нужно оптимизировать за­ново. А тут еще разработчики новой платформы предлагают вам очередную новую технологию программирования, которая опять позволит создать вы­дающуюся программу для данного компьютера. Программы переписываются, и так по кругу в течении многих лет.
Выбор технологии параллельного программирования — это и в самом деле вопрос не простой. Если попытаться сделать обзор средств, которые могут помочь в решении задач на параллельном компьютере, то даже поверхност­ный анализ приведет к списку из более 100 наименований: НОРМА, Т-система, ARCH, А++/Р++, ABCL, Adl, Ada, ATLAS, Aztec, BIP, BLACS, BSPlib, BlockSolve95, BERT 77, CVM, Counterpoint, CC++, Charm/Charm++, Cilk, CFX, Cray MPP Fortran, Concurrent Clean, Converse, CODE, DOUG, DEEP, DVM, Erlang, EDPEPPS, F1DAP, FFTW, FLUENT, FM, F—, Fortran 90/95, Fortran D95, Fortran M, Fx, FORGE, GA, GALOPPS, GAMESS, Guassian, GRADE, Haskell, HPVM, HPF, HPC, HPC++, HeNCE, 1CC, JIAJIA, JOSTLE, KELP, КАР, LAPACK, LPARX, Linda, Maisie, Mentat, mpC, MPC++, MP1, MPL, Modula-3, NAG, Nastran, NESL, NAMD, OOMP1, OpenMP, Occam, Orca, Opus, P4, Para++, ParJava, Parsec, Parallaxis, Phantom, Phosphorus, Pict, pC++, P-Sparslib, P1M, ParMETIS, PARPACK, PBLAS, PETSc, PGAPack, PLAPACK, PIPS, Pthreads, PVM, Quarks, Reactor, ShMem, SVMlib, Sisal, SR, sC++, ScaLAPACK, SPRNG, TOOPS, TreadMan, Treadmarks, TRAPPER, uC++, Vienna Fortran, VAST, ZPL.
В некоторых случаях выбор определяется просто. Например, вполне жиз­ненной является ситуация, когда воспользоваться можно только тем, что установлено на доступном вам компьютере. Другой аргумент звучит так: "... все используют МР1, поэтому и я тоже буду...". Если есть возможность и желание сделать осознанный выбор, это обязательно нужно делать. Посове­туйтесь со специалистами. Проблемы в дальнейшем возникнут в любом слу­чае, вопрос только насколько быстро и в каком объеме. Если выбор будет правильным, проблем будет меньше. Если неправильным, то тоже не отчаи­вайтесь, будет возможность подумать о выборе еще раз. Сделав выбор не­сколько раз, вы станете специалистом в данной области, забудете о своих
Глава 5. Технологии параллельного программирования
221
прежних интересах, скажем, о квантовой химии или вычислительной гидро­динамике. Не исключено, что в итоге вы сможете предложить свою техно­логию и найти ответ на центральный вопрос параллельных вычислений: "Как создавать эффективные программы для параллельных компьютеров?"
В данной главе мы рассмотрим различные подходы к программированию параллельных компьютеров. Одни широко используются на практике, дру­гие интересны своей идеей, третьи лаконичны и выразительны. Хотелось показать широкий спектр существующих средств, но и не сводить описание каждой технологии до одного абзаца текста. Противоречивая задача. Однако надеемся, что после изучения каждого раздела вы сможете не только прово­дить качественное сравнение технологий, но и самостоятельно писать со­держательные программы.
Знакомясь с различными системами параллельного программирования, обя­зательно обратите внимание на следующее обстоятельство. Если вы решаете учебные задачи или производственные задачи небольшого размера, вам поч­ти наверняка не придется задумываться об эффективности использования параллельной вычислительной техники. В этом случае выбор системы про­граммирования практически не имеет значения. Используйте то, что вам больше нравится. Но как только вы начнете решать большие задачи и, осо­бенно, предельно большие многовариантные задачи, вопрос эффективности может оказаться ключевым.
Очень скоро станет ясно, что при использовании любой системы параллель­ного программирования желание повысить производительность вычисли­тельной техники на вашей задаче сопровождается тем, что от вас требуется все больше и больше каких-то новых сведений о структуре задачи, програм­мы или алгоритма. Ни одна система параллельного программирования не гарантирует высокую эффективность вычислительных процессов без пре­доставления дополнительных сведений.
Все эти сведения нетрадиционны и нетривиальны. Не очень даже ясно, в какой форме они должны быть предоставлены. О том, как получать такие сведения, мы будем подробно говорить в главах 6 к 7. Возможно, что опи­сываемые ниже системы параллельного программирования, а также опыт их использования приведут, в конце концов, к созданию дружественных по отношению к пользователям, высокоэффективных систем общения со сложной вычислительной техникой.
§ 5.1. Использование традиционных последовательных языков
Вариантом, на который согласились бы многие пользователи параллельных вычислительных систем, является использование традиционных последова­тельных языков программирования. Вариант не идеальный, но преимуществ
222
Часть II. Параллельное программирование
и в самом деле много: сохраняется весь уже созданный программный багаж, программист продолжает мыслить в привычных для него терминах, а всю дополнительную работу по адаптации программы к архитектуре параллель­ных компьютеров выполняет компилятор. На первый взгляд все кажется вполне реальным, однако попробуем оценить сложность задачи автома­тического распараллеливания программ компилятором.
Предположим, что мы хотим получить эффективную программу для парал­лельного компьютера с распределенной памятью. Мы специально сделали акцент на эффективности программы, поскольку именно в этом и состоит основная задача. Получить какой-нибудь исполняемый код не составляет ни­какой проблемы: для этого достаточно, например, просто скомпилировать программу для одного процессора. Но тогда зачем использовать параллельный компьютер, если при запуске на любой конфигурации всегда реально задейст-вуется лишь один процессор, и время работы программы не уменьшается?
Чтобы полностью использовать потенциал данной архитектуры, необходимо решить три основные задачи:
□  найти в программе ветви вычислений, которые можно исполнять парал­лельно;
□  распределить данные по модулям локальной памяти процессоров;
□  согласовать распределение данных с параллелизмом вычислений.
Если не решить первую задачу, то бессмысленно использовать многопро­цессорные конфигурации компьютера. Если решена первая, но не решены последние две задачи, то все время работы программы может уйти на обмен данными между процессорами. В таком случае про масштабируемость про­граммы можно забыть.
Указанные задачи в самом деле крайне сложны. Построить эффективные ал­горитмы их решения даже для узкого класса программ очень не просто, в чем читатель легко убедится сам, если рассмотрит несколько реальных примеров.
Попробуем немного упростить ситуацию, и будем рассматривать параллель­ные компьютеры с общей памятью. Казалось бы, остается лишь задача оп­ределения потенциального параллелизма программы, но все ли так просто? Рассмотрим следующий фрагмент программы, состоящий всего из трех (!) строк:
DO 10 i = 1, n DO 10 j = 1, n 10                   U(i + j) = U(2*n -i-j+1) * q + p
Какие итерации данной циклической конструкции являются независимыми, и можно ли фрагмент исполнять в параллельном режиме? Несмотря на ис­ключительно маленький размер исходного текста, вопрос совсем не триви-
Глава 5. Технологии параллельного программирования
223
альный. Прежде, чем читать дальше, попробуйте найти ответ самостоятель­но, хотя из общих соображений уже ясно, что если задается вопрос "можно ли?..", то ответом будет — "можно".
Рис. 5.1. Информационная структура фрагмента при п = 10
На рис. 5.1 показана информационная структура данного фрагмента для случая п = 10. По рисунку можно сразу определить, что ни один из циклов не является параллельным. Однако даже имея под рукой этот рисунок, не­просто определить, что исходный фрагмент можно преобразовать к эквива­лентной форме, в которой оба внутренних цикла уже будут параллельными:
DO 10 i = 1, n
DO 20 j = 1, n - i 20 U(i + j) = U(2*n - i - j + l)*q + p
DO 30 j = n - i + 1, n 30 U(i + j) = U(2*n - i - j + l)*q + p 10 continue .
224
Часть II. Параллельное программирование
Если исходный фрагмент (п = 1000) выполнить на векторно-конвейерном компьютере Cray С90, то его производительность составит около 20 Мфлопс при пиковой производительности почти в 1 Гфлопс. Основная причина низкой производительности заключается в том, что компилятор не может самостоятельно найти такую эквивалентную форму фрагмента, в которой все итерации внутреннего цикла были бы независимы, и, следовательно, он не может векторизовать фрагмент. Одновременно заметим, что производи­тельность этого же компьютера на преобразованном фрагменте уже составит около 420 Мфлопс. И фрагмент состоит всего из трех строк, и все индекс­ные выражения и границы циклов заданы явно, но определить параллель­ные свойства совсем не просто...
Рассмотрим теперь такой фрагмент:
DO 10 i = 1, n 10                U(i) = Func(U, i)
где Func — это функция пользователя. Чтобы ответить на вопрос, являются ли итерации цикла независимыми, компилятор должен определить, не ис­пользуются ли функцией Func элементы массива и. Если в теле данной функции где-то используется, например, элемент u(i - 1), то итерации за­висимы. Если сама функция Func явно не использует массив и, но в ее теле где-то стоит вызов другой функции, которая в свою очередь и использует элемент и (i - 1), то итерации цикла опять-таки будут зависимы. В общем случае, компилятор должен уметь анализировать цепочки вызовов произ­вольной длины и выполнять полный межпроцедурный анализ. В некоторых случаях это сделать можно, но в общем случае задача крайне сложна.
А какой вывод может сделать компилятор о независимости итераций такого фрагмента:
DO 10 i = 1, n 10                U(i) = A(i) + U(IU(i) )
Если нет никакой априорной информации о значениях элементов массива косвенной адресации iu, а чаще всего ее нет, то ничего определенного ска­зать нельзя. Во всяком случае, поскольку параллельная реализация может привести к изменению результата, то компилятор "для надежности" сгене­рирует последовательный код. Компилятор плохой? Нет, просто в данном случае он ничего иного в принципе сделать не может.
Подобных примеров, когда компиляторам сложно определить истинную структуру фрагмента, а значит и сложно получить его эффективную парал­лельную реализацию, можно привести много. Наверное, не стоит сильно винить компиляторы в неспособности решения возникающих проблем. Да­же в теории полностью проанализировать произвольный фрагмент, запи­санный в соответствии с правилами языка программирования, невозможно. Если архитектура компьютеров не очень сложна, то компиляторы вполне в
Глава 5. Технологии параллельного программирования
225
состоянии сгенерировать эффективный код и с обычных последовательных программ. В противном случае компилятору необходимы "подсказки", со­держащие указания на те или иные свойства программ.
Подсказки компилятору могут быть выражены в разной форме. В одних случаях используются специальные директивы, записанные в комментариях, в других случаях в язык вводятся новые конструкции, часто используются дополнительные служебные функции или предопределенные переменные среды окружения. Типичная связка: традиционный последовательный язык + какая-либо комбинация из только что рассмотренных способов. На использовании специальных директив в комментариях к тексту программы основано и одно из самых известных расширений языка Fortran для работы на параллельных компьютерах — High Performance Fortran (HPF). В середи­не 90-х годов прошлого столетия с HPF были связаны очень большие наде­жды, поскольку язык был сразу ориентирован на разработку переносимых параллельных программ. Появление HPF по времени совпало с периодом бурного развития компьютеров с массовым параллелизмом, и проблема пе­реносимости программ стала исключительно актуальной. Однако на этом пути не удалось найти приемлемого решения. Сложность конструкций HPF оказалась непреодолимым препятствием на пути создания по-настоящему эффективных компиляторов, что, естественно, предопределило отказ от него со стороны пользователей.
Технология программирования ОрепМР
Одним из наиболее популярных средств программирования компьютеров с общей памятью, построенных на подобных принципах, в настоящее время является технология ОрепМР. За основу берется последовательная програм­ма, а для создания ее параллельной версии пользователю предоставляется набор директив, процедур и переменных окружения. Стандарт ОрепМР разра­ботан для языков Fortran (77, 90, и 95), С и C++ и поддерживается практи­чески всеми производителями больших вычислительных систем. Реализации стандарта доступны как на многих UNIX-платформах, так и в среде Win­dows NT. Поскольку все основные конструкции для этих языков похожи, то рассказ о данной технологии мы будем вести на примере только одного из них, а именно на примере языка Fortran.
Как в рамках правил ОрепМР пользователь должен представлять свою па­раллельную программу? Весь текст программы разбит на последовательные и параллельные области (рис. 5.2). В начальный момент времени порожда­ется нить-мастер или "основная" нить, которая начинает выполнение про­граммы со стартовой точки. Здесь следует сразу оговориться, почему вместо традиционного для параллельного программирования термина "процесс" появился новый термин — "нить" {thread, легковесный процесс, иногда "поток"). Технология ОрепМР опирается на понятие общей памяти, и по­этому она, в значительной степени, ориентирована на SMP-компьютеры. На
226
Часть II. Параллельное программирование
подобных архитектурах возможна эффективная поддержка нитей, испол­няющихся на различных процессорах, что позволяет избежать значительных накладных расходов на поддержку классических UNIX-процессов.
ч------------------ последовательная секция
<------- параллельная секция
<------------------ последовательная секция
ПП...П <--------- параллельная секция
ч------------------ последовательная секция
Рис. 5.2. ОрепМР: процесс исполнения программы
Основная нить и только она исполняет все последовательные области про­граммы. Для поддержки параллелизма используется схема FORK/JOIN. При входе в параллельную область нить-мастер порождает дополнительные нити (выполняется операция FORK). После порождения каждая нить получает свой уникальный номер, причем нить-мастер всегда имеет номер 0. Все по­рожденные нити исполняют один и тот же код, соответствующий парал­лельной области. При выходе из параллельной области основная нить до­жидается завершения остальных нитей, и дальнейшее выполнение программы продолжает только она (выполняется операция JOIN).
В параллельной области все переменные программы разделяются на два класса: общие (SHARED) и локальные (PRIVATE). Общие переменные всегда существуют лишь в одном экземпляре для всей программы и доступны всем нитям под одним и тем же именем. Объявление локальных переменных вы­зывает порождение своего экземпляра каждой переменной для каждой нити. Изменение нитью значения своей локальной переменной, естественно, ни­как не влияет на изменение значения этой же локальной переменной в других нитях.
По существу, только что рассмотренные понятия областей программы и классов переменных определяют общую идею написания параллельной программы в рамках ОрепМР: некоторые фрагменты текста программы объ­являются параллельными областями; именно эти области и только они ис­полняются набором нитей, которые могут работать как с общими, так и с локальными переменными. Все остальное — это конкретизация деталей и описание особенностей реализации данной идеи на практике.
Глава 5. Технологии параллельного программирования
227
Рассмотрим базовые положения и основные конструкции ОрепМР. Все ди­рективы ОрепМР располагаются в комментариях и начинаются с одной из следующих комбинаций: !$омр, с$омр или *$омр (напомним, что строка, начинающаяся с одного из символов '! ', ' с' или ' *' по правилам языка Fortran считается комментарием). В дальнейшем изложении при описании конкретных директив для сокращения записи мы иногда будем опускать эти префиксы, хотя в реальных программах они, конечно же, всегда должны присутствовать. Все переменные среды окружения и функции, относящиеся к ОрепМР, начинаются с префикса омр_.
Описание параллельных областей. Для определения параллельных областей программы используется пара директив
!$ОМР PARALLEL
<параллельный код программы> !$ОМР END PARALLEL
Для выполнения кода, расположенного между данными директивами, ни­тью-мастером дополнительно порождается omp_num_threads - 1 нитей, где omp_num_threads — это переменная окружения, значение которой пользова­тель задает перед запуском программы и, вообще говоря, может изменять. Нить-мастер всегда получает номер 0. Все нити исполняют код, заключен­ный между данными директивами.
После end parallel автоматически происходит неявная синхронизация всех нитей. Как только все нити доходят до этой точки, нить-мастер продолжает выполнение последующей части программы, а остальные нити уничтожаются.
Параллельные секции могут быть вложенными одна в другую. По умолчанию, вложенная параллельная секция исполняется одной нитью. Необходимую стратегию обработки вложенных секций определяет переменная omp_nested, значение которой можно изменить с помощью функции omp_set_nested.
Число нитей в параллельной секции можно менять. Если значение переменной
OMP_DYNAMIC раВНО 1, ТО С ПОМОЩЬЮ фуНКЦИИ OMP_SET_NUM_THREADS ПОЛЬЗО-
ватель может изменить значение переменной omp_num_threads, а значит и число порождаемых при входе в параллельную секцию нитей. Значение пе­ременной OMP_DYNAMIC Контролируется функцией OMP_SET_DYNAMIC.
Необходимость порождения нитей и параллельного исполнения кода парал­лельной секции пользователь может определять динамически с помощью дополнительной опции if в директиве:
!$ОМР PARALLEL IF(<условие>)
Если <условие> не выполнено, то директива не срабатывает и продолжается обработка программы в прежнем режиме.
Мы уже говорили о том, что все порожденные нити исполняют один и тот же код. Теперь нужно обсудить вопрос, как разумным образом распределить
228
Часть II. Параллельное программирование
между ними работу. Для распределения работы в рамках ОрепМР можно ис­пользовать четыре варианта:
□  программирование на низком уровне;
□  директива do для параллельного выполнения циклов;
□  директива sections для параллельного выполнения независимых фраг­ментов программы;
□  директива single для однократного выполнения участка кода. Программирование на низком уровне предполагает распределение работы с
ПОМОЩЬЮ фуНКЦИЙ OMP_GET_THREAD_NUM И OMP_GET_NUM_THREADS, ВОЗВра-
щающих номер нити и общее количество порожденных нитей соответствен­но. Например, если написать фрагмент вида:
IF(OMP_GET_THREAD_NUM() .EQ. 3) THEN
<индивидуальный код для нити с номером 3> ELSE
<код для всех остальных нитей> ENDIF
то часть программы между директивами if. . .else будет выполнена только нитью с номером 3, а часть между else. . .endif — всеми остальными. Как и прежде, исходный код остается одинаковым для всех нитей. Однако ре­альная передача управления в нем будет идти для разных нитей по-разному, поскольку функция omp_get_thread_num () возвратит значение 3 только для нити с номером 3.
Если в параллельной секции встретился оператор цикла, то согласно обще­му правилу он будет выполнен всеми нитями, т. е. каждая нить выполнит все итерации данного цикла.
Для распределения итераций цикла между нитями нужно использовать дирек­тиву do:
!$ОМР DO [опция [[,] опция]...]
<о!о-цикл> [!$ОМР END DO]
Данная директива относится к идущему следом за ней оператору do.
□  Опция schedule определяет конкретный способ распределения итераций данного цикла по нитям.
□   static [,m] — блочно-циклическое распределение итераций. Первый блок из m итераций выполняет первая нить, второй блок — вторая и т. д. до последней нити, затем распределение снова начинается с первой ни­ти. Если значение m не указано, то все множество итераций делится на
Глава 5. Технологии параллельного программирования
229
непрерывные куски примерно одинакового размера, и таким образом по­лученные куски распределяются между нитями.
□  dynamic [,m] — динамическое распределение итераций с фиксирован­ным размером блока. Сначала все нити получают порции из m итераций, а затем каждая нить, заканчивающая свою работу, получает следующую порцию, содержащую также m итераций. Если значение m не указано, оно принимается равным единице.
□  guided [,m] — динамическое распределение итераций блоками умень­шающегося размера. Сначала размер выделяемых блоков берется доста­точно большим, а в процессе работы программы он все время уменьша­ется. Минимальный размер блока итераций равен т. Размер первона­чально выделяемого блока зависит от реализации. Если значение m не указано, оно принимается равным единице. В ряде случаев такое распре­деление позволяет аккуратнее разделить работу и сбалансировать загрузку нитей.
□  runtime — способ распределения итераций цикла выбирается во время работы программы в зависимости от значения переменной omp_schedule.
Выбранный способ распределения итераций указывается в скобках после опции schedule, например:
!$ОМР DO SCHEDULE (DYNAMIC, 10)
В данном примере будет использоваться динамическое распределение ите­раций блоками по 10 итераций.
В конце параллельного цикла происходит неявная барьерная синхронизация параллельно работающих нитей: их дальнейшее выполнение происходит толь­ко тогда, когда все они достигнут данной точки. Если в подобной задержке нет необходимости, то завершающая директива end do nowait позволяет ни­тям, уже дошедшим до конца цикла, продолжить выполнение без синхрони­зации с остальными. Если директива end do в явном виде не указана, то в конце параллельного цикла синхронизация все равно будет выполнена.
На организацию параллельных циклов накладывается несколько естествен­ных ограничений. В частности, предполагается, что корректная программа не должна зависеть от того, какая именно нить какую итерацию параллель­ного цикла выполнит. Нельзя использовать побочный выход из параллель­ного цикла. Размер блока итераций, указанный в опции schedule, не должен изменяться в рамках цикла.
Рассмотрим следующий пример. Будем предполагать, что он расположен в параллельной секции программы.
!$ОМР DO SCHEDULE (STATIC, 2) DO i = 1, n DO j = 1, m
230
Часть II. Параллельное программирование
A(i, j) = (B(i, j - 1) + B(i - 1, j)) /2.0
END DO END DO !$OMP END DO
В данном примере внешний цикл объявлен параллельным, причем будет использовано блочно-циклическое распределение итераций по две итерации в блоке. Относительно внутреннего цикла никаких указаний нет, поэтому он будет выполняться последовательно каждой нитью.
Параллелизм на уровне независимых фрагментов оформляется в ОрепМР с помощью парной директивы sections. . .end sections и какого-то числа директив section, расположенных внутри этой пары. Например:
!$ОМР SECTIONS !$ОМР SECTION
<фрагмент 1> !$ОМР SECTION
<фрагмент 2> !$ОМР SECTION
<фрагментЗ> !$ОМР END SECTIONS
В данном примере программист описал, что все три фрагмента можно ис­полнять параллельно. Каждый из таких фрагментов будет выполнен только один раз какой-либо одной нитью. Если число нитей больше числа секций, то какие нити для каких секций задействовать, а какие нити не использо­вать вовсе, решают авторы конкретной реализации ОрепМР. В конце конст­рукции предполагается неявная синхронизация работы нитей. Если в по­добной синхронизации нет необходимости, то может быть использована директива END SECTIONS nowait.
В директивах do и sections можно использовать опции firstprivate и lastprivate, каждую со своим списком переменных. Эти опции управляют инициализацией локальных переменных перед входом в данные конструк­ции, а также определяют те значения, которые переменные будут иметь по­сле завершения параллельного цикла и обработки секций.
Если в параллельной секции какой-либо участок кода должен быть выпол­нен лишь один раз, то его нужно поставить между директи­вами single...end single. Подобная необходимость часто возникает при работе с общими переменными. Указанный участок будет выполнен только одной нитью. Если в конце не указана директива end single nowait, то выполнится неявная синхронизация всех нитей.
Одно из базовых понятий ОрепМР — классы переменных. Все переменные, используемые в параллельной секции, могут быть либо общими, либо ло-
Глава 5. Технологии параллельного программирования
231
кальными. Общие переменные описываются директивой shared, а локаль­ные — директивой private. Каждая общая переменная существует лишь в одном экземпляре и доступна для каждой нити под одним и тем же именем. Для каждой локальной переменной в каждой нити существует отдельный экземпляр данной переменной, доступный только этой нити.
Предположим, что следующий фрагмент расположен в параллельной сек­ции:
I = OMP_GET_THREAD_NUM() PRINT *, I
Если переменная i в данной параллельной секции была описана как ло­кальная, то на выходе будет получен весь набор чисел от 0 до omp_num_threads - 1. Числа будут идти, вообще говоря, в произвольном порядке, но каждое число встретится только один раз. Если же переменная i была объявлена общей, то единственное, что можно сказать с уверенно­стью — мы получим последовательность из omp_num_threads чисел, лежа­щих в диапазоне от 0 до omp_num_threads - 1 каждое. Сколько и каких именно чисел будет в последовательности, заранее сказать нельзя. В пре­дельном случае это может быть даже набор из omp_num_threads одинаковых чисел 10. В самом деле, предположим, что все процессы, кроме процесса i0, выполнили в каком-то порядке первый оператор. Затем их выполнение по какой-то причине было приостановлено. В это время процесс с номером i0 присвоил это значение переменной i, а поскольку данная переменная явля­ется общей, то одно и то же значение в последствии и будет выведено каж­дой нитью. Чтобы избежать подобной неопределенности, программист дол­жен сам следить за корректностью использования общих переменных различными нитями.
Рассмотрим следующий пример программы.
PROGRAM HELLO
INTEGER NTHREADS, TID, OMP_GET_NUM_THREADS, +OMP_GET_THREAD_NUM
С Порождение нитей с локальными переменными !$ОМР PARALLEL PRIVATE(NTHREADS, TID)
С Получить и напечатать свой номер TID = OMP_GET_THREAD_NUM() PRINT *, 'Hello World from thread = ', TID
С Участок кода для нити-мастера IF (TID .EQ. 0) THEN
232
Часть II. Параллельное программирование
NTHREADS = OMP_GET_NUM_THREADS()
PRINT *, 'Number of threads = ', NTHREADS
ENDIF
С Завершение параллельной секции !$ОМР END PARALLEL END
Каждая нить выполнит фрагмент кода, представляющий параллельную сек­цию, и напечатает приветствие вместе с номером нити. Дополнительно, нить-мастер напечатает общее число порожденных нитей. В объявлении па­раллельной секции явно указано, что переменные nthreads и tid являются локальными. Для определения общего числа нитей и их номеров использо­ваны библиотечные функции omp_get_num_threads и omp_get_thread_num.
Теперь рассмотрим программу сложения векторов.
PROGRAM VEC_ADD_DO INTEGER N, CHUNK, I PARAMETER (N = 1000) PARAMETER (CHUNK =100) REAL A(N), B(N), C(N)
! Инициализация массивов DO I = 1, N
A(I) = I * 1.0 B(I) = A(I) END DO
!$OMP PARALLEL SHARED(A,B,C,N) PRIVATE(I) !$OMP DO SCHEDULE(DYNAMIC,CHUNK)
DO I = 1, N
C(I) = A(I) + B(I)
END DO !$OMP END DO NOWAIT
!$OMP END PARALLEL END
В данном примере массивы а, в, с и переменная n объявлены общими. Пе­ременная i является локальной, каждый процесс будет иметь свою копию данной переменной. Итерации параллельного цикла будут распределяться между нитями динамически. Размер блоков фиксирован и равен chunk.
Глава 5. Технологии параллельного программирования                                                233
Синхронизации нитей в конце параллельного цикла не будет, т. к. исполь­зована КОНСТРУКЦИЯ NOWAIT.
Целый набор директив в ОрепМР предназначен для синхронизации работы нитей. Самый распространенный и простой способ синхронизации — это барьер. Он оформляется с помощью директивы
!$ОМР BARRIER
Все нити, дойдя до этой директивы, останавливаются и ждут пока все ос­тавшиеся нити не дойдут до этой точки программы, после чего все нити продолжают работать дальше.
Пара директив master. . .end master выделяет участок кода, который будет выполнен только нитью-мастером. Остальные нити просто пропускают дан­ный участок и продолжают работу с оператора, расположенного следом за директивой end master. Неявной синхронизации данная директива не предполагает.
С помощью директив
!$ОМР CRITICAL [ (<имя_критической_секции>) ]
!$ОМР END CRITICAL [ (< имя_ критической_секции >) ]
оформляется критическая секция программы. В каждый момент времени в критической секции может находиться не более одной нити. Если критиче­ская секция уже выполняется какой-либо нитью Pq, то все другие нити, вы­полнившие директиву critical для секции с данным именем, будут забло­кированы, пока нить Pq не закончит выполнение данной критической секции. Как только Pq выполнит директиву end critical, одна из заблоки­рованных на входе нитей войдет в секцию. Если на входе в критическую секцию стояло несколько нитей, то случайным образом выбирается одна из них, а остальные заблокированные нити продолжают ожидание. Все неиме­нованные критические секции условно ассоциируются с одним и тем же именем. Все критические секции, имеющие одно и то же имя, рассматри­ваются единой секцией.
Модифицируем приведенный выше пример, связанный с печатью номера нити, следующим образом:
!$ОМР CRITICAL
I = OMP_GET_THREAD_NUM()
PRINT *, I !$OMP END CRITICAL
Даже в том случае, если переменная i будет объявлена общей, все равно на выходе гарантированно появятся все числа от 0 до omp_num_threads - 1.
234
Часть II. Параллельное программирование
Теперь посмотрим, есть ли разница между данным фрагментом, в котором используется критическая секция и общая переменная i, и фрагментом без критической секции с локальной переменной I. Результат, как мы только что видели, может различаться лишь в порядке появления чисел. Наборы чисел будут одинаковыми. Однако в исполнении этих фрагментов разница сущест­венная. Если есть критическая секция, то в каждый момент времени фрагмент будет обрабатываться лишь какой-либо одной нитью. Остальные нити, даже если они уже подошли к данной точке программы и готовы к работе, будут ожидать своей очереди. Если критической секции нет, то все нити могут од­новременно выполнить данный участок кода. С одной стороны, критические секции предоставляют удобный механизм для работы с общими переменны­ми. Но, с другой стороны, пользоваться им нужно осмотрительно, поскольку критические секции добавляют последовательные участки кода в параллель­ную программу, что может снизить ее эффективность.
Частым случаем использования критических секций на практике является обновление общих переменных. Например, если переменная sum является общей и оператор вида sum = sum + Expr находится в параллельной секции программы, то при одновременном выполнении данного оператора не­сколькими нитями можно получить некорректный результат. Чтобы избе­жать такой ситуации, можно воспользоваться механизмом критических сек­ций или специально предусмотренной для таких случаев директивой atomic:
!$ОМР ATOMIC
SUM = SUM + Expr
Данная директива относится только к идущему непосредственно за ней опе­ратору. В данном примере atomic гарантирует корректную работу с общей переменной sum.
Поскольку в современных параллельных вычислительных системах может использоваться сложная структура и иерархия памяти, пользователь должен иметь гарантии того, что в необходимые ему моменты времени каждая нить будет видеть единый согласованный образ памяти. Именно для этих целей и предназначена директива
!$ОМР FLUSH [ список_переменных ]
Выполнение данной директивы предполагает, что значения всех перемен­ных, временно хранящиеся в регистрах, будут занесены в основную память, все изменения переменных, сделанные нитями во время их работы, станут видимы остальным нитям, если какая-то информация хранится в буферах вывода, то буферы будут сброшены, и т. п. Поскольку выполнение данной директивы в полном объеме может повлечь значительные накладные расхо­ды, а в какой-то момент нужна гарантия согласованного представления не всех, а лишь отдельных переменных, то эти переменные можно явно пере­числить списком.
Глава 5. Технологии параллельного программирования
235
Мы не будем детальнее разбирать конструкции данной технологии. Читатель сможет найти полные тексты спецификаций ОрепМР для языков Fortran, С и C++ на сайте http://www.openmp.org.
Чем привлекательна технология ОрепМР? Можно отметить несколько мо­ментов, среди которых стоит особо подчеркнуть два. Во-первых, технология изначально спроектирована таким образом, чтобы пользователь мог работать с единым текстом для параллельной и последовательной программ. Обыч­ный компилятор на последовательной машине директивы ОрепМР просто "не замечает", поскольку они расположены в комментариях. Единственным источником проблем могут стать переменные окружения и специальные функции. Однако для них в спецификациях стандарта предусмотрены спе­циальные "заглушки", гарантирующие корректную работу ОрепМР-программы в последовательном случае. Для этого нужно только перекомпи­лировать программу и подключить другую библиотеку.
Другим достоинством ОрепМР является возможность постепенного, "инкре-ментного" распараллеливания программы. Взяв за основу последовательный код, пользователь шаг за шагом добавляет новые директивы, описывающие новые параллельные конструкции. Нет необходимости сразу писать парал­лельную программу целиком, ее создание ведется последовательно. Это упро­щает как процесс программирования, так и отладку.
Система программирования DVM
Модель, положенная в основу языков параллельного программирования Fortran-DVM [53] и C-DVM [30], объединяет элементы моделей паралле­лизма по данным и управлению. Базирующаяся на этих языках DVM-система разработки параллельных программ создана в Институте приклад­ной математики им. М. В. Келдыша РАН.
DVM-система состоит из пяти основных компонентов: компиляторы с язы­ков Fortran-DVM и C-DVM, система поддержки выполнения параллельных программ, отладчик параллельных программ, анализатор производительно­сти, предсказатель производительности.
При проектировании DVM-системы авторы опирались на следующие прин­ципы.
□  Система должна базироваться на высокоуровневой модели выполнения па­раллельной программы, удобной и понятной для программиста, привык­шего программировать на последовательных языках.
□   Спецификации параллелизма должны быть прозрачными для обычных ком­пиляторов. Программа на языках Fortran-DVM и C-DVM, помимо опи­сания алгоритма средствами традиционных языков Fortran 77 или С, содержит спецификации параллелизма — правила параллельного вы­полнения этого алгоритма. Эти спецификации, которые по-другому на-
236
Часть II. Параллельное программирование
зывают директивами, должны быть "невидимы" для стандартных ком­пиляторов.
□  Языки параллельного программирования должны представлять собой традиционные языки последовательного программирования, расширен­ные спецификациями параллелизма. Эти языки должны предлагать про­граммисту модель программирования, близкую к модели выполнения. Знание программистом модели выполнения его программы и ее близость к моде­ли программирования существенно упрощает для него анализ производи­тельности программы и проведение ее модификаций, направленных на достижение приемлемой эффективности.
□   Основная работа по реализации модели выполнения параллельной програм­мы (например, распределение данных и вычислений) должна осуществ­ляться динамически системой поддержки выполнения DVM-программ. Это позволяет обеспечить динамическую настройку DVM-программ при их запуске на параметры приложения и конфигурацию параллельного ком­пьютера.
Следствием последнего пункта является то, что программист может иметь один вариант программы для выполнения как на последовательных и парал­лельных компьютерах различной конфигурации, так и на неоднородных се­тях ЭВМ. Это значительно облегчает отладку и дальнейшее сопровождение программы.
Таковы общие принципы. Параллельная программа на исходном языке Fortran-DVM (C-DVM) превращается DVM-препроцессором в обычную программу на языке Fortran 77 (С) с вызовами функций системы поддерж­ки. Поскольку основой для организации межпроцессорного взаимодействия в системе поддержки является MPI, то программа может выполняться всю­ду, где есть MPI и компиляторы с языков С и Fortran 77. Этим поддержива­ется высокая степень переносимости. В будущем планируется автоматиче­ское преобразование DVM-программ в ОрепМР-программы.
Для языка Fortran все DVM-директивы оформлены в виде строк коммента­риев, начинающихся с cdvm$, *dvm$ или idvm$. В языке С директивы DVM оформляются в виде макросов. Семантика директив в языках Fortran и С практически одинакова, что позволяет иметь на компьютере единую систе­му поддержки выполнения DVM-программ.
Модель выполнения DVM-программы можно описать следующим образом.
DVM-программа исполняется на виртуальной многопроцессорной системе. Виртуальной многопроцессорной системой называется та машина, которая предоставляется программе пользователя аппаратурой и базовым системным программным обеспечением. Для распределенной вычислительной системы примером такой машины может служить MPI-машина. В этом случае вирту­альная многопроцессорная система — это группа MPI-процессов, которые
Глава 5. Технологии параллельного программирования
237
создаются при запуске параллельной программы на выполнение. Другим примером виртуальной системы является PVM.
Виртуальная многопроцессорная система всегда представляется в виде многомерной решетки процессоров. Число процессоров виртуальной много­процессорной системы и конкретный способ ее представления задаются при запуске DVM-программы.
В момент запуска DVM-программа начинает свое выполнение с первого оператора программы сразу на всех процессорах виртуальной многопроцес­сорной системы. В это время в DVM-программе существует единственный поток управления (единственная ветвь).
В любой DVM-программе могут быть использованы два уровня параллелиз­ма. На верхнем уровне в программе описывается какое-то число независи­мых ветвей (задач), которые могут выполняться параллельно. Задачи DVM — это независимые по данным крупные блоки программы. В конце ветвей допускается выполнение глобальной редукционной операции. В рам­ках каждой ветви могут дополнительно выделяться параллельные циклы. Никакой другой иерархии параллелизма DVM не допускает, и описать в те­ле параллельного цикла еще несколько независимых ветвей нельзя.
При входе в параллельную конструкцию, т. е. в параллельный цикл или в область параллельных задач, поток управления разбивается на некоторое количество независимых потоков, каждый из которых определяет процесс вычислений на соответствующих процессорах. При выходе из параллельной конструкции потоки управления на всех процессорах вновь становятся оди­наковыми.
Все переменные DVM-программы размножаются по всем процессорам. Это означает, что на каждом процессоре будет своя локальная копия каждой пе­ременной, с которой и будет происходить работа. Исключение составляют лишь специально указанные "распределенные" массивы, способ физиче­ского расположения которых определяется соответствующей директивой.
Любой оператор присваивания DVM-программы выполняется в соответст­вии с правилом собственных вычислений. Это означает, что он будет выполнен тем процессором, на котором распределена переменная, стоящая в левой части оператора присваивания.
Любая DVM-программа работает в соответствии с моделью SPMD на всех выделенных ей процессорах.
Основные конструкции DVM
Общая схема отображения программы и взаимосвязь основных понятий по­казана на рис. 5.3. Отображение виртуальных процессоров на физические осуществляется средствами операционной системы.
238
Часть II. Параллельное программирование
Рис. 5.3. Отображение последовательной программы в системе DVM
Распределение массивов. Для распределения массивов в системе DVM ис­пользуется директива distribute. Данная директива имеет описательный статус и может быть использована в неисполняемой части программы ана­логично операторам real, dimension и т. п. Рассмотрим отображение одно­мерного массива.
CDVM$ DISTRIBUTE имя-массива (формат) [ONTO Т(п)]
где поле формат может принимать одно из нескольких значений:
□  block — отображение равными блоками;
□  wgt_block(wb,nwb) — отображение взвешенными (неравными) блоками;
□   * — отображение целым измерением.
Предположим, что при запуске программы задана линейка виртуальных процессоров p(np). При отображении равными блоками измерение массива разбивается на np равных блоков, причем i-ый блок отображается на вирту­альный процессор р (i). При отображении взвешенными блоками задается вектор весов wb размера nwb для каждой точки (или группы соседних точек) измерения распределяемого массива. Измерение массива делится на np бло­ков так, чтобы минимизировать отклонение веса каждого блока (суммы ве-
Глава 5. Технологии параллельного программирования
239
сов точек блока) от среднего значения. Такое отображение позволяет точнее сбалансировать загрузку процессоров. Отображение целым измерением озна­чает, что измерение не будет распределяться между процессорами.
Если присутствует опция onto т (п), то массив отображается на ту часть ли­нейки процессоров р (np) , на которую отображена n-ая задача вектора задач т.
Распределение многомерного массива осуществляется через независимое распределение каждого его измерения. При этом число распределенных из­мерений массива не может превышать числа измерений решетки виртуаль­ных процессоров.
Выравнивание массивов. Для создания эффективных программ мало задать рас­пределение массивов. Во многих случаях требуется отобразить несколько мас­сивов согласованно друг с другом. Причин для этого много. Например, согла­сованное распределение потребуется для массивов, которым присваиваются новые значения в одном цикле, поскольку в противном случае не удастся со­блюсти правило собственных вычислений. Согласованное отображение не­скольких массивов нужно и для того, чтобы уменьшить количество общих данных, доступ к которым требует существенных накладных расходов.
Для организации согласованного отображения нескольких массивов исполь­зуется механизм выравнивания одного массива относительно другого с по­мощью ДИреКТИВЫ ALIGN.
Рассмотрим следующий фрагмент программы.
REAL A(N), B(N) CDVM$ DISTRIBUTE A (BLOCK) CDVM$ DISTRIBUTE В (BLOCK)
DO i = nl, n2 B(i) = A(f (i)) END DO
Пусть owN(B(i)) обозначает виртуальный процессор, на который распределен элемент в (i). В соответствии с правилом собственных вычислений оператор на i-й итерации цикла будет выполняться на процессоре owN(B(i)). Если элемент A(f (i)) будет распределен на процессор owN(B(i)), то для каждого витка цикла все данные будут распределены на одном процессоре. Чтобы обеспечить такую локализацию данных, необходимо вместо директивы
CDVM$ DISTRIBUTE В (BLOCK)
применить директиву выравнивания массивов
CDVM$ ALIGN B(i) WITH A(f(i))
Директива устанавливает соответствие между элементами в (i) и а (f (i)), при котором они будут распределены на один и тот же процессор.
240
Часть II. Параллельное программирование
В любой момент можно динамически изменять текущее распределение дан­ных с помощью директив redistribute и realign. Однако пользоваться та­кой возможностью нужно аккуратно, поскольку перераспределение может потребовать значительных затрат времени.
Параллельное выполнение циклов. Директива параллельного выполнения цик­лов parallel встроена в язык Fortran в виде спецкомментария следующего вида:
CDVM$ PARALLEL (/;, ..., im) ON A(Lh ..., Ln)
где /j — это управляющие параметры циклов /и-мерного тесно вложенного гнезда циклов, расположенного сразу после данной директивы, Lk = akxij + bkэто линейная функция от управляющего параметра у'-го цик­ла, а— это идентификатор массива данных или вектора задач (1 <j<m, 1 < к < п). Все управляющие параметры циклов перечисляются в том поряд­ке, в каком расположены циклы в тексте программы.
Данная директива устанавливает соответствие между итерацией многомер­ного цикла с индексами (/ь ..., im) и элементом A(Ly, ..., Ln). Итерация цикла (/ь ..., im) будет выполняться тем процессором, на который отображен эле­мент массива A(Ly,..., Ln).
Рассмотрим следующий пример.
CDVM$ PARALLEL (i) ON B(i) DO i = nl, n2
CALL XSUM(A, B, i, nl, n2) END DO
Данная директива говорит о том, что каждая итерация i0 будет выполнена тем процессором, на который распределен элемент B(i0).
Здесь следует сделать несколько дополнительных замечаний о выполнении операторов присваивания DVM-программы. Основной закон — это правило собственных вычислений. Оператор всегда должен выполняться тем процес­сором, на котором расположена переменная, стоящая в левой части. Та пе­ременная, которой присваивается значение, определяет процессор, выпол­няющий оператор. Следствием этого факта является то, что с помощью перераспределения данных можно управлять балансировкой вычислитель­ной нагрузки между процессорами.
Предположим, что в тексте программы есть такой фрагмент:
DO i = 1, n
B(i) = A(i) + C(i) END DO
Если перед циклом нет директивы parallel, то данный цикл будет выпол­няться всеми процессорами по правилу собственных вычислений. При этом
Глава 5. Технологии параллельного программирования
241
возможны две ситуации. Если массив в не является распределенным, то он размножен по всем процессорам, и, следовательно, все процессоры выпол­нят весь цикл полностью от первой итерации до последней. Если массив в является распределенным, то каждый процессор выполнит только те итера­ции /, для которых элементы в (i) распределены именно на этот процессор.
Теперь предположим, что перед циклом расположена директива parallel. Каждый процессор выполнит те итерации, которые предписаны данной ди­рективой в соответствии с ее частью on. Однако для корректной работы программы предписание директивы должно полностью соответствовать пра­вилу собственных вычислений. Явная избыточность? В некотором смысле — да, но она необходима для получения эффективных программ. Если есть директива parallel, то компилятор не вставляет никаких проверок на при­надлежность вычисляемого элемента данному процессору. Каждый процес­сор выполняет только ту порцию итераций, которая предписана директивой. Во главу ставится эффективность, а за корректностью программы должен следить пользователь. Если директивы parallel нет, то для соблюдения правила собственных вычислений перед каждым оператором компилятор автоматически вставит дополнительную проверку. Будет гарантия коррект­ности работы программы за счет снижения эффективности ее работы.
Отображение задач. Директива map встроена в язык Fortran в следующем виде:
CDVM$ MAP T\n) ONTO Щу.12, -, jy.ji)
где T{n) — это я-ая задача вектора задач Г, a P{ii.i2, ■■■, j\-j2) — это секция ре­шетки виртуальных процессоров.
Директива map специфицирует то, что n-ая задача вектора Т будет выпол­няться секцией виртуальных процессоров P(iyi2, —, Л72)- Все отображенные на эту задачу массивы (директива distribute) и вычисления (директива parallel) будут автоматически отображены на ту же секцию виртуальных процессоров P(iy:i2, ...,jy.j2).
Что касается задач, то в DVM существуют две формы отображения блоков программы на задачи. Статическая форма является прямым аналогом парал­лельных секций во многих других языках, в частности, директивы sections в ОрепМР. Статическая форма отображения выглядит, например, так:
С                описание массива задач
CDVM$ TASK MB (3)
CDVM$ TASK_REGION MB CDVM$ ON MB(1)
CALL JAC0BY(A1, Bl, M, N1 + 1) CDVM$ END ON
242
Часть II. Параллельное программирование
CDVM$ ON MB (2)
CALL JACOBY(A2, B2, Ml + 1, N2 + 1) CDVM$ END ON CDVM$ ON MB(3)
CALL JACOBY(A3, ВЗ, M2, N2) CDVM$ END ON CDVM$ END TASK_REGION
В динамической форме итерация цикла отображается на задачу. Эта специ­фикация синтаксически похожа на обычную директиву параллельного цик­ла, но существует семантическое отличие. В параллельном цикле на каждом процессоре из решетки виртуальных процессоров выполняется непрерыв­ный диапазон итераций цикла, а в цикле параллельных задач каждая итера­ция цикла выполняется на секции данной решетки. Пример динамической формы отображения блоков программы на задачи может быть таким:
CDVM$ TASK_REGION MB CDVM$ PARALLEL (I) ON MB (I) DO I = 1, NT
END DO CDVM$ END_TASK REGION
Общие данные. Общими данными в системе DVM считаются те данные, ко­торые вычисляются на одних процессорах, а используются на других. Все общие данные в DVM делятся на четыре группы: "соседние" общие данные, REMOTE-данные, "редукционные" данные и across-данные.
"Соседние" общие данные. Рассмотрим следующий фрагмент программы
CDVM$ PARALLEL (i) ON B(i), SHAD0W_RENEW (A(dl:d2)) DO i = 1 + dl, N - d2
B(i) = A(i - dl) + A(i + d2) END DO
Необходимые каждому процессору общие данные размещены на соседних виртуальных процессорах. Спецификация shadow_renew указывает, что в данном цикле используются новые значения общих данных из массива а, значение dl указывает размер требуемых данных от левого соседа, a d2 — от правого. Для доступа к данным, размещенным на других процессорах, ис­пользуются так называемые теневые грани массива. Теневую грань пользова­тель может представлять себе буфером, который является непрерывным продолжением локальной секции массива в памяти процессора. Перед вы­полнением цикла требуемые данные автоматически пересылаются с сосед­них процессоров в теневые грани массива а на каждом процессоре. Доступ к
Глава 5. Технологии параллельного программирования
243
этим данным при выполнении цикла производится обычным образом и ни­чем не отличается от доступа к данным, изначально размещенным на про­цессорах. Фраза "может представлять себе буфером..." появилась не случай­но, т. к. в конкретных реализациях это может быть не так. Например, в системах с общей памятью реализация теневых граней может осуществляться просто через синхронизацию доступа к соответствующим частям массива. Дополнительно в DVM предусмотрена и асинхронная форма данной специ­фикации, которая позволяет совместить вычисления и пересылку данных в теневые грани.
REMOTE-данные. Иногда доступ к удаленным данным требует заведения спе­циальных буферов и соответствующей замены ссылок на массив, через ко­торые происходят обращения к удаленным элементам массива.
Рассмотрим следующий фрагмент программы.
DIMENSION А(100,100), В(100,100) СТОМ$ DISTRIBUTE (*,BLOCK) :: А CDVM$ ALIGN B(I, J) WITH A(I, J)
CDVM$ REMOTE_ACCESS (А (50, 50))
С замена А(50, 50) ссылкой на буфер на всех процессорах OWN(X) и С рассылка значения А(50, 50) по всем процессорам X = А(50, 50)
CDVM$ REMOTE_ACCESS (В(100, 100))
С пересылка значения В (100, 100) в буфер процессора OWN(A(l, 1) А(1, 1) = В(100, 100)
CDVM$ PARALLEL (I, J) ON A (I, J) , REMOTE_ACCESS (B(I, n) ) С рассылка значений В(I, п) по процессорам OWN(A(I, J)) DO I = 1, 100 DO J = 1, 100
A(I,J) = B(I,J) + B(I,n) END DO END DO
Первые две директивы remote_access описывают удаленные ссылки для отдельных операторов. Директива remote_access в параллельном цикле специфицирует удаленные данные (элементы n-го столбца матрицы в) для всех процессоров, на которых выполняется цикл. При этом на каждый про­цессор будет переслана только необходимая ему часть столбца матрицы в.
244
Часть II. Параллельное программирование
Редукционные данные. Данные этой группы появляются при выполнении разного рода глобальных операций. Сначала операция выполняется на каж­дом процессоре на основе тех данных, которые на нем размещены. Частич­ный результат заносится в редукционную переменную процессора. Затем по редукционным переменным вычисляется результат глобальной операции.
Рассмотрим следующий фрагмент программы.
CDVM$ PARALLEL (I) ON B(I), REDUCTION (SUM(s)) DO I = 1, n
S = S + B(I) END DO
Имеющаяся зависимость по переменной s, вообще говоря, требует последо­вательного выполнения итераций цикла с передачей значения переменной s от каждого отработавшего процессора к следующему. Но если ситуация по­зволяет пренебречь отсутствием ассоциативности у машинных операций сложения, то такой цикл можно эффективно распараллелить. Для этого на каждом процессоре вводятся локальные копии переменной s, в которых при параллельном выполнении цикла будут храниться частичные суммы. После выполнения цикла суммирование всех частичных сумм даст искомую гло­бальную сумму.
Подобные общие данные называются редукционными. При их специфика­ции необходимо указать и ту редукционную операцию, которую требуется выполнить после цикла для объединения частичных результатов, посчитан­ных на каждом процессоре. Аналогично приведенному примеру можно по­ступить для распараллеливания циклов, в которых вычисляется, например, максимальное или минимальное значение элементов массива, произведение элементов массива или выполняется ряд других операций.
ACROSS-данные. Рассмотрим фрагмент программы, в котором пересчитыва-ются элементы массива в.
CDVM$ PARALLEL (I) ON B(I), ACROSS (B(dl:d2)) DO I = 1 + dl, n - d2
B(I) = B(I - dl) + B(I + d2) END DO
Указанные в директиве элементы массива в являются ACROSS-данными. В от­личие от соседних общих данных, across-данные используются и обновляют­ся в одном и том же цикле, определяя информационную зависимость. Пара­метры dl и d2 указывают размеры общих данных на соседних процессорах слева и справа соответственно. Подобная зависимость по данным опять-таки требует последовательного выполнения итераций цикла с передачей значений обновленных общих данных от одного процессора другому. Однако в некото­рых случаях можно эффективно распараллелить такой цикл, если организо-
Глава 5. Технологии параллельного программирования
245
вать его конвейерное выполнение. Это, в частности, можно сделать, когда двумерный цикл распределен по одному измерению на линейку процессоров. Идея конвейерного выполнения заключается в том, что первый процессор выполняет часть "своих" итераций цикла и передает следующему процессору соответствующую порцию обновленных общих данных. После получения этих данных второй процессор начинает выполнять "свои" итерации цикла парал­лельно с выполнением первым процессором второй порции итераций, и т. д. Оптимальное число итераций в порции зависит от общего количества итера­ций цикла, времени выполнения каждой итерации, числа процессоров, а так­же латентности и пропускной способности коммуникационной среды. Систе­ма поддержки DVM-программ обеспечивает автоматическую передачу обновленных общих данных между процессорами.
Как мы видим, DVM-программа может выполняться на любом количестве процессоров, начиная с одного процессора. Это является следствием того, что директивы параллелизма в DVM не зависят ни от количества процессоров, ни от конкретных номеров процессоров. Единственным исключением явля­ется уровень задач. По требованию директивы map пользователь обязан явно описать массив виртуальных процессоров. При этом количество процессо­ров, описанных в программе, не должно превышать количества процессо­ров, задаваемых при запуске программы. Эта согласованность легко дости­гается использованием встроенной функции number_of_processors о, значением которой является количество процессоров, задаваемых при за­пуске программы.
В заключение рассмотрим пример параллельной программы на языке Fortran-DVM, реализующей алгоритм Якоби для решения сеточных уравнений.
PROGRAM JAC_DVM
PARAMETER (L = 8, ITMAX = 20) REAL A(L, L) , B(L, L) CDVM$ DISTRIBUTE (BLOCK, BLOCK) :: A CDVM$ ALIGN В (I, J) WITH A (I, J) С массивы А и В распределяются блоками
PRINT *, *********** TEST JACOBI **********' DO IT = 1, ITMAX CDVM$ PARALLEL (J, I) ON A (I, J ) DO J = 2, L - 1 DO I = 2, L - 1
A(I, J) = B(I, J) END DO END DO CDVM$ PARALLEL (J, I) ON В (I, J) , SHADOW_RENEW (A)
246
Часть II. Параллельное программирование
С двумерный параллельный цикл, итерация (j, i) выполняется, С на том процессоре, где размещен элемент B(i, j) С копирование теневых элементов массива А С с соседних процессоров перед выполнением цикла DO J = 2, L - 1 DO I = 2, L - 1
B(I, J) = (A(I - 1, J) + A(I, J - 1) + *                          A(I + 1, J) + A(I, J + 1) )/4
END DO END DO END DO
OPEN (3, FILE = 'JACOBI.DAT', FORM = 'FORMATTED') WRITE (3,*)B CLOSE (3) END
Массив а распределяется на двумерную решетку виртуальных процессоров, поскольку количество форматов block определяет и количество измерений решетки процессоров. При запуске программы на выполнение может быть задана любая конкретная двумерная решетка процессоров. На рис. 5.4 пока­зано отображение массива а на процессорную решетку 3x3. Согласно дирек­тиве align, элемент в (I, j) будет распределен на тот же процессор, где раз­мещен и элемент массива а (i, j).
Обе директивы parallel описывают распределение итераций цикла, согла­сованное с распределением массивов айв: итерация цикла с индексами (J, I) будет выполняться на том же процессоре, где размещены элементы A(i,j) и B(i, j). Перестановка индексов вызвана только особенностью рас­пределения памяти в языке Fortran. Из соображений эффективности внут­ренний цикл лучше организовать так, чтобы он перебирал расположенные подряд элементы столбца (в C-DVM такая перестановка не потребуется).
Для второй директивы parallel необходимо описать общие теневые дан­ные. Ширина теневых граней определяется разницей индексных выражений элементов массивов, расположенных в левой и правой частях оператора присваивания. Для данного примера ширина всех граней равна 1. Схема об­мена теневыми данными для одного процессора показана на рис. 5.4.
Отладка и оптимизация DVM-программ. Для отладки DVM-программ авторы системы разработали как набор инструментальных средств, так и специаль­ную технологию отладки. Функциональная отладка DVM-программ [31] осуществляется поэтапно. Сначала программа отлаживается на рабочей станции как обычная последовательная программа с использованием штат­ных средств отладки. Затем на той же рабочей станции программа пропус­кается в специальном режиме для проверки DVM-указаний. Это позволяет
Глава 5. Технологии параллельного программирования
247
выявить их правильность и полноту. На следующем этапе программа может быть пропущена на параллельной машине в режиме сравнения промежуточ­ных результатов ее параллельного выполнения с эталонными результатами, полученными при ее последовательном выполнении.
Ац
А12
A-I3
А14
А21
А22
А23
А24
Лл
^32
A33
^34
А41
А42
A43
2
А13
А,4
Al5
Al6
А17
А23
А24
А25
А26
А27
Азз/
А37
А34
А35
Д
Н36
(
А44
А45
А46
3
Ai6
А17
Al8
А26
А27
А28
Азб
А37
Азе
А47
А48
Лл
А32
Азз
А41
А42
А43
А44
А51
А52
А53
А54
А61
А62
А63
А64
А71
А72
А73
А А А
63/ 64            65
^37
А38
А46
А47
А48
А56
А57
А58
А66
А67
А68
А77
А78
А74 А75
Аб1
Аб2
Абз
А71
А72
А73
А74
Aei
A82
Аб4
Аб5
Абб
У
А73
\
А74
А75
А76
Авз
Ав4
Ав5
Авб
\l
Аб7
Аб8
А76
А77
А78
Авб
Ав7
Локальные части массива
Теневые элементы
Рис. 5.4. Отображение массива А(8, 8) на процессорную решетку 3x3
Для отладки программы на реальной параллельной машине используются средства трассировки, позволяющие зафиксировать последовательность об­ращений к переменным и их значения, а также последовательность вызовов функций системы поддержки выполнения DVM-программ.
Повысить эффективность DVM-программ помогает анализатор производи­тельности, который позволяет пользователю получить информацию об ос­новных показателях эффективности выполнения его программы или ее час­тей на параллельной системе. Для повышения эффективности программ можно использовать специальный инструмент — предсказатель производи-
248
Часть II. Параллельное программирование
тельности. Он позволяет на рабочей станции смоделировать выполнение DVM-программы на параллельной ЭВМ с заданными заранее параметрами (топология коммуникационной сети, ее латентность и пропускная способ­ность, а также производительность процессоров) и спрогнозировать для та­кой системы характеристики выполнения программы.
В настоящее время система DVM может использоваться на рабочих станци­ях SGI, HP, IBM, SUN, на персональных ЭВМ с операционными система­ми UNIX и Windows 95/98/2000/NT. Система установлена и работает на та­ких параллельных вычислительных системах, как МВС-1000М, МВС-1000/200, Parsytec СС, Convex SPP 1600, на вычислительных кластерах НИВЦ МГУ и в других организациях.
Исходные тексты системы, библиотеки выполняемых программ, документа­ция, примеры и другие материалы доступны через Интернет по адресу http://www.keldysh.ru/dvm/.
Система программирования трС
Язык трС является, по-видимому, первым языком высокого уровня, разрабо­танным специально для программирования неоднородных сетей. Он позволя­ет программисту определить все основные свойства параллельного алгоритма, влияющие на скорость его выполнения в неоднородной вычислительной сре­де. Необходимое число параллельных процессов, объем вычислений и переда­ваемых данных в рамках каждого процесса, сценарий взаимодействия процес­сов и многие другие характеристики доступны программисту для создания эффективных программ. Важно и то, что трС позволяет менять эти характе­ристики динамически во время выполнения программы. При этом информа­ция, извлеченная из описания параллельного алгоритма, вместе с данными о реальной производительности процессоров и коммуникационных каналов, помогает системе программирования трС найти эффективный способ ото­бражения процессов mpC-программы на компьютеры сети.
Язык и система программирования трС разработана в Институте систем­ного программирования РАН. Детали описания и текущее состояние проек­та можно найти в [32] или на сайте http://www.ispras.ru/~mpc.
Основы программирования на трС. Язык программирования трС является строгим расширением языка С, ориентированным на параллельные вычис­ления на неоднородных сетях. mpC-программа — это множество параллель­ных процессов, взаимодействующих посредством неявной передачи сооб­щений. Используя язык трС, программист явно нигде не указывает, сколько процессов составляют программу и на каких компьютерах эти про­цессы выполняются. Это делается уже пользователем программы в момент ее запуска внешними по отношению к языку средствами. Исходный код на трС, составленный в соответствии с моделью SPMD, управляет лишь тем, какие именно вычисления выполняются каждым из процессов программы.
Глава 5. Технологии параллельного программирования
249
Группе процессов, совместно выполняющих некоторый параллельный алго­ритм, в языке трС соответствует понятие сети. Сеть в трС — это механизм, позволяющий программисту абстрагироваться от реальных физических про­цессов параллельной программы. В простейшем случае сетью является мно­жество виртуальных процессоров. Определение сети в программе вызывает создание группы реальных процессов, представляющей эту сеть: каждому виртуальному процессору соответствует отдельный процесс параллельной программы. Заметим, что в разные моменты времени выполнения парал­лельной программы один и тот же реальный параллельный процесс может представлять различные виртуальные процессоры различных сетей.
Определив один раз сеть, программист вызывает отображение ее виртуаль­ных процессоров на реальные процессы параллельной программы, и это отображение сохраняется на все время жизни сети. Рассмотрим следующий пример.
#include <mpc.h>#define N 3int [*]main() {net SimpleNet(N) mynet; [mynet ]MPC_Printf("Hello, world!\n");}
В программе сначала определяется сеть mynet, состоящая из n виртуальных процессоров, а затем на этой сети вызывается библиотечная функция MPC_Printf. Выполнение данной программы заключается в параллельном вызове функции MPC_Printf теми n процессами программы, на которые ото­бражены виртуальные процессоры сети mynet. Это отображение осуществля­ется системой программирования языка трС во время выполнения програм­мы. Выполнение функции MPC_Printf каждым процессом заключается в посылке сообщения "Hello, world!" на терминал пользователя, с которого была запущена эта программа. В результате пользователь увидит n приветст­вий "Hello, world! " — по одному от каждого вовлеченного процесса.
Спецификатор [*] перед именем main в определении главной функции гово­рит, что код этой функции в обязательном порядке выполняется всеми про­цессами параллельной программы. Такие функции в трС называются базовыми. Корректная работа таких функций возможна только при условии их вызова всеми процессами параллельной программы. Контроль за корректно­стью вызовов базовых функций осуществляется компилятором. В отличие от функции main, для корректной работы функции MPC_Printf, вообще говоря, не требуется ее вызова всеми процессами параллельной программы. Более того, вполне осмысленным и корректным является вызов этой функции лю­бым отдельно взятым процессом параллельной программы. Такие функции в трС называются узловыми. Узловые функции не связаны явно ни с заданием параллелизма, ни с взаимодействием процессов. Они могут быть вызваны как отдельным процессом параллельной программы, так и группой процессов.
Перейдем к следующему примеру.
#include <mpc.h>
#include <sys/utsname.h>#define N 3int [*]main() { net SimpleNet(N) mynet;
250
Часть II. Параллельное программирование
struct utsname [mynet]un; [mynet ] uname (Sun) ;
[mynet]MPC_Printf("Hello world! I'm on \"%s\".\n",
un.nodename);}
Данная программа выводит на терминал пользователя более содержательные послания от тех процессов параллельной программы, на которые отобража­ются виртуальные процессоры сети mynet. Кроме приветствия "Hello, world!", процесс сообщает имя компьютера, на котором он выполняется. Для этого в программе определяется переменная un, распределенная по сети mynet. Только процессы, соответствующие виртуальным процессорам сети mynet, содержат в своей памяти копию этой переменной. Эти же процессы выполняют вызов функции uname, что специфицируется конструкцией [mynet] перед именем функции. После этого поле nodename в каждой ко­пии распределенной структуры un будет содержать ссылку на имя того ком­пьютера, на котором соответствующий процесс выполняется. В дальнейшем локальные копии распределенных переменных иногда будем называть про­екциями переменных на процессах или виртуальных процессорах.
Время жизни сети mynet, как и время жизни переменной un, ограничено блоком, в котором эта сеть определяется. При выходе из этого блока все процессы программы, захваченные под виртуальные процессоры сети mynet, освобождаются и могут быть использованы при создании других сетей. Та­кие сети называются в трС автоматическими. В отличие от автоматических, время жизни статических сетей ограничено лишь временем выполнения программы. Статические и автоматические сети в трС полностью анало­гичны статическим и автоматическим переменным в языке С.
Следующие две программы демонстрируют различие статических и автома­тических сетей. Программы выглядят практически идентичными. Обе они циклически выполняют блок, включающий определение сети и выполнение уже знакомых вычислений на этой сети. Единственным, но существенным, отличием является то, что в первой программе определяется автоматическая сеть, а во второй — статическая. Первая программа выглядит так.
#include <mpc.h> #include <sys/utsname.h> idefine Nmin 3
idefine Nmax 5int [*]main() { repl n; for(n = Nmin; n <= Nmax; n++) { auto net SimpleNet(n) anet;
struct utsname [anet]un; [anet]uname(&un); [anet]MPC_Printf("An autom.network of %d; I'm on %s.\n", [anet]n, un.nodename); } }
Глава 5. Технологии параллельного программирования
251
При входе в блок на первом витке цикла создается автоматическая сеть из трех виртуальных процессоров (n = Nmin = з), которая при выходе из цикла уничтожается. При входе в блок на втором витке цикла создается новая ав­томатическая сеть уже из четырех виртуальных процессоров, которая также прекращает свое существование при выходе из блока. К моменту выполнения повторной инициализации цикла (операция п++) эта 4-процессорная сеть уже не существует. Наконец, на последнем витке при входе в блок создается авто­матическая сеть из пяти виртуальных процессоров (n = Nmax = 5). Поскольку каждый раз сеть создается заново, то на каждой итерации цикла имена ком­пьютеров, выдаваемые функцией MPC_Printf, могут быть различными.
Заметим, что переменная п в данном примере распределена по всем процес­сам параллельной программы. Ее определение содержит ключевое слово repl, сокращение от replicated. Оно информирует компилятор о том, что значения этой переменной у разных процессов параллельной программы равны между собой. Другими словами, проекции данной переменной на всех процессах имеют одно и то же значение. Такие распределенные пере­менные называются в трС размазанными, а значение размазанной перемен­ной называется размазанным значением. Компилятор с языка трС контро­лирует объявленное программистом свойство размазанности и предупреж­дает обо всех случаях, когда оно может быть нарушено.
Теперь рассмотрим тот же пример, но уже с использованием статической сети.
#include <mpc.h> #include <sys/utsname.h> idefine N rain 3
idefine N max 5int [*]main() {repl n; for(n = Nmin; n <= N max; n++) { static net SimpleNet(n) snet; struct utsname [snet]un;          [snet]uname(&un);
[snet]MPC_Printf("A static network of %d; I'm on %s.\n", [snet]n, un.nodename); } }
При входе в блок на первом витке цикла создается сеть из трех виртуальных процессоров. Однако при выходе из блока она не уничтожается, а просто перестает быть видимой. В этом случае блок является не областью сущест­вования сети, а областью ее видимости, как это и предписано для конструк­ций языка С. Во время выполнения повторной инициализации и проверки условия цикла эта статическая 3-процессорная сеть существует, но недос­тупна. При повторных входах в блок на последующих витках цикла никакие
252
Часть II. Параллельное программирование
новые сети не создаются, а просто становится видна та статическая сеть из трех виртуальных процессоров, которая была создана при первом входе в блок вне зависимости от значения переменной п. Имя anet из предыдущего примера на разных витках цикла обозначало совершенно разные сети. Имя snet данного примера на каждой итерации обозначает одну и ту же сеть, существующую с момента первого входа в блок, в котором она определяется, и до конца выполнения программы. Это же подтверждает и выдача, содер­жащая на каждой итерации один и тот же набор имен компьютеров.
Важнейшим атрибутом сети является ее тип. Это обязательная часть опреде­ления любой сети. Всякому определению сети должно предшествовать оп­ределение соответствующего сетевого типа. В рассмотренных нами приме­рах определение сетевого типа simpieNet находится среди прочих стандартных определений языка трС в файле mpc.h и включается в про­грамму с помощью директивы препроцессора #inciude. Определение simpieNet выглядит следующим образом:
nettype SimpieNet(int n) { coord I = n;
};
Определение вводит имя сетевого типа simpieNet, параметризованного це­лым параметром п. В теле определения объявляется координатная переменная I, изменяющаяся в пределах от о до п - 1. Тип simpieNet является про­стейшим параметризованным сетевым типом и соответствует сетям, состоя­щим из п виртуальных процессоров, упорядоченных в соответствии со сво­им номером.
Рассмотрим следующую программу.
#include <mpc.h>#define N 5int [*]main() { net SimpieNet(N) mynet;
int [mynet]my_coordinate;
my_coordinate = I coordof mynet;
if(my_coordinate%2 = 0) [mynet]MPC_Printf("Hello, even world!\n");
else [mynet]MPC_Printf("Hello, odd world!\n"); }
Данная программа демонстрирует, каким образом можно запрограммировать выполнение различных вычислений виртуальными процессорами с различ­ными координатами. В программе используется бинарная операция coordof, левым операндом которой в этом примере является координатная переменная i, а правым сеть mynet. Результатом выполнения операции бу­дет целое значение, распределенное по сети mynet. После выполнения при­сваивания my_coordinate = i coordof mynet значения проекций перемен­ной my_coordinate будут равны координатам соответствующих виртуальных процессоров в сети mynet. В результате виртуальные процессоры с четными
Глава 5. Технологии параллельного программирования
253
координатами выведут на терминал пользователя приветствие
"Hello, even world!", а виртуальные процессоры с нечетными координа­тами напишут "Hello, odd world!".
Сети в языке трС не являются абсолютно не зависимыми друг от друга. Каждая вновь создаваемая сеть имеет в точности один виртуальный процес­сор, общий с уже существующими сетями. Этот виртуальный процессор на­зывается родителем создаваемой сети и является тем связующим звеном, через которое передаются результаты вычислений на сети в случае прекра­щения ее существования. Родитель сети явно или неявно специфицируется ее определением.
До сих пор ни одна из сетей не была определена с явным указанием роди­теля. Во всех случаях родитель специфицировался неявно, и этим родителем был виртуальный хост-процессор. В любой момент выполнения любой трС-программы гарантируется существование предопределенной сети host, со­стоящей из единственного виртуального процессора, отображаемого на хост-процесс. Рассмотрим следующую программу.
#include <mpc.h>nettype AnotherSimpleNet(int n) { coord I = n; parent [0];
};
idefine N 3int [*]main() { net AnotherSimpleNet(N) [host]mynet; [mynet] MPC_Printf("Hello, world!\n");}
Эта программа полностью эквивалентна самой первой программе. Разница лишь в том, что в определении сети неявная спецификация родителя сети заменена на явную. Еще одно различие можно найти в определении сете­вого типа. Там добавлена строка, явно специфицирующая координаты роди­теля в сетях этого типа (по умолчанию родитель имеет нулевые координаты в создаваемой сети). Если бы нам по какой-то причине понадобилось, что­бы родитель сети mynet имел не наименьшую, а наибольшую координату, то в определении сетевого типа AnotherSimpleNet вместо спецификации parent [0] следовало бы использовать спецификацию parent [п - 1].
Естественным развитием общей идеологии трС является функция MPCBarrier, которая позволяет синхронизировать работу виртуальных про­цессоров любой сети. В частности, программа
#include <mpc.h> idefine N 5 int [ * ] main () {
net SimpleNet(N) mynet; [mynet]: {
int my_coordinate;
254
Часть II. Параллельное программирование
my_coordinate = I coordof mynet; if(my_coordinate%2 = 0)
MPC_Printf("Hello, even world!\n"); ( [(N)mynet])MPC_Barrier(); if(my_coordinate%2 = 1)
MPC_Printf("Hello, odd world!\n"); } }
выводит сообщения от виртуальных процессоров с нечетными координата­ми на терминал пользователя только после того, как будут выведены сооб­щения от всех виртуальных процессоров с четными координатами. Одно­временно заметим, что в этой программе на барьере ждут только процессы, реализующие сеть mynet.
Вызов функции MPC_Barrier в предыдущей программе выглядит несколько необычно. Действительно, эта функция принципиально отличается от всех функций, которые встречались до сих пор, и представляет собой сетевую функцию. В отличие от базовых функций, которые всегда выполняются все­ми процессами параллельной программы, сетевые функции выполняются на сетях и, следовательно, могут выполняться параллельно с другими сетевыми или узловыми функциями. По сути, сетевые функции являются полным аналогом базовых, но применительно к конкретным сетям. Верно и обрат­ное утверждение: базовая функция это та же сетевая функция, только при­мененная не к конкретной сети, а к всеобъемлющей сети с именем '*'.
Различие базовых, сетевых и узловых функций определяется степенью ин­тегрированное™ процессов, выполняющих каждый вид функций. Наиболее интегрированные — это базовые функции. Все процессы программы выпол­няют код базовой функции. Это обязательно. Именно поэтому бессмыслен­но говорить о параллельном вызове двух базовых функций. Наименее интег­рированными являются узловые функции. Это обычные последовательные функции, которые могут выполняться как отдельным процессом, так и не­сколькими процессами. Узловые функции не описывают параллельных вы­числений и коммуникаций между процессами. Между базовыми и узловыми функциями расположены сетевые функции. Сетевая функция описывает вычисления и коммуникации на некоторой абстрактной сети, являющейся ее формальным параметром. Две сетевые функции можно вызвать одновре­менно на двух непересекающихся сетях, и они будут выполняться парал­лельно. Это означает, что аппарат узловых и сетевых функций поддерживает параллелизм задач.
Дополнительно сетевые функции обеспечивают модульное параллельное программирование. Программист может реализовать тот или иной парал­лельный алгоритм в виде сетевой функции, а все остальные могут использо-
Глава 5. Технологии параллельного программирования
255
вать такую программную единицу в своих приложениях параллельно с дру­гими вычислениями и коммуникациями без знания ее исходного кода.
Здесь же заметим, что все переменные, описанные в функции обычным об­разом, считаются распределенными по той же сети, на которой задана и са­ма функция.
Описание библиотечной функции MPC_Barrier, находящееся в файле mpc.h, выглядит следующим образом:
int [net SimpleNet(n) w] MPC_Barrier(void);
Любая сетевая функция имеет специальный сетевой формальный параметр. Этот параметр является сетью, на которой вызывается сетевая функция. В случае функции MPC_Barrier описание сетевого параметра имеет вид:
net SimpleNet(n) w
Данное описание, наряду с формальной сетью w, на которой выполняется функция MPC_Barrier, вводит параметр п — число виртуальных процессоров этой сети.
Если бы функция MPC_Barrier не была библиотечной, она могла бы быть определена, например, так:
int [net SimpleNet(n) w] MPC_Barrier(void) { int [w:parent]bs[n], [w]b = 1; bs[] = b; b = bs [ ] ;
}
В теле функции определяется автоматический массив bs из п элементов. Данный массив является динамическим, но язык трС это допускает. Мас­сив распределен по сети, являющейся родителем сети w (это специфициру­ется с помощью конструкции [w:parent], помещенной перед именем мас­сива в его определении). Здесь же определяется распределенная по сети w переменная ь. Следующая за этим определением пара операторов реализует барьер для виртуальных процессоров сети w. Каждый процесс, кроме про­цесса, собирающего значения массива bs, выполнит первый оператор и сра­зу перейдет к выполнению второго. Однако процесс, собирающий значения bs, не начнет выполнение второго оператора, пока полностью не закончит выполнение первого. Это произойдет только тогда, когда все процессы пе­редадут ему свои значения. После этого он перейдет к выполнению второго оператора, разблокировав стоящие на втором операторе процессы и завер­шив барьерную синхронизацию.
Следует оговориться, что для данной реализации барьера распределенность массива bs именно по родителю сети w несущественна. Важно лишь, чтобы сбор и рассылка данных выполнялись на одном и том же процессоре.
256
Часть II. Параллельное программирование
Вызов сетевой функции MPC_Barrier в предыдущем примере задает как фактический сетевой аргумент — сеть mynet, на которой в действительности вызывается функция, так и фактическое значение единственного параметра сетевого типа simpieNet. Последнее, на первый взгляд, кажется избыточ­ным. Однако надо учесть, что фактическим сетевым аргументом функции MPC_Barrier может быть сеть любого типа, а не только типа simpieNet. По существу, функция MPC_Barrier лишь интерпретирует ту совокупность процессов, на которой она вызвана, как сеть типа simpieNet.
Основным средством для неявного описания обменов данными в языке трС являются подсети — подмножество виртуальных процессоров некото­рой сети. Рассмотрим следующую программу.
#include <string.h> #include <mpc.h> #include <sys/utsname.h>
nettype Mesh(int m, int n) { coord I = m, J = n; parent [0,0]; };
idefine MAXLEN 256 int [ * ] main () {
net Mesh(2,3) [host]mynet; [mynet]: {
struct utsname un;
char me[MAXLEN], neighbour[MAXLEN]; subnet [mynet:I == 0]rowO, [mynet:I == 1]rowl; uname(Sun);
strcpy(me, un.nodename); [rowO]neighbour[] = [rowl]me[]; [rowl]neighbour[] = [row0]me[]; MPC_Printf("I'm (%d, %d) from \"%s\"\n"
"My neighbour (%d, %d) is on \"%s\".\n\n", I coordof mynet, J coorlof mynet, me, (I coordof mynet + 1)%2, J coordof mynet, neighbour); } }
В данной программе каждый виртуальный процессор сети mynet типа Mesh (2,3) выводит на терминал пользователя имя компьютера, на котором
Глава 5. Технологии параллельного программирования
257
его разместила система программирования трС, и имя компьютера, на ко­торый система поместила ближайший виртуальный процессор с соседней строки. Для этого определяются две подсети rowO и rowl сети mynet. Под­сеть rowO соответствует нулевой строке решетки виртуальных процессоров сети mynet (этот факт специфицируется с помощью конструкции [mynet:i == 0]). Аналогично, подсеть rowl соответствует первой строке решетки виртуальных процессоров сети mynet. В общем случае логические выражения, выделяющие виртуальные процессоры подсетей, могут быть до­вольно сложными. Например, выражение i<j&&j%2==o выделяет вир­туальные процессоры решетки, расположенные над главной диагональю в четных столбцах.
Выполнение присваивания [rowO]neighbourп = [rowl]men заключается в параллельной пересылке содержимого соответствующей проекции массива те от каждого у'-го виртуального процессора строки rowl каждому у'-му вир­туальному процессору строки rowO с последующим его присваиванием соот­ветствующей проекции массива neighbour. Аналогично, выполнение при­сваивания [rowl]neighbour[] = [row0]me[] заключается в параллельной пересылке содержимого проекций массива те от виртуальных процессоров подсети rowO соответствующим виртуальным процессорам подсети rowl с последующим их присваиванием проекциям массива neighbour. В результа­те проекция распределенного массива neighbour на виртуальный процессор сети mynet с координатами (0,у) содержит имя компьютера, на котором система программирования разместила виртуальный процессор с координа­тами (1,у). Проекция массива neighbour на виртуальный процессор с ко­ординатами (1,у) содержит имя компьютера, на котором оказался виртуаль­ный процессор с координатами (0,у).
Используемые в этой программе подсети, соответствующие строкам решет­ки процессоров сети mynet, можно было бы определить и неявно, без объ­явления подсети subnet. Соответствующие присваивания выглядели бы так:
[mynet:1 == 0]neighbour[] = [mynet:I == l]me[]; [mynet:I == 1]neighbour[] = [mynet:I == 0]me[];
В данном случае использование неявно определенных подсетей вполне оп­равдано, поскольку упрощает программу без потери эффективности или функциональных возможностей. Однако без явного определения подсети не всегда можно обойтись, в частности, сетевые функции нельзя вызывать на неявно определенных подсетях.
Определение сети вызывает отображение ее виртуальных процессоров на реальные процессы параллельной программы, которое сохраняется на все время жизни сети. На основании чего система программирования трС осуществляет это отображение, и каким образом программист может этим отображением управлять? Основная цель параллельных вычислений — это ускорение решения задачи. Поэтому критерием при отображении виртуаль-
258
Часть II. Параллельное программирование
ных процессоров сети на реальные процессы является минимизация времени выполнения соответствующей параллельной программы. При выполнении этого отображения mpC-система опирается как на информацию о конфигу­рации и производительности компонентов параллельной вычислительной системы, выполняющей программу, так и на информацию об объеме вычис­лений, которые предстоит выполнить различным виртуальным процессорам определяемой сети.
В рассмотренных до сих пор примерах объемы вычислений нигде не фигу­рировали. Поэтому система программирования по умолчанию считала, что всем виртуальным процессорам всех определяемых сетей предстояло выпол­нять одинаковые объемы вычислений. Исходя из этого, она старалась ото­бразить их таким образом, чтобы общее число виртуальных процессоров, отображенных в данный момент на различные реальные процессоры было бы приблизительно пропорционально мощности этих процессоров. При та­ком отображении все процессы, представляющие виртуальные процессоры сети, выполняют вычисления приблизительно с одной скоростью. Если объ­емы вычислений между точками синхронизации или обмена данными ока­жутся приблизительно одинаковыми, то процессы в этих точках не будут простаивать в ожидании друг друга, и параллельная программа получится сбалансированной.
Такое отображение было вполне приемлемым для всех уже рассмотренных нами программ. Однако в случае значительных различий в объемах вычис­лений, выполняемых разными виртуальными процессорами, подобное рас­пределение может привести к существенному замедлению программы.
Язык трС предоставляет программисту средства для спецификации относи­тельных объемов вычислений, выполняемых виртуальными процессорами сети. Рассмотрим схему программы расчета массы металлической конструк­ции, сваренной из N неоднородных рельсов.
typedef struct {double length; double width; double heigth; double mass;} rail; nettype HeteroNet(int n, double v[n]) { coord I = n; node { I >= 0: v[I];}; parent [0]; };
double MassOfRail(double 1, double w, double h, double delta) { double m, x, y, z; for(m =0., x = 0.; x < 1; x + = delta)
Глава 5. Технологии параллельного программирования                                                259
for (у =0.; у < w; у + = delta) for(z =0.; z < h; z + = delta) m + = Density(x,y,z); return m*delta*delta*delta;
}
int [*]main(int [host]argc, char **[host]argv) { repl N = [host]atoi(argv[1]); static rail [host]s[[host]N]; repl double volumes[N]; int [host]i; repl j;
[host]InitializeSteelHedgehog(s, [host]N); for (j = 0; j < N; j++)
volumes[j] = s[j].length*s[j].width*s[j].heigth;
recon MassOfRail(0.2, 0.04, 0.05, 0.005); { net HeteroNet(N, volumes) mynet; [mynet]: {
rail r; r = s [ ] ;
r.mass = RailMass(r.length, r.width, r.height, DELTA); [host]printf("The total weight is %g kg\n", [host] ((r.mass) [ + ])); } } }
В данной программе определяется сетевой тип HeteroNet, имеющий два па­раметра. Первый параметр является целым скалярным параметром п и задает число виртуальных процессоров сети. Второй векторный параметр v состоит из п элементов типа double. Именно этот параметр используется при специ­фикации относительных объемов вычислений, выполняемых различными виртуальными процессорами. Определение сетевого типа HeteroNet содержит необычное объявление node {i >= о: v [ i ]}, которое читается следующим об­разом: для любого i >= о относительный объем вычислений, выполняемых виртуальным процессором с координатой i, задается значением v[i].
260
Часть II. Параллельное программирование
Для параллельного вычисления общей массы металлического "ежа" создается сеть mynet, состоящая из N виртуальных процессоров, каждый из которых вычисляет массу одного из этих рельсов. Вычисление массы рельса осуще­ствляется интегрированием с фиксированным шагом заданной функции плотности Density по объему рельса. Очевидно, что объем вычислений для нахождения массы рельса пропорционален объему этого рельса. Поэтому, в определении сети mynet в качестве второго фактического параметра сетевого типа HeteroNet используется размазанный массив volumes, /-и элемент ко­торого содержит объем /-го рельса. Этим специфицируется, что объем вы­числений, выполняемый /-м виртуальным процессором сети mynet, пропор­ционален объему рельса, массу которого этот виртуальный процессор рассчитывает.
Отображение виртуальных процессоров на компьютеры основывается на информации об их производительности. По умолчанию система программи­рования языка трС использует одну и ту же методику оценки производи­тельности для всех участвующих в выполнении программы реальных про­цессоров. Эта оценка получается в результате выполнения специальной тестовой параллельной программы при установке mpC-системы в конкрет­ной параллельной вычислительной среде. Такая оценка является довольно грубой и может значительно отличаться от реальной производительности, достигаемой процессорами при выполнении конкретного кода. Именно по­этому язык трС предоставляет специальный оператор гесоп, позволяющий программисту изменять оценку производительности компьютеров, настраивая ее на те вычисления, которые будут в действительности выполняться.
В последнем примере этот оператор стоит непосредственно перед определе­нием сети mynet. Выполнение его заключается в том, что все физические процессоры, назначенные программе, параллельно выполняют специфици­рованный в этом операторе код (вызов функции RaiiMass с фактическими аргументами 20.о, 4.0, 5.0 и 0.5). Время, затраченное каждым из процес­соров на выполнение этого кода, используется для обновления оценки их производительности. Основной объем вычислений, выполняемый каждым из виртуальных процессоров сети mynet, как раз и приходится на вызов функции RaiiMass. Поэтому при создании этой сети система будет основы­ваться на реальной производительности физических процессоров, достигае­мой ими на этой части программы.
Важным обстоятельством является и то, что оператор гесоп позволяет об­новлять оценку производительности процессоров во время выполнения программы. В частности, это можно сделать непосредственно перед тем мо­ментом, когда оценка будет использоваться системой программирования. Это особенно важно, если параллельная вычислительная система использу­ется одновременно и для других вычислений. Реальная производительность процессоров, достигаемая при выполнении параллельной программы, может
Глава 5. Технологии параллельного программирования
261
меняться в больших пределах в зависимости от их загрузки другими, внеш­ними по отношению к этой программе, вычислениями. Использование опе­ратора гесоп позволяет создавать параллельные программы, реагирующие на изменение загрузки системы: вычисления перераспределяются по про­цессорам в соответствии с их фактической производительностью на момент выполнения этих действий.
До сих пор при описании неоднородных параллельных алгоритмов мы не учитывали ни затраты на передачу данных, ни способа взаимодействия про­цессов при выполнении алгоритма. Следующие два примера иллюстрируют соответствующие возможности языка трС.
Первая программа моделирует движение нескольких больших, удаленных друг от друга групп тел под влиянием гравитационного притяжения. По­скольку сила притяжения быстро падает с увеличением расстояния, дейст­вие большой группы тел можно приближенно заменить воздействием одного эквивалентного тела. Это позволяет естественным образом распарал­лелить вычисления, когда за каждую группу тел будет отвечать свой парал­лельный процесс. Этот процесс хранит атрибуты всех тел своей группы, а также суммарную массу и координаты центров масс других групп.
Параллельная программа будет реализовывать следующий алгоритм:
Инициализация всех групп на хост-процессе Рассылка групп по процессам Параллельное вычисление масс групп Обмен массами групп между процессами while (1) {
Визуализация групп хост-процессом
Параллельное вычисление центров масс групп
Обмен центрами масс между процессами
Параллельное обновление атрибутов групп
Сбор групп на хост-процессе }
Предполагается, что каждая итерация основного цикла алгоритма вычисляет новые координаты всех тел через некоторый фиксированный интервал вре­мени.
Ядром программы, реализующей этот алгоритм, является следующее опре­деление сетевого типа:
nettype Galaxy(m, k, n[m]) { coord I = m;
node { I >= 0: bench* ( (n[I]/k) * (n [I]/k) ) ; } ; link { I > 0 : length*(n[I]*sizeof(Body)) [I] - > [0]; };
262
Часть II. Параллельное программирование
parent [0]; scheme { int i ;
par (i = 0; i < m; i++) 100%% [i]; par (i = 1; i < m; i++) 100%% [i] - > [0]; }; };
Первая строка вводит имя Galaxy этого сетевого типа и список формальных параметров — целые скалярные параметры m и к, а также векторный пара­метр п, состоящий из m целых чисел. Следующая строка объявляет коорди­натную переменную i, изменяющуюся в пределах от о до m - 1. Далее виртуальные процессоры привязываются к этой системе координат, и опи­сываются абсолютные объемы вычислений, которые должны быть выполне­ны этими виртуальными процессорами. В качестве единицы измерения ис­пользуется объем вычислений, необходимый для расчета группы из к тел. Предполагается, что /-и элемент векторного параметра п равен числу тел в группе, обрабатываемой /-ым виртуальным процессором. Число операций, необходимое для обработки одной группы, пропорционально квадрату числа тел в этой группе. Поэтому, объем вычислений, выполняемый /-ым вирту­альным процессором в (n[i]/k)2 раз отличается от объема вычислений, при­нятого за единицу (это и записано в соответствующей строке).
Следующая строка специфицирует объемы данных в байтах, передаваемые между виртуальными процессорами во время выполнения алгоритма. Она просто говорит, что /-Й виртуальный процессор посылает атрибуты всех своих тел виртуальному хост-процессору. Заметим, что данное определение описывает в точности одну итерацию основного цикла алгоритма. Это явля­ется достаточно хорошим приближением, поскольку практически все вы­числения и обмены данными сосредоточены в этом цикле. Исходя из этого, общее время выполнения алгоритма приблизительно равно времени выпол­нения одной итерации, умноженному на общее число итераций.
Наконец, блок scheme описывает, как именно виртуальные процессоры взаимодействуют во время выполнения алгоритма. Сначала все виртуальные процессоры параллельно выполняют все 100% вычислений. Затем все вирту­альные процессоры, кроме хоста, параллельно посылают 100% тех данных, которые должны быть посланы виртуальному хост-процессору.
Наиболее важными фрагментами остального кода этой mpC-программы яв­ляются следующие:
void [*] main(int [host]argc, char **[host]argv)
{
TestGroup[] = (*AllGroups[0]) [] ;
Глава 5. Технологии параллельного программирования
263
recon Update_group(TestGroup, TestGroupSize); {
net GalaxyNet(NofG, TestGroupSize, NofB) g;
} }
Оператор recon использует вызов функции update_Group с фактическими параметрами TestGroup и TestGroupSize для обновления оценки произво­дительности физических процессоров, выполняющих программу. Основная часть общего объема вычислений, выполняемых каждым виртуальным про­цессором, приходится на вызов функции update_Group. Поэтому получен­ная оценка производительности реальных процессоров будет близка к их реальной производительности, достигаемой при выполнении этой программы. Следующая строка определяет абстрактную сеть g типа GalaxyNet с фактиче­скими параметрами NofG — фактическое число групп, TestGroupSize — раз­мер группы, используемой в тестовом коде, и NofB — массив из NofG эле­ментов, содержащих фактическое число тел в группах. Остальные вычисления и коммуникации будут выполняться на этой абстрактной сети. Система программирования трС отобразит виртуальные процессоры абст­рактной сети g на реальные параллельные процессы, составляющие парал­лельную программу. При выполнении этого отображения система програм­мирования использует информацию о конфигурации и производительности физических процессоров и коммуникационных каналов, а также информа­цию об описанном параллельном алгоритме.
Следующая программа умножает матрицу А на транспонированную матрицу В, т. е. выполняет матричную операцию С = АВТ, где А а В являются плот­ными квадратными матрицами размера n х п. Программа реализует неодно­родную одномерную версию параллельного алгоритма, используемого в пакете ScaLAPACK для умножения матриц. Алгоритм можно описать сле­дующим образом.
□ Каждый элемент матрицы С — это квадратный блок размера г х г. Еди­ницей вычисления является вычисление одного блока, т. е. умножение матрицы г х п на матрицу п х г. Для простоты предположим, что п делит­ся нацело на г.
П Матрицы А, В и С одинаковым образом разбиты на р горизонтальных полос, где р — это число процессоров. Каждый процессор отвечает за вычисление своей полосы результирующей матрицы С.
П На каждом шаге алгоритма рассылается строка блоков матрицы В, пред­ставляющая собой блочный столбец матрицы Вт. Все процессоры парал­лельно вычисляют соответствующую часть блочного столбца матрицы С.
264                                                                      Часть II. Параллельное программирование
П Все блоки матрицы С требуют одинакового количества арифметических операций, и каждый процессор выполняет объем работы, пропорцио­нальный числу выделенных ему блоков. Чтобы сбалансировать нагрузку процессоров, площадь полосы, отображенной на каждый процессор, должна быть пропорциональна его скорости.
Следующее определение сетевого типа ParaiieiAxBT описывает этот алго­ритм.
nettype ParaiieiAxBT(int р, int n, int r, int t, int d[p]) { coord I = p;
node { I >= 0: bench*(d[I]*n/r/t); }; link (J = p) {
I! = J: length*(d[J]*n*sizeof(double)) [J] - > [I]; };
parent [0]; scheme {
int i, j, PivotProcessor = 0, PivotRow = 0; for(i = 0; i < n/r; i++, PivotRow + = r) {
if(PivotRow >= d[PivotProcessor]) {
PivotProcessor++; PivotRow = 0; }
for (j = 0; j < p; j ++) if(j! = PivotProcessor) (100.*r/d[PivotProcessor])%%[PivotProcessor] - > [j]; par(j = 0; j < p; j ++) (100.*r/n)%%[j]; }
}; };
Предполагается, что тестовый код, используемый для оценки скорости ре­альных процессоров, умножает матрицу г х п на матрицу n х t, где t мало по сравнению с п и делится нацело на г. Предполагается также, что /-и элемент вектора d равен числу строк в полоске матрицы С, отображенной на /-и виртуальный процессор абстрактной сети, выполняющей этот алгоритм. Со­ответственно, объявление node специфицирует, что объем вычислений, вы­полняемых /-м виртуальным процессором в d[i]*n/r/t раз больше, чем объем вычислений, выполняемых тестовым кодом.
Глава 5. Технологии параллельного программирования
265
Объявление link содержит информацию о том, что каждый виртуальный процессор посылает свою полосу матрицы В всем остальным виртуальным процессорам.
Объявление scheme специфицирует n/г последовательных шагов этого алго­ритма. На каждом шаге, виртуальный процессор PivotProcessor, который хранит ведущую строку, посылает ее остальным виртуальным процессорам. При этом выполняется r/d [PivotProcessor] х100% объема передач от всего объема данных, передаваемых по соответствующему коммуникационному каналу. Затем все виртуальные процессоры параллельно вычисляют соответ­ствующую часть блочного столбца матрицы С, выполняя r/n х 100% объема вычислений каждый.
Наиболее интересными фрагментами остального кода этой программы яв­ляются:
recon SerialAxBT (а, Ь, с, г, n, t) ;
[host] : {
int j ;
struct {int p; double time;} min;
double time;
for(j = 1; j <= p; j ++) {
Partition(j, powers, d, n, r);
time = timeof(net ParallelAxBT(j, n, r, t, d) w); if (time < min. time) { min.p = j; min.t ime = t ime; } }
p = min.p; }
Partition (p, powers, d, n, r); {
net ParallelAxBT(p, n, r, t, d) w;
repl [w]N, [w]i;
int [w]myN;
N = [w]n;
myN = ([w]d)[I coordof w];
266
Часть II. Параллельное программирование
М : {
double A[myN/r] [г] [N] , ВТ [myN/r] [г] [N] , C[myN/r] [г] [N] , Brow[r] [N] ;
repl PivotProcessor, RelPivotRow, AbsPivotRow;
for(AbsPivotRow = 0, RelPivotRow = 0, PivotProcessor = 0; AbsPivotRow < N; RelPivotRow + = r, AbsPivotRow + = r)
{
if(RelPivotRow >= d[PivotProcessor]) { PivotProcessor ++; RelPivotRow = 0;
}
Brow[] = [w:I == PivotProcessor]ВТ[RelPivotRow/r][];
for(i = 0; i < myN/r; i ++)
SerialAxBT(A[i][0],Brow[0],C[j][0] +
AbsPivotRow,r,N,r); } } }
Оператор recon обновляет оценку производительности реальных процессо­ров, используя последовательное умножение тестовой матрицы г х п на тес­товую матрицу п х t с помощью функции seriaiAxBT (вычисления, выпол­няемые каждым из виртуальных процессоров как раз, в основном, и приходятся на вызов функции SeriaiAxBT).
Следующий блок, выполняемый виртуальным хост-процессором, вычисляет оптимальное число физических процессоров, вовлеченных в параллельное умножение матриц. Операция timeof оценивает время выполнения алго­ритма, специфицируемого его операндом — некоторым конкретным типом сети, без его фактического выполнения. Вычисленное значение переменной р будет использовано в качестве числа процессоров.
Определение сети w вызывает отображение ее виртуальных процессоров на физические процессоры таким образом, чтобы минимизировать время вы­полнения программы. Блок, следующий за определением сети w и выпол­няемый этой сетью, реализует алгоритм параллельного умножения матриц. Основные свойства этого алгоритма были описаны в определении сетевого типа ParallelAxBT.
Глава 5. Технологии параллельного программирования
267
Вопросы и задания
1.   Какие конструкции языка С препятствуют автоматическому распараллеливанию программ?
2.   Отвечая на предыдущий вопрос, вы сформировали список конструкций, пре­пятствующих автоматическому распараллеливанию программ. Внимательно про­анализируйте полученный список и подумайте, верно ли такое утверждение: "Любую программу, в которой нет конструкций из списка, можно автоматиче­ски распараллелить"?
3.   В цикле содержится вызов функции. Какие конструкции языков необходимо учитывать, чтобы определить, являются ли все итерации данного цикла незави­симыми или нет? Рассмотрите языки С и Fortran.
4.   Какой вычислительной системе лучше соответствует технология ОрепМР: вы­числительному кластеру из рабочих станций или SMP-компьютеру?
5.   Чем отличаются понятия "процесс" и "нить"?
6.   Верно ли утверждение: "OpenMP-программы работают согласно модели Single Program Multiple Data (SPMD)?"
7.   Чем различаются общие и локальные переменные в последовательной секции ОрепМР-программы?
8.   Допускает ли ОрепМР изменение числа параллельных нитей по ходу работы программы?
9.   Как будет обрабатываться такая конструкция: в параллельном цикле стоит вызов функции, в теле которой так же есть параллельный цикл?
10.   Почему в ОрепМР нет конструкций, аналогичных директивам DVM ALIGN и
DISTRIBUTE?
11.   Предположим, что в ОрепМР не было бы директивы BARRIER. Смоделируйте барьерную синхронизацию нитей с помощью других средств ОрепМР, в частно­сти, с помощью механизма критических секций.
12.   Можно ли автоматически конвертировать DVM-программу в программу на ОрепМР?
13.   Напишите с помощью ОрепМР, DVM и трС программы перемножения двух матриц, решения системы линейных уравнений методом Гаусса, нахождения обратной матрицы. Проанализируйте выполненную работу с точки зрения эф­фективности полученных программ, простоты написания программ, переноси­мости программ.
14.   Проанализируйте, что общего и чем различаются классы переменных в ОрепМР, DVM и трС.
15.   Для чего в DVM введена директива ALIGN?
16.   Сравните модели выполнения параллельных циклов в ОрепМР и DVM.
17.   Для нахождения суммы элементов вектора в рамках ОрепМР при сложении час­тичных сумм можно воспользоваться критической секцией. Как выполнить эту же операцию в DVM?
268
Часть II. Параллельное программирование
18.   Какая связь между понятиями "реальный процесс", "реальный процессор" и "виртуальный процессор" в трС?
19.   Что общего и чем различаются базовые, сетевые и узловые функции трС?
20.   Что общего и чем различаются распределенные и размазанные переменные в трС?
21.   Какие конструкции и понятия трС отражают ориентацию данной системы на программирование неоднородных вычислительных систем?
22.   Каким образом реализуется обмен данными между узлами сети в трС?
23.   С помощью каких средств можно оперативно отслеживать изменение загрузки различных узлов вычислительной сети, на которой работает трС-программа?
24.   Попробуйте выделить сильные и слабые стороны каждой из технологий OpenMP, DVM и трС.
§ 5.2. Системы программирования на основе передачи сообщений
Широкое распространение компьютеров с распределенной памятью опреде­лило и появление соответствующих систем программирования. Как прави­ло, в таких системах отсутствует единое адресное пространство, и для обме­на данными между параллельными процессами используется явная передача сообщений через коммуникационную среду. Отдельные процессы описыва­ются с помощью традиционных языков программирования, а для организа­ции их взаимодействия вводятся дополнительные функции. По этой причи­не практически все системы программирования, основанные на явной передаче сообщений, существуют не в виде новых языков, а в виде интер­фейсов и библиотек.
К настоящему времени примеров известных систем программирования на основе передачи сообщений накопилось довольно много: Shmem, Linda, PVM, MPI и др. В данном параграфе мы остановимся лишь на двух из них. Сначала мы расскажем о системе Linda, простой и красивой по своей идее системе программирования. Затем остановимся на наиболее используемой в настоящее время системе MPI.
Система параллельного программирования Linda
Система Linda разработана в середине 80-х годов прошлого века в Йельском университете, США [50]. Идея ее построения исключительно проста, а по­тому красива и очень привлекательна. Параллельная программа есть множе­ство параллельных процессов, где каждый процесс работает как обычная последовательная программа. Все процессы имеют доступ к общей памяти, единицей хранения в которой является кортеж. Отсюда происходит и спе­циальное название для общей памяти — пространство кортежей. Каждый
Глава 5. Технологии параллельного программирования
269
кортеж — это взятая в скобки упорядоченная последовательность значений, разделенных запятыми. Например,
("Hello", 42, 3.14), (5, FALSE, 97, 1024, 2), ("worker", 5)
Так, первый кортеж этого примера состоит из строки "Hello", элемента це­лого типа 42 и вещественного числа 3.14. Во втором кортеже есть элемент целого типа 5, элемент логического типа false и три целых числа. Последний кортеж состоит из двух элементов: строки "worker" и целого числа 5. Количе­ство элементов в кортеже, вообще говоря, может быть любым, однако кон­кретные реализации могут накладывать ограничения. Если первым элементом кортежа является строка, то эта строка называется именем кортежа.
Все процессы работают с пространством кортежей по принципу: поместить кортеж, забрать, скопировать. В отличие от традиционной памяти, процесс может забрать кортеж из пространства кортежей, после чего данный кортеж станет недоступным остальным процессам. В отличие от традиционной па­мяти, если в пространство кортежей положить два кортежа с одним и тем же именем, то не произойдет привычного для нас "обновления" значения переменной — в пространстве кортежей окажется два кортежа с одним и тем же именем. В отличие от традиционной памяти, изменить кортеж непо­средственно в пространстве нельзя. Для изменения значений элементов кортежа его нужно сначала оттуда изъять, затем процесс, изъявший кортеж, может изменить значения его элементов, и вновь поместить измененный кортеж в память. В отличие от других систем программирования, процессы в системе Linda никогда не взаимодействуют друг с другом явно, и их обще­ние всегда идет через пространство кортежей.
Интересно заметить, что по замыслу создателей системы Linda в любой по­следовательный язык достаточно добавить лишь четыре новые функции, после чего он становится средством параллельного программирования! Эти функции и составляют систему Linda: три для операций над кортежами и пространством кортежей и одна функция для порождения параллельных процессов. Для определенности, дальнейшее обсуждение системы и ее функций будем вести с использованием языка С.
Функция оит помещает кортеж в пространство кортежей. Например,
out ("GoProcess", 5);
помещает в пространство кортежей кортеж ("GoProcess", 5). Если такой кортеж уже есть в пространстве кортежей, то появится второй, что в прин­ципе позволяет иметь сколь угодно много экземпляров одинаковых корте­жей. По этой же причине с помощью функции out нельзя изменить кортеж, уже находящийся в пространстве. Для этого кортеж должен быть сначала оттуда изъят, затем изменен и после этого помещен назад. Функция out ни­когда не блокирует выполнивший ее процесс.
270
Часть II. Параллельное программирование
Функция in ищет подходящий кортеж в пространстве кортежей, присваивает значения его элементов элементам своего параметра-кортежа и удаляет най­денный кортеж из пространства кортежей. Например,
in("P", int i, FALSE);
Этой функции соответствует любой кортеж, который состоит из трех элемен­тов: значением первого элемента является строка "Р", второй элемент может быть любым целым числом, а третий должен иметь значение false. Подхо­дящим кортежем считается кортеж, имеющий столько же элементов того же типа, и, если указано конкретное значение, то и такое же значение. В данном примере подходящими кортежами могут быть ("Р", 5, false) или ("р",
135, FALSE) И Т. П., НО Не ("Р", 7.2, FALSE) ИЛИ ("Ргос", 5, FALSE). ПО­СКОЛЬКУ основной способ выборки данных из пространства кортежей опирается не на адрес, а на совпадении значений отдельных полей, то само пространст­во кортежей можно считать виртуальной общей ассоциативной памятью.
Если параметру функции in соответствуют несколько кортежей, случайным образом выбирается один из них. После нахождения кортеж удаляется из пространства кортежей, а неопределенным формальным элементам пара­метра-кортежа, содержащимся в вызове данной функции, присваиваются соответствующие значения. В нашем примере переменной i присвоится 5 или 135. Если в пространстве кортежей ни один кортеж не соответствует функции, то вызвавший ее процесс блокируется до тех пор, пока соответст­вующий кортеж в пространстве не появится.
Элемент кортежа в функции in считается формальным, если перед ним сто­ит определитель типа. Если используется переменная без типа, то берется ее значение и элемент рассматривается как фактический параметр. Например, во фрагменте программы
int i = 5;
in ("Р", i, FALSE);
функции in, в отличие от предыдущего примера, соответствует только кор­теж ("Р", 5, FALSE).
Если переменная описана до вызова функции и ее надо использовать как формальный элемент кортежа, нужно использовать ключевое слово formal или знак ?. Например, во фрагменте программы
j = 15;
in ("Р", ?i, j);
последнюю строку можно заменить и на оператор in ("Р", formal i, j). В этом примере функции in будет соответствовать, например, кортеж ("Р", 6, 15), но не ("Р", 6, 12). Конечно же, формальными могут быть и не­сколько элементов кортежа одновременно:
in ("Add If", int i, bool b);
Глава 5. Технологии параллельного программирования
271
Если после такого вызова функции в пространстве кортежей будет найден кортеж ("Add_if", 100, true), то переменной i присвоится значение юо, а переменной ь — значение true.
Функция read отличается от функции in лишь тем, что выбранный кортеж не удаляется из пространства кортежей. Все остальное точно так же, как и у функции in. Этой функцией удобно пользоваться в том случае, когда значе­ния переменных менять не нужно, но к ним необходим параллельный дос­туп из нескольких процессов. Иногда вместо обозначения read для данной функции используют обозначение rd.
Функция eval похожа на функцию out. Разница заключается в том, что для вычисления значения каждого поля, содержащего обращение к какой-либо функции, порождается отдельный параллельный процесс. На основе вычис­ленных значений функций, найденных в полях, eval формирует результи­рующий кортеж и помещает его в пространство кортежей. Например,
eval ("hello", funct(z), TRUE, 3.1415);
При обработке данного вызова система создаст новый процесс для вычис­ления функции funct (z). Когда процесс закончится и будет получено зна­чение w = funct (z), в пространство кортежей будет добавлен кортеж ("hello", w, true, 3.1415). Функция, вызвавшая eval, не ожидает завер­шения порожденного параллельного процесса и продолжает свою работу дальше. Следует отметить и то, что пользователь не может явно управлять размещением порожденных параллельных процессов на доступных ему про­цессорных устройствах — это Linda делает самостоятельно.
Порядок вычисления полей в кортеже заранее не определен. Не следует ис­пользовать запись вида out ("string", i++, i), поскольку непонятно, будет ли выполнен инкремент i до вычисления значения третьего элемента или по­сле. Аналогично, в операторе in ("string2", ?j, ?a[j]) нельзя предсказать, какое значение переменной j будет использовано в третьем элементе. Это может быть значение, полученное после выполнения операции ?j, либо зна­чение, присвоенное переменной до выполнения данного оператора.
Параллельная программа в системе Linda считается завершенной, если все порожденные процессы завершились или все они заблокированы функция­ми in И read.
По сути дела, описание системы закончено, и теперь можно привести не­сколько небольших примеров. Мы уже говорили о том, что параллельные процессы в системе Linda напрямую друг с другом не общаются, своего уникального номера-идентификатора не имеют и общего числа параллельно работающих процессов-соседей, вообще говоря, не знают. Однако если у пользователя есть в этом необходимость, то такую ситуацию очень просто смоделировать. Программа в самом начале вызывает функцию out:
out("Next", 1);
272
Часть II. Параллельное программирование
Этот кортеж будет играть роль "эстафетной палочки", передаваемой от про­цесса процессу: каждый порождаемый параллельный процесс первым делом выполнит следующую последовательность:
in ("Next", ?My_Id); out("Next", My_Id+l) ;
Первый оператор изымает данный кортеж из пространства, на его основе процесс получает свой номер My_id, и затем кортеж с номером для следую­щего процесса помещается в пространство. Заметим, что использование функции in в данном случае позволяет гарантировать монопольную работу с данным кортежем только одного процесса в каждый момент времени. После такой процедуры каждый процесс получит свой уникальный номер, а число уже порожденных процессов всегда можно определить, например, с помо­щью такого оператора:
read("Next", ?Num_Processes) ;
Теперь рассмотрим возможную схему организации программы для пере­множения С = АВ двух квадратных матриц размера n х п. Инициализирую­щий процесс использует функцию out и помещает в пространство кортежей исходные строки матрицы А и столбцы матрицы В:
for(i =1; i <= N; ++i) {
out ("A", i, <i-H строка A>); out("B", i, <±-u столбец B>);
}
Для порождения Nproc идентичных параллельных процессов можно вос­пользоваться следующим фрагментом:
for(i =0; i < Nproc; ++i)
eval("ParProc", get_elem_result());
Входные данные готовы, и нахождение всех N1 элементов Су результирую­щей матрицы можно выполнять в любом порядке. Главное — это распреде­лить работу между процессами, для чего процесс, инициирующий вычисле­ния, в пространство помещает следующий кортеж:
out("NextElementCij", 1);
Второй элемент данного кортежа всегда будет показывать, какой из N2 эле­ментов Су предстоит вычислить следующим. Базовый вычислительный блок функции get_eiem_resuit () будет содержать приведенный далее фрагмент:
in ("NextElementCij",   ? NextElement); if(NextElement < N*N)
out("NextElementCij   ", NextElement + 1) ;
Nrow = (NextElement -  1) / N + 1; /* определим номер строки */
Ncol = (NextElement -  1) % N + 1; /* определим номер столбца */
Глава 5. Технологии параллельного программирования
273
В результате выполнения данного фрагмента для элемента с номером NextEiement процесс определит его местоположение в результирующей мат­рице: номер строки Nrow и столбца Ncoi. Заметим, что если вычисляется по­следний элемент, то кортеж с именем "NextEiementcij" в пространство не возвращается. Когда в конце работы программы процессы обратятся к этому кортежу, они будут заблокированы, что не помешает нормальному заверше­нию программы. И, наконец, для вычисления элемента Су каждый процесс get_elem_result выполнит следующий фрагмент:
read("A", Nrow, ?row); read("В", Ncol, ?col); out("result", Nrow, Ncol, DotProduct(row,col));
где DotProduct — это функция, которая реализует скалярное произведение. Таким образом, каждый элемент произведения окажется в отдельном кор­теже в пространстве кортежей. Завершающий процесс соберет результат, поместив кортежи в соответствующие элементы матрицы С:
for (irow = 0; irow < N; irow++) for (icol = 0; icol < N; icol++)
in("result", irow + 1, icol + 1, ?C[irow][icol]);
He имея в системе Linda никаких явных средств для синхронизации процес­сов, совсем не сложно их смоделировать самим. Предположим, что в неко­торой точке нужно выполнить барьерную синхронизацию N процессов. Ка­кой-то один процесс, например, стартовый, заранее помещает в пространство кортеж ("ForBarrier", N). Подходя к точке синхронизации, каждый процесс выполняет следующий фрагмент, который и будет выпол­нять функции барьера:
in("ForBarrier", ? Bar); Bar = Bar - 1; if(Bar != 0) {
out("ForBarrier", Bar);
read("Barrier"); } else
out("Barrier");
Если кортеж с именем "ForBarrier" есть в пространстве, то процесс его изымает, в противном случае блокируется до его появления. Анализируя второй элемент данного кортежа, процесс выполняет одно из двух действий. Если есть процессы, которые еще не дошли до данной точки, то он возвра­щает кортеж в пространство с уменьшенным на единицу вторым элементом и встает на ожидание кортежа "Barrier". В противном случае он сам поме­щает кортеж в пространство "Barrier", который для всех является сигналом к продолжению работы.
274
Часть II. Параллельное программирование
Очень просто с помощью системы Linda написать программу, работающую по схеме мастер/рабочие (master/slaves). Процесс-мастер порождает какое-то число одинаковых процессов-рабочих, распределяет между ними работу и собирает результат. Текст подобной программы показан ниже.
main(argc, argv)
int argc;
char *argv[];
{
int nworker, j , hello();
nworker = atoi (argv[l]);
for (j = 0; j < nworker; j++)
eval ("worker", hello(j)); for(j = 0; j < nworker; j++)
in("done"); printf("Hello_world is finished.\n"); }
int hello (num) /** function hello **/
int num;
{
printf("Hello world from number %d.\n", num);
out("done");
return(0);
}
В целом, сильные и слабые стороны системы Linda понятны. Простота и стройность концепции системы является основным ее козырем. Однако эти же факторы оборачиваются большой проблемой на практике. Как эффек­тивно поддерживать пространство кортежей? Если вычислительная система обладает распределенной памятью, то общение процессов через пространст­во кортежей заведомо будет сопровождаться большими накладными расхо­дами. Если процесс выполняет функции read или in, то как среди потен­циально огромного числа кортежей в пространстве быстро найти подходящий? Подобные проблемы пытаются решить разными способами, например, введением нескольких именованных пространств, но все предла­гаемые решения либо усложняют систему, либо являются эффективными только для узкого класса программ.
Вместе с тем, некоторые прикладные пакеты построены именно на основе системы Linda. Удачным примером можно считать реализацию популярного пакета для квантово-химических расчетов Gaussian.
Глава 5. Технологии параллельного программирования
275
Современное состояние и дополнительную информацию по системе Linda можно найти на сайте http://www.cs.yale.edu/Linda/linda.html.
Система программирования MPI
Наиболее распространенной технологией программирования параллельных компьютеров с распределенной памятью в настоящее время является МР1. Мы уже говорили о том, что основным способом взаимодействия парал­лельных процессов в таких системах является передача сообщений друг другу. Это и отражено в названии данной технологии — Message Passing Interface. Стандарт МР1 фиксирует интерфейс, который должны соблюдать как система программирования МР1 на каждой вычислительной системе, так и пользователь при создании своих программ. Современные реализации чаще всего соответствуют стандарту МР1 версии 1.1. В 1997—1998 годах поя­вился стандарт МР1-2.0, значительно расширивший функциональность пре­дыдущей версии. Однако до сих пор этот вариант МР1 не получил широкого распространения. Везде далее, если иного не оговорено, мы будем иметь дело со стандартом 1.1.
МР1 поддерживает работу с языками С и Fortran. В данной книге все при­меры и описания всех функций будут даны с использованием языка С. Од­нако это совершенно не является принципиальным, поскольку основные идеи МР1 и правила оформления отдельных конструкций для этих языков во многом схожи.
Полная версия интерфейса содержит описание более 120 функций. Если описывать его полностью, то этому нужно посвящать отдельную книгу. На­ша задача — объяснить идею технологии и помочь освоить необходимые на практике компоненты. Наиболее употребимые функции мы опишем. Но если у читателя возникнут какие-либо вопросы по стандарту или желание узнать последние новости о развитии технологии МР1, мы советуем обра­титься к сайту http://www.mpiforum.org.
Интерфейс поддерживает создание параллельных программ в стиле M1MD, что подразумевает объединение процессов с различными исходными тек­стами. Однако на практике программисты гораздо чаще используют SPMD-модель, в рамках которой для всех параллельных процессов используется один и тот же код. В настоящее время все больше и больше реализаций МР1 поддерживают работу с нитями.
Все дополнительные объекты: имена функций, константы, предопределен­ные типы данных и т. п., используемые в МР1, имеют префикс mpi_. На­пример, функция посылки сообщения от одного процесса другому имеет имя MPi_send. Если пользователь не будет использовать в программе имен с таким префиксом, то конфликтов с объектами МР1 заведомо не будет. Все описания интерфейса МР1 собраны в файле mpi.h, поэтому в начале МР1-программы должна стоять директива #inciude <mpi. h>.
276
Часть II. Параллельное программирование
MPI-программа — это множество параллельных взаимодействующих про­цессов. Все процессы порождаются один раз, образуя параллельную часть программы. В ходе выполнения MPI-программы порождение дополнитель­ных процессов или уничтожение существующих не допускается.
Каждый процесс работает в своем адресном пространстве, никаких общих переменных или данных в MPI нет. Основным способом взаимодействия между процессами является явная посылка сообщений.
Для локализации взаимодействия параллельных процессов программы мож­но создавать группы процессов, предоставляя им отдельную среду для обще­ния — коммуникатор. Состав образуемых групп произволен. Группы могут полностью входить одна в другую, не пересекаться или пересекаться час­тично. При старте программы всегда считается, что все порожденные про­цессы работают в рамках всеобъемлющего коммуникатора, имеющего пре­допределенное имя mpi_comm_world. Этот коммуникатор существует всегда и служит для взаимодействия всех процессов MPI-программы.
Каждый процесс MPI-программы имеет уникальный атрибут номер процесса, который является целым неотрицательным числом. С помощью этого атри­бута происходит значительная часть взаимодействия процессов между собой. Ясно, что в одном и том же коммуникаторе все процессы имеют различные номера. Но поскольку процесс может одновременно входить в разные ком­муникаторы, то его номер в одном коммуникаторе может отличаться от его номера в другом. Отсюда два основных атрибута процесса: коммуникатор и номер в коммуникаторе.
Если группа содержит п процессов, то номер любого процесса в данной группе лежит в пределах от 0 до я — 1. Подобная линейная нумерация не всегда адекватно отражает логическую взаимосвязь процессов приложения. Например, по смыслу задачи процессы могут располагаться в узлах прямо­угольной решетки и взаимодействовать только со своими непосредственны­ми соседями. Такую ситуацию пользователь может легко отразить в своей программе, описав соответствующую виртуальную топологию процессов. Эта информация может оказаться полезной при отображении процессов про­граммы на физические процессоры вычислительной системы. Сам процесс отображения в MPI никак не специфицируется, однако система поддержки MPI в ряде случаев может значительно уменьшить коммуникационные на­кладные расходы, если воспользуется знанием виртуальной топологии.
Основным способом общения процессов между собой является посылка со­общений. Сообщение — это набор данных некоторого типа. Каждое сообще­ние имеет несколько атрибутов, в частности, номер процесса-отправителя, номер процесса-получателя, идентификатор сообщения и др. Одним из важных атрибутов сообщения является его идентификатор или тэг. По иден­тификатору процесс, принимающий сообщение, например, может различить два сообщения, пришедшие к нему от одного и того же процесса. Сам иден-
Глава 5. Технологии параллельного программирования
277
тификатор сообщения является целым неотрицательным числом, лежащим в диапазоне от 0 до 32 767. Для работы с атрибутами сообщений введена структура MPi_status, поля которой дают доступ к значениям атрибутов.
На практике сообщение чаще всего является набором однотипных данных, расположенных подряд друг за другом в некотором буфере. Такое сообще­ние может состоять, например, из двухсот целых чисел, которые пользова­тель разместил в соответствующем целочисленном векторе. Это типичная ситуация, на нее ориентировано большинство функций MPI, однако такая ситуация имеет, по крайней мере, два ограничения. Во-первых, иногда не­обходимо составить сообщение из разнотипных данных. Конечно же, можно отдельным сообщением послать количество вещественных чисел, содержа­щихся в последующем сообщении, но это может быть и неудобно програм­мисту, и не столь эффективно. Во-вторых, не всегда посылаемые данные занимают непрерывную область в памяти. Если в Fortran элементы столбцов матрицы расположены в памяти друг за другом, то элементы строк уже идут с некоторым шагом. Чтобы послать строку, данные нужно сначала упако­вать, передать, а затем вновь распаковать.
Чтобы снять указанные ограничения, в MPI предусмотрен механизм для вве­дения производных типов данных (derived datatypes). Описав состав и схему размещения в памяти посылаемых данных, пользователь в дальнейшем ра­ботает с такими типами так же, как и со стандартными типами данных MPI.
Поскольку собственные типы данных и виртуальные топологии процессов используются на практике не очень часто, то в данной книге мы не будем их описывать подробно.
Общие функции MPI
Прежде чем переходить к описанию конкретных функций, сделаем не­сколько общих замечаний. При описании функций мы всегда будем пользо­ваться словом оит для обозначения выходных параметров, через которые функция возвращает результаты. Даже если результатом работы функции является одно число, оно будет возвращено через один из параметров. Свя­зано это с тем, что практически все функции MPI возвращают в качестве своего значения информацию об успешности завершения. В случае успеш­ного выполнения функция вернет значение mpi_success, иначе — код ошибки. Вид ошибки, которая произошла при выполнении функции, мож­но будет понять из описания каждой функции. Предопределенные возвра­щаемые значения, соответствующие различным ошибочным ситуациям, оп­ределены в файле mpi.h. В дальнейшем при описании конкретных функций, если ничего специально не сказано, то возвращаемое функцией значение будет подчиняться именно этому правилу.
Далее мы рассмотрим общие функции MPI, необходимые практически в каждой программе.
278
Часть II. Параллельное программирование
int MPI_Init(int *argc, char ***argv)
Инициализация параллельной части программы. Все другие функции MPI могут быть вызваны только после вызова MPiinit. Необычный тип аргу­ментов MPi_init предусмотрен для того, чтобы иметь возможность передать всем процессам аргументы функции main.
Инициализация параллельной части для каждого приложения должна вы­полняться только один раз. Поскольку для сложных приложений, которые состоят из многих модулей и пишутся разными людьми, это отследить труд­но, введена дополнительная функция MPi_initiaiized:
MPI_Initialized (int *flag)
Здесь out flag — признак инициализации параллельной части программы.
Если функция MPi_init уже была вызвана, то через параметр flag возвра­щается значение 1, в противном случае 0.
int MPI_Finalize(void)
Завершение параллельной части приложения. Все последующие обращения к любым MPI-функциям, в том числе к MPiinit, запрещены. К моменту вызова MPi_Finaiize каждым процессом программы все действия, требую­щие его участия в обмене сообщениями, должны быть завершены.
Общая схема MPI-программы выглядит так:
main(int argc, char **argv) {
MPI_Init(Sargc, Sargv);
MPI_Finalize();
}
int MPI_Comm_size(MPI_Comm comm, int *size)
□  comm — идентификатор коммуникатора;
□  out size — число процессов в коммуникаторе comm.
Определение общего числа параллельных процессов в коммуникаторе comm. Результат возвращается через параметр size, для чего функции передается адрес этой переменной. Поскольку коммуникатор является сложной струк­турой, перед ним стоит имя предопределенного типа MPi_comm, определен­ного в файле mpi.h.
int MPI_Comm_rank(MPI_Comm comm, int *rank)
□  comm — идентификатор коммуникатора;
□  out rank — номер процесса в коммуникаторе comm.
Глава 5. Технологии параллельного программирования
279
Определение номера процесса в коммуникаторе comm. Если функция MPi_comm_size для того же коммуникатора comm вернула значение size, то значение, возвращаемое функцией MPi_comm_rank через переменную rank, лежит в диапазоне oTOflosize-i.
double MPI_Wtime(void)
Эта функция возвращает астрономическое время в секундах (вещественное число), прошедшее с некоторого момента в прошлом. Если некоторый уча­сток программы окружить вызовами данной функции, то разность возвра­щаемых значений покажет время работы данного участка. Гарантируется, что момент времени, используемый в качестве точки отсчета, не будет из­менен за время существования процесса. Заметим, что эта функция возвра­щает результат своей работы не через параметры, а явным образом.
Простейший пример программы, в которой использованы описанные выше функции, выглядит так:
main(int argc, char **argv) {
int me, size;
MPI_Init(Sargc, Sargv); MPI_Comm_rank(MPI_COMM_WORLD, &me); MPI_Comm_size(MPI_COMM_WORLD, Ssize); printf("Process %d size %d \n", me, size);
MPI_Finalize();
}
Строка, соответствующая функции printf, будет выведена столько раз, сколько процессов было порождено при вызове MPiinit. Порядок появле­ния строк заранее не определен и может быть, вообще говоря, любым. Га­рантируется только то, что содержимое отдельных строк не будет перемеша­но друг с другом.
Прием/передача сообщений между отдельными процессами
Все функции передачи сообщений в MPI делятся на две группы. В одну группу входят функции, которые предназначены для взаимодействия двух процессов программы. Такие операции называются индивидуальным или операциями типа "точка-точка". Функции другой группы предполагают, что в операцию должны быть вовлечены все процессы некоторого коммуника­тора. Такие операции называются коллективными.
280
Часть II. Параллельное программирование
Начнем описание функций обмена сообщениями с обсуждения операций типа "точка-точка". Все функции данной группы, в свою очередь, также де­лятся на два класса: функции с блокировкой (с синхронизацией) и функции без блокировки (асинхронные).
Прием/передача сообщений с блокировкой задаются конструкциями следую­щего вида.
int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm)
□  buf — адрес начала буфера с посылаемым сообщением;
□  count — число передаваемых элементов в сообщении;
□  datatype — тип передаваемых элементов;
□  dest — номер процесса-получателя;
□  msgtag — идентификатор сообщения;
□  comm — идентификатор коммуникатора.
Блокирующая посылка сообщения с идентификатором msgtag, состоящего из count элементов типа datatype, процессу с номером dest. Все элементы посылаемого сообщения расположены подряд в буфере buf. Значение count может быть нулем. Разрешается передавать сообщение самому себе. Тип пе­редаваемых элементов datatype должен указываться с помощью предопре­деленных констант типа, например, mpi_int, mpi_long, mpi_short,
MPI_LONG_DOUBLE, MPI_CHAR, MPI_UNSIGNED_CHAR, MPI_FLOAT ИЛИ С ПОМОЩЬЮ
введенных производных типов. Для каждого типа данных языков Fortran и С есть своя константа. Полный список предопределенных имен типов мож­но найти в файле mpi.h.
Блокировка гарантирует корректность повторного использования всех пара­метров после возврата из подпрограммы. Это означает, что после возврата из данной функции можно использовать любые присутствующие в вызове функции переменные без опасения испортить передаваемое сообщение. Вы­бор способа осуществления этой гарантии: копирование в промежуточный буфер или непосредственная передача процессу dest, остается за разработ­чиками конкретной реализации MPI.
Следует специально отметить, что возврат из функции MPi_send не означает ни того, что сообщение получено процессом dest, ни того, что сообщение покинуло процессорный элемент, на котором выполняется процесс, запус­тивший MPisend. Предоставляется только гарантия безопасного изменения переменных, использованных в вызове данной функции.
Подобная неопределенность далеко не всегда устраивает пользователя. Что­бы расширить возможности передачи сообщений, в MPI введены дополни-
Глава 5. Технологии параллельного программирования
281
тельные три функции. Все параметры у этих функций такие же, как и у функции MPi_send, однако у каждой из них есть своя особенность.
MPi_Bsend — передача сообщения с буферизацией. Если прием посылаемого сообщения еще не был инициализирован процессом-получателем, то сооб­щение будет записано в буфер и произойдет немедленный возврат из функ­ции. Выполнение данной функции никак не зависит от соответствующего вызова функции приема сообщения. Тем не менее, функция может вернуть код ошибки, если места под буфер недостаточно.
MPi_ssend — передача сообщения с синхронизацией. Выход из данной функции произойдет только тогда, когда прием посылаемого сообщения будет инициализирован процессом-получателем. Таким образом, заверше­ние передачи с синхронизацией говорит не только о возможности повтор­ного использования буфера, но и о гарантированном достижении процес­сом-получателем точки приема сообщения в программе. Если прием сообщения также выполняется с блокировкой, то функция MPissend со­храняет семантику блокирующих вызовов.
MPi_Rsend — передача сообщения по готовности. Данной функцией можно пользоваться только в том случае, если процесс-получатель уже иницииро­вал прием сообщения. В противном случае вызов функции, вообще говоря, является ошибочным и результат ее выполнения не определен. Во многих реализациях функция MPi_Rsend сокращает протокол взаимодействия между отправителем и получателем, уменьшая накладные расходы на организацию передачи.
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Status *status)
□  out buf — адрес начала буфера для приема сообщения;
□  count — максимальное число элементов в принимаемом сообщении;
□  datatype — тип элементов принимаемого сообщения;
□   source — номер процесса-отправителя;
□  msgtag — идентификатор принимаемого сообщения;
□  comm — идентификатор коммуникатора;
□  out status — параметры принятого сообщения.
Прием сообщения с идентификатором msgtag от процесса source с блоки­ровкой. Число элементов в принимаемом сообщении не должно превосхо­дить значения count. Если число принятых элементов меньше значения count, то гарантируется, что в буфере buf изменятся только элементы, соот­ветствующие элементам принятого сообщения. Если нужно узнать точное число элементов в принимаемом сообщении, то можно воспользоваться функциями MPI_Probe ИЛИ MPI_Get_count.
282
Часть II. Параллельное программирование
Блокировка гарантирует, что после возврата из функции все элементы со­общения уже будут приняты и расположены в буфере buf.
Ниже приведен пример программы, в которой нулевой процесс посылает сообщение процессу с номером один и ждет от него ответа. Если программа будет запущена с большим числом процессов, то реально выполнять пере­сылки все равно станут только нулевой и первый процессы. Остальные про­цессы после их порождения функцией MPi_init сразу завершатся, выпол­нив функцию MPI_Finalize.
#include "mpi.h" #include <stdio.h>
int main(argc,argv)
int argc;
char *argv[]; {
int numtasks, rank, dest, src, re, tag=l;
char inmsg, outmsg='x';
MPI_Status Stat;
MPI_Init(Sargc,Sargv);
MPI_Comm_size(MPI_COMM_WORLD, Snumtasks);
MPI_Comm_rank(MPI_COMM_WORLD, Srank);
if (rank ==0) { dest = 1; src = 1;
re = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); re = MPI_Recv(&inmsg, 1, MPI_CHAR, src, tag, MPI_COMM_WORLD, SStat); } else
if (rank == 1) { dest = 0; src = 0; re = MPI_Recv(&inmsg, 1, MPI_CHAR, src, tag, MPI_COMM_WORLD,
SStat); re = MPI_Send(&outmsg, 1, MPI_CHAR, dest, tag, MPI_COMM_WORLD); } MPI_Finalize (); }
Глава 5. Технологии параллельного программирования
283
В следующем примере каждый процесс с четным номером посылает сооб­щение своему соседу с номером, на единицу большим. Дополнительно по­ставлена проверка для процесса с максимальным номером, чтобы он не по­слал сообщение несуществующему процессу. Показана только схема программы.
main(int argc, char **argv)
{
int me, size;
int SOME_TAG=0;
MPI_Status status;
MPI_Init (Sargc, Sargv); MPI_Comm_rank (MPI_COMM_WORLD, &me); MPI_Comm_size (MPI_COMM_WORLD, Ssize); if ( (me % 2) == 0) {
if ((me+1) < size) /* посылают все процессы, кроме последнего */ MPI_Send (..., me+1, SOME_TAG, MPI_COMM_WORLD); } else
MPI_Recv (..., me-1, SOME_TAG, MPI_COMM_WORLD, Sstatus);
MPI_Finalize();
}
При приеме сообщения в качестве номера процесса-отправителя можно указать предопределенную константу mpi_any_source — признак того, что подходит сообщение от любого процесса. В качестве идентификатора при­нимаемого сообщения можно указать константу mpi_any_tag — это признак того, что подходит сообщение с любым идентификатором. При одновре­менном использовании этих двух констант будет принято любое сообщение от любого процесса.
Параметры принятого сообщения всегда можно определить по соответст­вующим полям структуры status. Предопределенный тип MPi_status опи­сан в файле mpi.h. В языке С параметр status является структурой, содер­жащей поля с именами mpi_source, mpi_tag и mpi_error. Реальные значения номера процесса-отправителя, идентификатора сообщения и кода ошибки доступны через status.mpi_source, status.mpi_tag и status.mpi_error. В языке Fortran параметр status является целочислен­ным массивом размера mpi_status_size. Константы mpi_source, mpi_tag и mpi_error являются индексами по данному массиву для доступа к значени­ям соответствующих полей, например, status (mpi_source) .
284
Часть II. Параллельное программирование
Обратим внимание на некоторую несимметричность операций посылки и приема сообщений. С помощью константы mpi_any_source можно принять сообщение от любого процесса. Однако в случае посылки данных требуется явно указать номер принимающего процесса.
В стандарте оговорено, что если один процесс последовательно посылает два сообщения другому процессу, и оба эти сообщения соответствуют одно­му и тому же вызову MPi_Recv, то первым будет принято сообщение, кото­рое было отправлено раньше. Вместе с тем, если два сообщения были одно­временно отправлены разными процессами и оба сообщения соответствуют одному и тому же вызову MPi_Recv, то порядок их получения принимающим процессом заранее не определен.
MPI не гарантирует выполнения свойства "справедливости" при распределе­нии приходящих сообщений. Предположим, что процесс Р\ послал сообще­ние, которое может быть принято процессом Р2. Однако прием может не произойти, в принципе, сколь угодно долгое время. Такое возможно, на­пример, если процесс Р3 постоянно посылает сообщения процессу Р2, также подходящие под шаблон mpi Recv.
Процесс 1           Процесс 2
MPI Send             MPI Send
•-•• X ■"■■
MPI Recv             MPI Recv
Рис. 5.5. Тупиковая ситуация при использовании блокирующих функций
Последнее замечание относительно использования блокирующих функций приема и посылки связано с возможным возникновением тупиковой ситуа­ции. Предположим, что работают два параллельных процесса и они хотят обменяться данными. Было бы вполне естественно в каждом процессе сна­чала воспользоваться функцией MPi_send, а затем MPi_Recv (схематично эта ситуация изображена на рис. 5.5). Но именно этого и не стоит делать. Дело в том, что мы заранее не знаем, как реализована функция MPi_send. Если разработчики для гарантии корректного повторного использования буфера посылки заложили схему, при которой посылающий процесс ждет начала приема, то возникнет классический тупик. Первый процесс не может вер­нуться из функции посылки, поскольку второй не начинает прием сообще­ния. А второй процесс не может начать прием сообщения, поскольку сам по похожей причине застрял на посылке. Выход из этой ситуации прост. Нуж-
Глава 5. Технологии параллельного программирования
285
но использовать либо неблокирующие функции приема/передачи, либо функцию совмещенной передачи и приема. Оба варианта мы обсудим ниже.
int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count)
□   status — параметры принятого сообщения;
□  datatype — тип элементов принятого сообщения;
□  out count — число элементов сообщения.
По значению параметра status данная функция определяет число уже при­нятых (после обращения к MPi_Recv) или принимаемых (после обращения к MPi_Probe или MPi_iprobe) элементов сообщения типа datatype. Данная функция, в частности, необходима для определения размера области памя­ти, выделяемой для хранения принимаемого сообщения.
int MPI_Probe(int source, int msgtag, MPI_Comm comm, MPI_Status *status)
□   source — номер процесса-отправителя или mpi_any_source;
□  msgtag — идентификатор ожидаемого сообщения или mpi_any_tag;
□  comm — идентификатор коммуникатора;
□  out status — параметры найденного подходящего сообщения.
Получение информации о структуре ожидаемого сообщения с блокировкой. Возврата из функции не произойдет до тех пор, пока сообщение с подходя­щим идентификатором и номером процесса-отправителя не будет доступно для получения. Атрибуты доступного сообщения можно определить обыч­ным образом с помощью параметра status. Следует особо обратить внима­ние на то, что функция определяет только факт прихода сообщения, но ре­ально его не принимает.
Прием/передача сообщений без блокировки. В отличие от функций с блоки­ровкой, возврат из функций данной группы происходит сразу без какой-либо блокировки процессов. На фоне дальнейшего выполнения программы одновременно происходит и обработка асинхронно запущенной операции. В принципе, данная возможность исключительно полезна для создания эф­фективных программ. В самом деле, программист знает, что в некоторый момент ему потребуется массив, который вычисляет другой процесс. Он за­ранее выставляет в программе асинхронный запрос на получение данного массива, а до того момента, когда массив реально потребуется, он может выполнять любую другую полезную работу. Опять же, во многих случаях совершенно не обязательно дожидаться окончания посылки сообщения для выполнения последующих вычислений.
Если есть возможность операции приема/передачи сообщений скрыть на фоне вычислений, то этим, вроде бы, надо безоговорочно пользоваться. Од­нако на практике не все согласуется с теорией. Многое зависит от конкрет­ной реализации. К сожалению, далеко не всегда асинхронные операции эф­фективно поддерживаются аппаратурой и системным окружением. Поэтому
286
Часть II. Параллельное программирование
не стоит удивляться, если эффект от выполнения вычислений на фоне пере­сылок окажется нулевым. Стандарт стандартом, но вы работаете в конкрет­ной среде, на конкретной вычислительной системе. А справедливости ради заметим, что стандарт эффективности и не обещал...
Сделанные замечания касаются только вопросов эффективности. В отноше­нии предоставляемой функциональности асинхронные операции исключи­тельно полезны, поэтому они присутствуют практически в каждой реальной программе.
int MPI_Isend(void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
□  buf — адрес начала буфера с посылаемым сообщением;
□  count — число передаваемых элементов в сообщении;
□  datatype — тип передаваемых элементов;
□  dest — номер процесса-получателя;
□  msgtag — идентификатор сообщения;
□  comm — идентификатор коммуникатора;
□  out request — идентификатор асинхронной операции.
Передача сообщения аналогична вызову MPisend, однако возврат из функ­ции MPi_isend происходит сразу после инициализации процесса передачи без ожидания обработки всего сообщения, находящегося в буфере buf. Это означает, что нельзя что-то записывать в данный буфер без получения до­полнительной информации, подтверждающей завершение посылки. Опре­делить тот момент времени, когда можно повторно использовать буфер buf без опасения испортить передаваемое сообщение, можно с помощью пара­метра request И функций MPI_Wait И MPI_Test.
Аналогично трем модификациям функции MPi_send, предусмотрены три до­полнительных варианта функций MPI_Ibsend, MPI_Issend, MPI_Irsend. К из­ложенной выше семантике работы этих функций добавляется асинхронность.
int MPI_Irecv(void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Request *request)
□  out buf — адрес начала буфера для приема сообщения;
□  count — максимальное число элементов в принимаемом сообщении;
□  datatype — тип элементов принимаемого сообщения;
□   source — номер процесса-отправителя;
□  msgtag — идентификатор принимаемого сообщения;
□  comm — идентификатор коммуникатора;
□  out request — идентификатор операции асинхронного приема сообщения.
Глава 5. Технологии параллельного программирования
287
Прием сообщения, аналогичный MPiRecv, однако возврат из функции про­исходит сразу после инициализации процесса приема без ожидания получе­ния и записи всего сообщения в буфере buf. Окончание процесса приема можно определить с помощью параметра request и процедур типа MPi_wait И MPI_Test.
Сообщение, отправленное любой из функций MPi_send, MPi_isend и любой из трех их модификаций, может быть принято любой из процедур MPi_Recv
И MPI_Irecv.
С помощью данной функции легко обойти возможную тупиковую ситуа­цию, показанную на рис. 5.5. Заменим вызов функции приема сообщения с блокировкой на вызов функции MPiirecv. Расположим его перед вызовом функции MPi_send, т. е. преобразуем фрагмент:
MPI_Send (...) MPI_Recv(...)
следующим образом
MPI_Irecv (...) MPI_Send (...)
В такой ситуации тупик гарантированно не возникнет, поскольку к моменту вызова функции MPisend запрос на прием сообщения уже будет выставлен.
int MPI_Wait(MPI_Request *request, MPI_Status *status)
□   request — идентификатор операции асинхронного приема или передачи;
□  out status — параметры сообщения.
Ожидание завершения асинхронной операции, ассоциированной с иденти­фикатором request И запущенной функциями MPI_Isend ИЛИ MPI_Irecv. Пока асинхронная операция не будет завершена, процесс, выполнивший функцию MPi_wait, будет заблокирован. Если речь идет о приеме, атрибуты и длину принятого сообщения можно определить обычным образом с по­мощью параметра status.
int MPI_Waitall(int count, MPI_Request *requests, MPI_Status *statuses)
□  count — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или пере­дачи;
□  out statuses — параметры сообщений.
288
Часть II. Параллельное программирование
Выполнение процесса блокируется до тех пор, пока все операции обмена, ассоциированные с указанными идентификаторами, не будут завершены. Если во время одной или нескольких операций обмена возникли ошибки, то поле ошибки в элементах массива statuses будет установлено в соответ­ствующее значение.
Ниже показан пример программы, в которой все процессы обмениваются сообщениями с ближайшими соседями в соответствии с топологией кольца.
#include "mpi.h" #include <stdio.h>
int main(argc,argv)
int argc;
char *argv[]; {
int numtasks, rank, next, prev, buf[2], tagl=l, tag2=2;
MPI_Request reqs[4];
MPI_Status stats [4];
MPI_In.it (Sargc, &argv) ;
MPI_Comm_size(MPI_COMM_WORLD, Snumtasks);
MPI_Comm_rank(MPI_COMM_WORLD, Srank);
prev = rank - 1;
next = rank+1;
if (rank == 0) prev = numtasks - 1;
if (rank == (numtasks - 1)) next = 0;
MPI_Irecv(&buf[0], 1, MPI_INT, prev, tagl, MPI_COMM_WORLD,
&reqs[0]); MPI_Irecv(&buf[1], 1, MPI_INT, next, tag2, MPI_COMM_WORLD, &reqs[1]);
MPI_Isend(&rank, 1, MPI_INT, prev, tag2, MPI_COMM_WORLD, &reqs[2]); MPI_Isend(&rank, 1, MPI_INT, next, tagl, MPI_COMM_WORLD, &reqs[3]);
MPI_Waitall(4, reqs, stats);
MPI_Finalize();
}
Глава 5. Технологии параллельного программирования
289
int MPI_Waitany( int count, MPI_Request *requests, int *index, MPI_Status *status)
□  count — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или передачи;
□  out index — номер завершенной операции обмена;
□  out status — параметры сообщения.
Выполнение процесса блокируется до тех пор, пока какая-либо асинхронная операция обмена, ассоциированная с указанными идентификаторами, не будет завершена. Если завершились несколько операций, то случайным об­разом будет выбрана одна из них. Параметр index содержит номер элемента в массиве requests, содержащего идентификатор завершенной операции.
int MPI_Waitsome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
□   incount — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или передачи;
□  out out count — число идентификаторов завершившихся операций обмена;
□  out indexes — массив номеров завершившихся операций обмена;
□  out statuses — параметры завершившихся операций приема сообщений.
Выполнение процесса блокируется до тех пор, пока одна из операций обме­на, ассоциированных с указанными идентификаторами, не будет завершена. Параметр out count содержит число завершенных операций, а первые outcount элементов массива indexes содержат номера элементов массива requests с их идентификаторами. Первые outcount элементов массива statuses содержат параметры завершенных операций.
int MPI_Test(MPI_Request *request, int *flag, MPI_Status *status)
□   request — идентификатор операции асинхронного приема или передачи;
□  out flag — признак завершенности операции обмена;
□  out status — параметры сообщения.
Проверка завершенности асинхронных функций MPi_isend или MPi_irecv, ассоциированных с идентификатором request. В параметре flag функция MPiTest возвращает значение 1, если соответствующая операция заверше­на, и значение 0 в противном случае. Если завершена процедура приема, то атрибуты и длину полученного сообщения можно определить обычным об­разом с помощью параметра status.
int MPI_Testall( int count, MPI_Request *requests, int *flag, MPI_Status *statuses)
□  count — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или передачи;
290
Часть II. Параллельное программирование
П out flag — признак завершенности операций обмена;
□  out statuses — параметры сообщений.
В параметре flag функция возвращает значение 1, если все операции, ассо­циированные с указанными идентификаторами, завершены. В этом случае параметры сообщений будут указаны в массиве statuses. Если какая-либо из операций не завершилась, то возвращается 0, и определенность элемен­тов массива statuses не гарантируется.
int MPI_Testany(int count, MPI_Request *requests, int *index, int *flag, MPI_Status *status)
□  count — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или пере­дачи;
□  out index — номер завершенной операции обмена;
□  out flag — признак завершенности операции обмена;
□  out status — параметры сообщения.
Если к моменту вызова функции MPi_Testany хотя бы одна из операций асинхронного обмена завершилась, то в параметре flag возвращается зна­чение 1, index содержит номер соответствующего элемента в массиве requests, a status — параметры сообщения. В противном случае в пара­метре flag будет возвращено значение 0.
int MPI_Testsome( int incount, MPI_Request *requests, int *outcount, int *indexes, MPI_Status *statuses)
□   incount — число идентификаторов асинхронных операций;
□   requests — идентификаторы операций асинхронного приема или пере­дачи;
□  out outcount — число идентификаторов завершившихся операций обмена;
□  out indexes — массив номеров завершившихся операций обмена;
□  out statuses — параметры завершившихся операций приема сообщений.
Данная функция работает так же, как и MPi_waitsome, за исключением то­го, что возврат происходит немедленно. Если ни одна из указанных опера­ций не завершилась, то значение outcount будет равно нулю.
int MPI_Iprobe( int source, int msgtag, MPI_Comm comm, int *flag, MPI_Status *status)
□   source — номер процесса-отправителя или mpi_any_source;
□  msgtag — идентификатор ожидаемого сообщения или mpi_any_tag;
□  comm — идентификатор коммуникатора;
Глава 5. Технологии параллельного программирования
291
П out flag — признак завершенности операции обмена;
□  out status — параметры подходящего сообщения.
Получение информации о поступлении и структуре ожидаемого сообщения без блокировки. В параметре flag возвращается значение 1, если сообщение с подходящими атрибутами уже может быть принято (в этом случае ее дей­ствие полностью аналогично MPi_Probe), и значение 0, если сообщения с указанными атрибутами еще нет.
Объединение запросов на взаимодействие. Процедуры данной группы позво­ляют снизить накладные расходы, возникающие в рамках одного процессо­ра при обработке приема/передачи и перемещении необходимой информа­ции между процессом и сетевым контроллером. Несколько запросов на прием и/или передачу могут объединяться вместе для того, чтобы далее их можно было бы запустить одной командой. Способ приема сообщения ни­как не зависит от способа его посылки: сообщение, отправленное с помо­щью объединения запросов либо обычным способом, может быть принято как обычным способом, так и с помощью объединения запросов.
int MPI_Send_init( void *buf, int count, MPI_Datatype datatype, int dest, int msgtag, MPI_Comm comm, MPI_Request *request)
□  buf — адрес начала буфера с посылаемым сообщением;
□  count — число передаваемых элементов в сообщении;
□  datatype — тип передаваемых элементов;
□  dest — номер процесса-получателя;
□  msgtag — идентификатор сообщения;
□  comm — идентификатор коммуникатора;
□  out request — идентификатор асинхронной передачи.
Формирование запроса на выполнение пересылки данных. Все параметры точно такие же, как и у подпрограммы MPi_isend, однако, в отличие от нее, пересылка не начинается до вызова подпрограммы MPi_startaii. Как и прежде, дополнительно предусмотрены варианты и для трех модификаций
ПОСЫЛКИ сообщений: MPI_Bsend_init, MPI_Ssend_init, MPI_Rsend_init.
int MPI_Recv_init( void *buf, int count, MPI_Datatype datatype, int source, int msgtag, MPI_Comm comm, MPI_Request *request)
□  out buf — адрес начала буфера приема сообщения;
□  count — число принимаемых элементов в сообщении;
□  datatype — тип принимаемых элементов;
□   source — номер процесса-отправителя;
□  msgtag — идентификатор сообщения;
292
Часть II. Параллельное программирование
П comm — идентификатор коммуникатора;
□  out request — идентификатор асинхронного приема.
Формирование запроса на выполнение приема сообщения. Все параметры точно такие же, как и у функции MPi_irecv, однако, в отличие от нее, ре­альный прием не начинается до вызова функции MPi_startaii.
MPI_Startall(int count, MPI_Request *requests)
□  count — число запросов на взаимодействие;
□  out requests — массив идентификаторов приема/передачи.
Запуск всех отложенных операций передачи и приема, ассоциированных с элементами массива запросов requests и инициированных функциями MPi_Recv_init, MPi_send_init или ее тремя модификациями. Все отложен­ные взаимодействия запускаются в режиме без блокировки, а их завершение можно определить обычным образом с помощью функций семейств MPI_Wait И MPI_Test.
Совмещенные прием и передача сообщений. Совмещение приема и передачи сообщений между процессами позволяет легко обходить множество подвод­ных камней, связанных с возможными тупиковыми ситуациями. Предполо­жим, что в линейке процессов необходимо организовать обмен данными между /-м и / + 1-м процессами. Если воспользоваться стандартными бло­кирующими функциями посылки сообщений, то возможен тупик, обсуж­давшийся ранее. Один из способов обхода такой ситуации состоит в исполь­зовании функции совмещенного приема и передачи.
int MPI_Sendrecv( void *sbuf, int scount, MPI_Datatype stype, int dest, int stag, void *rbuf, int rcount, MPI_Datatype rtype, int source, MPI_Datatype rtag, MPI_Comm comm, MPI_Status *status)
□   sbuf — адрес начала буфера с посылаемым сообщением;
□   scount — число передаваемых элементов в сообщении;
□   stype — тип передаваемых элементов;
□  dest — номер процесса-получателя;
□   stag — идентификатор посылаемого сообщения;
□  out rbuf — адрес начала буфера приема сообщения;
□   rcount — число принимаемых элементов сообщения;
□   rtype — тип принимаемых элементов;
□   source — номер процесса-отправителя;
□   rtag — идентификатор принимаемого сообщения;
□  comm — идентификатор коммуникатора;
□  out status — параметры принятого сообщения.
Глава 5. Технологии параллельного программирования
293
Данная операция объединяет в едином запросе посылку и прием сообще­ний. Естественно, что реализация этой функции гарантирует отсутствие ту­пиков, которые могли бы возникнуть между процессами при использовании
обыЧНЫХ блокирующих Операций MPI_Send и MPI_Recv.
Принимающий и отправляющий процессы могут являться одним и тем же процессом. Буфера приема и посылки обязательно должны быть различны­ми. Сообщение, отправленное операцией MPi_sendrecv, может быть приня­то обычным образом, и точно так же операция MPi_sendrecv может при­нять сообщение, отправленное обычной операцией MPisend.
Коллективные взаимодействия процессов
В операциях коллективного взаимодействия процессов участвуют все процессы коммуникатора. Соответствующая процедура должна быть вызвана каждым процессом, быть может, со своим набором параметров. Возврат из процедуры коллективного взаимодействия может произойти в тот момент, когда участие процесса в данной операции уже закончено. Как и для блокирующих проце­дур, возврат означает то, что разрешен свободный доступ к буферу приема или посылки. Асинхронных коллективных операций в MPI нет.
В коллективных операциях можно использовать те же коммуникаторы, что и были использованы для операций типа "точка-точка". MPI гарантирует, что сообщения, вызванные коллективными операциями, никак не повлияют и не пересекутся с сообщениями, появившимися в результате индивидуаль­ного взаимодействия процессов.
Вообще говоря, нельзя рассчитывать на синхронизацию процессов с помо­щью коллективных операций. Если какой-то процесс уже завершил свое участие в коллективной операции, то это не означает ни того, что данная операция завершена другими процессами коммуникатора, ни даже того, что она ими начата (конечно же, если это возможно по смыслу операции).
В коллективных операциях не используются идентификаторы сообщений.
int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int source, MPI_Comm comm)
□  out buf — адрес начала буфера посылки сообщения;
□  count — число передаваемых элементов в сообщении;
□  datatype — тип передаваемых элементов;
□   source — номер рассылающего процесса;
□  comm — идентификатор коммуникатора.
Рассылка сообщения от процесса source всем процессам, включая рассы­лающий процесс. При возврате из процедуры содержимое буфера buf про­цесса source будет скопировано в локальный буфер каждого процесса ком­муникатора comm. Значения параметров count, datatype, source И comm
294
Часть II. Параллельное программирование
должны быть одинаковыми у всех процессов. В результате выполнения сле­дующего оператора всеми процессами коммуникатора comm:
MPI_Bcast (array, 100, MPI_INT, 0, comm);
первые сто целых чисел из массива array нулевого процесса будут скопиро­ваны в локальные буфера array каждого процесса.
int MPI_Gather( void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int dest, MPI_Comm comm)
□   sbuf — адрес начала буфера посылки;
□   scount — число элементов в посылаемом сообщении;
□   stype — тип элементов отсылаемого сообщения;
□  out rbuf — адрес начала буфера сборки данных;
□   rcount — число элементов в принимаемом сообщении;
□   rtype — тип элементов принимаемого сообщения;
□  dest — номер процесса, на котором происходит сборка данных;
□  comm — идентификатор коммуникатора.
Сборка данных со всех процессов в буфере rbuf процесса dest. Каждый процесс, включая dest, посылает содержимое своего буфера sbuf процессу dest. Собирающий процесс сохраняет данные в буфере rbuf, располагая их в порядке возрастания номеров процессов. На процессе dest существенны­ми являются значения всех параметров, а на всех остальных процессах — только значения параметров sbuf, scount, stype, dest и comm. Значения параметров dest и comm должны быть одинаковыми у всех процессов. Па­раметр rcount у процесса dest обозначает число элементов типа rtype, принимаемых не от всех процессов в сумме, а от каждого процесса. С по­мощью похожей функции MPiGatherv можно принимать от процессов мас­сивы данных различной длины.
int MPI_Scatter(void *sbuf, int scount, MPI_Datatype stype, void *rbuf, int rcount, MPI_Datatype rtype, int source, MPI_Comm comm)
□   sbuf — адрес начала буфера посылки;
□   scount — число элементов в посылаемом сообщении;
□   stype — тип элементов отсылаемого сообщения;
□  out rbuf — адрес начала буфера сборки данных;
□   rcount — число элементов в принимаемом сообщении;
□   rtype — тип элементов принимаемого сообщения;
□   source — номер процесса, на котором происходит сборка данных;
□  comm — идентификатор коммуникатора.
Глава 5. Технологии параллельного программирования
295
Функция MPi_scatter по своему действию является обратной к MPi_Gather. Процесс source рассылает порции данных из массива sbuf всем п процес­сам приложения. Можно считать, что массив sbuf делится на п равных час­тей, состоящих из scount элементов типа stype, после чего /-я часть посы­лается /-му процессу. На процессе source существенными являются значения всех параметров, а на всех остальных процессах — только значе­ния параметров rbuf, rcount, rtype, source и comm. Значения параметров source и comm должны быть одинаковыми у всех процессов.
Аналогично функции MPi_Gatherv, с помощью функции MPi_Scatterv про­цессам можно отослать порции данных различной длины.
В следующем примере показано использование функции MPi_scatter для рассылки строк массива. Напомним, что в языке С, в отличие от Fortran, массивы хранятся в памяти по строкам.
#include "mpi.h" #include <stdio.h> idefine SIZE 4
int main(argc,argv)
int argc;
char *argv[];
{
int numtasks, rank, sendcount, recvcount, source;
float sendbuf[SIZE] [SIZE] = {
{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0} }; float recvbuf[SIZE];
MPI_Init(Sargc,Sargv); MPI_Comm_rank(MPI_COMM_WORLD, Srank); MPI_Comm_size(MPI_COMM_WORLD, Snumtasks);
if (numtasks == SIZE) { source = 1; sendcount = SIZE; recvcount = SIZE;
MPI_Scatter(sendbuf, sendcount, MPI_FLOAT, recvbuf, recvcount, MPI_FLOAT, source, MPI_COMM_WORLD);
296
Часть II. Параллельное программирование
printf("rank= %d Results: %f %f %f %f\n", rank, recvbuf[0],
recvbuf[l], recvbuf[2], recvbuf[3]); } else
printf("Число процессов должно быть равно %d. \n", SIZE);
MPI_Finalize();
}
К коллективным операциям относятся и редукционные операции. Такие операции предполагают, что на каждом процессе хранятся некоторые дан­ные, над которыми необходимо выполнить единую операцию, например, операцию сложения чисел или операцию нахождения максимального значе­ния. Операция может быть либо предопределенной операцией MPI, либо операцией, определенной пользователем. Каждая предопределенная опера­ция имеет свое имя, например, mpi_max, mpi_min, mpi_sum, mpi_prod и т. п.
int MPI_Allreduce( void *sbuf, void *rbuf, int count, MPI_Datatype da­tatype, MPI_Op op, MPI_Comm comm)
□   sbuf — адрес начала буфера для аргументов операции ор;
□  out rbuf — адрес начала буфера для результата операции ор;
□  count — число аргументов у каждого процесса;
□   datatype — тип аргументов;
□  ор — идентификатор глобальной операции;
□  comm — идентификатор коммуникатора.
Данная функция задает выполнение count независимых глобальных опера­ций ор. Предполагается, что в буфере sbuf каждого процесса расположено count аргументов, имеющих тип datatype. Первые элементы массивов sbuf участвуют в первой операции ор, вторые элементы массивов sbuf участвуют во второй операции ор и т. д. Результаты выполнения всех count операций записываются в буфер rbuf на каждом процессе. Значения параметров count, datatype, ор и comm у всех процессов должны быть одинаковыми. Из соображений эффективности реализации предполагается, что операция ор обладает свойствами ассоциативности и коммутативности.
int MPI_Reduce( void *sbuf, void *rbuf, int count, MPI_Datatype da­tatype, MPI_Op op, int root, MPI_Comm comm)
□   sbuf — адрес начала буфера для аргументов;
□  out rbuf — адрес начала буфера для результата;
□  count — число аргументов у каждого процесса;
□   datatype — тип аргументов;
Глава 5. Технологии параллельного программирования
297
П ор — идентификатор глобальной операции;
□   root — процесс-получатель результата;
□  comm — идентификатор коммуникатора.
Функция аналогична предыдущей, но результат операции будет записан в буфер rbuf не у всех процессов, а только у процесса root.
Синхронизация процессов
Синхронизация процессов в MPI осуществляется с помощью единственной функции MPl_Barrier.
int MPI_Barrier (MPI_Comm comm)
comm — идентификатор коммуникатора.
Функция блокирует работу вызвавших ее процессов до тех пор, пока все ос­тавшиеся процессы коммуникатора comm также не выполнят эту процедуру. Только после того, как последний процесс коммуникатора выполнит данную функцию, все процессы будут разблокированы и продолжат выполнение дальше. Данная функция является коллективной. Все процессы должны вы­звать MPi_Barrier, хотя реально исполненные вызовы различными процесса­ми коммуникатора могут быть расположены в разных местах программы.
Работа с группами процессов
Несмотря на то, что в MPI есть значительное множество функций, ориентиро­ванных на работу с коммуникаторами и группами процессов, мы не будем под­робно останавливаться на данном разделе. Представленная функциональность позволяет сравнить состав групп, определить их пересечение, объединение, до­бавить процессы в группу, удалить группу и т. д. В качестве примера приведем лишь один способ образования новых групп на основе существующих.
int MPI_Comm_split(MPI_Comm comm, int color, int key, MPI_Comm *newcomm)
□  comm — идентификатор существующего коммуникатора;
□  color — признак разделения на группы;
□   key — параметр, определяющий нумерацию в новых группах;
□  out newcomm — идентификатор нового коммуникатора.
Данная процедура разбивает все множество процессов, входящих в группу comm, на непересекающиеся подгруппы — одну подгруппу на каждое значе­ние параметра color. Значение параметра color должно быть неотрица­тельным целым числом. Каждая новая подгруппа содержит все процессы, у которых параметр color имеет одно и то же значение, т. е. в каждой новой подгруппе будут собраны все процессы "одного цвета". Всем процессам подгруппы будет возвращено одно и то же значение newcomm. Параметр key определяет нумерацию в новой подгруппе.
298
Часть II. Параллельное программирование
Предположим, что нужно разделить все процессы программы на две под­группы в зависимости от того, является ли номер процесса четным или не­четным. В этом случае в поле color достаточно поместить выражение My_ioi%2, где My_id — это номер процесса. Значением данного выражения может быть либо 0, либо 1. Все процессы с четными номерами автоматиче­ски попадут в одну группу, а процессы с нечетными в другую.
int MPI_Comm_fгее (MPI_Comm comm)
out comm — идентификатор коммуникатора.
Появление нового коммуникатора всегда вызывает создание новой структу­ры данных. Если созданный коммуникатор больше не нужен, то соответст­вующую область памяти необходимо освободить. Данная функция уничто­жает коммуникатор, ассоциированный с идентификатором comm, который после возвращения из функции будет иметь значение mpi_comm_null.
Дальнейшее развитие интерфейса. Стандарт MPI-2
В 1998 году была завершена основная часть работы, связанная с подготовкой новой версии MPI-2. Функциональность интерфейса новой версии значи­тельно расширилась. Однако до сих пор лишь очень немногие производители параллельной вычислительной техники поддерживают данный стандарт, ожи­дая, по всей видимости, широкого признания пользователями новой версии.
А для пользователей этот вопрос не столь очевиден, как кажется. С одной стороны, в новом стандарте отражены, казалось бы, только разумные конст­рукции, необходимые на практике. Возьмем, например, параллельный ввод/вывод. Для большого класса задач операции подобного типа всегда бы­ли узким местом и адекватные средства просто необходимы. Но, с другой стороны, пользователям не дается никаких гарантий, что такие конструкции будут эффективно реализованы. Будут ли они тратить свои силы для оче­редного переписывания программ в новых терминах? Далеко не факт...
Мы не будем столь же детально описывать функции нового стандарта. От­метим лишь основные изменения, сделанные в MPI-2 по сравнению с пре­дыдущими версиями.
Динамическое порождение процессов. В новом стандарте отошли от прежней статической модели параллельной программы. Ранее в программе один раз порождалось некоторое фиксированное число параллельных процессов, ко­торое в процессе работы не изменялось. В модели, заложенной в MPI-2, разрешается создание и уничтожение процессов по ходу выполнения про­граммы. Предусмотрен специальный механизм, позволяющий устанавливать связь между только что порожденными процессами и уже работающей ча­стью MPI-программы. Более того, в MPI-2 есть возможность установить связь между двумя приложениями даже в том случае, когда ни одно из них не было инициатором запуска другого.
Одностороннее взаимодействие процессов. Обмен сообщениями по традици­онной схеме send/Receive не всегда является удобным. Иногда активной
Глава 5. Технологии параллельного программирования
299
стороной вполне может быть только один процесс. Если он знает, какому процессу нужно передать данные и где их разместить, то зачем требовать активности получателя? Аналогично и в случае приема сообщений. Практи­ка показала, что схема Put /Get для многих приложений является более при­емлемой, что и послужило поводом для ее включения в MPI-2. Следует учесть, что схема Put /Get особенно актуальна на компьютерах с общей па­мятью, которые активно появляются в последнее время.
Параллельный ввод/вывод. MPI-2 предоставляет специальный интерфейс для работы процессов с файловой системой.
Расширенные коллективные операции. Во многие коллективные операции до­бавлена возможность взаимодействия между процессами, входящими в раз­ные коммуникаторы. Например, в стандартной операции рассылки сообще­ния MPi_Bcast рассылающий процесс может взаимодействовать с процессами другого коммуникатора. Другой пример расширения: многие коллективные операции внутри коммуникатора в MPI-2 могут выполняться в режиме in_piace, при котором входные и выходные буфера совпадают.
Интерфейс для C++. Представленные в MPI-1 интерфейсы для языков Fortran и С расширены интерфейсом для C++.
Полный вариант описания стандарта MPI-2, а также планы его развития, можно найти на сайте http://www.mpiforum.org.
Вопросы и задания
1.   Что общего и в чем различия между традиционной общей памятью в SMP-компьютерах и пространством кортежей в системе Linda?
2.   В программе необходимо использовать критическую секцию. Как реализовать критическую секцию в рамках системы Linda?
3.   Продумайте детали реализации системы Linda в Linux-кластере. Укажите воз­можные узкие места, влияющие на эффективность выполнения программ.
4.   Необходимо написать программу для компьютера с общей памятью. Чему отдать предпочтение: ОрепМР или Linda? Сравните данные технологии с различных то­чек зрения.
5.   С использованием системы Linda напишите программу, реализующую сложение элементов вектора по схеме сдваивания.
6.   Можно ли написать автоматический конвертор, преобразующий программы из ОрепМР в систему Linda и наоборот?
7.   Можно ли написать автоматический конвертор, преобразующий программы из MPI в систему Linda и наоборот?
8.   В распоряжении программистов есть, с одной стороны, MPI и ОрепМР, а с другой стороны, компьютеры с общей и распределенной памятью. Какая техно­логия программирования какой архитектуре лучше соответствует?
300
Часть II. Параллельное программирование
9. Чем отличаются процессы в MPI и в системе Linda?
10.   Что означает в MPI посылка и прием сообщения с блокировкой?
11.   Всем MPI-процессам надо обменяться сообщениями "по кольцу". Найдите та­кой способ организации программы, который бы гарантировал отсутствие тупи­ковой ситуации. Как сделать то же самое, но с использованием только блоки­рующих операций MPl_Send и MPl_Recv?
12.   Верно ли, что в коллективных операциях участвуют все процессы приложения?
13.   Могут ли каким-то образом общаться процессы, принадлежащие разным ком­муникаторам?
14.   Верно ли, что никакие два процесса программы не могут иметь одинаковые номера?
15.   Сравните технологии программирования MPI и DVM с различных точек зре­ния.
16.   Какая взаимосвязь между понятиями виртуальной топологии процессов в MPI и сетью в трС? Каково назначение этих конструкций?
17.   Можно ли в MPI-программе учесть возможную неоднородность вычислитель­ной системы?
18.   Почему в MPI нет механизма критических секций?
19.   Почему использование асинхронных операций обмена сообщениями, выпол­няющихся на фоне вычислений, вместо операций с блокировкой необязательно приведет к ускорению программы?
20.   Можно ли в MPI принять сообщение, не зная точно номера посылающего про­цесса?
21.   Известно, что MPI не гарантирует справедливости в распределении приходящих сообщений. Как в схеме мастер/рабочие гарантировать регулярную загрузку и регулярное обслуживание каждого подчиненного процесса?
22.   Как в MPI определить размер буфера, необходимого для приема сообщения?
23.   Какие особенности программно-аппаратной среды компьютера могут быть ис­пользованы системой поддержки MPI для эффективного выполнения функции
MPI_Startall?
24.   Какое выражение нужно поместить на место параметра color функции MPl_Comm_split, чтобы группу из 100 процессов разделить на две подгруппы с номерами от 0 до 40 и от 41 до 99?
25.   Реализуйте с использованием MPI метод Якоби по схеме DVM-программы, приведенной в предыдущем параграфе. Сравните полученную MPI-программу с аналогичной DVM-программой. Какие достоинства есть у каждой из этих про­грамм?
26.   Объясните, в каких случаях вы отдали бы предпочтение именно системе Linda, MPI, OpenMP, DVM, mpC?
Глава 5. Технологии параллельного программирования
301
§ 5.3. Другие языки
и системы программирования
У ученых всегда было много идей, на основе чего можно было бы построить новые системы программирования. Одни идеи отражали особенности архи­тектуры компьютеров, другие опирались на различные разделы математики или теоретического программирования, третьи отталкивались от предметной области решаемых задач. В качестве примеров подобных систем можно на­звать Sisal, Haskell, Cilk, Т-система, НОРМА и др. Систем много, но не надо забывать, что реальное распространение систем всегда определяли три фак­тора: простота программирования, эффективность программ, переносимость программ. Да, задача создания системы программирования, удовлетворяю­щей всем этим требованиям, пока не решена. Но потому мы и решили включить данный параграф в книгу, чтобы показать, насколько интересны­ми и неожиданными могут оказаться идеи, на базе которых можно создавать новые системы. Если возникнет желание работать в данном направлении, то не нужно сразу отказываться от, казалось бы, безумных идей. Возможно, что именно на стыке различных областей и будет найден разумный вариант, удовлетворяющий всем необходимым условиям.
В данном параграфе мы расскажем про две российские разработки: Т-сис-тему и язык НОРМА. Одна предлагает подход к автоматическому динамиче­скому распараллеливанию программ, а другая позволяет программировать практически в терминах математических формул.
Т-система
В этом разделе мы рассмотрим ключевые аспекты технологии автоматиче­ского динамического распараллеливания программ, известной как Т-система [42]. Разработка Т-системы была начата в Институте программных систем РАН (г. Переславль-Залесский) в конце 80-х годов прошлого столетия.
Наиболее характерной чертой Т-системы является использование парадигмы функционального программирования для обеспечения динамического рас­параллеливания программ. На этой основе удалось найти и реализовать в Т-системе интересные формы для организации параллельных вычислений, в частности, для синхронизации или распределения нагрузки. Вместе с тем, функциональный стиль Т-системы удалось удачно совместить с традицион­ными языками программирования с помощью расширений языков С, C++ или языка Fortran. Явные параллельные конструкции в языке отсутствуют, и программист в тексте явно не указывает, какие части программы следует выполнять параллельно.
Базовые принципы Т-системы опираются на результаты общей теории функционального программирования. Для их пояснения можно не вдаваться
302
Часть II. Параллельное программирование
в детали теории, а воспользоваться простой аналогией. Предположим, что у нас есть сложное арифметическое выражение, включающее много подвыра­жений, заключенных в скобки. Все эти подвыражения можно вычислять в любом порядке, и в каждом случае мы получим один и тот же результат (конечно же, рассматривается не машинная, а точная арифметика без оши­бок округления). В теории функционального программирования этот закон арифметики обобщается на произвольные рекурсивные функции.
Такой подход дает прямой метод для распараллеливания функциональных программ, построенных из "чистых" функций. Чистые функции — это одно из базовых понятий Т-системы, обозначающее функции без побочных эффек­тов. В каждый момент времени необходимо выделять готовые к вычисле­нию "подвыражения" и распределять их по имеющимся процессорам. За ос­нову берется граф, узлы которого представляют вызванные функции, а дуги соответствуют отношению "подвыражение—выражение".
Оказывается, что для добавления функциональной семантики в традицион­ный язык программирования достаточно ввести понятие неготового значе­ния. В языке С это достигается введением дополнительного атрибута у опи­сания переменных. Новое ключевое слово tvai в описании "tvai int i" определяет переменную, значение которой может быть целым числом или неготовым значением (пока не посчитанным). Для обозначения функций без побочных эффектов дополнительно используется слово tfun, выход Т-функции обозначается словом tout и т. д.
Рассмотрим простой пример.
tfun void tmain(...) { tvai int x, у, z, b = 1; int w, t, a = 5;
G(a, b, &x, &y, &z); /* результаты принимаем в x, у, z */ b = z; У = a; w = a; t = b+a*w;
}
Предположим, что функция G является чистой и имеет атрибут tfun. Будем исполнять пример "по шагам". Комментарии будут относиться к моменту завершения срабатывания очередного оператора.
1. G(a, b, &х, &у, &z). Поскольку функция g является чистой, то она мо­жет быть выполнена параллельно с основной программой. После выпол­нения этого оператора Т-система на основе данного вызова функции g оформит новую порцию работы, готовую к параллельному исполнению.
Глава 5. Технологии параллельного программирования
303
Вне зависимости от того, будет ли эта порция выполняться немедленно или же по каким-то причинам ее выполнение будет отложено, система перейдет к выполнению следующего оператора. Переменные х, у, z по­лучат статус неготовых переменных и будут содержать неготовые значения.
2.   ь = z. Теперь и переменная ь стала неготовой, а система без какой-либо задержки будет исполнять программу дальше. Этот пример служит иллю­страцией к тому, что неготовые значения можно копировать. Одновре­менно мы изменили состав потребителей результатов функции с. Теперь третий результат должен быть передан как в переменную z, так и в пере­менную ь. Заметим, что неготовность переменной ь в данный момент, конечно же, никак не влияет на то значение ь, которое было использова­но в качестве аргумента функции g.
3.   у = а. Переменная а — обычная, поэтому переменная у из "неготовой" станет "готовой" и получит то значение, которое имела переменная а.
4.   w = а. Этот оператор проблем не вызывает, поскольку в нем использова­ны только обычные переменные.
5.   t = b+a*w. На этом операторе выполнение фрагмента будет заблокирова­но до тех пор, пока функция g не вернет результат в переменную ь. Не­готовые значения можно копировать, что и было сделано во втором опе­раторе, но никаких других операций над ними выполнять нельзя.
Относительно данного фрагмента остался неясным лишь один вопрос. Мо­жет сложиться впечатление, что из-за третьего оператора фрагмент не будет обладать детерминированным поведением. В самом деле, если оператор "у = а" выполнится раньше завершения функции с, то значение перемен­ной у будет определяться функцией g, а если позже, то переменная у полу­чит значение а.
На самом деле детерминированность обеспечена. После выполнения дан­ного оператора значение у всегда будет равно значению а. В начале обра­ботки оператора "у = а" происходит блокировка переменной у. Выполняется проверка значения переменной и, если значение является неготовым, то выставляется отказ от ожидаемого значения и разрыв связи с его поставщи­ком. В нашем случае это отказ от выходного значения функции g. После всех этих проверок и выполнения оператора блокировка с переменной сни­мается. Такая последовательность гарантирует детерминированность пове­дения программы и обеспечивает привычную семантику: после выполнения "у = а" значение у будет равно значению а.
Диалект языка С, полученный добавлением новых ключевых слов, носит название ТС. Новый язык позволяет использовать привычную для многих программистов императивную нотацию для записи программ в функцио­нальном стиле.
304
Часть II. Параллельное программирование
Чтобы ознакомиться с базовыми конструкциями Т-системы и языка ТС, рассмотрим два простых примера: вычисление чисел Фибоначчи и рекур­сивный обход древовидной структуры данных.
Рассмотрим задачу нахождения «-ого числа Фибоначчи. Вычисление опира­ется на определение чисел Фибоначчи:
\1, если п = 1 или п = 2;
"1Л-1+Л-2. "^3-
Текст программы решения этой задачи с использованием Т-системы пока­зан ниже. Номера строк, конечно же, не являются частью Т-системы. Они нам понадобятся в дальнейшем для пояснения различных конструкций программы.
001 iinclude <tc.h> 002
003 #include <stdio.h>
004 #include <stdlib.h> 005
007 unsigned cfib (unsigned _n)
008 {
009   return _n < 2 ? _n : cfib(_n - 1) + cfib(_n - 2);
010 } 011
012 tfun void fib (unsigned _n, tout unsigned *_res)
013 {
014   if (_n < 32) {
015     *_res = cfib(_n);
016   } else {
017     tval unsigned resl, res2;
018     fib(_n - 1, &resl);
019     fib(_n - 2, &res2);
020     *_res = resl + res2;
021   }
022 } 023
024 int tmain (int argc, char* argv[])
025 {
026   unsigned n;
027   tval unsigned res;
028   if (argc < 2) {
Глава 5. Технологии параллельного программирования
305
029            terrprint(
030                 "Usage:\n"
031       "gr_fib <number>\n"
032     ) ;
033     return - 1;
034   }
035   n = (unsigned)atoi(argv[l]);
036   fib(n, &res);
037   (unsigned)res;
038   terrprint("fib(%u) = %u\n", n, res);
039       return 0;
040    }
В программе с помощью ключевого слова tvai определены переменные resi, res2 и res, способные хранить как обычное, так и неготовое значение. В тот момент, когда их адреса передаются в Т-функцию fib, а это вызовы fib в строках 18, 19 и 36, выполнение вызывающей функции не останавли­вается, и она продолжает работать дальше. Поскольку второй формальный параметр Т-функции fib имеет описание tout, то в моменты вызовов соот­ветствующая переменная resi, res2 и res становится "неготовой". Она бу­дет содержать то самое специальное неготовое значение. Когда вызванная функция fib вычислит результат и вернет его в переменную (строка 15 или 20), это значение будет заменено обычным "готовым" значением.
Обращение к неготовым переменным за их значением блокирует процесс вычисления функции. Например, такое может произойти в строке 20, где стоит обращение к переменным resi и res2. В строках 18 и 19 не требуется знания значения переменных resi и res2, поэтому оба вызова Т-функций fib будут выполнены без блокировки, и их вычисление может происходить параллельно.
Порожденные в процессе работы программы вызовы Т-функций fib (строки 18, 19 и 36) формируют независимые порции работы (гранулы па­раллелизма), готовые к вычислению. Все порции помещаются в очередь, откуда вычислительные процессы-исполнители получают для себя работу. Т-функции в тексте программы указывают шаблоны потенциальных парал­лельных фрагментов кода. Реальные параллельные фрагменты появляются и обрабатываются Т-системой во время работы программы, как результат ре­альных вызовов Т-функций.
Условный оператор, стоящий в строке 14, выполняет две задачи. С одной стороны, нужно в какой-то момент времени остановить образование новых порций работы и начать формировать окончательный результат. В этом смысле здесь полная аналогия с обычными рекурсивными функциями. С другой стороны, все формируемые порции работы должны быть достаточ-
306
Часть II. Параллельное программирование
но весомыми. Нужно скрыть накладные расходы, вызываемые передачей очередной порции какому-либо процессу, что можно сделать через увеличе­ние объема вычислений. Из этих соображений и появилась константа 32 в данной строке.
Ясно, что неготовые переменные в Т-системе являются основным средством синхронизации. Существенно различаются ситуации обращения к негото­вым переменным на чтение (доступ за значением) и на запись (доступ для присваивания):
□  при чтении неготовой переменной происходит блокировка процесса вы­числения, выполнившего такое обращение; его ожидание продлится до тех пор, пока переменная не получит готового значения; исключение со­ставляют лишь операции копирования (присваивания) неготовых пере­менных, при выполнении которых блокировки не возникает;
□  при записи обычного значения в неготовую переменную она становится готовой для всех своих потребителей, а ранее заблокированные на дан­ной переменной процессы выходят из состояния блокировки.
Теперь рассмотрим программу рекурсивного обхода дерева, которая проил­люстрирует работу с удаленными указателями.
001 iinclude <tc.h> 002
003 #include <stdio.h>
004 #include <stdlib.h> 005
006 struct tree {
007  struct tree tptr left;
008  struct tree tptr right;
009  int value;
010 }; 011
012 struct tree tptr create_tree(int deep) {
013   struct tree tptr res = tnew(struct tree);
014   res->value = 1;
015   if (deep <= 1) {
016     res ->left = NULL;
017     res ->right = NULL;
018   } else {
019     res->left = create_tree(deep - 1);
020     res ->right = create_tree(deep-1);
021   }
022   tvalidate(*res);
Глава 5. Технологии параллельного программирования
307
023   return res;
024  }
025 tfun void tsum(const struct tree tptr _tree,
026                 tout int *_res) {
027   tval int left_sum, right_sum; 028
029   if (_tree->left != NULL) {
030      tsum(_tree->left, &left_sum);
031    } else {
032      left_sum = _tree->value;
033    }
034    if (_tree->right != NULL) {
035     tsum(_tree->right, &right_sum);
036    } else {
037      right_sum = _tree->value;
038    }
039   *_res = left_sum + right_sum;
040  } 041
042 int tmain (int argc, char* argv[])
043  {
044    struct tree tptr tree = create_tree(12);
045   tval int sum;
046   tsum(tree, &sum) ;
047   terrprint("sum = %u\n", sum); 04 8       return 0;
049   }
В этой программе дерево структур с типом tree сначала порождается функ­цией create_tree, а затем обходится в рекурсивной функции tsum. Как и в предыдущем примере, происходит распараллеливание в каждом узле дерева: сначала порождаются вызовы для левой и правой ветви, и лишь затем про­исходит обращение к результатам обхода.
Ключевое слово tptr служит для описания глобальных ссылок на неготовые переменные. При операции чтения данных по Т-указателю может происхо­дить ожидание готовности значения и его подгрузка из узла вычислительной системы, в котором расположено значение, в узел, где произошло обраще­ние по Т-указателю. При операции записи значение передается в нужный узел системы, и там значение записывается в соответствующую Т-пере-менную, делая ее готовой.
308
Часть II. Параллельное программирование
Практика использования Т-системы рекомендует следующую последова­тельность шагов в процессе разработки программ на языке ТС.
1.   Разработка дизайна кода. На этом этапе решается вопрос о том, какие фрагменты алгоритма будут реализованы на языке ТС в виде Т-функций, а какие реализованы в виде привычного последовательно исполняемого кода на стандартных языках последовательного программирования С, C++ или Fortran.
2.   Реализация и первичная отладка на однопроцессорном компьютере. Разрабо­танная ТС-программа отлаживается на обычном однопроцессорном ком­пьютере в последовательном режиме без использования Т-системы. Для этого все новые ключевые слова, добавленные в язык С, автоматически переопределяются с помощью соответствующих макросов (например: tptr определятеся как "*"; tvai — как "пусто" и т. п.).
3.   Отладка на многопроцессорных установках. После отладки ТС-программы на однопроцессорном компьютере в последовательном режиме следует полнофункциональная отладка в параллельной вычислительной среде.
4.   Оптимизация. Последний этап, на котором с помощью трассировки, профилировки и других средств производится оптимизация программы, характерен для любой технологии параллельного программирования.
Ясно, что по сравнению с другими системами параллельного программиро­вания преимущества Т-системы наиболее отчетливо проявляются в двух си­туациях. Первая ситуация характерна тем, что до выполнения программы нет точной информации о том, как сбалансировать работу параллельных процессов. Вторая ситуация возникает тогда, когда вычислительная схема программы близка к функциональной модели и может быть эффективно представлена с помощью совокупности функций, рекурсивно вызывающих друг друга. Сильной стороной Т-системы является и то, что она в значи­тельной степени освобождает программиста от многих традиционных забот, характерных для параллельного программирования. Явная организация па­раллельных фрагментов программы, их распределение по узлам кластера, синхронизация работы фрагментов, явные операции обмена данными между ними — все это Т-система берет на себя.
Но, как обычно, недостатки являются продолжением достоинств. Существу­ет масса подводных камней, о которых явно не говорится, но которые нуж­но четко представлять. Типичный пример — это вызов Т-функции, кото­рый может вызвать пересылку данных из одного узла кластера в другой. Внешне ничего подозрительного нет, а накладные расходы могут оказаться очень большими. Если этот фактор на этапе проектирования программы не учесть, то с ее эффективностью могут возникнуть большие проблемы. Балансировка вычислительной нагрузки лежит на системе, но гарантии оптимальности она не дает. Опять-таки, при реализации программ для Т-системы программист обязан изложить алгоритм в функциональном сти-
Глава 5. Технологии параллельного программирования
309
ле и описать программу в виде набора чистых Т-функций. Это нельзя на­звать неудобным, но это, по крайней мере, непривычно. На программисте лежит и выбор оптимального размера потенциально параллельных фрагмен­тов. Очень маленькая вычислительная сложность Т-функций может привес­ти к чрезмерным накладным расходам. Слишком большая вычислительная сложность может привести к малому количеству порождаемых в процессе параллельных фрагментов и, как следствие, к неравномерной загрузке вы­числительных узлов системы.
Система программирования НОРМА
Язык НОРМА является специализированным непроцедурным языком, предназначенным для спецификации задач вычислительного характера, в частности, задач математической физики. Все конструкции языка носят декларативный характер и описывают правила вычисления значений. Ос­новное назначение языка состоит в автоматизации процесса разработки программ. Программист работает "почти" в терминах математических фор­мул, что значительно упрощает его работу. Задача транслятора усложняется. Помимо традиционных задач, в частности, синтаксического и семантиче­ского анализа, он выполняет синтез выходной программы.
Идеи, позволяющие автоматически строить программу по спецификации задачи, были сформулированы И. Б. Задыхайло в работе [29] еще в 1963 го­ду. Дальнейшее их развитие привело к появлению языка НОРМА и не­скольких версий транслятора для различных платформ.
Первоначально термин НОРМА расшифровывался как Непроцедурное Опи­сание Разностных Моделей Алгоритмов. В последствии появилась и другая трактовка: НОРМАльный уровень общения прикладного математика с ком­пьютером. Разработчик прикладных программ абстрагируется от особенно­стей конкретных компьютеров и мыслит в привычных терминах своей предметной области. Отталкиваясь от конкретных потребностей Института прикладной математики им. М. В. Келдыша РАН, авторы языка старались максимально упростить решение класса задач математической физики. Спе­цифика предметной области — это ориентация на сеточные методы. Именно этот факт наложил значительный отпечаток как на концепцию языка, так и на все его основные конструкции.
В записи программы на языке НОРМА не требуется никакой информации о порядке выполнения операций. Порядок предложений языка может быть про­извольным. Язык позволяет формулировать запрос на вычисления, не уточняя, каким именно образом вычисления следует организовать. Все информацион­ные связи выявляются и учитываются транслятором-синтезатором на этапах анализа исходной программы и синтеза выходного текста. На трансляторе ле­жит и выбор конкретного способа организации вычислений. В частности, на этапе синтеза результирующей программы он может сгенерировать как после­довательный, так и параллельный код.
310
Часть II. Параллельное программирование
Выбор высокого уровня языка НОРМА определяет его характерную черту — это язык с однократным присваиванием. Каждая переменная может принимать значение только один раз. Такие понятия, как память, побочный эффект, оператор присваивания и управляющие операторы в языке НОРМА отсутст­вуют просто "по определению". Во всех традиционных языках программиро­вания эти понятия есть, поскольку с их помощью нужно формулировать кон­кретный алгоритм с учетом вопросов экономии и распределения памяти, порядка выполнения операторов и т. п. Запись на языке НОРМА, по сущест­ву, является записью численного метода решения конкретной задачи.
Важно и то, что в записи на языке НОРМА отсутствуют избыточные ин­формационные связи. Такие связи обычно накладываются в процессе про­граммирования, причем особенно отчетливо это проявляется при оптимиза­ции алгоритмов и программ. Мы уже имели возможность убедиться, что определение точной информационной структуры программ является слож­ной задачей. НОРМА позволяет опустить этап последовательного програм­мирования, а для генерации параллельной программы предлагает сразу от­талкиваться от записи в терминах математических формул. С этой точки зрения было бы очень интересно исследовать потенциал языка НОРМА для программирования dataflow-компьютеров.
Полученные результаты были использованы авторами при создании реаль­ного транслятора-синтезатора. По описанию на языке НОРМА он позволяет получать выходные программы как для последовательных компьютеров, так и для параллельных вычислительных систем с распределенной и общей па­мятью. Выходные программы могут быть записаны на языках Fortran MPI, Fortran PVM, Fortran 77 и других диалектах Fortran.
Описание языка мы дадим по рабочему стандарту [2] и данным сайта http://www.keldysh.ru/norma.
Программа на языке НОРМА состоит из одного или нескольких разделов. Разделы могут быть трех видов — главный раздел, простой раздел и раздел-функция. Вид раздела определяется ключевыми словами main part, part и function соответственно. Разделы могут вызывать друг друга по имени и передавать данные при помощи механизма формальных и фактических па­раметров, либо через внешние файлы при помощи описаний input и output. Рекурсивные вызовы разделов запрещены.
Все имена, описанные в разделе, локализованы в этом разделе. Понятие глобальных переменных в языке НОРМА отсутствует.
Параметры, указанные в списке формальных параметров до ключевого сло­ва result, являются исходными данными. Все параметры, перечисленные после данного ключевого слова, являются результатами вычислений. Один и тот же параметр не может одновременно быть исходным и результатом: это приводит к повторному присваиванию значений переменным, что в языке
Глава 5. Технологии параллельного программирования
311
НОРМА запрещено. В разделе-функции ключевое слово result не исполь­зуется, поскольку результат вычисления функции связывается с именем и типом функции.
В теле раздела могут быть заданы описания, операторы и итерации. Порядок их расположения, вообще говоря, произвольный — возможные ограничения определяются при описании входного языка транслятора.
Базовым понятием языка НОРМА является область. Область — это сово­купность целочисленных наборов вида
{/ь ..., /„}, п > 0, /}•> 0,у'= 1...Л,
задающих координаты точек в «-мерном индексном пространстве. С каждой осью координат «-мерного пространства задачи связывается имя индекса.
Ключевым понятием при описании областей является понятие одномерной области. Одномерная область служит для задания диапазона точек на неко­торой оси координат индексного пространства. В простейшем случае при описании одномерной области указывается имя одномерной области, имя индекса и границы изменения значений индекса. Например, следующие два описания языка НОРМА
ks : (к = 1..п). js : (j = 1..10) .
вводят две одномерные области. В первом случае верхняя граница задана с помощью параметра п (значения всех параметров должны быть описаны в разделе описания параметров). Во втором случае размерность указана явно. Границами диапазона являются целые положительные константные выра­жения, построенные из целых констант, параметров области и арифметиче­ских операций.
Многомерная область строится при помощи операции произведения областей, обозначаемой знаком ",-". Пример описания двумерной области, полученной с помощью операции произведения одномерных областей DirK и DirL, по­казан ниже:
Square: (DirK: (k=1..15) ; DirL: (1=1..5))
Введенная прямоугольная область square является подмножеством двумер­ного индексного пространства. В нее входят точки, у которых первая коор­дината имеет значение от 1 до 15, а вторая — от 1 до 5.
Операция ",-" произведения прямоугольных областей А к В обладает свойст­вом коммутативности, т. е. А ; В = В ; А. Это, в частности, означает, что по­рядок направлений индексного пространства при описании области не фик­сируется. Если порядок направлений индексного пространства необходимо зафиксировать, то задаются описания индексов областей с помощью ключе­вого слова INDEX.
312
Часть II. Параллельное программирование
Необязательно все области описывать явно, можно воспользоваться уже введенными именами. Так, описание
NewGrid: (ks;j s).
определяет область через определенные выше одномерные области ks и js.
Введенные области можно изменять. Модификация областей может состоять в добавлении некоторого числа точек, в удалении точек или изменении диа­пазона. Модификация двух первых типов описывается при помощи функ­ций границ left (п) и right (п). Функция left применяется к левой границе диапазона, а функция right — к правой границе диапазона. Знак "+" перед функцией означает, что к одномерной области добавляются точки, знак "-" — что из одномерной области удаляются точки. Обе эти функции имеют лишь один параметр п, определяющий число точек, которые необходимо удалить или добавить к области. В качестве фактического параметра функ­ций left и right может быть задана только целая положительная константа. Обращение к функциям допустимо лишь в контексте с именем одномерной области, задающей модифицируемый диапазон. Предположим, что в про­грамме было описание:
DirK:(k=l..15)
Тогда область DirKm, состоящую из точек к=з..18, можно задать следую­щим образом:
DirKm: DirK-LEFT(2)+RIGHT(3)
Можно изменить составляющую одномерную область путем явного переоп­ределения диапазона. Для этого в модификации надо указать имя индекса и его новое значение. Предположим, что в программе было описание:
Square: (DirK:(k=l..15); DirL: (1=1..9) )
Для модификации сделанного описания и определения области, в которой один индекс будет по-прежнему меняться от 1 до 15, а другой уже от 10 до 22, можно воспользоваться такой конструкцией:
SquareNew: Square/l=10..22
Возможна модификация области при помощи простых соотношений, связы­вающих индексы области. Например, описания
KL: ((к=1..10);(1=1..10)). Diagonal: KL/k=l
задают область Diagonal, СОСТОЯЩУЮ ИЗ точек (k=l,l=l), (k=2,l=2), (k=3,l=3) , ..., (k=10,l=10) .
Следует отметить, что область определяет только значения координат точек индексного пространства, а не значения расчетных величин в этих точках. Если требуется вычислить значения величины Yy, i,j= 1...Я на некоторой
Глава 5. Технологии параллельного программирования
313
сетке Ху, i,j = l...n, введенной при решении задачи формулой Ху = F(i,j), то следует выполнить следующую последовательность шагов:
1.   Описать область, состоящую из точек i, j=i.. .п.
2.   Описать на этой области величины х и у.
3.   Задать на этой области правило вычисления значений сетки Ху = F(i, j) и правило вычисления значений Yy= G{Xy).
Помимо явного описания областей, в языке НОРМА предусмотрена воз­можность задания условных областей. Входят или не входят точки индекс­ного пространства в условную область определяется тем, выполняется или не выполняется некоторое условие. Идея задания условной области проста. Ранее определенная область D разбивается на две непересекающиеся подоб­ласти D\ и В2. Первая подобласть состоит из точек области D, в которых заданное условие принимает значение истина, а вторая подобласть из точек, в которых условие принимает значение ложь. Возьмем предыдущее описа­ние области square и на его основе зададим условные области:
Squarel, Square2: Square/ x[k,1] - y[k,l]<eps
Данное описание задает разбиение исходной области square на две подоб­ласти Squarel И Square2, причем Squarel П Square2=0, а SquarelUSquare2=Square. Подобласть Squarel СОСТОИТ ИЗ тех точек Square,
для которых условие х [к, 1] - у [к, l] <eps принимает значение истина, а по­добласть square2 из точек, в которых условие принимает значение ложь.
В языке определены два класса величин: скалярные величины (скаляры) и величины на области. Описание ставит в соответствие каждой величине уни­кальное в текущем разделе имя, а также задает тип величины: real, integer или double. Пример описания скаляров может быть таким:
VARIABLE kl,k2 INTEGER VARIABLE pi DOUBLE
Каждая величина на области связывается с указанной в описании областью. Эта область определяет имена индексов, которые могут использоваться в индексных выражениях при обращении к данной величине. Порядок указа­ния индексных выражений не является существенным. Для индексов не требуется специального описания, т. к. они вводятся при описании областей.
Опять воспользуемся прежним описанием области square и введем на его основе величины на области:
Square: (DirK:(k=l..15); DirL: (1=1..9) )
VARIABLE Xsum, Y DEFINED ON Square,
OneDimK DEFINED ON DirK, OneDimL DEFINED ON DirL
Приведенные описания определяют величины xsum и y на области square. Обе эти величины могут иметь в индексных выражениях индексы к и l. Be-
314
Часть II. Параллельное программирование
личины oneDimK и oneDimL определены на областях DirK и DirL, поэтому и в индексных выражениях они могут иметь индексы к и 1 соответственно.
Поскольку порядок направлений индексного пространства при описании области не фиксируется, то обращения y [ к - 1,1+1] иу[1+1,к - 1] экви­валентны. Кроме того, при обращении к величине на области действует правило задания индексов по умолчанию: индексные выражения, совпа­дающие с именем индекса, могут быть опущены. Именно поэтому все об­ращения xsum[k,i], xsum[k], Xsum[i] и Xsum к величине Xsum эквивалент­ны. Если необходимо одно индексное направление связать с другим, то это нужно делать явно. Например, диагональные элементы матрицы y можно определить как y [ k, i=к ] или просто y [ 1=к ].
В языке НОРМА определены лишь три вида операторов: скалярный опера­тор, оператор assume и вызов раздела.
Скалярный оператор предназначен для вычисления арифметических значений скаляров. По существу, это аналог оператора присваивания в традиционных языках программирования, в левой части которого указывается имя скаляра, а в правой части — скалярное арифметическое выражение. Например:
s = 234
рр = 12+x+SQRT(s+8)
Переприсваивание в скалярном операторе запрещено, поэтому оператор ви­да к = к - 1 по определению является неверным.
Оператор assume используется для вычисления арифметических значений величин, определенных на областях. Семантика этого оператора определяется следующим образом. Рассмотрим соотношение, записанное в условном виде так:
FOR D(ii, ..., /„) ASSUME
yl                       — J7\ V j\                                    V h                           (1th pr
^lndexL(,\...,im) T\AbviexR*(ih,...J„y •"' Index* «(<',,..., /„)' '
где:
□  D(i\, —, in) — область оператора assume;
□  (/'i, ..., /„) — индексы области d;
□   XJq ,l<q <k — имена величин, определенных на области;
□   IndexZ (ip), 1 < р < п — индексные выражения левой части;
□   lndexRJg(i\, ..., i„) — индексные выражения правой части;
□  F — функция, вычисляемая в правой части;
□   Other — это другие термы правой части.
\
Глава 5. Технологии параллельного программирования
315
Каждое такое соотношение задает правило F вычисления значений величи­ны X из левой части по значениям величин XЛ ,..., Xh и термов Other из правой части. Происходит это следующим образом:
□  определяются все точки (щ, ..., an) е D(i\, ..., /„);
□  для каждой точки (д]5 ..., a„) е В требуется вычислить значение величины X из левой части в точке IndexZ(c1, ..., а„);
П для каждой точки (а\, ..., an) е D вычисляются значения индексных вы­ражений h\.dsxRJ,(a\, ..., а„) всех величин Xlq, \<q<k, входящих в пра­вую часть соотношения, и определяется множество аргументов правой части:
□  если в некоторый момент времени для некоторой точки (щ, ..., а„) е В все аргументы из X вычислены, то возможно вычисление значения вели­чины Х^&хКа , а )из левой части; если не все аргументы определены,
то вычисление данной точки в данный момент невозможно (но это не означает, что оно вообще невозможно).
Данный оператор однозначно определяет правило для вычисления значений величины. При этом он не требует немедленного выполнения вычисления в данном месте программы и не задает порядка или способа вычисления. Оператор не содержит никакой информации о последовательном или па­раллельном исполнении. Рассмотрим уже знакомые нам описания:
Square: (DirK:(k=l..15); DirL: (1=1..9) ) VARIABLE Xsum, Y DEFINED ON Square,
Оператор вида
FOR Square ASSUME Xsum = 0
задает обнуление 135 элементов величины xsum. Способ реализации этого за­проса в языке НОРМА не фиксируется. Поскольку НОРМА является языком однократного присваивания, то следующий оператор является некорректным:
FOR Square ASSUME Xsum = Xsum+Y
По сути, основную идею языка НОРМА мы описали. Незатронутыми оста­лись разделы языка, связанные с заданием режима последовательного вы­числения, детали вызовов разделов программы и обращений к функциям, интерфейс с языком Fortran и специальная конструкция iteration, позво­ляющая задать итеративный вычислительный процесс. На понимание общей концепции языка они не влияют, а при необходимости читатель всегда мо­жет найти недостающие подробности в [2].
316
Часть II. Параллельное программирование
Рассмотрим пример программы, записанной на языке НОРМА для решения системы линейных уравнений методом Гаусса—Жордана. Расчетные форму­лы метода можно записать в следующем виде:
m0,ij
= °tj
%i =
h
mW
= mt-\
,ij/mt-\,t,t
rt,t =
rt.ht/mt.htj
mW
= mt-\
,ij- Щ-\,1
<?x
mW
rt,i =
n-\,i-
-mt-\,i,t*
rV
Xj= ,
rN,i
i=l...N j=l...N
i= 1...N
j=l...N        t=l...N
t= 1...N
j = 1...N        i = 1..J -l,t+ 1...N t = 1...N
t=\...N        i= 1..J- 1, t+ 1...N
i= 1...N
Текст программы приведен ниже. Нумерация строк не является элементом языка, а используется лишь для последующего объяснения смысла операто­ров.
1       MAIN PART Gauss.
2       BEGIN
3           INDEX k,l.
4           ks : (k=l..n).
5           Is : (1=1..n).
6           kls : (ks; Is) .
7           VARIABLE a DEFINED ON kls.
8           VARIABLE b, x DEFINED ON ks.
9           INPUT a(FILE='gauss') ON kls,
10                        b(FILE='gauss') ON ks.
11           DOMAIN PARAMETERS n=4.
12           COMPUTE Calculate(a ON kls, b ON ks RESULT x ON ks).
13           OUTPUT x(FILE='gauss.out',TAB(10),'Решение:?,STR(1)) ON ks.
14       END PART.
15       PART Calculate.
16           a,b RESULT x
! Раздел вычислений
17       BEGIN
18            INDEX i,j
19           so : (ijs: (is: (i=l..n); js: (j=l..n));ts: (t=0..n)).
20           slo:(ts;is). s:so/ts-LEFT(1). si:slo/t=l..n.
21           VARIABLE a DEFINED ON ijs.
22           VARIABLE b, x DEFINED ON is.
23           VARIABLE m DEFINED ON SO.
Глава 5. Технологии параллельного программирования
317
24           VARIABLE г DEFINED ON slo.
25           DOMAIN PARAMETERS n=4.
26           DISTRIBUTION INDEX i=2..8, j=l.
27            FOR so/t=0 ASSUME m=a.
28           FOR slo/t=0 ASSUME r=b.
29           sa,sb:s/i=t. sal,sbl:sl/i=t.
30           MACRO INDEX ti [t - l,i=t].
31           MACRO INDEX tij [ti,j=t].
32           FOR sa ASSUME m = m[ti] / m[tij].
33           FOR sal ASSUME r = r[ti] / m[tij].
34            FOR sb ASSUME m = m[t - 1] - m[t - 1,j=t] * m[i=t].
35           FOR sbl ASSUME r = r[t - 1] - m[t - l,j=t] * r[i=t].
36           FOR is ASSUME x = r[t=n].
37                  END PART
Прокомментируем операторы программы. Используемые ниже номера сов­падают с нумерацией строк программы.
(1)          Заголовок главного раздела программы Gauss.
(2)          Начало главного раздела.
(3)          Описание индексов областей.
(4—6) Определение одномерных областей is, js и многомерной области ijs, на которых определены начальные данные задачи.
(7—8) Описание переменных а, ь и х, где а — матрица коэффициентов системы, ь — столбец свободных членов, х — результат решения системы.
(9—10) Ввод начальных данных из файла gauss.dat.
(11)        Задание параметра областей.
(12)        Вызов раздела calculate, осуществляющего расчет.
(13)        Вывод результата в файл gauss.out.
(14)        Конец главного раздела.
(15)        Заголовок простого раздела calculate.
(16)        Обозначение входных (а, Ь) и выходных (х) параметров раздела.
(17)        Начало простого раздела.
(18)        Описание индексов областей.
(19)        Определяется многомерная область so, состоящая из областей ts и ijs. Область ijs, в свою очередь, состоит из is и js.
318                                                                      Часть II. Параллельное программирование
(20)         Определяются безусловная область sio и области s, si с помощью
операций модификации области.
(21—22)  Описание переменных, являющихся параметрами раздела.
(23—24)  Описание переменных m и г, участвующих в расчете.
(25)          Задание размерности областей.
(26)          Описание индексов распределения. (27—28)  Задание начальных значений m и г.
(29)         Описание вспомогательных условных областей sa, sb, sal, sbi.
(30—31) Описания индексных конструкций, которые служит для сокраще­ния записи сложных индексных выражений.
(32—36) Вычисления для соответствующих расчетных формул.
(37)         Конец раздела.
В чем особенность данной работы и данного направления в целом? В 1990 году вышла в свет работа А. Н. Андрианова, К. Н. Ефимкина, И. Б. Задыхайло "Непроцедурный язык НОРМА и методы его реализации", обобщившая некоторые предыдущие работы авторов. Об этой работе упо­минается в [2]. Внешне это выглядело как появление еще одного языка, направленного на создание пользовательской среды, удобной для разработ­ки прикладных программ. Однако в действительности данная работа прин­ципиально отличалась от большинства других работ. В ней впервые было ясно сказано, что в основу языка программирования для параллельных ком­пьютеров может быть положено описание математических соотношений без указания в нем какого-либо параллелизма или сведений об архитектуре вы­числительных машин. Это позволяет снять целый ряд трудностей. Матема­тические соотношения как язык описания заданий наиболее близки разра­ботчикам численных методов и прикладным программистам. Сохраняется весь внутренний параллелизм реализуемого алгоритма. Математические со­отношения не используют понятие памяти, в них отсутствует пересчет зна­чений переменных. Для таких языков существующая технология определе­ния параллелизма в программах реализуется значительно проще.
Перспективное направление, на котором в дальнейшем будет получено еще немало интересных результатов.
Вопросы и задания
1.   Приведите примеры алгоритмов, для которых использование Т-системы было бы вполне естественным.
2.   Приведите примеры алгоритмов, структура которых плохо соответствует "духу" Т-системы.
Глава 5. Технологии параллельного программирования
319
3.   Опишите, что должен учитывать и как должен быть устроен анализатор, опреде­ляющий "чистоту" функций? Рассмотрите случаи языков Fortran и С отдельно.
4.   Напишите программу нахождения определенного интеграла методом прямо­угольников с использованием Т-системы. Как будет выглядеть эта же программа в терминах MPI, OpenMP, DVM, mpC, Linda, языка НОРМА?
5.   Проведите численные эксперименты на любой доступной вам параллельной системе с указанными в предыдущем вопросе программами. Проведите анализ полученных результатов.
6.   Какие операции допустимы над неготовыми переменными в Т-системе?
7.   Как реализовать Т-систему на базе системы Linda?
8.   Как вы понимаете словосочетание "функциональное программирование"?
9.   Что означают термины "декларативный язык", "императивный язык", "непро­цедурный язык"?
10.   Для каких задач язык НОРМА подходит хорошо, а для каких его использование не будет выглядеть естественным?
11.   Почему в программе на языке НОРМА не может быть побочных эффектов?
12.   Какие проблемы возникнут на пути автоматического конвертирования операто­ров assume языка НОРМА в запись на МР1?
13.   Напишите программу перемножения двух матриц с использованием языка НОРМА
14.   В чем преимущество записи алгоритма с помощью математических формул пе­ред записью, скажем, на языке Fortran для исследования параллельных свойств этого алгоритма?
15.   **Напишите систему, которая по фрагменту на языке С (Fortran) восстанавлива­ет заложенные в него математические соотношения.
Глава 6
Тонкая информационная структура программ
Всегда не хватает времени, чтобы
выполнить работу как надо, но на то,
чтобы ее переделать, время находится.
Из законов Мерфи
Данная глава является достаточно трудной для чтения. Тем не менее, мы посчитали необходимым включить ее в эту книгу. Несколько мотивов стали побудительными для такого решения.
Как свидетельствует история, развитие и использование вычислительной техники постоянно сопровождается одним малоприятным явлением, дер­жащим в напряжении пользователей компьютеров, в особенности наиболее активную их часть. Меняются языки программирования, совершенствуются пользовательские среды. Но каждый раз, как только появляются компьюте­ры нового типа, приходится модернизировать прикладное программное обеспечение. Иногда изменения незначительны, но чаще всего они оказы­ваются радикальными. Несмотря на оптимистические заверения разработ­чиков новых языков программирования, в настоящее время нельзя быть уверенным, что программы, написанные на любом из них, будут широко востребованы хотя бы в течение ближайших 3—5 лет. Поэтому необходимо признать тот факт, что еще долгое время пользователи будут вынуждены видоизменять свои программы, подстраивая их под особенности компьюте­ров. Это обстоятельство заставляет думать о том, как облегчить такой труд. Но прежде всего нужно понять, в каком же направлении следует думать.
Начиная с 60-х годов прошлого столетия увеличение быстродействия ком­пьютеров определяется не столько уменьшением тактового периода эле­ментной базы, сколько принятием новых решений в архитектуре вычисли­тельных систем. При этом ускорение вычислений достигается за счет отображения характерных особенностей программ и алгоритмов в аппарату­ре. Уже на раннем этапе были подмечены локальность счета и частое ис­пользование лишь небольшого числа данных, выделены основные элементы программ — циклы, ветвления, процедуры и т. п. Это сразу нашло отраже­ние в буферах команд и регистрах, кэш-памяти, аппаратурной организации стека и во многом другом. Одновременно решалась и задача выравнива­ния скоростей работы различных узлов вычислительных систем (вспомните 1-й закон Амдала как одно из следствий утверждения 2.4. Одни аппаратур-
Глава 6. Тонкая информационная структура программ
321
ные решения практически не требовали программной поддержки (кэш­память), другим необходима динамическая поддержка (виртуальная память), а третьим было достаточно предварительного распределения работы на эта­пе компиляции после проведения статического анализа программ (прямо адресуемые регистры). Во всем этом проявлялась вполне определенная тен­денция: характерные особенности вычислений рано или поздно находили свое отражение в архитектурных особенностях компьютеров, а дополни­тельные аппаратурные возможности стимулировали развитие новых алго­ритмов. В конечном счете задачи решались более эффективно.
Совсем не просто выделить характерные особенности вычислений и дока­зать, что они требуют аппаратурной поддержки. Не всегда легко оценить и аппаратурные новшества. В связи с этим приведем два примера. В § 4.4 бы­ло рассмотрено решение систем линейных алгебраических уравнений с ле­вой треугольной матрицей методом обратной подстановки с порядком сум­мирования по возрастанию и убыванию индексов. Напомним, что оба алгоритма имели принципиальные различия в свойствах параллельности вычислений. Однако будет совсем не правильно считать характерным свой­ством проявления параллелизма именно суммирование в порядке возраста­ния индексов. Ведь если рассмотреть аналогичную задачу с правой тре­угольной матрицей, то все будет наоборот. Другой пример. В 1953 г. была введена в эксплуатацию отечественная вычислительная машина "Стрела", созданная под руководством известных конструкторов Ю. Я. Базилевского и Б. И. Рамеева. Наряду с обычными машинными командами она имела и так называемые групповые команды. Они позволяли достаточно просто записы­вать группы операций над содержимым ячеек памяти, номера которых представляли арифметические прогрессии. Исполнялись групповые коман­ды в среднем несколько быстрее, чем те же группы команд, оформленные программно. Однако это ускорение достигалось не за счет использования конвейерности вычислений, а за счет аппаратурной поддержки организации соответствующих циклов. Если бы в то время математики смогли разглядеть в групповых операциях то, что теперь называется векторными операциями, и доказать, что они отражают характерные особенности вычислений, вполне возможно, что первые векторные компьютеры появились бы значительно раньше. Не говоря уже о том, что значительно раньше началось бы внедре­ние идей параллелизма и конвейерности в разработку новых численных ме­тодов. Но групповые операции не получили тогда должного развития. Время было упущено.
В период безраздельного господства однопроцессорных компьютеров инте­рес к выявлению особенностей вычислительных процессов со стороны как инженеров, так и математиков был исключительно эпизодическим и не ка­сался фундаментальных основ строения алгоритмов и программ. Все изме­нилось с появлением вычислительных систем параллельной архитектуры. Эти системы потребовали совсем иной организации вычислений, обобщен-
322
Часть II. Параллельное программирование
но называемой теперь "параллельными вычислениями". Очень скоро стало ясно, что имеющихся знаний недостаточно для обеспечения эффективного функционирования новой техники. Этому были свои причины.
В течение нескольких десятилетий концепция однопроцессорных компью­теров обращала внимание разработчиков алгоритмов и программ только на две характеристики, связанные с вычислительной техникой, — это общее число операций и объем требуемой памяти. Даже такой важный фактор, как влияние ошибок округления, чаще всего в конкретных разработках выпадал из сферы внимания. Что же касается структурных свойств алгоритмов, на­пример, таких как модульность, то их изучение так и не вышло из зачаточ­ного состояния. Все это в конечном счете привело к тому, что к моменту широкого внедрения параллельных вычислительных систем в практику ре­шения больших задач вычислительная математика оказалась без нужного багажа знаний. Без нужного багажа знаний оказались и смежные науки, в частности, связанные с разработкой алгоритмических языков, компиляторов и архитектуры вычислительных систем.
Можно возражать против таких выводов. Но, подбирая аргументы, давайте перечислять не только достижения. Давайте задавать вопросы, на которые не можем найти ответы. Попробуем докопаться до причин, мешающих их получить. И тогда станет ясно, как мало мы знаем в области параллельных вычислений.
О том, что в этой области действительно имеются серьезные проблемы, го­ворят и такие факты. Количество работ, посвященных распараллеливанию, на текущий момент исчисляется тысячами. Однако до сих пор не так уже много ясности в том, что же реально сделано, как это можно использовать, где узкие места и что надо делать дальше. Конечно, создано программное обеспечение для конкретных параллельных систем. Однако появление но­вых систем снова приводит к необходимости пересматривать методы и программы. Созданы компиляторы для новой техники. Но по общему при­знанию работают они не очень эффективно и полученные с их помощью программы не позволяют полностью использовать возможности параллель­ных систем.
Некоторую завесу над причиной многочисленных трудностей приоткрывает работа [67]. В ней на большом фактическом материале авторы проанализи­ровали методы выявления параллелизма, заложенные в компиляторы для параллельных вычислительных систем начала 90-х годов прошлого столетия. Оказалось, что сфера применения каждого из методов очень узка. В целом же при анализе индексных выражений компиляторы "спотыкаются" при­мерно в 85% случаев. Это говорит о том, что при несомненных достижениях в вопросах конструирования высокопроизводительных параллельных систем того времени технология организации на них самих вычислительных про­цессов была на относительно низком уровне.
Глава 6. Тонкая информационная структура программ
323
С тех пор прошло более 10 лет. За это время появилось много новых идей как в распараллеливании вычислений, так и в организации параллельных процессов. Но ситуация в общем изменяется мало. Для пользователя она скорее даже ухудшается. Языки программирования становятся зависящими от особенностей архитектур вычислительных систем и начинают приобре­тать черты языков низкого уровня. Например, такие языки, как PVM и MPI по существу уже являются коммуникационными автокодами, хотя и на мак­роуровне. На пользователя все в большей степени перекладывается забота об эффективности организации вычислительных процессов, включая обна­ружение в программах и алгоритмах необходимых свойств параллельности и коммуникационных свойств.
Постоянная нехватка должной поддержки со стороны языков программиро­вания, компиляторов и операционных систем в обеспечении эффективности процессов решения задач привела к созданию специализированных про­граммных комплексов по анализу пользовательских программ и их преобра­зованию под требования конкретных параллельных языков программирова­ния и собственно паралельных вычислительных систем. Эти комплексы реализуются на автономных компьютерах и не связаны с компиляторами целевых вычислительных систем. Поэтому на них могут быть реализованы самые передовые методы исследования и преобразования программ. Пред­ставляя собою автономные программные системы, указанные комплексы ока­зались удобным инструментом для выполнения различных работ, когда программы одного вида нужно перевести в эквивалентные программы дру­гого вида. Среди зарубежных автономных систем наиболее известна система Parafrase и созданная на ее основе серия пакетов КАР [47, 52, 58], а также система Forge [62]. Среди отечественных — система V-Ray [65].
Работа с автономными системами по исследованию и преобразованию поль­зовательских программ выявила ряд интересных обстоятельств. Во-первых, с помощью таких систем можно значительно уменьшить время реализации программ на параллельных вычислительных системах. По сравнению с тем временем, которое показывают программы, пропущенные через штатные компиляторы без предварительной оптимизации, почти всегда удается полу­чать ускорение в несколько раз. Ускорение удается получать даже в тех слу­чаях, когда программы предварительно оптимизируются вручную. Как пра­вило, оно тем больше, чем больше и сложнее исходные пользовательские программы. Во-вторых, для достижения предельно возможного ускорения приходится применять существенно более сложные методы исследования и преобразования программ, чем те, которые традиционно включаются в ком­пиляторы. Сейчас они настолько сложны, что об их ручном применении не может быть и речи. Однако для компьютерного и, тем более, автономного ис­пользования эти методы вполне приемлемы. И, наконец, работа с автономны­ми системами отчетливо высветила тот факт, что для успешного освоения
324
Часть II. Параллельное программирование
вычислительных систем параллельной архитектуры пользователям необхо­димо иметь возможность получать самые подробные сведения о самых раз­личных деталях структуры своих алгоритмов и программ.
Настоящая глава посвящена фундаментальным основам построения методов для получения и использования таких сведений. Эти основы связаны с изу­чением тонкой информационной структуры алгоритмов и программ, другими словами, информационных связей на уровне отдельных операций или от­дельных срабатываний отдельных операторов. В обсуждении затрагиваемых вопросов мы не будем следовать исторической линии, а изложим свою соб­ственную концепцию. На наш взгляд, она достаточно четко построена и впитала в себя лучшее из разрозненных результатов и идей, полученных к настоящему времени. Эта концепция является тем стержнем, опираясь на который, можно оценить место каждого конкретного результата или идеи и понять, как все они связаны друг с другом.
Развивая наши исследования, мы будем уделять особое внимание анализу за­писей алгоритмов с помощью математических соотношений и в виде про­грамм на последовательных языках. Объясняется это тем, что именно в этих формах записана большая часть мирового багажа алгоритмических знаний. К данному багажу приходится обращаться очень часто и не только в связи с освоением вычислительной техники параллельной архитектуры. Поэтому всегда полезно знать о нем как можно больше. Наш основной интерес будет связан, конечно, с анализом этого багажа с точки зрения выявления возмож­ностей лучшей организации параллельных вычислительных процессов. Про­водя анализ, мы будем формально изучать только тексты программ и описы­вающие алгоритмы математические соотношения и без особых оговорок не будем использовать какие-либо дополнительные сведения.
§ 6.1. Графовые модели программ
В главе 4 мы уже касались вопроса использования математических моделей для изучения некоторых явлений. Модели возникают естественным обра­зом, если по каким-либо причинам явления не поддаются точному описа­нию, по крайней мере, на момент исследования. Почти всегда это имеет место при изучении природных явлений. В этом случае модель впитывает в себя имеющиеся сведения о конкретном явлении, а детальное ее изучение позволяет обнаруживать либо новые черты явления, либо факты несоответ­ствия модели явлению. Однако нередко возникают другие ситуации, когда об изучаемом явлении или объекте вроде бы должно быть известно все, т. к. оно описано абсолютно точно. Тем не менее, и в этих случаях приходится использовать модели. Только теперь они вводятся не из-за нехватки сведе­ний, касающихся описания, а по иным причинам. Например, если само описание очень сложно с точки зрения применяемого аппарата исследова-
Глава 6. Тонкая информационная структура программ
325
ний, или требуется найти какой-либо простой критерий, характеризующий некоторое свойство, или когда, вообще, не очень ясно, что придется иссле­довать в будущем, а аппарат исследований надо создавать уже сегодня, и т. п. Конечно, как сами модели, так и уровни их сложности могут быть самыми разными. Но в данной ситуации считается обязательным выполне­ние следующего требования: если какое-то искомое свойство обнаружено в модели, то оно же должно присутствовать и в изучаемом явлении или объ­екте, возможно, с какой-то известной или проверяемой точностью. Типич­ным примером такой ситуации является случай, когда явление приближенно описывается моделью, представляющей систему дифференциальных уравне­ний, а эта модель, в свою очередь, аппроксимируется моделью, представ­ляющей систему алгебраических уравнений.
Нашей ближайшей целью является рассмотрение моделей для программ на последовательных языках программирования. Как объекты исследований программы описаны абсолютно точно. Но даже с точки зрения их реализа­ции на однопроцессорных компьютерах возникает много вопросов, ответы на которые не видны из текста программ. Например, как провести такую реорганизацию программы, чтобы минимизировать число используемых пе­ременных, наилучшим образом использовать кэш-память, минимизировать число обменов с медленной памятью и др. При выявлении в программах скрытого параллелизма и свойств, необходимых для эффективного его ис­пользования, вопросов возникает еще больше.
Эти и подобные им вопросы связаны со знанием явных или скрытых свойств программы, определяющих ее строение или, как говорят иначе, ее информационную структуру. Мы будем считать, что информационная струк­тура программы есть совокупность сведений о том, как отдельные элементы программы связаны между собой. Конечно, такое определение вряд ли можно назвать точным. Но его и нельзя существенно улучшить. Дело в том, что любой последовательный язык определяет некоторый класс алгоритмов, причем достаточно широкий. Программа на этом языке есть не что иное, как запись одного из алгоритмов. Понятие алгоритма является в математике первичным и его нельзя строго определить через другие понятия. Поэтому нельзя в общем случае и строго определить, как устроены алгоритмы. Любая попытка "точнее" определить понятие структуры программы неизбежно бу­дет связана с сужением множества изучаемых ее свойств.
В основе изучения информационной структуры программ уже давно сфор­мировались два подхода. Первый называется денотационным и опирается на исследование состояния памяти программ. В практических приложениях он используется редко. По этой причине мы его рассматривать не будем. Вто­рой подход называется операционным. Исполнение (развитие) любой про­граммы представляется в виде набора выполненных действий (операций), некоторым образом связанных между собой. Содержание действий, их вы-
326
Часть II. Параллельное программирование
числительная мощность и способ связи в рамках данного подхода не фик­сируются. Они определяются в каждом конкретном случае по-своему в за­висимости от целей исследования. Операционный подход в практических приложениях используется очень часто. Более того, он очевидным образом порождает класс моделей программ, называемых графовыми. В этом классе каждая модель представляет граф, в котором вершины соответствуют каким-то множествам действий программы, а дуги (ребра) так или иначе связаны с отношениями между ними. Диапазон уровней сложности графовых моделей огромен. Отдельная вершина может представлять и программу целиком, и какие-то ее большие или малые фрагменты и даже отдельные элементарные операции. И на все это накладывается многообразие связей. С такими моде­лями мы будем иметь дело в настоящей главе.
Опираясь на аксиоматику операционного подхода, в любой программе мож­но выделить два типа действий, которые, следуя [27], будем называть преобразователями и распознавателями. Преобразователи осуществляют пе­реработку информации, а распознаватели определяют последовательность срабатываний преобразователей в процессе работы программы. Первому ти­пу действий соответствуют, например, операторы присваивания. Их левая часть определяет область памяти программы, подвергающейся воздействию преобразователя, а переменные из правой части показывают на необходи­мые для выполнения данного действия аргументы. Второму типу действий в программе отвечают альтернативные операторы: условные, разного рода пе­реключатели и т. п. Основное их назначение состоит в выборе одной из не­скольких возможных альтернатив дальнейшего следования.
Везде далее будем предполагать, что множества операторов, соответствую­щих этим двум типам действий, не пересекаются. В реальных программах это не всегда так. Условное выражение альтернативного оператора вправе содержать вызов функции, которая помимо выполнения своих внутренних действий может иметь побочный эффект, влияющий на дальнейший ход развития программы. Другим примером может служить оператор присваива­ния, правая часть которого содержит в качестве терма условное арифмети­ческое выражение. Такие примеры в явном виде не допускают строгого де­ления на преобразователи и распознаватели. Однако с помощью очевидных элементарных преобразований, канонизирующих тексты программ, они могут быть приведены к нужной форме.
Рассмотрим иллюстративный пример, к которому будем обращаться не­сколько раз. Пусть решается система линейных алгебраических уравнений Ах = b с левой двухдиагональной матрицей порядка п. Обозначим через оь ..., ап диагональные элементы матрицы А, через cj, ..., с„ — ее поддиаго-нальные элементы, через by, ..., b„ — правые части, через х\, ..., х„ — коор­динаты решения. Допустим, что необходимо найти максимальную коорди-
Глава 6. Тонкая информационная структура программ
327
нату вектора-решения. Соответствующий алгоритм может быть записан в следующем виде, например, на языке типа Fortran:
1         у = by/ау
2        х=у
3          DO / = 2, n
4                    х = (bj — срс)/ау                                                                                                            (6.1)
5                  if (x<y) go to 7
6                  у =x
7             END DO
После выполнения программы значение переменной у будет равно макси­мальному из чисел х\, ..., хп. Здесь операторы с метками 1, 2, 4, б являются преобразователями, операторы с метками з, 5, 7 — распознавателями.
Между действиями программы устанавливаются, как правило, два типа от­ношений. Первый тип определяется фактом выполнения одного действия непосредственно за другим. Если какие-либо два действия находятся в этом отношении, то будем говорить, что между ними установлена связь по управ­лению или операционная связь. Второй тип связан с использованием одним действием в качестве аргументов результатов выполнения других действий. Он определяет информационную связь. Оба типа отношений в общем случае вводят на множестве действий частичный порядок.
Каждому оператору исходной программы поставим в соответствие вершину графа — экземпляр преобразователя или распознавателя в зависимости от типа оператора. Получим множество вершин, между которыми согласно ис­ходной программе определим отношение, соответствующее передаче управ­ления. Если текст программы допускает выполнение одного оператора не­посредственно за другим, то соответствующие вершины соединим дугой, направленной от предшественника к последователю. Получим граф, кото­рый обычно называется графом управления, управляющим графом, графом пе­редач управления или просто графом программы [27]. Его основным свойст­вом является независимость от входных данных программы. Множества вершин и дуг для каждой программы фиксированы и образуют единствен­ный граф. Этот граф задает одну из моделей программы. На рис. 6.1 он представлен для примера (6.1).
•----------------*+----------------*■
12 3 4 5 6 7
Рис. 6.1. Управляющий граф
328
Часть II. Параллельное программирование
Отталкиваясь от графа управления, можно получить другие модели программ. Если забыть о конкретном содержании каждой вершины и обозначить ее про­сто некоторым символом, то получим схему Мартынюка. Если, кроме этого, каждому входу и выходу оператора (аргументу и результату) сопоставить сим­волы переменных (ячеек памяти), то схема Мартынюка преобразуется в схему Лаврова. Схемы Мартынюка очень часто используются при оптимизирующих преобразованиях программ, т. к. являются удобным средством исследования их логической структуры. С помощью схем Лаврова удалось, например, найти решение задачи минимизации числа переменных.
Теперь предположим, что каким-то образом мы определили начальные дан­ные программы и наблюдаем за ее выполнением на обычном последова­тельном вычислителе. Каждое срабатывание каждого оператора (а оно не­обязательно будет единственным) будем фиксировать отдельной вершиной. Получим множество, которое количественно почти всегда будет отличаться от множества вершин графа управления. В отличие от графа управления, в данном случае порядок непосредственного срабатывания операторов можно определить точно. Соединив вершины дугами передач управления, получим ориентированный граф, носящий название операционно-логической истории программы [27]. Он представляет единственный путь от начальной вершины к конечной. По существу это не что иное, как последовательность срабаты­вания преобразователей и распознавателей исходной программы при задан­ных входных данных. В операционно-логической истории от входных дан­ных зависит практически все: общее число вершин, количество вершин, соответствующих одному оператору, и даже набор присутствующих преобра­зователей и распознавателей. Граф управления свободен от подобных кон-кретностей. На рис. 6.2 представлена операционно-логическая история программы (6.1) для случая п = 3, Ъ\ = 0, Ь2 = Ьт, = ау = а2«з = с2 = сз = 1-
Рис. 6.2. Операционно-логическая история
На рис. 6.3 представлена операционно-логическая история той же програм­мы (6.1), но для других входных данных п = 3, by = О, Ъ2 = с2 = — 1,
h = «1 = а2 = аъ = с3 = 1.
Рис. 6.3. Другие входные данные
Глава 6. Тонкая информационная структура программ
329
Преобразуем операционно-логическую историю следующим образом: все вершины, отвечающие одному оператору программы, объединим в одну с со­хранением входных и выходных дуг и ликвидацией образующихся кратных дуг. Легко проверить, что полученный граф является подграфом графа управ­ления программы, соответствующим конкретным входным данным. Объеди­няя подобным образом все операционно-логические истории программы вме­сте, мы можем, тем не менее, не получить всего графа управления. Такая ситуация является в некотором смысле вырожденной и говорит о плохом про­ектировании исходной программы. Однако на практике она вполне реальна. Объединяя графы на рис. 6.2, 6.3, мы получим граф на рис. 6.1.
Изменим графовую основу. Будем среди операторов принимать во внимание только преобразователи, а в качестве отношения между ними брать отноше­ние информационной зависимости. Построим сначала граф, в котором вер­шины соответствуют операторам-преобразователям. Две вершины соединим информационной дугой, если между какими-нибудь срабатываниями соот­ветствующих операторов теоретически возможна информационная связь. Полученный граф называется информационным графом программы [27]. Как и в случае графа управления информационный граф не зависит от входных данных. В нем могут быть "лишние" дуги, которые не реализуются либо при конкретных входных данных, либо совместно с другими дугами. Информа­ционный граф также представляет одну из моделей программы. Но в отли­чие от графа управления эта модель применяется на практике очень часто. В частности, она использовалась в упоминавшихся ранее системе Parafrase и серии пакетов КАР. На рис 6.4 представлен информационный граф про­граммы (6.1).
О
•--------------------£*£---------
12 4 6
Рис. 6.4. Информационный граф
Снова каким-то образом определим начальные данные программы и будем наблюдать за ее выполнением на последовательном вычислителе. Каждое срабатывание каждого оператора-преобразователя будем фиксировать от­дельной вершиной. Соединив вершины дугами передач информации, мы получим ориентированный граф, называемый историей реализации програм­мы [27]. На рис. 6.5, 6.6 представлены истории реализации программы (6.1) для случаев входных данных, соответствующих рис. 6.2, 6.3.
Объединив эти истории, мы получим информационный граф программы (6.1), представленный на рис. 6.4.
330
Часть II. Параллельное программирование
2
4
• 6
1
1
2
4
6
Рис. 6.5. История реализации
Рис. 6.6. Другие входные данные
Таким образом, мы имеем четыре основные графовые модели. Это — граф управления, информационный граф, операционно-логическая история и история реализации. Первые две модели не зависят от входных данных и строятся непосредственно по тексту программы. Две последние модели для своего построения формально требуют слежения за выполнением всех опе­раторов. Понятно, что сложность построения моделей возрастает в порядке указанного перечисления. Первые две модели давно и успешно применяются на практике. Последние две модели до недавнего времени применялись, главным образом, в теоретических исследованиях, что связано, естественно, со сложностью их практического построения. Достоинством всех моделей является то, что они существуют для всех программ. На основе этих моделей можно строить различные смешанные модели, в которых одни фрагменты программы представляются информационным графом или графом управле­ния, а другие — той или иной историей. Существуют преобразования, пере­водящие одни модели в другие [16].
Среди рассмотренных моделей как в теоретическом отношении, так и в от­ношении возможных перспектив практического применения наибольший интерес представляет история реализации программы. Зная эту модель, можно получить, наверное, любые сведения, касающиеся процессов реали­зации описанного программой алгоритма. Например, определить множества независимых друг от друга операций, найти подходящее распределение опе­раций по процессорам вычислительной системы, обнаружить различные узкие места и т. п. Сказанное не покажется большим преувеличением, если обратить внимание на совпадение истории реализации программы с введен­ным в § 4.2 графом алгоритма. Конечно, имеется в виду алгоритм, описан­ный рассматриваемой программой. С некоторыми из задач вокруг графа алгоритма мы уже сталкивались ранее.
Чтобы на основе графа алгоритма решать какие-то задачи, необходимо иметь соответствующий аппарат. Граф алгоритма, как и история реализации программы, не подчеркивают в своих определениях какие-либо специфиче-
Глава 6. Тонкая информационная структура программ
331
ские особенности. Кажется, что мы должны их рассматривать как произ­вольные ориентированные ациклические графы. Действительно, любой та­кой граф может быть графом некоторого алгоритма, который при подходя­щем выборе языка программирования может быть описан некоторой программой. Но основной аппарат для решения задач с произвольными графами связан с перебором возможных вариантов. И главная цель выбора метода исследования состоит в минимизации числа переборов.
Допустим, что решается задача построения максимальной параллельной формы графа алгоритма. Если граф имеет m дуг, то легко предложить метод, дающий эту форму за число переборов порядка ш2. Схема метода похожа на доказательство утверждения 4.1. С точки зрения теории графов это — хоро­ший результат. Однако заметим, что исходный алгоритм реализуется на од­нопроцессорном компьютере за время, пропорциональное ш. Следователь­но, время исследования параллельной структуры алгоритма будет намного превышать время его реализации. Ситуация оказывается значительно хуже, если рассмотреть задачу оптимального размещения операций по процессо­рам вычислительной системы. Согласно [25], наиболее интересные вариан­ты этой задачи относятся к так называемым NP-полным задачам, для реше­ния которых уже требуется выполнить порядка ш\ переборов.
Тратить столь большое время на исследование структуры алгоритма стоит только в том случае, если результаты исследования будут использоваться многократно. Но здесь нас поджидает еще одна неприятность. Снова на­помним, что граф алгоритма в общем случае зависит от входных данных. В частности, от размеров используемых матриц, шагов сетки и т. п. Поэтому любое описанное исследование мы можем провести только для фиксиро­ванных значений входных данных. Различных значений входных данных, как правило, бесконечно много и заранее совсем не ясно, как результаты исследований при разных значениях входных данных связаны между собой. Все это, в конце концов, сводит на нет сколько-нибудь значимую возмож­ность исследования графа алгоритма как произвольного графа. Возможно, что именно поэтому, возникнув достаточно давно, такое понятие, как исто­рия реализации программы, долгое время не могло превратиться в конст­руктивный инструмент изучения структуры алгоритмов и программ.
Но правомерно ли все-таки всегда граф алгоритма и, соответственно, исто­рию реализации программы рассматривать как произвольные графы? Если допустить, что граф алгоритма является произвольным, то записать алго­ритм можно, лишь записав независимо друг от друга все операции и связи между ними. Длина записи в этом случае окажется пропорциональной числу операций. В практической деятельности не приходится иметь дело с боль­шими алгоритмами подобного рода, в первую очередь, именно потому, что их невозможно записать в компактном виде. Поэтому используемые в дей­ствительности алгоритмы нельзя отнести к произвольным. Они обладают рядом характерных отличий, связанных с существованием периодически
332
Часть II. Параллельное программирование
повторяемых участков вычислений. Следовательно, есть основания предпо­лагать, что изучая реальные алгоритмы и выделяя их специфику, удастся избежать при исследовании структуры алгоритмов NP-сложности больших дискретных задач. NP-сложность — это та реальная опасность, которая мо­жет возникнуть при неудачном выборе метода исследований.
В §4.4 мы рассмотрели несколько примеров графов простых алгоритмов. Все они в той или иной мере оказались устроенными регулярно. Поэтому, например, выявить в них параллелизм удалось легко. Вся эта регулярность и легкость связана только с тем, что вершины графа алгоритма были располо­жены не хаотически в каком-то случайном пространстве, а в том простран­стве и в тех его точках, которые задавали множества индексов, используе­мых для описания алгоритмов. Если бы вершины графов даже таких простых примеров были расположены беспорядочно, то вряд ли в них уда­лось бы разглядеть хотя бы какой-нибудь параллелизм.
Понимание того, что для эффективного изучения структуры алгоритмов и программ необходима более тесная связь графовых моделей с формами записи алгоритмов, пришло довольно давно. Однако разнообразие возникающих идей было невелико. Все они "крутились" вокруг следующих положений:
□  необходимо изучать структуру на уровне отдельных операций;
□  для успешного изучения структуры необходимо согласовывать размеще­ние вершин графов с множеством индексов, используемых для описания алгоритма;
□  необходимо научиться как можно точнее определять по тексту програм­мы информационные связи между отдельными операциями.
Одними из первых работ, затрагивающих эти положения, были работы Л. Лампорта [55, 56]. Объектом исследований в них были тесно вложенные циклы на языке Fortran. Здесь впервые введено новое понятие "пространст­во итераций". Это есть множество целочисленных точек, координаты кото­рых совпадают с координатами допустимых значений параметров циклов тесно вложенного гнезда. Именно в этих точках надо размещать операции, представляющие отдельные срабатывания тела гнезда циклов. Тем самым по существу была предложена модель "координатного" размещения истории реализации программ. Важным моментом явилось также то, что было про­демонстрировано достоинство такого размещения. Предложенные в работах методы координат и гиперплоскостей оказались настолько удобными для проведения аналитического изучения параллелизма, что были включены в ряд параллелизующих компиляторов для преобразования тесно вложенных гнезд циклов к "параллельному" виду.
Несмотря на сказанное, работы Л. Лампорта имели скорее методологиче­ское, чем практическое значение. Они убедительно показали, в каком на­правлении следует двигаться в исследовании структуры алгоритмов и про-
Глава 6. Тонкая информационная структура программ
333
грамм, но уровень продвижения был не очень большим. Тесно вложенные гнезда циклов представляют очень узкий класс программ, которые даже в качестве фрагментов встречаются не так уж часто. Но для них все понятия имеют прекрасные геометрические интерпретации. Собственно говоря, эти интерпретации и являются зерном работ и составляют, на наш взгляд, ос­новную их ценность. К сожалению, "геометрия" тесно вложенных гнезд циклов не распространяется непосредственно на более сложный класс про­грамм. Затруднение вызывает даже распространение такого простого поня­тия, как пространство итераций, не говоря уже о более сложных понятиях типа гиперплоскости. Аналитическая часть работ Л. Лампорта является сла­бой. Все доказанные утверждения, в том числе, относящиеся к определению информационных связей между операциями, предполагают достаточно же­сткие ограничения на тело исследуемых циклических конструкций.
Трудности определения информационных связей между операциями по тек­сту программы привели к появлению большого числа работ, связанных с графовыми моделями иного типа, называемыми графами зависимостей. В этих моделях отношение информационной связи заменяется значительно более широким отношением зависимости. Именно, две операции или два оператора называются зависимыми, если при их выполнении имеют место обращения к одной и той же переменной (ячейке памяти). Так как инфор­мационная связь также реализуется через обращение к одной переменной, то ясно, что она есть частный случай зависимости. Очевидно, что устанав­ливать факт зависимости значительно проще, чем факт информационной связи. Во всех этих моделях мы, как правило, имеем дело только с операто­рами-преобразователями .
В примере (6.1) имеются две изменяемые переменные х а у. Если в качестве вершин графа зависимостей взять операторы-преобразователи с метками 1, 2, 4, б, то получим граф, представленный на рис. 6.7.
12       4       6
Рис. 6.7. Общий граф зависимостей
Как и положено, информационный граф на рис. 6.4 является его подгра­фом. Обратим внимание на то, что переменные х и у в примере (6.1) в раз­ных операторах играют разную роль: где-то они определяют аргументы, где-то результат. На рис. 6.7 разделение ролей не отмечено. К этому вопросу мы еще вернемся.
334
Часть II. Параллельное программирование
Как и в рассмотренных ранее моделях многое зависит от того, представля­ют ли вершины графов зависимостей операторы или отдельные срабатыва­ния операторов. Допустим, что осуществляется суммирование чисел О], ..., an. Один из алгоритмов последовательного суммирования записыва­ется так:
15 = 0
(6.2)
DO / = 1, И
2 s = s + а,
END DO
Если построить граф зависимостей, в котором вершины являются операто­рами, то поскольку имеется только одна изменяемая переменная s, он будет таким, как на рис. 6.8.
Построим теперь для примера (6.2) граф зависимостей, считая вершины от­дельными срабатываниями операторов. Для п = 4 он представлен на рис. 6.9.
/-
^==
~^Г^
Ч
S*^5
0
1
"==г
-*•
4
2
3
Рис. 6.8. Зависимость между операторами
Рис. 6.9. Зависимость между операциями
Заметим, что в вершину с номером п входит п дуг, в вершину с номером я — 1 входит л — 1 дуг и т. д. Большое число входящих дуг в графах зависи­мостей в общем случае оказывается платой за легкость проверки выполне­ния отношения зависимостей. Об этом мы также будем еще говорить. А по­ка данный факт только отметим.
Одной из первых работ, в которой достаточно четко описывается понятие зависимости и его использование для преобразования программ, является работа [60]. Всего предложено достаточно много графов зависимостей. Они отличаются друг от друга различием типов анализируемых программ — от тесно вложенных гнезд циклов до программ общего вида, разнообразием содержания вершин — от операторов программы до отдельных их срабаты­ваний или, другими словами, до отдельных операций, а также разнообрази­ем видов и количеств описывающих зависимости дуг. Некоторые из графов зависимостей активно использовались в параллелизующих компиляторах.
Близко к графам зависимостей стоит граф влияния [6]. Будем говорить, что одна операция влияет на другую, если изменением значения переменной, которую вычисляет первая операция, можно изменить значение перемен-
Глава 6. Тонкая информационная структура программ
335
ной, которую вычисляет вторая операция. Построим ориентированный граф. Будем считать операции вершинами графа. Соединим дугой каждую из пар вершин, в которых одна из соответствующих им операций влияет на другую. Пусть направление дуги совпадает с направлением влияния. Такой граф и будем называть графом влияния. Для примера (6.2) граф влияния при п = 4 совпадает с графом зависимостей на рис. 6.9. Но в общем случае эти графы различны. Отношения управления, информационной связи и за­висимости не являются транзитивными. В отличие от них отношение влия­ния транзитивно. За исключением вырожденных случаев история реализа­ции программ есть остовный подграф графа влияния. Более того, граф влияния оказывается транзитивным замыканием истории реализации. Сама же история реализации по отношению к графу влияния представляет его минимальный по числу дуг подграф, обладающий следующим свойством: в обоих графах любые две вершины либо связаны путем, либо не связаны од­новременно. К подобным минимальным графам мы будем обращаться по­стоянно. История реализации программы (6.2) представлена на рис. 6.10.
•------*•------**------>•------*•
12        2 2 2
Рис. 6.10. Минимальный граф зависимостей
Как мы уже неоднократно отмечали, среди графовых моделей история реа­лизации программы наиболее интересна. Понятно, что она также является одним из графов зависимостей. Поэтому исследования графов зависимостей вроде бы должны были приближать к лучшему пониманию того, как в дей­ствительности устроена история реализации. Однако долгое время эти при­ближения были настолько грубыми, что в отношении истории реализации программы не удавалось получить никаких существенно новых сведений.
Для того чтобы установить самые интересные факты, касающиеся структуры алгоритма, необходимо исследовать его описания в терминах самых "мелких" операций, которые, тем не менее, все еще воспринимаются иссле­дователями. В вычислительных задачах такими операциями, как правило, являются числовые операции умножения, сложения, деления и т. п. Для нас сейчас важно то, что все они имеют малое число аргументов. Обычно их один-два. Следовательно, в этих случаях история реализации программы должна обладать очень важным свойством. Именно, в каждую ее вершину входит конечное число дуг, не зависящее от общего числа вершин в истории. Общий недостаток большинства работ по графам зависимостей как в про­шлом, так и сейчас заключается в том, что прямо или косвенно в них рас­сматриваются только такие ситуации, в которых число дуг, в среднем вхо-
336
Часть II. Параллельное программирование
дящее в одну вершину, не ограничивается сверху равномерно по входным данным и поэтому может быть очень большим. История реализации про­граммы в подобных работах может изучаться, в основном, через ее погруже­ние в большие графы. Но в графах, имеющих много лишних дуг, изучение свойств истории возможно лишь через какие-то достаточные критерии. При всей своей полезности область практического применения достаточных кри­териев весьма ограничена. С их помощью нельзя гарантировать выполнение многих условий, они не указывают пути преодоления узких мест, связанных с невыполнением критериев, и т. п.
Впервые общие принципы метода построения по тексту программы точной истории ее реализации были изложены в книге [9]. В этой книге история называлась решетчатым графом алгоритма. Детали метода описаны в работе [15] и книгах [10, 64], где история реализации программы уже выступает под именем графа алгоритма. В книгах [10, 11, 64] детально изучены алгебраиче­ские свойства графов алгоритмов. В книге [6] и работах [8, 12] описаны раз­личные применения графа алгоритма. На основе разработанной методоло­гии построения, исследования и применения графа алгоритма и связанных с ним других графов создана практически работающая автономная система V-Ray. С некоторыми результатами ее использования можно познакомиться в работе [65] и далее в этой книге.
Данная глава посвящена различным аспектам исследования программ, свя­занным с историями их реализаций. Мы будем называть всюду такие исто­рии графами алгоритмов. Как уже отмечалось, формально между понятиями истории реализации программы и графа алгоритма, описанного той же программой, нет различий. Но нам кажется, что название "граф алгоритма" лучше отражает суть дела. Например, один и тот же алгоритм может быть описан на одном и том же языке разными программами. Соответствующие истории формально также будут разными, если для расположения вершин использовать один и тот же принцип. Однако как графы все истории изо­морфны между собой. Следовательно, они скорее отражают не программы, а описываемый ими алгоритм. История реализации есть графовая модель программы. Слово "граф" в названии "граф алгоритма" подчеркивает это об­стоятельство. Можно привести и другие аргументы в пользу использования названия "граф алгоритма".
Вопросы и задания
1.   Что означает случай, когда в программе нет преобразователей? Имеют ли смысл такие программы?
2.   Что означает случай, когда в программе нет распознавателей? Имеют ли смысл такие программы?
3.   Как выглядят управляющие и информационные графы для пп. 1, 2?
Глава 6. Тонкая информационная структура программ
337
4.   Что означает несвязность управляющего или информационного графа с точки зрения параллельных вычислений?
5.   Может ли быть управляющий граф несвязным, а информационный граф связ­ным и наоборот?
6.   Докажите, что если информационный граф несвязный, то граф алгоритма также несвязный?
7.   Верно ли аналогичное утверждение относительно управляющего графа?
8.   Докажите, что если пересечение множеств переменных, находящихся в правых и левых частях преобразователей, пустое, то граф алгоритма также пустой, т. е. не имеет ни одной дуги.
9.   Если граф алгоритма пустой, то означает ли это пустоту управляющего и/или информационного графа?
10.   Приведите примеры, когда граф влияния не совпадает с графом зависимостей.
11.   Приведите примеры, когда граф алгоритма связный, а граф влияния пустой.
§ 6.2. Выбор класса программ
По определению, граф алгоритма существует для любой программы. Как этого ни хотелось бы, трудно даже поверить в то, что удастся разработать эффективный метод его построения и исследования для любой программы. Поэтому можно лишь надеяться, что мы сможем указать достаточно широ­кий класс программ, для которого, в свою очередь, сможем выполнить все намеченное. Но прежде всего необходимо определиться с языком описания программ.
В качестве средства описания алгоритмов выберем подмножество языка Fortran. Этот выбор объясняется следующими соображениями. Мы будем изучать тонкую структуру алгоритмов на уровне связей между отдельными операциями. Тонкая структура весьма чувствительна к мельчайшим измене­ниям алгоритма. Например, уже отмечалось, что изменение порядка выпол­нения операций в пределах действия законов коммутативности, ассоциатив­ности и дистрибутивности при точных вычислениях может очень сильно изменить результат, если реально операции приходится выполнять в усло­виях влияния ошибок округления. Поэтому порядок выполнения операций алгоритма должен быть описан максимально точно. Обязательность описа­ния порядка присутствует в языках программирования. Этому условию удовлетворяет не один язык Fortran, но и такие популярные в настоящее время языки как С\С++ и ряд других. Дополнительным аргументом в поль­зу выбора языка Fortran является то, что именно на нем записана большая часть алгоритмического багажа в мире. Несмотря на многочисленные по­пытки разработчиков новых языков программирования "похоронить" язык Fortran, он продолжает активно использоваться до сих пор, особенно в сфе­ре решения научно-технических задач. К тому же, этот язык близок по сво-
338
Часть II. Параллельное программирование
ей форме к математическим соотношениям, к которым мы планируем обра­титься позднее. И, наконец, забегая несколько вперед, скажем, что будем использовать такое подмножество языка Fortran, которое с точностью до обозначений присутствует во всех языках программирования. В этих усло­виях выбор языка почти безразличен.
Однако совсем не безразличен выбор класса программ. Интуитивно ясно, что он должен существенно отличаться от тесно вложенных гнезд циклов. Но совсем не ясно, в какую сторону и насколько. Чтобы ответить на много­численные вопросы, были собраны данные по реально используемым про­граммам с помощью специальной системы статистического анализа [17]. В качестве объекта исследований был взят набор программ на Fortran, напи­санных для решения задач минимизации, линейной алгебры и математиче­ской физики. Суммарный объем выборки составил более 12 000 инструкций языка. В работе над исследуемыми программами, т. е. в их написании и от­ладке, принимало участие достаточно много людей из разных коллективов. Поэтому случайные отклонения, вызванные индивидуальными стилями программирования, не могли сильно повлиять на итоговую статистику. Ста­тистика собиралась так, чтобы получить информацию о наиболее типичных конструкциях и частоте их появления. Кроме этого, на типичных конструк­циях отрабатывалась и совершенствовалась методология анализа структуры программ. Вот некоторые из результатов.
□  Структура вхождений DO-циклов. Фиксировались все формы вложенно­сти операторов DO (DO-циклов). Сложные конструкции дополнительно разбивались на составные части по их вложенности, которые также включались в общую статистику. Выяснилось, что тесно вложенных гнезд циклов всего около 7%, причем такие гнезда с вложенностью 3 и более не встречались ни разу. Учитывая популярность тесно вложенных гнезд в исследовательских работах, была дополнительно определена доля про­грамм, в которых хотя бы просто содержались подобные гнезда. Резуль­тат оказался еще хуже. Это говорит о том, что методы, ориентированные только на тесно вложенные циклы, не будут иметь большой перспективы. Интересным оказалось то, что около 40% всех конструкций представляют собой циклы, в тело которых линейно входит большое число других цик­лических конструкций. В среднем около 5, а максимальная последова­тельность состояла из 19 конструкций. Примерно в 20% случаев отдель­ные срабатывания тел одиночных циклов оказались независимыми друг от друга только за счет несовпадения идентификаторов входов и выходов операторов.
□  Мощность и внешняя вложенность DO-циклов. Рассматривались все опе­раторы цикла DO и для каждого определялись две характеристики, по­зволяющие хотя бы приблизительно оценить вклад фрагмента в общее время счета. Первая — внешняя вложенность. Она определяется глуби-
Глава 6. Тонкая информационная структура программ
339
ной погружения в другие циклы и равна числу циклов, охватывающих данный оператор. Вторая — мощность. Она равна максимальной глубине вложенности входящих в цикл операторов относительно рассматривае­мого оператора DO. Мощность любого цикла не меньше единицы, в то время как внешняя вложенность может быть равной нулю. Проведенное исследование говорит о том, что реальные циклические конструкции не являются даже правильными гнездами и содержат по несколько других операторов цикла на каждом уровне. Например, в анализируемом мате­риале имелось 119 циклов внешней вложенности 0 и мощности 2, но 280 циклов внешней вложенности 1 и мощности 1. Это значит, что для проведения эффективного анализа необходимо разрабатывать и исполь­зовать методы, умеющие исследовать совместное функционирование ли­нейно расположенных циклических конструкций.
□  Количество максимально вложенных циклов. Была сделана попытка опре­делить число наиболее критичных по времени счета мест в программе. Для этого сначала определялась мощность программы как наибольшая мощность ее циклических конструкций. Затем по каждой мощности оп­ределялась доля программ, имеющих 1, 2, 3 и более максимально вло­женных циклов. Обнаружен довольно интересный факт — доля про­грамм, содержащих более одного критического фрагмента, достаточно велика. В частности, среди программ мощности 3 свыше 20% имеют 6 и более циклических конструкций максимальной вложенности.
□  Структура тел внутренних циклов. Исследовалось, какие типы операторов входят в самые внутренние циклы, т. е. циклы мощности 1. Статистика собиралась раздельно по циклам с различной внешней вложенностью, чтобы выявить структуру наиболее значимых по времени счета фрагмен­тов. Обнаружено, что с ростом глубины вложенности заметно растет доля циклов, тела которых содержат только операторы присваивания. Напри­мер, для циклов глубины вложенности 1 эта доля составляет 73%, для циклов глубины вложенности 3 — 93%. Следовательно, циклическим конструкциям с телами циклов, состоящими только из операторов при­сваивания, необходимо уделять особое внимание.
П Структура вхождения альтернативных операторов. Альтернативные опера­торы осуществляют выбор дальнейшего действия в зависимости от вы­полнения некоторого условия. В статистике учитывались условные опе­раторы, вычисляемый переход и переход по предписанию. Проверялось, задают ли альтернативные операторы ветвления в рамках тела данного цикла или же возможен побочный выход из цикла и соответственно пре­кращение его выполнения. Одновременно определялось, насколько часто в программах встречались безусловные фрагменты. Важным выводом яв­ляется то, что относительно немного циклов имеют побочный выход. Это дает уверенность в их хорошей исследуемости.
340
Часть II. Параллельное программирование
Выбирая класс анализируемых программ, приходится решать две противо­речивые задачи. С одной стороны, хотелось бы его взять как можно более широким, с тем чтобы он покрывал значительную часть, а в идеале даже все практически встречающиеся программы или их наиболее значимые фраг­менты. С другой стороны, чем шире класс программ, тем меньше надежды на то, что для любой его программы удастся провести одинаково глубокий анализ. Умение, граничащее с искусством и удачей, состоит в нахождении "золотой середины". Проведение только что описанного статистического анализа как раз и было направлено на ее поиск. После некоторых как удач­ных, так и неудачных попыток было решено класс анализируемых программ сделать двухуровневым. Первый или базовый уровень образует строго опи­санный так называемый линейный класс. Для любой его программы прово­дится самый тщательный анализ информационной структуры. Второй уро­вень открытый. В него входят все программы, которые каким-либо способом сводятся к программам базового уровня.
Всюду в дальнейшем, если не сделано специальных оговорок, будем считать, что алгоритм записан с помощью следующих средств языка:
□  в программе может использоваться любое число простых переменных и переменных с индексами;
□  единственным типом исполнительного оператора может быть оператор присваивания, правая часть которого есть арифметическое выражение; допускается любое число таких операторов;
□  все повторяющиеся операции описываются только с помощью циклов DO; структура вложенности циклов может быть произвольной; шаги из­менения параметров циклов всегда равны +1; если у цикла нижняя гра­ница больше верхней, то цикл не выполняется;
□  допускается использование любого числа условных и безусловных опера­торов перехода, передающих управление "вниз" по тексту; не допускается использование побочных выходов из циклов;
□  все индексные выражения переменных, границы изменения параметров циклов и условия передачи управления задаются, в общем случае, неод­нородными формами, линейными как по параметрам циклов, так и по внешним переменным программы; все коэффициенты линейных форм являются целыми числами;
□  внешние переменные программы всегда целочисленные, и вектора их значений принадлежат некоторым целочисленным многогранникам; конкретные значения внешних переменных известны только перед нача­лом работы программы и неизвестны в момент ее исследования.
Программы, удовлетворяющие описанным условиям, будем называть линейными или принадлежащими линейному классу. Они и будут предметом
Глава 6. Тонкая информационная структура программ
341
всех наших исследований. Особая специфика записи операторов не нужна. Если требуется приблизить форму записи алгоритмов к математической, то будем писать индексы переменных так же, как в математических соотноше­ниях, т. е. справа от переменной снизу и/или сверху. Если же нужно анали­зировать фрагменты конкретных реальных программ, то будем брать их та­кими, какие они есть. При этом, возможно, придется использовать прямые заглавные буквы, писать индексы рядом в скобках и т. д.
Здесь уместно сделать одно замечание относительно терминологии. Обычно под переменной с индексами понимается весь массив простых переменных, объединенных общим идентификатором. При изучении тонкой структуры программы такая "групповая" переменная очень неудобна. Гораздо удобнее рассматривать массив как группу простых переменных, идентификаторы ко­торых составлены из идентификатора массива и индексов. Всюду в даль­нейшем мы будем понимать под переменной с индексами отдельный эле­мент массива, а не весь массив.
Как показывает статистика, многие значимые фрагменты практически ис­пользуемых программ непосредственно принадлежат линейному классу. По своему выбору линейный класс не претендует на особую широту, хотя даже он намного перекрывает все типы программ, которые изучаются в рамках аналогичных подходов. Далее будет показано, что линейный класс может быть исследован полностью. Кроме этого, в главе 8 мы установим, что суще­ствуют многочисленные приемы, позволяющие сводить к линейным фраг­менты программ, формально не принадлежащие к линейному классу. Со­гласно статистике, учитывая все такие приемы, удается привести к линейному классу более 90—95% текста практически используемых про­грамм. В целом среди программ на последовательных языках линейный класс играет примерно такую же роль, как линейная алгебра в численных методах, линейное программирование в экстремальных задачах, линейные задачи математической физики в нелинейных процессах и т. п.
Наша ближайшая цель будет состоять в изучении информационной структу­ры программ из линейного класса, опираясь только на анализ текста про­грамм, без привлечения каких-либо дополнительных сведений как о самих программах, так и об описанных ими алгоритмах. Такой подход к изучению программ называется статическим. Его основное достоинство заключается в том, что вся информация о структуре программы получается до ее реализа­ции и, следовательно, может быть использована наиболее эффективно. Главная трудность проведения статического анализа связана с тем, что тек­сты программ почти всегда зависят от внешних переменных, значения кото­рых не известны. Поэтому все исследования приходится проводить для па­раметризованных объектов.
342
Часть II. Параллельное программирование
Вопросы и задания
1.   Рассмотрите любой известный вам язык программирования. Как на этом языке описывается линейный класс программ?
2.   Приведите различные примеры программ, которые формально не являются линей­ными, но становятся таковыми после каких-то эквивалентных преобразований.
3.   'Пусть программа содержит вызовы процедур. Предложите различные способы замены этих вызовов программными фрагментами, при которых исходная про­грамма становится линейной и сохраняет все прежние информационные зависи­мости.
4.   Приведите различные примеры программ, которые формально не являются ли­нейными и которые вы не можете привести к линейным с помощью эквивалент­ных преобразований.
5.   Как выглядят графы алгоритмов для примеров п. 4?
6.   Насколько различны графы алгоритмов для примеров п. 4?
7.   Как часто примеры п. 4 встречались в вашей практике?
§ 6.3. Графы зависимостей и минимальные графы
Прежде всего необходимо точно описать множество вершин. Пусть задана произвольная линейная программа. Перенумеруем подряд сверху вниз все операторы циклов do, точнее, все параметры циклов. Обозначим параметры через /], ..., 1П. Будем считать, что из двух параметров младшим (старшим) является тот, у которого номер меньше (больше). Перенумеруем также свер­ху вниз все операторы присваивания и обозначим их через F]_, ..., Fm.
С каждым оператором присваивания свяжем тесно вложенное гнездо цик­лов. Его можно получить, оставляя в программе только те циклы do, в тела которых входит рассматриваемый оператор. Будем называть такое гнездо циклов опорным для данного оператора. Пусть опорное гнездо оператора Ft описывается параметрами /,- , ...,/,- . Рассмотрим арифметическое простран-
1                                     .V/
ство, координаты точек которого совпадают со значениями этих параметров. Его размерность равна Sj. Назовем такое пространство опорным. Если опера­тор не входит ни в какое гнездо, то размерность опорного пространства бу­дем считать равной 0. Не ограничивая существенно общности, можно пред­полагать, что отдельный оператор описывается гнездом циклов, у которых нижние и верхние границы изменения каждого из параметров совпадают. Такое предположение позволяет приписать какие-то параметры даже от­дельным операторам.
Границы изменения параметров циклов опорного гнезда определяют в опор­ном пространстве линейный многогранник. Будем называть его опорным для
Глава 6. Тонкая информационная структура программ
343
оператора /} и обозначать Vt. Ветвления, определяющие условия срабатывания оператора Fh вырезают из опорного многогранника некоторую область. При­нимая во внимание целочисленность параметров циклов, эту область можно описать конечным числом замкнутых линейных многогранников. Будем ее
также называть опорной и обозначать V',•. По определению V, с V и Совокупность опорных областей V',• для / = 1, 2, ..., m назовем линейным про­странством итераций. Это есть многосвязная область, состоящая из линейных многогранников, принадлежащих разным арифметическим пространствам. Факт, что некоторые или даже все многогранники могут порождаться одними и теми же параметрами циклов, не имеет сейчас никакого значения. Это будет отражаться лишь в том, что такие многогранники будут в чем-то похожи по расположению в своих пространствах и иметь какие-то размеры одинаковы­ми. По определению линейной программы каждый из параметров пробегает некоторое множество целочисленных значений с шагом +1. Назовем точку линейного пространства итераций целочисленной, если все ее координаты являются целыми числами. Совокупность всех целочисленных точек линей­ного пространства итераций назовем просто пространством итераций.
Подчеркнем, что границы изменения параметров циклов и условия ветвле­ния могут зависеть от внешних переменных. Поэтому размеры всех много­гранников и их конфигурации могут зависеть от значений этих переменных. Как правило, с ростом значений переменных размеры многогранников не­ограниченно увеличиваются. Это является характерной чертой пространства итераций.
Будем называть реализацию оператора Ft при конкретных значениях пара­метров циклов / ,•,...,/,• срабатыванием или итерацией. Для линейных
программ сложность оператора Ft не зависит от значений внешних пере­менных. Обычно она невелика. Поэтому множество операций, реализуемых программой, однозначно определяется множеством всех срабатываний всех операторов /}. Совокупность всех реализаций всех операторов, относящихся к одному значению параметра какого-то цикла, будем называть итерацией цикла.
Каждому срабатыванию оператора Ft при конкретных значениях параметров циклов I/ , ..., Ij поставим в соответствие точку пространства итераций из
области Vi с теми же значениями координат /(- , ..., /(- . Тем самым уста-
навливается взаимно однозначное соответствие между пространством итера­ций и множеством всех срабатываний всех операторов программы. Прини­мая во внимание структуру отдельных срабатываний, мы получаем отобра­жение на пространство итераций множества всех исполняемых операций. Заметим, что все точки одной области V',• и только они соответствуют сра­батываниям одного оператора /}.
344
Часть II. Параллельное программирование
Строя в дальнейшем какие-то графы на множестве операций программы, мы будем всегда в качестве множества вершин брать пространство итераций. Вершины будем называть точками или векторами.
Каждая линейная программа задает множество исполняемых операций. При реализации программы операции выполняются в строго определенном по­рядке, который диктуется самой программой. Этот порядок называется лексикографическим. Будем его обозначать символом -<. Нестрогий лекси­кографический порядок будем обозначать символом < .
Утверждение 6.1
Пусть точка J пространства итераций относится к /-ому оператору программы и описывается параметрами / . ,..., / . , точка I относится к /-ому оператору и
описывается параметрами /,-,..., /(- . Пусть, например, /< i. Тогда:
•    если пересечение совокупностей номеров у'ь ...,js. и /ь ..., is пусто, то лю­бой параметр У. младше любого параметра 1- ;
•    если пересечение совокупностей номеров /ь ...,js. и /ь ..., is не пусто, то
любой параметр, номер которого входит в пересечение, младше любого параметра, номер которого не входит в пересечение; любой параметр У,,
номер которого не входит в пересечение, младше любого параметра /,-,
номер которого также не входит в пересечение.
Согласно свойствам операторов do, области действия любых двух таких опе­раторов могут быть или вложенными одна в другую, или следующими одна за другой. Поэтому, если в пересечении есть хотя бы один общий параметр, то это означает, что у обоих опорных гнезд есть общий оператор цикла. В этом случае любой оператор цикла, содержащий в своем теле рассматри­ваемый общий оператор и присутствующий в одном из опорных гнезд, дол­жен присутствовать и в другом опорном гнезде. Новому общему оператору цикла соответствует младший параметр.
Это утверждение позволяет ввести отношение порядка в линейном про­странстве итераций. Будем также называть его лексикографическим и обозна­чать тем же символом -<. Рассмотрим любую пару различных точек У, /. Пусть точка У описывается параметрами У,- , ..., У,- , точка / описывается
параметрами У •,..., У- . Будем считать, что J < I, если:
П либо пересечение совокупностей номеров пусто и у < /;
□  либо пересечение совокупностей номеров не пусто, значения всех одина­ковых параметров попарно совпадают и у < /;
□  либо пересечение совокупностей номеров не пусто, значения каких-то младших одинаковых параметров могут попарно совпадать, но среди
Глава 6. Тонкая информационная структура программ
345
одинаковых параметров, значения которых попарно не совпадают, самый младший параметр точки / имеет меньшее значение, чем тот же параметр точки /.
Если рассматривать точки пространства итераций как точки линейного про­странства итераций, то лексикографический порядок в линейном простран­стве итераций автоматически порождает лексикографический порядок в про­странстве итераций. По построению он таков, что J < I тогда и только тогда, когда при последовательной реализации программы срабатывание оператора, соответствующее точке /, будет осуществляться раньше срабаты­вания оператора, соответствующего точке /.
Сказанное означает, что порядок выполнения операций в программе естест­венным образом порождает порядок в обоих пространствах итераций. Этот порядок проверяется конструктивно.
Теперь будем строить ориентированные графы. Две точки пространства ите­раций называются зависимыми, если при срабатываниях соответствующих им операторов имеют место обращения к одной и той же переменной. Со­единим все или какие-то пары зависимых точек дугами, направляя их от лексикографически младших точек к лексикографически старшим. Тем са­мым получим ориентированный граф. Все графы такого вида называются графами зависимостей. Они отличаются друг от друга как типом обращения к переменной (использование ее значения или пересчет ее значения), так и числом дуг. Не каждая пара зависимых точек обязательно будет соединяться дугой. Но если пара зависимых точек соединяется дугой, то с данной дугой однозначно связывается некоторая переменная. С ней сначала что-то делается в операции, соответствующей начальной точке дуги, а затем с ней же что-то делается в операции, соответствующей конечной точке дуги. Это "что-то" в обеих точках может быть как одним и тем же, так и различным.
Заметим, что дуги связывают пары точек пространства итераций, хотя в дей­ствительности они относятся к конкретным входам или выходам операций, задаваемых этими точками. У любой операции выход всегда один, но входов может быть несколько. Если начальная и/или конечная точка дуги связана с каким-либо входом, то для полноты сведений о графе зависимостей такую точку и инцидентные с ней дуги надо бы пометить номером входа. Без до­полнительных оговорок мы будем всюду предполагать, что такая разметка вершин и дуг имеется.
Различают четыре типа зависимостей. Для всех начальных (конечных) точек дуг одного типа имеет место один и тот же тип обращения к переменной, определяющей зависимость. Именно, либо всюду значение переменной ис­пользуется в качестве аргумента при срабатывании оператора, либо всюду результат срабатывания оператора используется для замены значения пере­менной. Будем обозначать использование значения переменной в качестве аргумента символом "in", использование результата для замены значения
346
Часть II. Параллельное программирование
символом "out". Парой in-out (in-in, out-out, out-in) будем обозначать тип зависимости. Левый (правый) символ в паре относится к начальной (конечной) точке дуги зависимости. Традиционно для типов зависимости используются следующие названия:
  out-in — истинная или dataflow-зависимость;
П  in-out — антизависимость;
П  out-out — зависимость по выходу;
П  in-in — зависимость по входу.
Соответственно этому различают четыре типа графов зависимостей. Все дуги любого графа имеют один и тот же тип зависимости.
Каждая точка пространства итераций однозначно определяет переменные, значения которых используются как аргументы при срабатывании соответ­ствующего ей оператора, и переменную, значение которой изменяется. За­висимость может определяться по любой из этих переменных. Число аргу­ментов может быть более одного. Поэтому зависимости типа *-in могут определяться в каждой точке более чем одной переменной независимо от того, какой смысл имеет символ *. Зависимости типа *-out определяются только одной переменной.
Понятия зависимостей и графов зависимостей являются традиционными и используются достаточно давно. Однако долгое время на их основе не уда­валось получить существенные результаты в изучении параллельной струк­туры программ из какого-нибудь представительного класса. Это связано с тем, что само по себе понятие зависимости является достаточно широким, как и понятие влияния. В частности, при большом числе вершин почти всегда будут существовать вершины, в которые будет входить много дуг за­висимостей. Кроме этого, изучению и применению графов зависимостей в значительной мере мешает нетранзитивность отношения зависимости. Ус­пех в использовании графов зависимостей пришел лишь тогда, когда среди всего их многообразия стали применяться в определенном смысле мини­мальные графы.
Для каждого типа зависимостей существует граф, содержащий максималь­ное число дуг. Он получается в том случае, когда дугой соединяется каждая пара точек, зависимых хотя бы по одной переменной. Будем называть этот граф максимальным. Зафиксируем какую-нибудь вершину. Разобьем все множество дуг максимального графа, входящих в данную вершину, на группы. Отнесем к одной группе дуги зависимостей по одной и той же переменной. В каждой группе выберем дугу, у которой начальная вершина ближе всего лексикографически к зафиксированной вершине. Построим ориентирован­ный граф, в котором множество вершин совпадает с множеством точек про­странства итераций, а множество дуг — с выбранным множеством. Будем называть такой граф минимальным снизу.
Глава 6. Тонкая информационная структура программ
347
В соответствии с введенными типами зависимостей всего имеется четыре минимальных графа. В каждую вершину минимального снизу графа типа *-in может входить несколько дуг, но не больше, чем число аргументов, которые имеет соответствующая вершине операция. В каждую вершину ми­нимального графа типа *-out может входить не более одной дуги. Мини­мальный граф типа out-out (граф зависимостей по выходу) представляет совокупность попарно непересекающихся путей. Каждый из путей объединяет множество вершин, в которых пересчитывается одна и та же переменная. На разных путях этого графа пересчитываются разные переменные.
Кроме минимальных снизу можно построить и другие графы, минимальные в каком-то смысле. Снова рассмотрим максимальный граф. Разобьем все множество дуг максимального графа, выходящих из данной вершины, на груп­пы. Отнесем к одной группе дуги зависимости по одной и той же перемен­ной. В каждой группе выберем дугу, у которой конечная вершина ближе всего лексикографически к зафиксированной вершине. Построим ориенти­рованный граф, в котором множество вершин совпадает с множеством точек пространства итераций, а множество дуг — с выбранным множеством. Бу­дем называть такой граф минимальным сверху. Снова имеется четыре типа таких графов.
Легко проверить, что для зависимостей по выходу минимальные сверху и снизу графы совпадают. Совпадают между собой и оба графа для зависимо­стей по входу. Для двух остальных типов зависимостей минимальные сверху и снизу графы различаются. Минимальный сверху граф антизависимостей будем называть графом обратных зависимостей. Его не следует путать с ми­нимальным снизу графом антизависимостей. Это разные графы, хотя во многом они похожи.
В графах, минимальных снизу, конечная вершина дуги однозначно (с точ­ностью до фиксации номера аргумента в зависимостях типа *-in) определя­ет начальную вершину. В графах, минимальных сверху, наоборот: начальная вершина дуги (с точностью до фиксации номера аргумента в зависимостях типа in-*) однозначно определяет конечную. Из каждой вершины мини­мального сверху графа типа in-* может выходить несколько дуг, но не больше, чем число аргументов, которые имеет соответствующая вершине операция. Из каждой вершины минимального сверху графа типа out-* мо­жет выходить не более одной дуги.
Как мы увидим в дальнейшем, между различными типами графов зависимо­стей нет принципиального различия ни по устройству, ни по способам по­строения. Для каждого типа зависимостей наибольший интерес представляет минимальный снизу или сверху граф. Именно эти графы будут предметом наших дальнейших исследований. Говоря далее о графе зависимостей, мы бу­дем иметь в виду минимальный граф любого типа. Ни в каком другом смысле название "граф зависимостей" использоваться не будет. Минимальный снизу
348
Часть II. Параллельное программирование
граф истинных зависимостей, очевидно, есть ничто иное, как граф алгоритма, имея в виду алгоритм, описанный исследуемой программой.
Итак, пусть задана произвольная линейная программа. Количество входных и выходных вхождений переменных в каждом операторе конечно. Поэтому формально минимальный снизу (сверху) граф зависимостей можно описать некоторой конечнозначной функцией Ф, заданной в пространстве итераций. Для любой точки / пространства итераций множество значений функции Ф(7) есть множество тех точек пространства итераций, из которых (в кото­рые) идут дуги графа в точку (из точки) /. В общем случае число значений функции Ф может быть различным в разных точках. Всегда существуют точ­ки, в которых функция Ф не определена.
Принципиальный вопрос состоит в выяснении того, как устроена функция Ф. Пусть в пространстве итераций задана система однозначных функций Ф^. Будем говорить, что граф, описываемый функцией Ф, покрывается сис­темой функций Ф^, если при любых значениях внешних переменных для каждой точки / из пространства итераций, в которой определена функция Ф, найдется такая подсистема функций Ф^ , что множество значений
функции Ф(7) совпадает с множеством значений функций Ф^ (Г) с точно­стью до кратности отдельных значений. Покрывающие функции Ф^ могут обладать каким-нибудь характерным свойством, которое нельзя проверить на дискретном множестве значений аргументов. Например, таким свойством является свойство линейности. В этом случае без дополнительных поясне­ний будем считать, что покрывающие функции определены в линейном пространстве итераций.
Для всех исследований, проводимых в дальнейшем с графами зависимостей, фундаментальное значение имеет следующая теорема.
Теорема 1
Для любой линейной программы любой минимальный снизу или сверху граф зависимостей покрывается конечной системой функций, линейных как по про­странству итераций, так и по пространству внешних переменных программы. Число этих функций не зависит от значений внешних переменных. В линейном пространстве итераций функции определены на линейных многогранниках. Грани многогранников описываются уравнениями, также линейными как по пространству итераций, так и по пространству внешних переменных.
Мы не будем сейчас заниматься доказательством этой теоремы из-за ее сложности. Желающих познакомиться с ней мы отсылаем к статье [63]. Здесь же дадим краткий комментарий.
Утверждение теоремы кажется удивительным, хотя оно и ожидалось в ка­кой-то мере. В самом деле, если программа зависит от внешних перемен­ных, то от их значений будут зависеть и графы зависимостей. Из определе­ния графов не видно, как влияют на их структуру внешние переменные.
Глава 6. Тонкая информационная структура программ
349
Однако совершенно очевидно, что при увеличении значений внешних пе­ременных графы могут увеличиваться неограниченно. Согласно утвержде­нию теоремы вся зависимость покрывающих функций от внешних перемен­ных сосредоточена в их свободных членах и в свободных членах уравнений гиперплоскостей, описывающих грани многогранников. Все свободные чле­ны являются функциями, линейно зависящими от внешних переменных.
Число покрывающих функций зависит от многих факторов. В частности, оно зависит от арифметической природы коэффициентов линейных выра­жений и числа исполняемых операторов программы. На практике число по­крывающих функций оказывается не очень большим. Исследование реаль­ных программ показало наличие двух особенностей. Во-первых, общее число линейных покрывающих функций пропорционально числу операто­ров присваивания в программе. Коэффициент пропорциональности не пре­восходит нескольких единиц. На меньшее число функций рассчитывать не приходится, если операторы связаны друг с другом. Во-вторых, в подав­ляющем большинстве случаев система покрывающих функций описывает граф зависимостей абсолютно точно. Именно, почти всегда для любой по­крывающей функции Ф^- и любой целочисленной точки / из ее области оп­ределения пара точек Ф^(^) и / определяет дугу графа зависимостей. Нам известно лишь несколько реальных примеров, в которых системы покры­вающих функций задают не граф зависимостей, а какое-то его расширение.
В силу фундаментального значения теоремы 1 для изучения структуры по­следовательных программ мы дадим ей специальное название "теорема об информационном покрытии ".
Если программа принадлежит линейному классу, то согласно теореме 1, граф описываемого программой алгоритма покрывается системой линейных функций, заданных на линейных многогранниках. Программа может фор­мально не принадлежать линейному классу, но граф алгоритма может, тем не менее, покрываться таким же образом. Как мы увидим в дальнейшем, информационная структура соответствующих алгоритмов исследуется доста­точно полно. По аналогии с программами подобные алгоритмы будем назы­вать линейными или принадлежащими линейному классу. Один из фундамен­тальных вопросов связан с тем, как распознавать линейные алгоритмы по их записям.
Изучение информационной структуры алгоритмов и программ есть, в пер­вую очередь, изучение структуры всех максимальных графов зависимостей. Значение минимальных графов зависимостей для решения данной задачи оп­ределяется двумя фактами. Согласно теореме 1 все минимальные графы могут быть явно описаны конечным числом линейных функций и, следова­тельно, могут быть эффективно исследованы. Кроме этого, любая дуга лю­бого максимального графа может быть представлена одним или двумя путя­ми минимальных графов. Точнее, справедливо
350
Часть II. Параллельное программирование
Утверждение 6.2
Если две зависимые точки пространства итераций не связаны дугой минималь­ного графа, то они связаны:
•     путем минимального графа зависимостей по входу для зависимых по входу точек;
•     путем минимального графа зависимостей по выходу для зависимых по вы­ходу точек;
•     путем, составленным из пути минимального графа зависимостей по выходу и дуги минимального снизу графа истинных зависимостей, для истинно за­висимых точек;
•     путем, составленным из пути минимального графа зависимостей по входу и дуги минимального снизу графа антизависимостей, для антизависимых точек.
Доказательства всех фактов проводятся аналогично. Рассмотрим его, напри­мер, для случая истинно зависимых точек. Пусть точки х, у истинно зави­симы. Допустим, что операция, соответствующая точке х, перевычисляет значение переменной а, а операция, соответствующая точке у, использует значение переменной а в качестве одного из своих аргументов. По предпо­ложению х < у. Рассмотрим множество всех точек jq, ..., хр таких, которые лексикографически находятся между х и у и в которых перевычисляются значения переменной а. Не ограничивая общности можно считать, что х -< х\ -< Х2 ■<...-< хр -< у. Если множество точек х\, ..., хр пустое, то точки х, у должны быть связаны дугой истинной зависимости. Если же оно не пустое, то точки х, jq, ..., хр связаны путем минимального графа по выходу, а точки хр, у дугой истинной зависимости.
Возможность представления графов зависимостей линейными функциями делает актуальной задачу разработки эффективных алгоритмов вычисления по тексту программы коэффициентов этих функций и коэффициентов ли­нейных неравенств, описывающих области определения линейных функций. Данной задаче будут посвящены ближайшие исследования. Для всех типов графов зависимостей они проводятся одинаково с точностью до небольших терминологических различий. Для определенности мы будем исследовать граф алгоритма. В отношении других графов ограничимся минимальными комментариями.
Вопросы и задания
1.   Рассмотрим линейное тесно вложенное гнездо циклов с телом, состоящим из одного оператора-преобразователя. Покажите, что в этом случае понятия опор­ного многогранника, опорной области и линейного пространства итераций сов­падают и представляют один линейный многогранник.
2.   Верно ли утверждение п. 1, если тело гнезда циклов состоит из нескольких опе­раторов-преобразователей?
Глава 6. Тонкая информационная структура программ
351
3.   Укажите характерный признак линейного пространства итераций для линейного тесно вложенного гнезда циклов с несколькими операторами.
4.   Пусть в линейном замкнутом многограннике V задан лексикографический по­рядок. Постройте какую-нибудь функцию f[x), для которой при любых точках х, у е К выполняются соотношения
> 0, если х >- у;
Ах) - f(y) - = 0, если х = у;
< 0, если х -< у.
5.   Является ли функция Дх) из п. 4 непрерывной в многограннике V.
6.   Будет ли функция Дх) из п. 4 задавать лексикографический порядок во всем пространстве?
7.   Как изменяется функция Дх) из п. 4, если размеры многогранника V неограни­ченно увеличиваются?
8.   Обратите внимание на то, что все дуги графа алгоритма, выходящие из одной вершины, связаны с одной и той же переменной.
9.   Что означает пустота одного или нескольких типов графов зависимостей с точ­ки зрения параллельных вычислений?
10.   *Линейные многогранники, на которых заданы покрывающие функции в тео­реме 1, зависят от внешних переменных. Как меняется взаимное расположение многогранников при изменении значений внешних переменных?
11.   "Попробуйте построить необходимый и достаточный критерий, позволяющий по виду программы устанавливать принадлежность описываемого ею алгоритма ли­нейному классу, или докажите невозможность существования такого критерия.
12.   Докажите, что для графа зависимостей по выходу любые две покрывающие функции целочисленно не пересекаются, как по областям определений, так и по областям значений.
§ 6.4. Простые и элементарные графы
Рассмотрим произвольную программу из линейного класса. Выберем какую-нибудь дугу графа алгоритма и зададимся следующим вопросом: "Все ли элементы программы определяют выбранную дугу?". Цель этого вопроса со­стоит в попытке расщепить программу на такие фрагменты, чтобы каждый из них определял какое-то подмножество дуг. Если ответ на вопрос будет отрицательным, то задачу построения дуг графа алгоритма для исходной программы можно будет свести к последовательности аналогичных задач для меньших программ. Можно надеяться, что эта процедура упростит по­строение графа.
Выберем в программе любой исполняемый оператор Ft, и пусть точка / про­странства итераций принадлежит опорной области Vi. Каждая дуга графа алгоритма связана с вполне конкретной переменной. Если дуга входит в
352
Часть II. Параллельное программирование
точку /, то соответствующая дуге переменная однозначно определяется этой точкой и каким-то входом оператора /}. Вообще говоря, разные входы опре­деляют в точке / разные переменные, но в частных случаях переменные могут совпадать. Чтобы найти начальную точку дуги, нужно, согласно опре­делению минимального снизу графа, рассмотреть те точки, в которых пере-считывается значение переменной, и среди них выбрать точку, ближайшую к / лексикографически снизу.
Таким образом, любая выбранная дуга графа алгоритма исходной програм­мы будет входить в множество дуг графа алгоритма любой другой програм­мы, если для нее:
□  пространство итераций является подмножеством пространства итераций исходной программы;
□  лексикографический порядок в пространстве итераций совпадает с ис­ходным порядком;
□  пространство итераций содержит конечную точку дуги, и в этой точке используется соответствующая выбранной дуге переменная;
□  пространство итераций содержит все точки, в которых эта переменная пересчитывается, и которые лексикографически младше конечной точки дуги.
Для определения множества дуг графа алгоритма не нужны сведения о функциональном содержании вершин. Значение имеет только то, какие пе­ременные используются на входах и выходах соответствующих вершинам операторов присваивания. Построим, исходя из данной программы, семей­ство формальных программ по следующим правилам. Зафиксируем какой-нибудь вход какого-нибудь оператора. Он определяет имя переменной, про­стой или с индексами. Вычеркнем все правые части всех исполняемых опе­раторов. Оставим только в правой части выбранного оператора переменную, описывающую фиксированный вход. Вычеркнем в левых частях исполняе­мых операторов все вхождения всех переменных, имеющих имя, отличное от имени переменной в фиксированном входе. Теперь вычеркнем все опера­торы, у которых оказались пустыми левые и правые части, все ветвления, которые охватывают только пустые операторы, и все циклы с пустыми тела­ми и телами, содержащими только условные операторы. Если окажется, что после всех вычеркиваний какой-нибудь условный оператор осуществляет передачу управления на несуществующий оператор, то заменим ее переда­чей управления на ближайший снизу из оставшихся операторов.
С формальной точки зрения оставшаяся часть программы не является "фор-транной" программой, т. к. левые и правые части некоторых операторов могут быть пустыми. Мы можем сделать ее "фортранной" программой, если пустые части заменим любыми переменными. Единственное требование со­стоит в том, чтобы никакая переменная справа не совпадала с переменной слева.
Глава 6. Тонкая информационная структура программ
353
Любая программа такого типа называется простой, а ее граф — простым графом. Каждая программа из линейного класса порождает множество про­стых программ. Их ровно столько, сколько вхождений всех переменных в правые части операторов присваивания.
Чтобы сравнить графы алгоритмов исходной программы и порожденной ею простой программы, полезно сначала сравнить их пространства итераций. Линейное пространство итераций исходной программы есть объединение всех опорных областей V',•. Если у программы вычеркивается 5-ый оператор, то из пространства итераций исключается опорная область Vs. Поэтому без ограничения общности можно считать, что линейное пространство итера­ций простой программы есть объединение опорных областей тех операто­ров, следы от которых остались в программе. Пространство итераций про­стой программы снова есть множество всех целочисленных точек ее линейного пространства. Очевидно также, что лексикографический порядок в линейном или дискретном пространстве итераций простой программы, порожденный самой простой программой, совпадает с лексикографическим порядком исходной программы, рассматриваемым лишь в этих пространст­вах. Заметим еще, что пространство итераций каждой простой программы содержит все точки, в которых пересчитываются переменные с зафиксиро­ванным ранее именем и не пересчитываются никакие другие переменные.
Теперь сравним процесс построения графа алгоритма исходной программы и графов алгоритмов простых программ. Зафиксируем какой-нибудь вход какого-нибудь оператора присваивания. Это однозначно фиксирует какую-нибудь простую программу. Множества точек пространств итераций обеих программ, в которых определен фиксированный вход, совпадают. Более то­го, в каждой точке обе программы определяют одну и ту же переменную. Множества точек пространств итераций обеих программ, в которых пере-считывается любая общая переменная, также совпадают. Никаких других переменных, кроме переменных с выбранным именем, простые программы не пересчитывают. Поэтому имеет место общее
Утверждение 6.3
Для любой программы из линейного класса минимальный граф зависимостей любого типа есть объединение графов того же типа всех простых программ.
Определение простой программы немного меняется, смотря по тому, какой рассматривается тип зависимостей. Для истинных зависимостей простая программа имеет один фиксированный вход и все выходы, в которых пере­считываются переменные с именем, фиксированным на входе. Именно этот вариант мы только что обсуждали. Для антизависимостей простая програм­ма имеет один фиксированный выход и все входы, в которых используются переменные с именем, фиксированным на выходе. Для зависимостей по вы­ходу простая программа имеет один фиксированный выход и содержит все
354
Часть II. Параллельное программирование
остальные выходы, в которых пересчитывается переменная с фиксирован­ным именем. Для зависимостей по входу простая программа имеет один фиксированный вход и содержит все остальные входы, в которых использу­ется переменная с фиксированным именем. С точностью до этих различий все остальные рассуждения о расщеплении минимальных графов на простые остаются без изменений.
Процесс расщепления линейных программ и соответствующих им мини­мальных графов зависимостей можно продолжить. Снова рассмотрим его на примере графа алгоритма. Вычеркнем в простой программе все операторы присваивания, кроме оператора, имеющего фиксированный вход, и какого-нибудь оператора, пересчитывающего переменную с фиксированным име­нем. В частности, второй оператор может совпадать с первым. Теперь вы­черкнем все циклы с пустыми телами и телами, содержащими только услов­ные операторы. Если окажется, что после всех вычеркиваний какой-либо условный оператор осуществляет передачу управления на несуществующий оператор, то заменим ее передачей управления на ближайший снизу из ос­тавшихся операторов.
Любая программа такого типа называется элементарной, а ее граф — элементарным графом. Элементарная программа содержит не более двух операторов присваивания, имеющих в совокупности одну и ту же перемен­ную на единственном входе и единственном выходе. Кроме этого, она мо­жет содержать какое-то число условных операторов перехода, в том числе, даже в случае одного исполняемого оператора. Каждая простая программа порождает столько элементарных программ, сколько в ней операторов, пе­ресчитывающих переменные с фиксированным именем. Легко видеть, что любая дуга графа алгоритма исходной программы является дугой графа ал­горитма одной из элементарных программ. Следовательно, справедливо
Утверждение 6.4
Для любой программы из линейного класса минимальный граф зависимостей любого типа есть остовный подграф объединения минимальных графов того же типа всех элементарных программ.
Напомним, что подграф называется остовным, если множество его вершин совпадает с множеством вершин графа.
Итак, отыскание минимального графа зависимостей для программы из ли­нейного класса сводится к отысканию аналогичных графов для элементарных программ. Эти программы содержат не более двух операторов и также при­надлежат линейному классу. Они очень просто устроены. Поэтому появляется уверенность в возможности построения для них минимальных графов.
Подчеркнем еще раз, что рассмотренные в данном параграфе расщепления линейных программ на простые и элементарные носят формальный характер. Компоненты расщепления могут даже не быть программами в строгом
Глава 6. Тонкая информационная структура программ
355
смысле этого слова, т. к. некоторые части каких-то операторов могут просто отсутствовать. Такие расщепления формально не связаны с расщеплениями исходного алгоритма на более простые алгоритмы. Они связаны лишь с рас­щеплениями различных графов, относящихся к исходному алгоритму, на более простые графы. Расщепление графов сопровождается расщеплением вершин, если соответствующие операции имеют более одного аргумента. При объединении графов расщепленные вершины сливаются.
Вопросы и задания
1.   Внимательно проанализируйте примеры 6.1—6.3 из § 6.8 с точки зрения процедуры расщепления программ и графов на простые и элементарные.
2.   Как зависит расщепление в этих примерах от типа определяемого графа зависи­мостей?
§ 6.5. Лексикографический порядок и /.-свойство матриц
В общем случае линейное пространство итераций представляет собой слож­ное множество, в котором введен лексикографический порядок. Рассмотрим этот порядок более внимательно в одном арифметическом пространстве размерности п. Будем считать, что для точек х = (х\, , хп), у = (у\, , у„) вы­полняется отношение х -< у, если для некоторого s, 1 < s < п, имеют место соотношения
Х{ = У\, ...,XS- i= ys- i,xs< ys.
Это определение лексикографического порядка полностью согласовано с определением, данным в §6.3.
Утверждение 6.5
Пусть в вещественном арифметическом пространстве в двух системах коорди­нат введены отношения лексикографического порядка. Для того чтобы эти от­ношения совпадали на всем пространстве, необходимо и достаточно, чтобы матрица перехода от одной системы координат к другой была левой треуголь­ной с положительными диагональными элементами.
Обозначим через S~l матрицу перехода от старой системы координат к но­вой. Пусть х, у — любые различные векторы пространства. В новой системе координат они будут задаваться векторами Sx и Sy. Заметим, что условия х -< у и Sx -< Sy эквивалентны условиям (х — у) -< 0 и S(x — у) -< 0. В свою очередь, последние условия эквивалентны отрицательности первых ненуле­вых координат векторов х — у и S(x — у). Рассмотрим множество Zвекторов, имеющих первую ненулевую координату отрицательной. Теперь надо пока­зать, что для того чтобы множество Z было инвариантно к умножению на
356
Часть II. Параллельное программирование
матрицу S, необходимо и достаточно, чтобы матрица S была левой треуголь­ной с положительными диагональными элементами.
Достаточность очевидна. Предположим, что из z е Z следует Sz е Z. Допус­тим, что для некоторого у > 1 элемент sy первой строки матрицы S отличен от нуля. Возьмем подмножество векторов, у которых первая координата равна — 1, у'-я координата произвольная, а все остальные равны нулю. Это подмножество принадлежит Z. Однако при умножении векторов подмноже­ства на матрицу S первая координата образов не может оставаться неполо­жительной в силу неравенства нулю элемента sy. Следовательно, множество Z не является инвариантным к умножению на матрицу S. Матрица S невы­рожденная. Поэтому диагональный элемент первой строки матрицы S будет положительным, а все ее элементы справа от диагонали — нулевыми. Пусть для / > 1 первые / строк матрицы S обладают нужным свойством. Допустим, что для некоторого у > / + 1 элемент s,+ \t j матрицы S отличен от нуля. Возьмем подмножество векторов, у которых (/ + 1)-я координата равна — 1, у'-я координата произвольная, а все остальные равны нулю. При умножении векторов подмножества на матрицу S первые / координат образов будут всегда нулевыми. Далее, повторяя почти дословно рассуждения, касающиеся первой строки, показываем, что (/ + 1)-я строка матрицы S также имеет нужный вид. Аналогичный вид будет иметь матрица S~l.
Таким образом, если при сохранении лексикографического порядка на всем пространстве мы хотим найти систему координат, обладающую каким-либо дополнительным свойством, то выбор систем, вообще говоря, не очень ши­рок. Именно, мы можем гарантированно осуществлять поиск только среди тех систем координат, которые связаны друг с другом преобразованиями с левыми треугольными матрицами, имеющими положительные диагональные элементы.
Среди разных задач, связанных с лексикографическим порядком, довольно часто мы будем иметь дело с задачей поиска в замкнутой области точки, старшей в лексикографическом отношении. Будем называть такую точку точкой лексикографического максимума. Пусть область D, в которой ищется точка лексикографического максимума, является ограниченным многогран­ником и задается системой линейных неравенств
(а,, х) < а,;
(6.3)
т, х) < от.
Здесь а\, ..., атвекторы размерности п и т> п. Обозначим точку макси­мума через xq. Точка максимума единственная и должна быть одним из уг­лов многогранника. В самом деле, если xq не является углом, то существует принадлежащий многограннику отрезок прямой, для которого точка xq бу­дет внутренней. Но на любом отрезке лексикографический максимум дос-
Глава 6. Тонкая информационная структура программ
357
тигается на одном из концов. Следовательно, точка х0 действительно един­ственная и должна быть одним из углов. Поэтому для отыскания точки лек­сикографического максимума многогранника в общем случае нужно пере­брать лишь его углы. Угловые точки являются решениями систем линейных алгебраических уравнений вида
(а,- , х) = ст,- ;
(6.4)
с невырожденной матрицей. Кроме этого, они должны удовлетворять систе­ме неравенств (6.3).
В наших задачах системы неравенств типа (6.3) никогда не будут иметь большие размеры. Поэтому поиск точки лексикографического максимума в многограннике вроде бы можно осуществлять на основе прямого исследова­ния всех его углов. Для этого нужно лишь перебрать все возможные систе­мы линейных алгебраических уравнений вида (6.4) и среди принадлежащих многограннику их решений выбрать лексикографически старшее. Однако прямое решение данной задачи возможно только тогда, когда можно осуще­ствить сравнение двух решений. В наших задачах правые части а, будут за­висеть от внешних переменных, значения которых неизвестны. В этом слу­чае сравнение решений приводит к появлению большого числа новых неравенств относительно этих переменных. Поэтому мы постараемся найти критерий отбора систем (6.4), основанный на проверке подходящих свойств их матриц.
Не ограничивая общности, можно считать, что точка х0 является решением системы линейных алгебраических уравнений
ь х) = сть
(6.5)
\Gn, X) — On
с невырожденной матрицей. Вычитая из первых п неравенств (6.3) соответ­ствующие равенства (6.5) и вводя обозначение у = х — х0, получим систему неравенств
(«ь У) < 0;
(6.6)
(««, У) < о. Покажем сначала, что если в угловой точке xq многогранника (6.3) достигается лексикографический максимум, то в этой точке существует система из п ли­нейных алгебраических уравнений вида (6.5) такая, что все решения соответ­ствующей системы неравенств (6.6) будут лексикографически меньше нуля.
358
Часть II. Параллельное программирование
Пусть угловая точка х0 является решением системы линейных алгебраиче­ских уравнений
ь х) = сть
(flp, х) = ир, где р > п. Система неравенств, аналогичная (6.6), такова:
(«ь У) < 0;
р, у) < 0.
Если в точке xq достигается лексикографический максимум, то в этом и только в этом случае все решения системы неравенств будут лексикографи­чески меньше нуля. Но в общем случае р > п и не ясно, всегда ли найдется подсистема из п неравенств, обладающая аналогичным свойством.
Возьмем любую левую треугольную матрицу S с единичными диагональны­ми элементами и сделаем замены
Z=Sy, b,= (S-^a,
для 1 < / < р. Система неравенств
(buz)<0;
Р, Z) < о
с векторами bt лексикографически эквивалентна системе с векторами щ. Именно, если у -< 0, то z -< 0 и наоборот. Это следует из утверждения 6.5. Подберем матрицу S и перенумеруем векторы at так, что векторы bt будут иметь специальный вид.
Подстановка в систему неравенств любого вектора, который лексикографи­чески больше нуля, должна привести хотя бы в одном неравенстве к замене знака < на знак > . Возьмем в качестве у вектор, у которого все координаты нулевые, кроме последней, которая равна 1. Лексикографически он больше нуля. Результат его подстановки говорит о том, что среди последних коор­динат векторов щ есть хотя бы одна положительная. Не ограничивая общно­сти, можно считать, что / = п. Выполнение этого условия можно обеспечить перенумерацией векторов щ. Пусть в матрице S среди внедиагональных эле­ментов ненулевыми могут быть лишь элементы последней строки. Очевид­но, что матрицу S можно подобрать так, чтобы все координаты вектора Ъп, кроме последней, стали нулевыми. Последняя координата вектора Ьп совпа­дает с последней координатой вектора а„ и, следовательно, является поло-
Глава 6. Тонкая информационная структура программ
359
жительной. Предположим, что вектор а„ уже имеет такой же вид, как и по­лученный вектор Ъп.
Возьмем далее в качестве у вектор, у которого все координаты нулевые, кроме предпоследней, которая равна 1. Лексикографически он также боль­ше нуля. Результат его подстановки в систему неравенств говорит о том, что среди предпоследних координат векторов ah в предположении, что вектор а„ имеет указанный выше вид, есть хотя бы одна положительная. Согласно договоренности, предпоследняя координата вектора а„ нулевая. Поэтому можно считать, что i = п — 1. Выполнения этого условия можно добиться перенумерацией векторов ah не затрагивая вектор а„. Пусть в матрице S сре­ди внедиагональных элементов ненулевыми могут быть лишь элементы предпоследней строки. Вектор а„ при умножении на такую матрицу S не изменится. Предпоследнюю строку матрицы S можно подобрать так, что все координаты вектора Ь„ - \, кроме двух последних, станут нулевыми. Послед­ние две координаты вектора b„ - \ будут совпадать с последними двумя ко­ординатами вектора ап - \ и, следовательно, предпоследняя координата бу­дет положительной.
Продолжая описанный процесс, заключаем, что с помощью перенумерации векторов-столбцов щ и выбора матрицы S можно получить эквивалентную систему неравенств, в которой векторы-столбцы Ь\, ..., Ь„ образуют левую треугольную матрицу с положительными диагональными элементами. Век­торы Ь\, ..., Ь„ линейно независимы, их число равно п и система неравенств с такими векторами имеет только такие решения, которые лексикографиче­ски меньше нуля. Что и требовалось показать.
Теперь мы хотим найти некоторое характерное свойство векторов щ, ..., а„, если система неравенств (6.6) имеет лишь такие решения, которые лекси­кографически меньше нуля.
Для дальнейшего нам потребуется утверждение, называемое леммой Фарка-ша—Минковского [48]. Мы приведем ее без доказательства, изменив фор­мулировку применительно к нашим исследованиям.
Утверждение 6.6 (лемма Фаркаша—Минковского)
Пусть задана система векторов Ьь ..., Ьр. Рассмотрим множество ^решений q системы неравенств (bh q) < 0, 1 < / < р. Тогда множество всех векторов Ь, для которых выполняются неравенства (b, q) < 0 при всех q е К и только оно пред-ставимо в виде
7 = 1
где X, > 0, !</</>.
360
Часть II. Параллельное программирование
Пусть ек есть к-й координатный вектор, т. е. все его координаты нулевые, кроме к-&, которая равна 1. Обозначим а„ + 2к- \ = еь ап + 2к = ~ ек и Рас~ смотрим системы неравенств
(я,, и) <0;
(а„, и) < 0;
(6.7) К + ь и) <0;
К + гь ") ^ 0. Для каждого к = 0, 1, ..., л — 1 добавление 2& последних неравенств выделя­ет из множества решений системы (6.6) подмножество, для которого первые к координат решений являются нулевыми. По построению все решения сис­темы первых п неравенств из (6.7) лексикографически меньше нуля. Поэто­му при каждом к = 0, 1, ..., л — 1 любое решение системы (6.7) имеет непо­ложительными первые к + 1 координат. Это означает, в частности, что для каждого к = 0, 1, ..., я — 1 выполняется неравенство (е^ + \, и) < 0 для всех решений системы неравенств (6.7). В соответствии с утверждением 6.6 за­ключаем, что
п +
ек + 1 - 2^к1 а''                                         (6.8)
где 7i'c +1) > 0 для всех /, к.
Пусть А есть матрица, столбцами которой являются векторы щ, ..., ап. По построению матрица А невырожденная. Обозначим через д/-1) столбец мат­рицы А~] с номером /. Будем считать, что по определению а</_1^ = 0. Умно­жая равенство (6.8) слева на матрицу А-1, находим, что
^ХьГ'Ч + Х^ч1'-                (6-9)
/ = 1                          / = 0
Здесь vfk + !) — какие-то числа.
Если любое решение системы неравенств (6.6) лексикографически меньше нуля, то для столбцов матрицы А~] имеет место представление (6.9). В част­ности, все элементы первого столбца матрицы А~1 неотрицательные. Любой столбец, кроме первого, получается как сумма линейных комбинаций пре­дыдущих столбцов и некоторого вектора с неотрицательными координатами. Отсюда следует, что в каждой строке матрицы А~1 первый ненулевой эле­мент является положительным. Пусть теперь столбцы матрицы А~1 имеют представление (6.9). Покажем, что любое ненулевое решение системы нера­венств (6.6) лексикографически меньше нуля.
Глава 6. Тонкая информационная структура программ
361
Предположим, что ненулевой вектор у является решением системы нера­венств (6.6). Тогда
(щ,у) = 5,;
(<*„, У) = 5„,
где все 5г < 0. Представим эту систему в матрично-векторной форме А'у = 5, где 5 = (5ь ..., 5Й). Так как векторы щ линейно независимы, то вектор 5 не равен нулю. Имеем у = (А')~]5 = (А~])'д. Пусть у = {у\, ..., у„). Представляя координаты вектора у через элементы матрицы А~] и вектор 5, находим, что
(о,(-1), 5)=У];
и<-1), 5) = у„.
Умножая соотношение (6.9) справа скалярно на 5 и обозначая у$ =
= (ci()(~]\ 5), имеем
к
yk + l=^ + i + JJvf + [)yJ                                  (6.10)
7 = 0
для к = 0, 1, ..., я — 1. Ясно, что у$ = 0 и число
?A + i = I>f + 1)8^0.                              (6.11)
i = 1
Вектор у ненулевой по предположению. Следовательно, не все его коорди­наты нулевые. Согласно (6.10), (6.11) первая ненулевая координата вектора у отрицательная, т. е. у -< 0.
Покажем теперь, что столбцы любой матрицы А~] имеют представление (6.9), если только в каждой ее строке первый ненулевой элемент положи­тельный. Рассмотрим столбец «["/. В предыдущих столбцах с номерами
1, 2, ..., к, вообще говоря, могут находиться первые ненулевые элементы каких-то строк. Пусть они находятся в столбцах с номерами k < h < ••• < hk- к и' соответственно, в строках с номерами ly, lj, ..., /s .
Все координаты, кроме координат с номерами 1\, ..., I , для всех векторов a\(~l\ ..., ak(~l\ в том числе, векторов яН\ ..., аН-1 являются нулевыми.
'l                            lsk
Напомним, что все первые ненулевые элементы в строках матрицы положи­тельные. Поэтому всегда можно подобрать такие числа
v^ + [),..., v^ +!), начиная с v(:k + [\ что в векторе
h                           hk                                              hk
-дИ) +v<* + 1)oH) +v(* + 1)fl<-1) +... + v{k + i)a{rl)                 (6-12)
362
Часть II. Параллельное программирование
все координаты будут неположительными. Действительно, в векторе — aj~+(, кроме возможных ненулевых координат с номерами 1\, /2, ..., /? могут быть
ненулевыми те координаты, которые соответствуют первым ненулевым эле­ментам строк, находящимся в (к + 1)-м столбце матрицы А~\ если, конеч­но, они там есть. Но эти элементы отрицательные по условию. Они не ме­няются при прибавлении к вектору — а[~/ любой линейной комбинации векторов ai ,...,at. За счет выбора коэффициента v^ + 1)b векто-
ре - я£~+\ + v\k + ^а,--1^ можно дополнительно сделать неположительными те
sk                 sk
координаты, номера которых совпадают с номерами строк, чьи первые не­нулевые элементы находятся в г -м столбце. Подбираем аналогичным спо­собом коэффициенты v,- _b...,v, делая в сумме (6.12) неположительными
sk
координаты с номерами /], /2, ..., /?; и, следовательно, все координаты. За
счет подбора неотрицательных коэффициентов \[к + *) равенство (6.9) те­перь удовлетворяется тривиально.
Будем говорить, что невырожденная матрица А обладает L-свойсшвом, если в каждой строке матрицы А~] первый ненулевой элемент положительный. Очевидно, что любая правая треугольная матрица с положительными диаго­нальными элементами обладает Z-свойством. Перестановка столбцов матри­цы и умножение слева на любую правую треугольную матрицу с положи­тельными диагональными элементами сохраняет Z-свойство. Несмотря на простоту этих фактов, они оказываются полярными при исследовании кон­кретных матриц относительно Z-свойства. Проведенные исследования пока­зывают, что справедливо
Утверждение 6.7
Точка лексикографического максимума в многограннике (6.3) совпадает с тем из его углов, в котором описывающая его матрица А имеет обратную и облада­ет /.-свойством.
Заметим, что критерий отбора систем (6.4), основанный на проверке Z-свойства их матриц, может выделить более одной системы. Чтобы опреде­лить среди них нужную, необходимо решить все выделенные системы и среди их решений взять то, которое принадлежит многограннику. Если пра­вые части неравенств (6.3) зависят от внешних переменных, то проверка принадлежности решения многограннику снова приводит к появлению но­вых неравенств относительно этих переменных. Однако таких неравенств уже немного, т. к. лишь немного матриц среди проверяемых обладает Z-свойством.
Глава 6. Тонкая информационная структура программ
363
Вопросы и задания
1.   Среди углов многогранника (6.3) может существовать несколько углов, которые описываются матрицами с Х-свойством. Как это согласуется с единственностью точки лексикографического максимума в многограннике?
2.   Докажите, что при изменении правых частей неравенств (6.3) любое ребро мно­гогранника не меняет своего векторного направления до тех пор, пока оно не стягивается в точку.
3.   Пусть правые части неравенств (6.3) являются линейными функциями параметра /. Докажите, что при изменении / любое ребро многогранника может стягиваться в точку не более одного раза.
4.   Докажите, что в условиях п. 3 существует такое t0, что для t > t0 длина любого ребра многогранника либо остается постоянной, либо асимптотически является линейной функцией от t.
5.   *С учетом полученных сведений о многогранниках снова вернитесь к вопросу п. 10 из § 6.3.
6.   Пусть правые части неравенств (6.3) являются линейными функциями координат вектора /. Обратите внимание, что любой угол многогранника есть также линей­ная вектор-функция от этих координат.
7.   В условиях п. 6 постройте модель изменения многогранника (6.3) при изменении координат вектора t, если, например, сами координаты принадлежат некоторому усеченному конусу.
§ 6.6. Построение минимальных графов зависимостей
При построении минимальных графов зависимостей будем руководствоваться теоремой 1. Будем считать, что мы умеем строить минимальные графы, если удастся разработать алгоритм вычисления коэффициентов линейных покры­вающих функций и коэффициентов, определяющих области их задания. Снова ограничимся рассмотрением графа алгоритма.
Поставим сначала математически задачу нахождения минимального графа зависимостей для элементарной программы. Линейное пространство итера­ций элементарной программы в общем случае состоит из двух опорных об­ластей: области V/, которая связана со входом одного оператора, и облас­ти Vj, которая связана с выходом другого оператора. Если элементарная программа содержит только один оператор, то опорные области V; и Vj совпадают. Пусть дуга идет из вершины / е Vj в вершину I е V-t. Это оз­начает, что в вершине / вычисляется значение некоторой переменной, а в вершине I это же значение этой же переменной используется. Более того,
364
Часть II. Параллельное программирование
эта переменная не перевычисляется ни в одной вершине, которая лексико­графически старше /, но младше I.
Пусть элементарная программа описывается переменной с индексами. Пе­ременные на выходе и входе имеют одно и то же имя, но по форме выраже­ний могут различаться индексами. Обозначим через p(J, N) вектор, коорди­натами которого являются индексы выходной переменной, через q(J, N) — вектор индексов входной переменной. В общем случае для программы из линейного класса векторные функции p(J, N) и q(J, N) являются неодно­родными и линейными как по векторам параметров циклов I, /, так и по вектору N внешних переменных программы. Если в действительности пе­ременная является простой, то без ограничения общности можно считать p(J, N) = q(J, TV) = 0. Обозначим через Q/, Qy множества целочисленных точек /, / соответственно в опорных областях V-t, Vj.
Зафиксируем входную вершину I. Это однозначно определяет вектор индек­сов q(J, N). Чтобы из вершины / выходила дуга в вершину I, необходимо, чтобы вектор q(J, N) совпадал с вектором индексов p(J, TV). Поэтому первое, что мы должны сделать, — это потребовать выполнение равенства
p(J, N) = q(J, N).                                         (6.13)
В действительности задача значительно сложнее. Нужно не только найти какие-то решения уравнения (6.13) относительно /, но и добиться выполне­ния условий
J < I, J е Q.j, I е Q7.                                      (6.14)
Задача (6.13) при условии (6.14) может иметь неединственное решение. Од­нако на самом деле нужно найти на множестве всевозможных решений / из (6.13), (6.14) то решение J0, которое является лексикографически самым старшим. Такое решение
/о = lex.max/                                            (6.15)
уже единственное.
Решение Jq = Jq(I, N) задачи (6.13)—(6.15) зависит как от вектора парамет­ров циклов У", так и от вектора N внешних переменных программы. Напом­ним, что по определению линейной программы множество допустимых век­торов N принадлежит некоторой совокупности многогранников, конкретных для каждой определенной программы. Для реальных программ многогран­ники часто оказываются неограниченными. Множество допустимых векто­ров I также принадлежит некоторой совокупности многогранников. Эти многогранники всегда ограниченные и, как правило, зависят от вектора внешних переменных N, значения которых неизвестны.
В целом задача (6.13)—(6.15) является целочисленной, т. к. целочисленными являются векторы I, J, N, коэффициенты линейной системы (6.13) и ко-
Глава 6. Тонкая информационная структура программ
365
эффициенты линейных многогранников из Vh К,-. Пусть вектор ^фикси­рован. Если при каком-то I функция Jq(I, TV) определена, то ее значение задает начальную точку дуги, идущей в точку I. Если в точке / функция Jo(I, N) не определена, то в точку / не идет дуга. Именно целочисленность делает решение задачи (6.13)—(6.15) особенно трудным.
Теперь снова вернемся к задаче (6.13)—(6.15). Заменим эту задачу ее непре­рывным аналогом, руководствуясь следующими идеями. Если решение Jq(I, N) непрерывной задачи при допустимых целочисленных векторах I, N окажется целочисленным, то оно будет совпадать с решением дискретной задачи (6.13)—(6.15). По форме непрерывная задача почти совпадает с рас­смотренной в §6.5 задачей отыскания лексикографического максимума. Нужно лишь заменить каждое равенство из (6.13) системой противополож­ных неравенств и аккуратно свести к системе неравенств условие J < I. Кроме этого, нужно принять во внимание, что многогранников может быть несколько. После этого для решения непрерывной задачи можно использо­вать аппарат матриц с Х-свойством.
Пусть вершина I соответствует оператору присваивания с номером /, вер­шина / — оператору с номером j. Обозначим через У, , ..., У,- и /•,...,/
1                   qi               J\                  Jpj
координаты этих вершин. Предположим для определенности, что первые s параметров циклов, соответствующих опорным гнездам вершин I, J, явля­ются общими. Если общих параметров нет, то проверка условия J < I ста­новится тривиальной и сводится к сравнению i,j. Пусть, например, i>j. Построим систему альтернативных многогранников. Первый из них описы­вается плоскостью:
J\            h
(6.16)
JL
и рассматривается только в случае / > j. Все остальные альтернативные многогранники рассматриваются во всех случаях. Второй многогранник бу­дет таким:
J\            h
(6.17)
7.V-1                     <v-l '
В каждом последующем многограннике число описывающих его равенств будет уменьшаться. Неравенство всегда будет одно.
366
Часть II. Параллельное программирование
Третий многогранник описывается следующим образом:
Jj'" = i, ;                                                (6Л8)
Js-2            h-2 '
J:    </,-    -1.
И, наконец, последний альтернативный многогранник будет таким:
/у, </,•,-!.                                             (6.19)
По построению любая точка любого альтернативного многогранника удов­летворяет условию J < I. Более того, лексикографически ближе к / будет та точка, которая находится в альтернативном многограннике с меньшим номером. Это обстоятельство позволяет решать непрерывную задачу, заме­няя условие J < I условием принадлежности / сначала первому альтерна­тивному многограннику, затем второму и т. д. В общем случае опорные об­ласти Vj и Vj могут состоять из нескольких многогранников. Допустим, что
мы умеем строить элементарный граф для случая, когда эти области содер­жат только по одному многограннику. Решим все такие задачи, выбирая из Vj и Vj по одному многограннику. Чтобы по их решениям построить ис­тинный элементарный граф, надо для каждого многогранника из Vt взять решения, соответствующие всем многогранникам из Vj, и найти их лекси­кографический максимум. Эта задача относительно простая, и мы рассмот­рим ее несколько позднее. А пока будем считать, что каждая из опорных областей К,- и Vj описывается только одним многогранником.
Рассмотрим первый альтернативный многогранник. Теперь непрерывная задача становится такой: в многограннике, заданном равенствами (6.13), ус­ловием J е Vj а соотношениями (6.16), найти точку /, обеспечивающую
лексикографический максимум. Процесс ее решения состоит из следующих шагов:
□  выписываются в форме (6.3) все линейные ограничения на координаты искомой точки /; в левых частях ограничений указывается зависимость от координат /; в правых частях ограничений указывается зависимость от координат I к N;
П по выписанной системе ограничений находятся все системы линей­ных алгебраических уравнений типа (6.4), матрицы которых обладают Z-свойством;
□  решаются все найденные системы линейных алгебраических уравнений относительно координат точки /; тем самым определяются точки /' —
Глава 6. Тонкая информационная структура программ
367
претенденты на решение задачи; координаты всех точек-претендентов являются линейными функциями координат точек / и вектора N неиз­вестных переменных;
□  координаты каждой точки-претендента /' подставляются во все линей­ные ограничения на координаты искомой точки /; совокупность полу­ченных равенств и неравенств порождает линейный многогранник Vi',
которому должна принадлежать точка /. Конфигурация и размеры мно­гогранника могут зависеть от внешних переменных;
□  рассматривается пересечение многогранников Vj и Vt '; если оно не пус­то, то в точках / этого пересечения точка /' дает искомое решение /0 не­прерывной задачи; если пересечение пусто, то точка /' как претендент на решение задачи не рассматривается;
□  строится объединение пересечений многогранников V{ и Vi ', получен­ных для всех претендентов /'.
Повторяются все шаги с заменой первого альтернативного многогранника вторым и многогранника Vj его неисчерпанной пересечениями частью.
Процесс перебора альтернативных многогранников продолжается до тех пор, пока не будут исчерпаны все из них. После окончания перебора какая-то часть Vj должна остаться неисчерпанной. Эта и только эта часть соот­ветствует тем целочисленным точкам /, в которых элементарная программа использует на входе входные данные элементарной задачи. Индексы пере­менных, определяющих входные данные, задаются вектором q(I, N). Только в эти точки / не входят дуги элементарного графа.
Сделаем несколько замечаний. Проверка пересечения многогранников Vj, Vj ' на непустоту порождает ограничения на неизвестные переменные, оп­ределяющие размеры многогранников. Довольно часто эти переменные яв­ляются плохо описанными, и их значения не могут быть определены из тек­ста программы. Поэтому проверка непустоты пересечения может быть связана с получением дополнительной информации, касающейся области изменения внешних переменных. Вообще говоря, при одних и тех же зна­чениях переменных пересечения многогранников Vj и Vt ' могут иметь не­пустые пересечения между собой для разных точек-претендентов /'. Однако напомним, что точка лексикографического максимума в многограннике единственная. Поэтому на пересечении пересечений точки-претенденты должны совпадать.
Итак, решение непрерывной задачи для любой элементарной программы из линейного класса в случае, когда каждая из опорных областей Vt и Vj опи­сывается только одним многогранником, представимо в виде конечной сие-
368
Часть II. Параллельное программирование
темы функций, линейных как по пространству итераций, так и по про­странству внешних переменных программы. Число этих функций не зависит от значений внешних переменных. Как следует из процесса построения, каждая из функций задана в какой-то области, принадлежащей Vh Граница
области составлена из линейных кусков гиперплоскостей. Число этих кус­ков и их конфигурация также не зависят от значений внешних переменных. Такую область всегда можно представить как объединение конечного числа линейных многогранников, у которых какие-то грани могут быть открыты­ми. Эти грани мы можем сделать замкнутыми, изменив на единицу свобод­ный член в описании соответствующей гиперплоскости. При этом в каждом из многогранников не будет потеряна ни одна из целочисленных точек. На­помним, что только такие точки представляют для нас истинный интерес. По построению все полученные замкнутые линейные многогранники будут непересекающимися попарно.
Решение /0(7, N) непрерывной задачи обладает двумя особенностями. Во-первых, оно получено в форме, соответствующей теореме 1. И, во-вторых, если в целочисленной точке / значение функции J0(I, N) оказывается цело­численным, то пара точек Jq(I, N) а I задает дугу элементарного графа.
Если подматрица системы уравнений (6.13), относящаяся к вектору /, име­ет полный столбцовый ранг, то граф алгоритма элементарной программы определяется в основном этой системой. Именно, если при заданных цело­численных векторах /, N система (6.13) имеет целочисленное решение /о е Qy, то это решение будет единственным, и пара точек Jq, I определяет дугу графа. Если же система (6.13) не имеет ни одного целочисленного ре­шения, то в точку / не входит дуга графа. В этих условиях решение Jq(I, N) непрерывной задачи совпадает с решением системы (6.13) и поэтому опи­сывает элементарный граф абсолютно точно.
Утверждение 6.8
Пусть подматрица системы (6.13), относящаяся к вектору /, имеет полный столбцовый ранг. Если в целочисленной точке / решение J0(I, TV) непрерывной задачи оказывается целочисленным и принадлежит пространству итераций, то пара точек /0.1 определяет дугу элементарного графа. Если же в целочислен­ной точке /решение J0(I, N) непрерывной задачи не является целочисленным, то в точку /не входит дуга графа.
Если система (6.13) не удовлетворяет условию утверждения 6.8, то решение Jo(I, N) непрерывной задачи не всегда описывает элементарный граф точно. По-прежнему, в случае целочисленных векторов /0, / эта пара определяет дугу графа. Но если для целочисленного вектора / вектор Jq оказывается не­целочисленным, то это не означает отсутствие дуги. В случае, когда дуга существует, разность между начальной точкой дуги и непрерывным решени­ем представляет вектор, который лексикографически максимальный, но не больше нуля, и принадлежит ядру подматрицы системы (6.13), относящейся
Глава 6. Тонкая информационная структура программ
369
к вектору /. Поэтому решение дискретной целочисленной задачи (6.13)— (6.15) можно представить как решение /0(7, N) непрерывной задачи, к кото­рому в точках, соответствующих целочисленным /, внесены поправки. Все коэффициенты, описывающие кусочно-линейную функцию Jq(I, TV), явля­ются рациональными. Поэтому поправки оказываются периодическими, за исключением участков вблизи границы многогранника Vj. Относительно
легко поправки строятся в том случае, когда ядро подматрицы системы (6.13), относящейся к вектору /, имеет размерность 1. В общем же случае алгоритм построения поправок оказывается очень запутанным.
Для того чтобы понять, насколько необходимы на практике более сложные алгоритмы решения дискретной задачи (6.13)—(6.15) в условиях, выходящих за рамки утверждения 6.8, был поставлен эксперимент. Были найдены ре­шения непрерывных задач, возникающих в самых различных реальных при­кладных программах. Оказалось, что подавляющее большинство решений является целочисленным. И лишь в нескольких случаях они не были таки­ми. Но каждый раз, когда решение не было целочисленным, не выполня­лось условие утверждения 6.8.
Даже если предположить, что могут появляться отдельные примеры, для которых решение непрерывной задачи не будет целочисленным, но будет целочисленным решение дискретной задачи, — это еще не является веским аргументом в пользу применения существенно более тяжелых методов ре­шения задачи (6.13)—(6.15). Заметим, что не представляет особого труда на­писать абстрактную элементарную программу, для которой указанные усло­вия действительно имеют место. Возможно, что сказанное означает только то, что множество реальных программ из линейного класса устроено сущест­венно проще, чем класс в целом. Напомним, что структура любых линейных программ описывается теоремой 1.
Всюду в дальнейшем мы будем считать, что любой элементарный граф мо­жет быть точно представлен решением непрерывной задачи.
Согласно утверждению 6.3, для любой программы из линейного класса ее минимальный граф зависимостей есть объединение минимальных графов всех простых программ. Процесс нахождения минимальных графов зависи­мостей для элементарных программ определен с точностью до сделанных выше оговорок. Поэтому для построения минимального графа линейной программы достаточно уметь строить простой граф из элементарных. Если будут построены расширения простых графов, то их объединение даст рас­ширение минимального графа исходной программы. Для дальнейшего нам потребуется почти очевидное
Утверждение 6.9
Пусть в вещественном линейном пространстве заданы два замкнутых линей­ных в общем случае пересекающихся многогранника А{ и А2. Существует сие-
370
Часть II. Параллельное программирование
тема замкнутых линейных не пересекающихся многогранников А'ь ..., А'р таких, что множество целочисленных точек пространства, попавших как в многогран­ник Дь так и в многогранник Д2, совпадает с множеством целочисленных точек, попавших в какие-то многогранники из А'ь ..., А'р.
Рассмотрим у любую простую программу и все порожденные ею элементар­ные программы. Пусть каждый элементарный граф представлен системой линейных функций, заданных на замкнутых линейных непересекающихся многогранниках. Возьмем объединение этих систем. Все многогранники из объединения принадлежат одной и той же опорной области Vt Многогран­ники из объединения могут попарно пересекаться, но лишь в том случае, когда они относятся к разным элементарным графам. Принимая во внима­ние утверждение 6.9, можно считать без ограничения общности, что мно­гогранники в объединении не пересекаются. Такое предположение приводит к некоторому увеличению числа линейных функций, которыми представля­ется каждый элементарный граф. Однако сейчас это не имеет какого-либо значения.
Итак, пусть объединение элементарных графов описывается системой непе­ресекающихся многогранников, заданных в опорной области Vi. На каждом
из многогранников определено несколько линейных функций со значения­ми в разных опорных областях. Из этого описания легко получить описание простого графа. Рассмотрим ту же систему многогранников и пусть А один из них. Предположим, что на А определены линейные функции Ф], ..., Ф^. В каждую целочисленную точку У из А входит одна и только одна дуга про­стого графа. Начальная точка этой дуги есть целочисленный лексикографи­ческий максимум из точек Ф^У), ..., Ф^(У).
Возьмем любые две функции Фг, Фу. Возможны две принципиально различ­ные ситуации. В одной из них в многограннике А существуют две точки У, L такие, что, например, Ф/(У) -< Ф/(У), но ФДХ) -^ФДХ). Подобные функции будем называть лексикографически не упорядоченными на многограннике А. В другой ситуации для любых точек У из А имеем, например Фг(У) < ФД-0-Эти функции будем называть лексикографически упорядоченными. В частно­сти, функции Ф/, Фу будут строго лексикографически упорядоченными,
если их значения относятся к опорным гнездам циклов, не имеющих общих параметров.
Пусть функции Ф/, Фу не являются лексикографически упорядоченными. Рассмотрим гиперплоскость, полученную приравниванием самых младших из общих координат Ф, и Фу. Она разделит многогранник А на три много­гранника. На одном из них А/ ~< Ау, на другом А,- >- Ау и на третьем у функций Ф/, Фу совпадают младшие общие координаты. Первые два многогранника являются полуоткрытыми, т. к. в них не входят грани, соответствующие ги-
Глава 6. Тонкая информационная структура программ
371
перплоскости. Мы можем сделать эти многогранники замкнутыми, если из­меним на ± 1 свободные члены уравнений гиперплоскостей этих граней. При этом в каждом многограннике сохранится множество целочисленных точек. Теперь рассмотрим функции Фг, Фу на третьем многограннике и по­вторим для него процедуру расщепления, приравняв следующие по стар­шинству общие координаты. Будем повторять процедуру расщепления до тех пор, пока не будут приравнены все общие координаты. В результате многогранник А будет расщеплен на многогранники A'l5 ..., A'q. Они линей­ные замкнутые непересекающиеся и в совокупности содержат те и только те целочисленные точки, в которые входят дуги простого графа. На этих мно­гогранниках функции Фг и Фу лексикографически упорядочены. Если Ф/ < Фу на каком-то из многогранников и Ау целочисленная, то целочис­ленный лексикографический максимум из точек Фг-(/) и Фу (Г) всегда равен Фу(7) независимо от того, какова функция Ф/.
Процесс расщепления многогранника А можно продолжить, сравнивая по­очередно все пары функций из Фь ..., Ф^. Это позволяет привести описание простого графа к некоторому каноническому виду. В каноническом виде про­стой граф описывается системой линейных замкнутых непересекающихся многогранников Aj, ..., А/, расположенных в опорной области V-t. На каждом из многогранников А,- заданы линейные функции Ф,-,..., Ф,-. Их значения
принадлежат разным опорным областям. Функции лексикографически упо­рядочены, т.е. Ф, -< Ф, -<-<Q>i для всех точек из многогранника А,.
Пусть / есть произвольная целочисленная точка из многогранника А/. На­чальная точка дуги простого графа, входящей в точку /, есть последняя це­лочисленная точка в ряду точек Ф, (/),..., Ф, (/). Если в этом ряду нет це­лочисленных точек, то в точку / дуга простого графа не входит.
Заметим, что аналогичный канонический вид имеет место для элементарных и простых графов во всех случаях. Даже тогда, когда не выполняется усло­вие утверждения 6.8. Единственное отличие будет состоять в том, что в об­щем случае значения разных функций Ф,-,..., Фл необязательно принад­лежат разным опорным областям. Конечно, не ограничивая общности, можно считать, что среди функций, значения которых принадлежат одной и той же опорной области, нет совпадающих. Во всем остальном канониче­ский вид сохраняется.
Выше мы предполагали при построении элементарного графа, что каждая из опорных областей описывается только одним многогранником. Теперь ясно, что отказ от этого предположения приводит к двухступенчатому процессу построения элементарного графа. Сначала строится семейство графов путем выбора всевозможных пар многогранников из V} и Vt, а затем лексикогра­фический максимум из них находится по только что описанной процедуре.
372
Часть II. Параллельное программирование
Всюду в дальнейшем мы будем считать, что любой минимальный граф программы из линейного класса представлен как объединение простых гра­фов, описанных в каноническом виде. Как показывает анализ большого числа реальных программ, почти всегда набор Ф, ,..., Ф, состоит из одной
целочисленной функции. И лишь очень редко некоторые из наборов состо­ят из нескольких функций.
С формальной точки зрения предшествующие исследования относятся к минимальным снизу графам. Для минимальных сверху графов исследования и результаты отличаются незначительно. Прокомментируем эти различия на примере графа обратных зависимостей.
Понятия простой и элементарной программы остаются такими же, как для графа алгоритма. Утверждения 6.3 и 6.4 снова имеют место. Основная задача (6.13)—(6.15) заменяется на следующую: найти целочисленную функцию Jq = = /0Ю, где
/0 = lex.min /, p(J, N) = q(J, N) J у I, Je Qj, I £ Q/.
Естественно, что в тексте начальная и конечная вершины дуги меняются местами. Опять сначала решаем непрерывную задачу. Справедлив аналог утверждения 6.7. Нужно лишь в определении Z-свойства слово "поло­жительный" заменить словом "отрицательный", а в тексте утверждения слово "максимума" заменить словом "минимума". При построении альтернативных многогранников неравенства меняются на противоположные и —1 в них за­меняется на +1. При построении простых и им подобных графов из функ­ций Ф;],...,Ф, надо выбирать не лексикографически старшую, а лексико­графически младшую. Во всем остальном построение элементарного и простого графов остается без изменения.
Сложность построения минимальных графов зависимостей во многом опре­деляется возможной неединственностью решений уравнения (6.13). Среди всех его решений / необходимо найти лексикографически ближайшее к точке / снизу. Именно это обстоятельство вносит в процесс наибольшие трудности. Неединственность решений уравнения (6.13) имеет место тогда и только тогда, когда значения переменных пересчитываются более одного раза. Однако пересчет полностью отсутствует в программах на языках одно­кратного присваивания и в любых математических соотношениях. Ничто не мешает математические соотношения оформлять как программы без пере­счета значений переменных. В этом случае уравнение (6.13) либо не будет иметь ни одного решения, либо будет иметь только одно. Процесс построе­ния минимальных графов значительно упрощается, а некоторые графы про­сто пропадают. Например, графы зависимостей по выходу и графы антиза­висимостей всегда будут пустыми.
Глава 6. Тонкая информационная структура программ
373
Вопросы и задания
1.   Внимательно проанализируйте примеры 6.1—6.4 из § 6.8 с точки зрения процесса построения минимальных графов зависимостей.
2.   В чем меняется процесс, если строятся минимальные графы зависимостей других типов?
3.   Опишите процесс построения минимальных графов зависимостей для случая, когда ни одна переменная в программе не пересчитывается.
§ 6.7. Циклы ParDO
и избыточные вычисления
Отметим, что покрывающие функции не могут быть произвольными линей­ными функциями. Все графы зависимостей обладают общим свойством. Именно, для каждой дуги конечная вершина лексикографически старше начальной. Такие графы называются лексикографически правильными. Любой лексикографически правильный граф является ациклическим.
Изучение параллельной структуры программ связано с изучением множеств операций, которые можно выполнять независимо друг от друга. Дадим оп­ределения соответствующих понятий. Пусть в пространстве итераций задан ориентированный лексикографически правильный граф G. Это может быть какой-нибудь из минимальных графов зависимостей или некоторый под­граф любого из объединений минимальных графов или какой-либо другой граф. Если программа зависит от внешних переменных, то от них будет за­висеть и граф G Рассмотрим непересекающиеся множества М\, ..., Мр вер­шин пространства итераций. Назовем эти множества параллельными по графу G или просто параллельными, если при любых фиксированных значениях внешних переменных:
□  любой путь графа G, связывающий две вершины одного множества, це­ликом лежит в этом множестве;
□  никакие две вершины из разных множеств не связаны путем графа G.
Понятие параллельных множеств имеет очень прозрачный смысл. Допус­тим, что граф G является объединением всех четырех минимальных графов. Предположим, что выполнены все операции, которые соответствуют на­чальным вершинам дуг, идущих в точки одного множества. Тогда можно начинать выполнение операций этого множества независимо от того, выпол­няются или не выполняются какие-либо другие операции, в том числе, опе­рации из других множеств. При этом гарантируется, что получаемые резуль­таты будут правильными.
Под параллельной структурой программы мы будем понимать совокупность сведений о параллельных множествах в пространстве итераций. Сюда же
374
Часть II. Параллельное программирование
будем относить и сведения о тех преобразованиях, целью которых является либо выявление, либо изменение параллельных множеств. Говоря о парал­лелизме в программах, обычно обсуждают два его вида: макропараллелизм и микропараллелизм. Макропараллелизм связан с ситуацией, когда все или хо­тя бы часть из параллельных множеств содержат много точек. Микропарал­лелизм имеет дело с теми случаями, при которых в каждом из параллельных множеств находится всего лишь несколько точек. Типичным для микропа­раллелизма является ситуация, когда множества содержат только по одной точке. В частности, параллельными будут те множества-точки пространства итераций, которые не связаны дугой графа G.
Рассмотрим какой-нибудь цикл do произвольной программы из линейного класса. Его также можно считать программой из линейного класса. Пара­метры циклов, внешних по отношению к данному циклу do, играют в нем такую же роль, как и внешние переменные программы. Их значения хотя и фиксированы к началу выполнения цикла, но на момент исследования не­известны. Можно лишь утверждать, что вектор параметров внешних циклов является целочисленным и принадлежит совокупности линейных много­гранников. Рассмотрим далее пространство итераций цикла do. Все его точ­ки можно разбить на непересекающиеся множества, если отнести к каждому множеству те из них, которые соответствуют одной и той же итерации цик­ла. Обозначим эти множества через М\, ..., Мр. Пусть в пространстве итера­ций цикла введен ориентированный лексикографически правильный граф G. Будем говорить, что цикл имеет тип ParDO по графу G, если множества М\, ..., Мр параллельны по этому графу при любых допустимых, но фикси­рованных значениях внешних переменных.
Заметим, что при любых значениях внешних переменных группы операций, соответствующие множествам М\, ..., Мр, могут выполняться последова­тельно друг за другом без выполнения каких-либо других операций. Это оз­начает, что если множества М\, ..., Мр параллельны по графу G, то несвя­занность путем любой пары точек, принадлежащих различным множествам Mh эквивалентна их несвязанности дугами. Поэтому имеет место
Утверждение 6.10
Пусть в пространстве итераций цикла do задан ориентированный лексикогра­фически правильный граф G. Для того чтобы цикл do был циклом ParDO по графу С, необходимо и достаточно, чтобы никакая пара точек пространства итераций, соответствующих одним и тем же значениям параметров внешних циклов, но различным значениям параметра рассматриваемого цикла, не была бы связана дугой графа G.
Теперь рассмотрим для цикла do его минимальные графы зависимостей или какие-нибудь объединения этих графов.
Если цикл do имеет тип ParDO по графу алгоритма, то никакие пары точек пространства итераций, соответствующие разным итерациям цикла, не мо­гут быть связаны дугами графа алгоритма. Это означает, что при последова-
Глава 6. Тонкая информационная структура программ
375
тельном исполнении цикла результаты реализации операций, относящихся к разным итерациям цикла, не влияют друг на друга. Потенциально итерации цикла ParDO по графу алгоритма можно выполнять независимо друг от дру­га. Однако для осуществления такой возможности нужно быть уверенным в правильности порядка пересчета значений одних и тех же переменных на разных итерациях цикла.
Пусть цикл do имеет тип ParDO по объединению графов алгоритма, антизави­симостей и зависимостей по выходу. В этом случае в цикле не существует ни одной пары операций, относящихся к разным итерациям, в которых одна и та же переменная или пересчитывается, или пересчитывается и использует­ся. Однако могут существовать пары операций, в которых используется одна и та же переменная. Итерации такого цикла можно выполнять в любом по­рядке. При этом гарантируется правильность получаемых результатов. Цик­лы подобного типа можно реализовывать на любых многопроцессорных вы­числительных системах. Но наиболее эффективно они реализуются на системах с общей памятью. На системах с распределенной памятью возмож­ны большие временные потери из-за межпроцессорных обменов.
Предположим, наконец, что цикл do имеет тип ParDO по объединению всех че­тырех минимальных графов зависимостей. Теперь любые пары операций из разных итераций циклов всегда имеют дело с разными переменными. Поэто­му такие циклы эффективно реализуются на любых вычислительных системах, в том числе и на многопроцессорных системах с распределенной памятью.
Таким образом, графы зависимостей цикла do играют исключительно важ­ную роль в распознавании параллельных свойств цикла. В силу этого необ­ходимо иметь эффективные критерии установления свойства ParDO по та­ким графам. Пусть граф G представлен покрывающими функциями вида х = Jy + ф. Здесь / числовая матрица, вектор ср линейно зависит от внешних переменных цикла, векторы х и у задают точки пространства итераций цик­ла. Заметим, что параметру цикла соответствуют первые координаты векто­ров х, у. Один из критериев таков.
Утверждение 6.11
Пусть граф С задан покрывающими функциями вида х = Jy + ср. Для того чтобы цикл имел тип ParDO по графу С, достаточно, чтобы граф G был пустым или для всех покрывающих функций первое равенство в выражениях х = Jy + ср имело вид xi = у\.
Почти всегда это условие на практике оказывается и необходимым. Напри­мер, оно заведомо будет таким, если граф описывается покрывающими функциями точно и покрывающие функции являются целочисленными.
В заключение отметим следующее. Мы описали критерий принадлежности цикла типу ParDO на основе графов, построенных для цикла. Критерий ока­зался очень простым. Но если мы хотим исследовать много циклов, то при­дется строить и много графов, например, графов зависимостей. Вообще гово­ря, можно было бы поступить иначе. Именно, ввести графы в пространстве
376
Часть II. Параллельное программирование
итераций программы в целом и построить критерий принадлежности цикла типу ParDO на основе анализа свойств этих графов. Такая идея реализуется конструктивно. Однако мы не будем ее развивать здесь по следующим причи­нам. Во-первых, критерий становится несколько сложнее. Он связан с уста­новлением целочисленной совместности некоторых целочисленных систем относительно небольших размеров. Во-вторых, графы нужного вида не всегда можно построить для программы в целом из-за каких-то особенностей, не относящихся к исследуемому циклу. И, наконец, алгоритм построения графов зависимостей оказался столь эффективным, что его многократное применение не приводит к большим дополнительным временным затратам.
Конечно, определение циклов ParDo далеко не исчерпывает сферу примене­ния графов зависимостей. В качестве еще одного, вообще говоря, нетриви­ального примера их прямого использования укажем на возможность оты­скания так называемых избыточных вычислений. Предположим, что некоторая переменная получила значение а, а затем значение р. Если зна­чение а нигде не было использовано, то соответствующее ему вычисление и называется избыточным.
Причин появления в программе избыточных вычислений может быть доста­точно много. Во-первых, это желание написать компактную программу. Следующий фрагмент
DO / = 1, П DO j = 1, П
END DO
a„ = 1
END DO
представляет типичную программу получения единичной матрицы. Каждый диагональный элемент во внутреннем цикле сначала определяется нулем, а затем заменяется на единицу без использования нулевого значения.
Во-вторых, распространенной причиной избыточных вычислений являются обычные ошибки программирования. Допустим, что программист захотел найти сумму элементов каждой строки матрицы А, но сделал ошибку и в последнем операторе присваивания вместо переменной s написал ss. В итоге получилась такая программа
DO / = 1, П
s = 0
DO j = 1, П
s = s + a,j
END DO
bj=ss
END DO
Глава 6. Тонкая информационная структура программ
377
Данная ошибка приведет к появлению избыточных вычислений: значение переменной s, вычисленное на последней итерации внутреннего цикла, ни­где не использовано, а затем на следующей, кроме последней, итерации внешнего цикла переменная s примет значение 0. И, наконец, источником многочисленных избыточных вычислений оказываются постоянные пере­делки программ, когда вычеркиваются группы операторов, вставляются "заплатки" и при этом нередко что-то остается в программах лишнее.
Умение находить избыточные вычисления — важная задача. Очевидно, что избыточные вычисления будут генерировать те и только те точки простран­ства итераций, из которых выходит дуга графа зависимостей по выходу, но не выходит ни одна дуга графа алгоритма. Зная покрывающие функции ука­занных графов, обнаружить избыточные вычисления совсем не трудно.
Вопросы и задания
1.   Пусть пространство итераций линейной программы разбито на непересекаю­щиеся множества My, ..., Мр и на этом пространстве задан лексикографически правильный граф G. Докажите, что для того, чтобы множества My, ..., Мр были попарно параллельны по графу, необходимо и достаточно, чтобы они не были связаны дугами графа G.
2.   В чем различие и общность утверждения п. 1 и утверждения 6.10?
3.   Какими свойствами дополнительно обладают множества My, ..., Mpva п. 1, если в качестве графа С взять минимальный граф зависимостей определенного типа или объединение всех четырех минимальных снизу графов?
4.   В чем различие и общность утверждения п. 3 и исследования, проведенного ра­нее для цикла do?
5.   Пусть My, ..., Мр представляют множества точек отдельных итераций цикла DO, упорядоченные по параметру цикла. Покажите, что My, ..., Мр образуют ярусы обобщенной параллельной формы любого лексикографически правильного графа.
6.   Укажите условия, при которых множества My, ..., Мр образуют ярусы строгой параллельной формы лексикографически правильного графа.
7.   Рассмотрим линейное тесно вложенное гнездо циклов. Предположим, что все циклы этого гнезда имеют тип ParDO по лексикографически правильному графу С. Докажите, что никакие точки пространства итераций гнезда циклов не связа­ны дугами графа С.
8.   Сохранятся ли полученные выше результаты, если отказаться от требования, чтобы граф С был лексикографически правильным?
9.   Как за конечное число действий проверить лексикографическую правильность графа, если он задан линейными функциями на линейных многогранниках?
10. Насколько упрощается процесс реализации циклов ParDO, если в исходной программе не пересчитывались никакие переменные?
378
Часть II. Параллельное программирование
§ 6.8. Примеры
Во всех приводимых здесь примерах мы снова ограничимся рассмотрением графов алгоритмов. Основная цель заключается в иллюстрации различных теоретических понятий и действий из §§6.3—6.7. Начнем с тех примеров, которые уже исследовались в §4.4.
Пример 6.1. Запись основной части алгоритма (4.5) вычисления произведе­ния двух матриц такова:
DO i=l,n DO j=\,n DO k=\,n
a,j = a,j+b„fikj
END DO END DO END DO
Эта программа есть тесно вложенное гнездо циклов с параметрами /, j, к. Па­раметр / самый младший, параметр к самый старший. Опорное пространство единственного оператора имеет размерность 3. Опорный многогранник V сов­падает с опорной областью, т. к. отсутствуют альтернативные операторы.
Линейное пространство итераций совпадает с опорным многогранником К и представляет трехмерный куб 1 < /,_/', к < п. Пространство итераций Q опи­сывается множеством точек в этом кубе, имеющих целочисленные коорди­наты /, у, к. Согласно утверждению 6.3, граф алгоритма есть объединение графов алгоритмов всех простых программ. Всего существует три простые программы и они имеют вид:
□ DO / = 1, П                             П DO / = 1, П                             П DO / = 1, П
DO j
= 1,
Я
DO
к=У,
Я
aij =
ay
END
DO
END
DO
) DO
DO j = 1, Я                                    DO j = 1, Я
DO к = 1, Я                                   DO к = 1, Я
0 = Ъ                                       0 = ckj
END DO
END DO END DO
END DO
END DO
END DO
где символ 0 означает пустую часть оператора. Ясно, что в данном случае все простые графы зависимостей совпадают с элементарными. Переменные с именами b и с не пересчитываются. Поэтому графы алгоритмов для двух последних простых программ будут пустыми, т. е. не имеющими ни одной дуги. Следовательно, граф алгоритма для примера 6.1 совпадает с графом алгоритма первой простой программы.
Глава 6. Тонкая информационная структура программ
379
Рассмотрим для нее задачу (6.13)—(6.15). Вектор N одномерный и его единст­венная координата равна п > 1. В обозначениях задачи точка / имеет коорди­наты /= (i,j, к). Предположим, что искомая точка / обладает координатами J~ (i',J:'> к'). Соответственно q(I, N) = (i,j), p(J, N) = (i',j). Векторное ра­венство (6.13) превращается в систему линейных алгебраических уравнений
'' = '. j'=j относительно неизвестных i',j', к'. Ясно, что /' = /, / = j, и остается опре­делить неизвестное к' из условий, что точка / обеспечивает лексикогра­фический максимум при J < I, J е Q. Согласно общему процессу для этого нужно найти лексикографический максимум во втором альтернативном многограннике (6.17). Он описывается соотношениями
' ' = i,j'=j,k'<k-l, 1 < /', /, к', /, j, к<п.
Даже не используя Z-свойство, очевидно, что к' = к — 1, если п > 2 и 2 < к < п — 1. Появившееся ограничение п > 2 вместо допустимого ограни­чения п > 1 отражает тот факт, что при п = 1 граф алгоритма будет содер­жать только одну вершину и, следовательно, не будет иметь ни одной дуги. Покрывающая функция Ф графа алгоритма одна. Она имеет вид
Ф
flOO^ 010 001
(
j
к
о 1
определена на многограннике 1 < / < я, l<j<n, 2<k<nu описывает граф точно. Как и следовало ожидать, граф алгоритма, определенный по фор­мальным правилам, полностью совпадает с графом на рис. 4.3, а, который был угадан интуитивно. Принимая во внимание вид покрывающей функции Ф, заключаем согласно утверждению 6.11, что два внешних цикла рассматри­ваемой программы перемножения матриц имеют тип ParDO.
Пример 6.2. Исследуем теперь формальным образом алгоритм (4.7). Его за­пись выглядит следующим образом:
1 X\ = bi
DO Х = 2,П
2        X; = bi
DOj=l,i-l                                                                                                       (6.20)
END DO END DO
В программе имеется два параметра /, j. Параметр / младше параметра j. Оператору Fk соответствует оператор с меткой, равной к. С этими операто-
380
Часть II. Параллельное программирование
рами связаны следующие опорные пространства: с оператором F\ простран­ство нулевой размерности, с оператором Fi пространство размерности 1 с координатой /, с оператором Ft, пространство размерности 2 с координата­ми /,_/'. Опорное гнездо, например, для оператора с меткой з таково:
DO 7 = 2, «
DO 7= 1,/-1
о             X/ X/ ciipvj
END DO END DO
Опорный многогранник V\ для оператора F\ есть точка, опорный много­гранник Кг для оператора Fj есть отрезок 2 < i < п, опорный многогранник Vj для оператора Fj есть треугольник, заданный неравенствами 2 < / < «, 1 <_/'< / — 1. Будем считать, что единственная точка из V\ имеет координату z = 0. Опорные многогранники совпадают с опорными областя­ми, т. к. отсутствуют альтернативные операторы. Линейное пространство итераций есть объединение отрезка V2, треугольника К3 и точки, соответст­вующей оператору F\. Операционное содержание вершин из многогранника Vj иное, чем у вершин из многогранников V\ и К2.
Программа (6.20) расщепляется на 5 простых программ по общему числу всех входов всех ее операторов. Так как не все входные переменные пере-считываются в программе, то нужно рассмотреть только две простые про­граммы. Это
1
xi = 0
1
XX =0
DO i = 2,n
DO i = 2,n
Xi = 0
2
Xi=0
DOj= 1, i- 1
DOj= 1, i- 1
Xi Xi
3
Xi Xj
END DO
END DO
END DO
END DO
простая программа из (6.21) расще
*i = 0
DO / = 2, n
do i = 2,n
2 Xi = 0
D0J= 1,/-1
D0j= 1, /-1
0=Xi
3 0=Xi
END DO
END DO
(6.21)
1
X\ = 0                         do / = 2, n               ❒ do / = 2, n
D0j= 1, /-1
X;=X;                            (6.22)
END DO END DO
Глава 6. Тонкая информационная структура программ
381
END DO                                      END DO
Вторая простая программа из (6.21) также расщепляется на 3 элементарные:
1
*1 = 0
do i = 2,n
do i = 2,n
DO Х = 2,П
2
x, = 0
D0j= \,i-
-1
DOy'= \,i-\
D0j= 1, 7-1
3
X, = Xj
<Z>=Xj
3
0=JCy
END DO
END DO
END DO
END DO
END DO
END DO
(6.23)
Первый элементарный граф из (6.22) пустой, т. к. пересчитанное значение переменной х\ в операторе с меткой 1 нигде не используется в операторе с меткой з в силу ограничения 2 < i < п. Остальные элементарные графы не­пустые. Покрывающие функции элементарных графов будем помечать дву­мя или тремя индексами. Первый индекс снизу означает номер по метке опорного пространства, в котором расположена область определения функ­ции. Второй индекс снизу означает номер по метке опорного пространства, в котором расположена область значений функции. Если функций с одина­ковыми нижними индексами несколько, то они различаются верхними ин­дексами. Стрелка в обозначениях вида Vt -> Vj относится не к дугам графа, а лишь показывает, где у покрывающей функции находится область опреде­ления и куда она отображается. Дуги графов всегда имеют обратное направ­ление. Итак, покрывающие функции для элементарных графов таковы:
Ф13 = 0;
d\
(1,0)
2 < i< n, 1 <j < i (
1, V3 -> V2;
Ф
23
Ф
33
П<П
+
■1
, 3</</j, 2<y</-l, K3-> V3;
01
V
Ф13 = (0, 0)
yh
yh
2<i<n, /=1, K3-> Vi,
, 3<i<n, 2<y< 1, V3^ V2;
0\
Ф23 = (0, 1)
yh
fOlV/^
(
3 <i<n, 2<j<i- 1, V3 -^V3
Ф
33
01
yh
1
382
Часть II. Параллельное программирование
Напомним, что в концевых точках каждой дуги пересчитывается и исполь­зуется одна и та же переменная. В каждой точке всех элементарных графов, относящихся к одной и той же простой программе, также используется одна и та же переменная. Именно эти обстоятельства дают возможность убирать лишние дуги при объединении элементарных графов в простой граф. В ча­стности, по данной причине при построении простого графа для первой простой программы из (6.21) от второй элементарной программы из (6.22) остаются только дуги, входящие в вершины приу= 1. Все остальные дуги "перекрываются" дугами из третьей элементарной программы. По этой же причине при построении простого графа для второй простой программы из (6.21) не входит ни одна дуга от второй элементарной программы из (6.23). Все они "перекрываются" дугами из третьей элементарной программы в (6.23).
Рис. 6.11. Опорные многогранники
Вообще говоря, опорные многогранники могут располагаться относительно друг друга как угодно. Однако в целях наглядности или в каких-либо иных целях бывает удобно располагать их рядом в одном пространстве. Такое размещение опорных многогранников для примера 6.2 приведено на рис. 6.11 для п = 5. Здесь вершина в верхнем левом углу находится в точке с координатами /= 1, j= 0. Координаты соседних точек различаются на 1. Сейчас выбор расположения опорных многогранников определяется жела­нием сравнить граф алгоритма (4.7), представленный на рис. 4.4, а, и граф алгоритма (6.20), построенный формальным образом. Суммируя сказанное о покрывающих функциях простых графов для простых программ из (6.21) и принимая во внимание выбранное размещение для опорных многогранни­ков, заключаем, что покрывающие функции для графа алгоритма, описан­ного программой (6.20), будут иметь следующий вид:
Глава 6. Тонкая информационная структура программ
383
Ф,
Гооул
ГП
1, K,-> Vu
00
чЛ
о,
,2<i<n, j
Ф,
ПоУЛ
( 0\
2</<л, y=l, K3-> K2;
01
\h
/
-1
-1
1
Ф,
roiV/^
3</</j, 2<y</-l, K3^ K3:
01
По
01
kJj
Ф4
J
3</<л, 2 <y</-l, K3^ K3.
Легко проверить, что описываемый этими функциями граф полностью сов­падает при п = 5 с графом на рис. 4.4, а. Покрывающие функции описыва­ют граф алгоритма точно. Из утверждения 6.11 не следует, что хотя бы один цикл имеет тип ParDO. И это действительно так, что видно из рис.4.4, а. Но из этого же рисунка видно, что параллелизм имеется на множествах точек пространства итераций, параллельных оси / или, что в данном случае одно и то же, перпендикулярных оси у. Этот параллелизм не описывается циклами типа ParDO непосредственно. Опять же из рисунка видно, что циклы по / и/ можно переставить и тогда отмеченный параллелизм уже будет описываться циклом ParDO. К этим вопросам мы вернемся в главе 7.
Пример 6.3. Таким же образом исследуем алгоритм (4.8). Его запись выгля­дит следующим образом:
1      Ху = Ьу
DO / = 2, П
2          x,= b,                                                                                                                                    (6J4) DO /=/"1, 1,-1
END DO END DO
Формально эта программа не принадлежит к линейному классу, т. к. шаг изменения параметра у во втором цикле равен — 1, а не +1. Сделав замену переменных у = i — z, приведем ее к линейной:
Ху = Ьу
DO / = 2, я
ж,- = Ь,
(6.25)
DO Z = 1, / - 1
384
Часть II. Параллельное программирование
END DO END DO
В целом анализ программы (6.25) осуществляется по той же схеме, что и для программы (6.20). Программа (6.25) тоже расщепляется на 5 простых про­грамм, из которых 3 простые программы имеют пустые графы алгоритмов. Каждая из двух оставшихся простых программ расщепляется на три элемен­тарные. Мы не будем проводить детальный анализ программы (6.25), а ог­раничимся тем, что приведем покрывающие функции для графа алгоритма, описанного программой (6.24). Чтобы иметь возможность сравнить полу­ченный граф с графом на рис. 4.4, б, расположим опорные многогранники V\, Vj, V3 программы (6.24) так, как они показаны на рис. 6.12. Здесь верх­няя угловая вершина находится в точке с координатами / = 1, j = 1. Покры­вающие функции имеют следующий вид:
Ф =
00
+ . , 2 < / < n, j = 1, К3 -> Vx;
1
v j
J
ф,
АОУП (
+
0"
1
< i
1
2</<л, у=1, К3-> К2;
01
vJ/
Ф,
01
foiyn
00
3 < /<«, 2<j<i- 2, V3 -> V3;
Ф4
3 <j + 1 < i<n, V3^ V3.
Рис.6.12. Опорные многогранники
Глава 6. Тонкая информационная структура программ
385
Снова они описывают граф алгоритма точно. По всем критериям и рис. 4.4, б видно, что никакого параллелизма в алгоритме примера 6.3 нет.
Пример 6.4. Пусть элементы и^ одномерного массива переставляются по та­кому алгоритму
DO / = 1, П DO j = 1, П
ui+j= u2n + l-i-j
END DO
END DO
Эта программа интересна, с одной стороны, своей предельной простотой и, с другой стороны, зависимостью индексных выражений от внешних перемен­ных. Посмотрим еще раз, как для данного примера строится формально граф алгоритма. В программе имеется одна входная переменная и2и + i - /-у и одна выходная переменная щ+j. Поэтому существует только один элементарный граф и он совпадает с графом алгоритма в целом. Линейное пространство итераций V представляет квадрат 1 < /, j < п. Приравнивая индексы перемен­ных, заключаем, что при построении графа всегда придется решать уравнение
/' +/ = 2п+ 1 - i-j                                     (6.26)
относительно неизвестных /', /, где
1</', /, U ]<п.                                        (6.27)
На множестве решений уравнения (6.26) при ограничениях (6.27) необходи­мо найти точку с координатами /', /, которая лексикографически ближе всего снизу к точке с координатами /, j.
Согласно (6.17), второй альтернативный многогранник, определяющий усло­вия лексикографического предшествования, описывается следующим образом
/' = /,/< у-1.                                          (6.28)
Из равенства (6.26) и равенства в (6.28) получаем систему линейных алгеб­раических уравнений
»'+/ = 2л+ I- i-j;
V = i.
Отсюда находим, что
'' = ''                                                  (6.29)
/ = -2i-j+ 2п+ 1.
Подставляя эти выражения для /', / в неравенства (6.27) и в неравенство из (6.28), заключаем, что должны выполняться такие неравенства:
1 < / < л;
l<j<n;
386
Часть II. Параллельное программирование
1 <2п+ 1 -2i-j<n;                                     (6.30)
п+ 1 < i+j. При тех значениях /, j, которые удовлетворяют этой системе неравенств, граф будет задаваться функцией (6.29). Перепишем систему (6.30) в таком виде
2i+j<2n;                                        (6-31>
п + 1 < / + у.
Ее совместность теперь очевидна.
Рассмотрим третий альтернативный многогранник (6.19). Он описывается так:
/' < / - 1.
Из уравнения (6.26) находим, что
f = 2n+l-i-j- /'.                                     (6.32)
Подставим это выражения для j' в (6.27). Соберем вместе все неравенства для /', представив их в виде (6.3). Имеем
/' < л;
~'" * ~ 1;                                              (6-33)
— i' < i + j — п — 1;
/' < — i — j + 2n;
V < / - 1
и, кроме этого,
i < i< n, 1 <j< n.                                        (6.34)
Из системы неравенств (6.33) необходимо выбрать такие подсистемы, матрицы которых относительно определяемых параметров обладают Z-свойством. Так как неизвестный параметр только один, то порядок мат­риц будет равен 1. Такие матрицы обладают Z-свойством тогда и только то­гда, когда их единственный элемент положительный. Следовательно, для определения параметра /' из системы (6.33) необходимо рассмотреть лишь первое, четвертое и пятое неравенства.
Первое неравенство /' < п дает /' = п. При этом условии последнее неравен­ство из (6.33) всегда оказывается невыполненным, если принять во внима­ние (6.34).
Четвертое неравенство /' < — / — j + 2п дает /' = — / —j + 2п. Из (6.32) нахо­дим, что у' = 1. В этих условиях неравенства (6.27), (6.33) становятся такими
1 < / < п;                                                (6.35)
l<j<n;
Глава 6. Тонкая информационная структура программ
387
я < i + у <2n — 1; 2я + 1 < 2/ + у. Множество решений системы (6.35) не пересекается с множеством решений системы (6.31). Оно описывается также неравенствами
/< я;
j ^ п;
(6.36) i + j<2n-l;
In + 1 <2i+j.
На этом множестве граф задается функцией
i' = -i-j+2n;                                         (637)
Г = 1-Пятое неравенство i < / — 1 дает /' = / — 1. Из (6.32) находим, что у' = — 2/ — — у + 2я + 2. В этих условиях неравенства (6.27), (6.33) становятся такими:
2 < / < я;
1<у<я;                                                (б-38)
n + 2<2i + j<2n + 1.
Множество решений системы (6.37), не принадлежащих системе (6.31), опи­сывается неравенствами
/+у<я;                                                (6-39)
я + 2 < 2/ + у. На этом множестве граф алгоритма будет задаваться функцией
l' = l~l'>                                                    (6.40)
У = - 2i-j+ 2п + 2.
Множества (6.31), (6.36), (6.39) не покрывают все целочисленные точки квадрата 1 < / < я, 1 <у < я. Остается область, описываемая неравенствами
1 < /;                                                  (б-41)
2/ + у<я + 1,
а также целочисленная точка с координатами (я, я). Во всех этих точках в качестве аргумента операции берется одно из входных данных.
Итак, для примера (6.4) множество функций, покрывающих граф алгоритма,
388
Часть II. Параллельное программирование
выглядит следующим образом:
ф
( 1 ovn (
на многограннике (6.39);
-2-1
^ 1
2-1
+
In + 2
О Л + 1
Фо =
на многограннике (6.31);
(6.42)
yJy
/.
(2п\ 1
на многограннике (6.36).
l-i vn
Ф4
О О
j
Все эти функции действуют из V в V. На многограннике (6.41) и в точке с координатами (п, п) покрывающие функции не определены. Будем считать условно, что здесь заданы пустые функции соответственно Ф] и Ф5. Их вве­дение удобно для последующих иллюстраций. На рис. 6.13 представлены области определения функций Ф]—Ф5. Здесь на области с номером /задана функция Ф/. Область с номером / расположена между сплошной линией слева от цифры / и пунктирной линией справа от той же цифры. На рисун­ке приведены уравнения сплошных линий. Уравнения соседних пунктирных линий получаются из них уменьшением свободных членов на 1. Совокуп­ность (6.42) покрывающих функций представляет граф алгоритма точно.
Рис. 6.13. Области определения покрывающих функций
Рассмотренный пример показывает неожиданную ситуацию. Оказывается, что даже в простейших по форме записи алгоритмах поведение дуг их графов мо­жет описываться довольно сложно, но в то же время точно. Следовательно, простота записей алгоритмов не является гарантией простоты информацион-
Глава 6. Тонкая информационная структура программ
389
ных связей. Не в этом ли факте скрывается причина многочисленных трудно­стей изучения тонкой структуры алгоритмов и программ?
Теперь рассмотрим процесс построения графа алгоритма для внутреннего цикла примера 6.4. Условие совпадения значений параметра внешнего цик­ла для концевых вершин дуг графа дает равенство /' = /. Поэтому из равен­ства (6.26) заключаем, что
'' = ''                                                  (6.43)
j' = 2n+ 1-2/ -j.
Снова должны выполнятся неравенства (6.27). Кроме этого, должно быть справедливо неравенство предшествования/ <j— 1. Собирая все неравенства и подставляя в них значения /',_/' из (6.43), находим, что покрывающая граф функция только одна и ее можно, например, записать в следующем виде:
ф"
( 1 ovn ( о ^
(6.44)
2-1
yJy
2n + l
Она задана на многограннике
2i+j<2n;
(6.45)
n+ 1 < / + j;
V = i.
Мы специально записали функцию Ф" и область ее определения в таком виде, чтобы обратить внимание на совпадение Ф" с функцией Ф3 из (6.42). Дело в том, что граф алгоритма для внутреннего цикла примера 6.4 можно было бы находить из представления (6.42) графа целиком. Для этого из всех дуг (6.42) надо оставить только те, для концевых точек которых значения первых координат совпадают. Такие дуги в данном конкретном случае опи­сываются лишь функцией Ф3.
Из представления (6.44), (6.45) легко находим вид покрывающей функции (6.44), соответствующий теореме 1. Именно,
Ф"С/) = 2л+ 1-2/ -j. Задана она на отрезке
п + 1 - / < j < п,
если 1 < / < я/2, если я/2 < / < п -
(6.46)
п + 1 — / < j < 2п — 2/,
1.
На дополнительных отрезках
1 <j < п - i и 2п - 2/ + 1 <j < п, если [я/2] + 1 < / < п - 1, (6.47)
покрывающая функция Ф" не определена. В этих точках в качестве аргу­мента операции берется либо одно из входных данных, либо какое-то зна-
390
Часть II. Параллельное программирование
чение функции, вычисленное при меньших значения параметра /. Снова будем считать, что на дополнительных отрезках заданы соответственно пус­тые функции Ф' и Ф'".
В примере 6.4 ни один из циклов не является циклом ParDO. Тем не менее, параллелизм в этом примере имеется, и он значителен. Интересно отметить, что пример 6.4 пропускался на многих зарубежных параллелизующих ком­пиляторах и автономных системах выявления параллелизма, но всюду без­успешно. Это что-то говорит об уровне используемых технологий анализа структуры программ.
Пример 6.5. Рассмотрим программу решения системы линейных алгебраиче­ских уравнений Ах = b методом Жордана без выбора ведущего элемента.
DO к = 1, п
1                   k = 1М*
DO р = 2, П
2                                    lp=~ apk
END DO
DO j = к + 1, n + 1
3                                    Uj = «i/i do i=2, n
END DO
5                                     anj = Uj
END DO END DO
Здесь в массиве А элементов ay размеров n(n + 1) в первых п столбцах зада­ются столбцы матрицы А системы, в последнем столбце — правая часть Ь. На месте матрицы системы получается обратная к ней матрица А~1, на мес­те правой части — решение х. Этот пример более сложный. Рассмотрим его с точки зрения принадлежности циклов типу ParDo по графу алгоритма. По­крывающие функции Ф^ будем помечать тремя индексами снизу и одним
сверху. Первый индекс снизу означает номер по метке опорного простран­ства, в котором расположена область определения функции. Второй индекс снизу означает номер по метке опорного пространства, в котором располо­жена область значений функции. Третий индекс снизу означает номер входа оператора, к которому относится функция. Если функций с одинаковыми нижними индексами несколько, то они различаются верхними индексами. Мы не будем разбирать процесс построения графа, а воспользуемся V-Ray System для его получения. Будем считать, что п > 3.
Глава 6. Тонкая информационная структура программ
391
Имеем
Ф|41
Г1]
Г-П
1
к +
0
0
V )
2
, 2<к<п;
(Ю\(к\
, 2 < к < n, р = п;
, 2<к<п, 2 < р<п-1;
Ф
251
10
v /
уР;
о
V /
Ю" 10
V
+
0
01
V /
W
1
ф
241
КГ
01
+
'-Г 0
00
V /
UJ
2
, 2<k<n, k + l<j<n + l;
341
Ф312 = (1 0)
(к\
, 1<к<п, к + l< j <п + \;
yJj
Г100!
(кЛ
(-\\
010
j
+
0
001
V /
i
V )
1
, 2<k<n, k + l<j<n + l, 2 < / < я - 1;
Ф
441
Г100!
f_1l
010
V /
J
i
V )
+
0
, 2< к < n, к + l< j <n + l,i = n;
Ф
451
Ф422
10 0"
001
V       /
ГАЛ
, 1 < к < n, k + l<j<n + l, 2<i<n;
v j
f100l
V
010
V /
J
i
V /
, 1 < к < n, k + l<j<n + l, 2 < i < n;
o
432
10"
01
V /
(k\
, l<k<n, k + \<j<n + \.
Ф
531
yJ;
Цикл по параметру к не имеет тип ParDO. Это следует из того, что сущест­вуют покрывающие функции, например, Ф[41, Ф'251 и др., у которых аргу­менты и значения имеют разные координаты, соответствующие параметру к.
392
Часть II. Параллельное программирование
Цикл по параметру р имеет тип ParDO, что вытекает хотя бы из отсутствия покрывающих функций вида Ф^* • Граф алгоритма для него пустой. В про­странстве итераций цикла по параметру j действуют функции Ф341, Ф441»
Ф451 > Ф432 и ^ki • Для всех них аргументы и значения имеют одни и те же координаты, соответствующие параметру j. Поэтому данный цикл заведомо имеет тип ParDO. В пространстве итераций цикла по параметру / действует
только одна функция Ф441 • Для нее аргументы и значения имеют разные координаты, соответствующие параметру /. Но аргумент всегда лексикогра­фически старше значения. А т. к. для функции Ф441 координата аргумента,
соответствующая параметру /, меньше аналогичной координаты значения, то граф алгоритма для цикла по параметру / пустой, а цикл имеет тип ParDO.
Заметим, что тип ParDO по графу алгоритма означает лишь потенциальную возможность выполнять итерации цикла параллельно. Чтобы она была ре­альной, нужно чтобы на разных итерациях не использовались одни и те же переменные. Этому условию удовлетворяют циклы по параметрам р и j. Од­нако цикл по параметру / ему не удовлетворяет, хотя граф алгоритма для данного цикла пустой. Формально это обстоятельство иллюстрируется тем, что для цикла по параметру / не является пустым минимальный граф анти­зависимостей.
Обратим также внимание на то, что в данном цикле непустота графа анти­зависимостей связана с глобальными переменными. Поэтому вводить новые переменные для устранения антизависимостей следует аккуратно.
Глава 7
Эквивалентные преобразования программ
Если бы строители возводили здания
так же, как программисты пишут
программы, первый залетевший дятел
разрушил бы цивилизацию.
Из законов Мерфи
В предыдущей главе мы познакомились с фундаментальными основами изу­чения тонкой информационной структуры алгоритмов и программ. Они были связаны с введением и построением минимальных графов зависимостей. Даже первые исследования показали, что эти графы несут много полезных сведе­ний. Одной из целей обращения к тонкой структуре является поиск новых способов описания алгоритмов, более приспособленных к использованию на различных вычислительных системах, чем традиционные записи на последо­вательных, расширенных или параллельных языках программирования. Соб­ственно говоря, данный поиск идет постоянно на протяжении всей истории развития программирования. Просто сейчас становится ясно, что каким-то образом в нем должна более полно учитываться информация о структуре ал­горитмов. В конце концов, не так важно, в какую конкретную форму будут облечены новые способы. Важно лишь, чтобы они были приемлемы для поль­зователей и не требовали от него предоставления сведений о компьютерно-зависимых свойствах алгоритмов. Вся необходимая информация должна авто­матически получаться только из описания алгоритмов в процессе либо ком­пиляции, либо предварительной их обработки с помощью автономных систем.
О некоторых возможностях получения такой информации пойдет речь в на­стоящей главе. Более детально будут рассмотрены две идеи. Одна из них связана с представлением минимальных графов зависимостей в виде век­торных полей в линейном пространстве итераций. Такие поля в математиче­ском анализе часто исследуются с помощью параметризованных семейств гладких многообразий, покрывающих в совокупности все пространство. Ос­новной момент исследований состоит в изучении пересечения отдельных векторов с отдельными многообразиями при изменении параметра. В каче­стве семейства многообразий мы будем брать поверхности уровней скаляр­ных функций. Другая идея связана с преобразованиями программ, которые приходится выполнять после получения необходимых сведений. Мы будем разбираться, какие преобразования можно делать, какие нельзя и почему.
394
Часть II. Параллельное программирование
§ 7.1. Развертки графа
Параллелизм вычислений, задаваемый циклами ParDO, не всегда представляет весь параллелизм в программе. Более общий аппарат выявления массового параллелизма связан с использованием специальных функций на графах.
Пусть в пространстве итераций задан ориентированный лексикографически правильный граф G. Рассмотрим вещественный функционал / определен­ный на его вершинах. Предположим, что дуга графа идет из точки и в точку v. Будем говорить, что функционал/возрастает (не убывает) вдоль дуг графа G, если/fv) >/(w) (Av) ^Ли)) Для всех паР точек и, v, связанных дугами гра­фа G. Назовем функционал / строгой (обобщенной) разверткой графа G, если он строго возрастает (не убывает) вдоль дуг графа.
Степень важности разверток для исследования графов определяется их свойствами. Пусть известна какая-нибудь строгая развертка. Тогда на любой ее поверхности уровня никакие точки пространства итераций не могут быть связаны дугами графа G. Более того, никакие из этих точек не могут быть связаны даже путями графа. Поэтому любые непересекающиеся множества точек любой поверхности уровня любой строгой развертки являются парал­лельными по графу G.
Рассмотрим теперь любую обобщенную развертку / Разобьем точки про­странства итераций на группы. Будем относить к одной группе только те из них, которые принадлежат одной и той же поверхности уровня функциона­ла/ Перенумеруем группы по возрастанию значений функционала. Это по­зволяет расщепить описанный программой алгоритм на фрагменты. Каждый из фрагментов соответствует одной группе точек пространства итераций. Фрагменты могут выполняться последовательно друг за другом в порядке возрастания номеров групп. Если в действительности развертка является строгой, то каждый из фрагментов можно расщепить на параллельные мно­жества операций. Одно множество соответствует тем операциям, которые определяют одну точку пространства итераций.
Множество обобщенных разверток не пусто для любого графа G Например, обобщенной разверткой будет любой функционал / который принимает одинаковые значения во всех точках пространства итераций, т. е. является константой. Нетривиальным примером является следующая развертка.
Не ограничивая общности, будем считать, что программа представляет после­довательность гнезд циклов с какими-то телами. Возможно, что для некото­рых циклов верхние и нижние границы изменения значений параметров будут совпадать. Перенумеруем подряд в порядке лексикографического роста все значения параметров самых внешних циклов. Тем самым каждой точке про­странства итераций будет присвоен некоторый номер. Естественно, могут су­ществовать точки с одинаковыми номерами. Введем в пространстве итераций
Глава 7. Эквивалентные преобразования программ
395
вещественный функционал, положив его значение в точке, равное номеру точки. Не трудно проверить, что этот функционал является обобщенной раз­верткой. Обобщенной разверткой будет и любая неубывающая функция от номеров значений параметров циклов. Все такие развертки называются основными. Подчеркнем, что основные развертки не зависят от графа.
Множество обобщенных разверток замкнуто в отношении некоторых опера­ций над ними. Из определения разверток следует, что справедливо
Утверждение 7.1
Обобщенной разверткой является:
•    сумма двух обобщенных разверток;
•     произведение обобщенной развертки на неотрицательное число;
•     максимум из двух обобщенных разверток;
•     минимум из двух обобщенных разверток.
С нетривиальными свойствами разверток можно познакомиться в [10, 64].
Любая развертка позволяет расщепить пространство итераций на группы. Однако от развертки зависит число групп и число точек в группах, характер связей между группами и между точками в группе. Среди разверток можно выделить два особых типа. Один тип составляют развертки, которые обеспе­чивают отсутствие связей внутри групп. Это строгие развертки. Второй тип составляют развертки, которые обеспечивают отсутствие связей между груп­пами. Такие развертки будем назвать расщепляющими. Они позволяют рас­щепить описанный программой алгоритм на фрагменты, не связанные меж­ду собой по графу G. Это означает, что поверхности уровней расщепляющей развертки представляют множества, параллельные по тому же графу G. К расщепляющим отнесем и развертки, являющиеся константами. Такие развертки не имеют практического значения, но полезны при проведении теоретических исследований. Очевидно, что если цикл имеет тип ParDO по графу G, то основная развертка в пространстве итераций цикла является расщепляющей для графа G.
Пусть для графа G построены обобщенные развертки /j и fa. Функционал f~ А + fi также будет разверткой. Допустим, что в пространстве итераций имеются точки х\ и х2 такие, что f(x\) = ]\x-i), но f\(x{) ^/lfe). Предполо­жим, например, что f\(xi) >fi(x2). Отсюда сразу же вытекает, что fi(xi) <fi(x2)- Если точки связаны путем графа G, то путь может идти лишь из точки с меньшим значением развертки в точку с большим ее значением. Поэтому мы заключаем, что точки х\ и х2 не могут быть связаны путем гра­фа G. Аналогичный вывод имеет место и в случае предположения
/l(*l)</l(*2)-
396
Часть II. Параллельное программирование
Следовательно, справедливо
Утверждение 7.2
Пусть для графа С известны обобщенные развертки /J v\f2. Возьмем любые по­ложительные числа а, р и построим развертку/= a/J + $f2. Если в пространст­ве итераций существуют точки jq и х2 такие, что
Лх1)=Лх2),Л(х1)*Л(х2),                                             (7.1)
то они не могут быть связаны путем графа G.
Это утверждение позволяет выделять многоярусные параллельные множест­ва в пространстве итераций с помощью обобщенных разверток. Предполо­жим, что найдено не менее двух независимых разверток. Возьмем их сумму. Она является обобщенной разверткой. По этой развертке в соответствии с ее поверхностями уровней расщепим пространство итераций на последова­тельно связанные между собой группы. Для всех точек одной группы вы­полняется равенство из (7.1). Теперь возьмем любую из разверток и в соот­ветствии с ее поверхностями уровней расщепим каждую из групп на множества. Каждое из множеств соответствует пересечению поверхностей уровней суммы разверток и выбранной развертки. Для точек одной группы, но принадлежащих разным множествам, выполняется неравенство из (7.1). Поэтому множества в каждой группе оказываются параллельными по графу G. Выберем далее любую развертку, отличную от первой и в соответствии с ее поверхностями уровней расщепим каждое из множеств на подмножества. Подмножества, порожденные каждым множеством, также оказываются па­раллельными по графу G. Следовательно, параллельными будут все подмно­жества, порожденные одной группой. Более дробное расщепление может осуществляться до тех пор, пока не останется неиспользованной одна из тех разверток, которые образовали исходную сумму разверток. Параллелизм в пространстве итераций, определяемый поверхностями уровней разверток, называется скошенным. Частный случай этого параллелизма, определяемый циклами ParDO, называется координатным.
Большое значение, которое играют развертки при изучении структуры по­следовательных программ, вынуждает искать эффективные методы их по­строения. В первую очередь они нужны для разверток минимальных графов зависимостей. Однако даже в этом случае множество разверток в целом уст­роено очень сложно. Тем не менее, для минимальных графов программ из линейного класса существуют и достаточно простые, но представительные развертки, которые, к тому же, можно конструктивно построить.
Рассмотрим в линейном пространстве итераций вещественный функционал, обладающий следующими свойствами:
□  его область определения есть линейный замкнутый многогранник в опорном многограннике;
□  он линеен как по точкам пространства итераций, так и по внешним пе­ременным.
Глава 7. Эквивалентные преобразования программ
397
Естественно, возникает вопрос о возможности представления разверток сис­темой линейных функционалов. Если такие развертки существуют, то будем их называть кусочно-линейными.
Имеется определенное сходство между системой покрывающих функций из теоремы 1 и рассматриваемыми линейными функционалами. Но между ни­ми существует и принципиальное различие. Пусть граф зависимостей пред­ставлен системой покрывающих функций. Многогранники, на которых они определены, могут пересекаться по дискретному пространству итераций и никогда в совокупности не покрывают его полностью. Допустим, что раз­вертку можно представить системой линейных функционалов. В этом случае многогранники, на которых заданы функционалы, не могут пересекаться по дискретному пространству итераций, но в совокупности должны покрывать его полностью.
Мы уже отмечали, что в наших исследованиях граф, для которого должны находиться развертки, будет на самом деле либо одним из минимальных графов зависимостей, либо каким-нибудь подграфом объединения мини­мальных графов. Теорема 1 играет решающую роль в том, что процесс по­строения кусочно-линейных разверток можно сделать конструктивным.
Рассмотрим векторы N= (N\, ..., Ns) внешних переменных программы. Предположим, что они принадлежат некоторой совокупности многогранни­ков, в общем случае, неограниченных. Допустим, что в линейном простран­стве итераций задана система замкнутых линейных многогранников V]_, ..., Vm, которые не пересекаются по дискретному пространству итераций, но в совокупности покрывают его полностью. В частности, многогранники Vj могут совпадать с опорными многогранниками, но могут и отличаться от них. Пусть в линейном пространстве итераций задана другая система замк­нутых линейных многогранников Vy, которые могут пересекаться и иметь различные размерности. Будем считать, что обе системы многогранников связаны между собой следующим образом: если многогранник Vy имеет не­пустое пересечение с многогранником Vj, то он входит в Vt целиком. Вы­полнения этого условия можно всегда добиться либо за счет укрупнения многогранников Vj, либо за счет дробления многогранников Vy, либо за счет обеих операций вместе. Если каждый из многогранников Vy входит в какую-нибудь опорную область, а в качестве Vj взяты опорные многогранники, то данное условие также выполняется. Предположим, что вершины всех мно­гогранников являются линейными неоднородными функциями переменных Nh ..., Ns.
Допустим, что граф G в пространстве итераций задан системой покрываю­щих функций. Пусть каждая из них является линейной как по точкам про­странства итераций, так и по переменным TV], ..., Ns. Предположим, что об­ластью определения отдельной покрывающей функции является один из многогранников Vy, а область ее значений входит в один из многогранников
398
Часть II. Параллельное программирование
V\, ..., Vm. Выполнения последнего условия снова можно добиться за счет подходящего выбора многогранников. И снова оно выполняется, если в ка­честве Vj взяты опорные многогранники, а граф G является одним из графов зависимостей.
Допустим, что заданная на многограннике Vy покрывающая функция имеет вид х = Jy + ф и ее значения принадлежат V^. Здесь / числовая матрица, вектор ф линейно зависит от внешних переменных. Любой граф зависимо­стей для линейной программы удовлетворяет этим требованиям. Пусть обобщенная развертка на Vj имеет вид (Ь, у) + 5, а на V^ вид (а, х) + у. Направляющие векторы a, b и свободные члены у, 5 неизвестны и подлежат нахождению. Будем искать векторы a, b как не зависящие от переменных N], ..., Ns, а свободные члены у, 5 как линейные неоднородные функции от этих переменных.
Так как из функционалов (а, х) + у и (Ь, у) + 5 должна составляться обоб­щенная развертка, то для всех у е Vy обязано выполняться неравенство
(а, х) + у<(Ь,у) + 5
или, другими словами,
(JTa - Ъ, у) < ~ (а, Ф) + 5 - у.                                 (7.2)
Обозначим через у1 вершины многогранника Vy. Известно [4], что каждая точка ограниченного линейного многогранника может быть представлена как выпуклая линейная комбинация его вершин, т. е.
У = ^а'у1' а' -0' Ха' =1"                           (7-3)
Искомые функционалы удовлетворяют неравенству (7.2) для всех у е Vy. Представление (7.3) показывает, что неравенство (7.2) эквивалентно сле­дующей системе неравенств
(JTa - b, У) < - (а, Ф) + 5 - у.                                (7.4)
Согласно предположениям, у1 и ф являются линейными неоднородными функциями от N\, ..., Ns. Пусть
у = уо + yiTVi + ... + ysNs,
5 = 50 + Si^! + ... + dsNs.
Перепишем неравенство (7.4) следующим образом:
(/о о" §o)+(//+Ti- ONi +••• + (//+ У,- h)Ns <0,         (7-5)
где все fj являются какими-то конкретными линейными однородными
функциями неизвестных координат направляющих векторов а, Ь.
По предположению, вектор N внешних переменных принадлежит некоторой системе многогранников, в общем случае, неограниченных. Для каждого из
Глава 7. Эквивалентные преобразования программ
399
многогранников существует конечное число точек Nk = \N\ , ..., Nk) и конечное число векторов Mq = \М\ , ..., Mqs) таких, что
^=2^* +Хм^>                            (7.6)
k                          q
где
а, >0, £а, =1, Р^°-                          (7'7)
к
Неравенства (7.5) имеют место для всех N из (7.6), (7.7). В частности, нера­венства
(/"о +Yo-§o)+(/,/+Y,-§,к +... + (/"/ +y,-5>.f<0;
(/i'+Yl -5,)м/ +... + (// +у,-5,)д/«<0
выполняются для всех к, q. Они могут быть получены из (7.5), если поло­жить сначала а^ = 1 для фиксированного км fiq = 0 для всех q и затем а^ = 1 для фиксированного к и р? = + со для фиксированного #. Значения остальных Р? в последнем случае можно считать равными 0. Неравенства (7.8) не зависят от Ni, ..., Ns. Если мы каким-нибудь способом определим направляющие век­торы a, b и свободные члены у, 5 из (7.8), то с учетом (7.6)—(7.8) заключаем, что неравенства (7.5) будут выполняться для всех допустимых векторов N из выбранного многогранника при тех же a, b и у, 5.
Обозначим через t вектор, составленный для всех многогранников Vt из ко­ординат векторов а и коэффициентов разложения свободных членов у по параметрам 1, Ni, ..., Ns. Будем называть его направляющим вектором кусоч­но-линейной развертки. Зафиксируем какой-нибудь многогранник, в котором определен вектор внешних переменных N. Соберем далее вместе неравенст­ва типа (7.8) для всех покрывающих функций графа. Левую их часть можно представить в виде произведения At, где А есть числовая матрица. Прове­денные исследования показывают, что имеет место
Теорема 2
Для любой линейной программы и любого линейного многогранника, задающе­го область изменения внешних переменных, существует не зависящая от зна­чений внешних переменных матрица А такая, что любой ненулевой вектор t, удовлетворяющий векторному неравенству
At < 0,                                                         (7.9)
является направляющим вектором кусочно-линейной развертки.
Эта теорема совместно с утверждением 7.2 дает мощный инструмент для исследования структуры графов. Находить развертки из системы нера­венств (7.9) можно с помощью симплекс-метода [4].
400
Часть II. Параллельное программирование
Ответ на вопрос, существуют ли кусочно-линейные развертки, не связанные с решением неравенства (7.9), определятся тем, насколько избыточно набор покрывающих функций описывает граф G. Если набор описывает граф точ­но, то никаких других кусочно-линейных разверток не существует. Как уже отмечалось, минимальные графы зависимостей для реальных программ поч­ти всегда описываются точно.
Матрица А из теоремы 2 зависит от выбора многогранников Vt и Vy. Как правило, в качестве Vt берутся опорные многогранники, а в качестве Vy многогранники, на которых задаются покрывающие функции. Другой выбор имеет смысл делать лишь в тех случаях, когда нужно принять во внимание какие-то частные особенности покрывающих функций и их областей опре­деления. Если многогранники V/ и Vy фиксированы, то остается зависимость матрицы А от многогранника, в котором заданы внешние параметры. В том случае, когда область определения внешних переменных состоит из не­скольких многогранников, направляющие векторы кусочно-линейных раз­верток для каждого из многогранников будут удовлетворять своей системе неравенств.
Утверждение 7.3
Пусть /есть любая развертка графа G. Граф С всегда можно расщепить на та­кие графы G{ и С2, что развертка /будет строгой для графа G{ и расщепляю­щей для графа С2.
Рассмотрим поверхности уровней развертки/ Будем считать, что у графа G и получаемых из него графов G\ и G2 одно и то же множество вершин. От­несем к графу (jj (G2) те дуги графа G, концевые вершины которых лежат на разных (одних и тех же) поверхностях уровней функции / По построению функция/является строгой разверткой для графа G\, и расщепляющей для графа G2.
Само по себе это утверждение, конечно, тривиально. Нетривиальным, одна­ко, может быть осуществление разбиения графа G. Для кусочно-линейных разверток и графов, заданных кусочно-линейными функциями, это разбие­ние можно выполнить конструктивно.
Предположим, что внешние переменные заданы одним многогранником. Рассмотрим поведение кусочно-линейной развертки на семействе дуг, опре­деляемом одной покрывающей функцией х = Jy + ср. Чтобы неравенст­во (7.2) было строгим для всех у и всех N, необходимо и достаточно, чтобы неравенство (7.4) было строгим для всех / и всех N. Для этого, в свою оче­редь, необходимо и достаточно, чтобы для всех / и всех к было строгим пер­вое неравенство из (7.8). Поэтому для того чтобы развертка с направляю­щим вектором t, удовлетворяющим неравенству (7.9), была строгой при всех у £ Vy и всех N для покрывающей функции х = Jy + ср, необходимо и доста­точно, чтобы среди координат вектора At, соответствующих этой покры-
Глава 7. Эквивалентные преобразования программ
401
вающей функции, отрицательными были все те, которые определяются пер­вым неравенством из (7.8).
Если при каком-то у и некотором N неравенство (7.2) в действительности оказывается равенством, то при этом же N и каких-то / должны быть равен­ствами неравенства (7.4). Но тогда при этом N неравенство (7.2) будет ра­венством для всех тех и только тех у, которые принадлежат выпуклой ли­нейной оболочке вершин у1, обращающих неравенство (7.4) в равенство. Естественно, что этой же линейной оболочке принадлежит и рассматривае­мая точка у. Для того чтобы при фиксированном / и том же самом N нера­венство (7.4) становилось равенством, необходимо и достаточно, чтобы для данного / какие-то из первых неравенств (7.8) были в действительности равенствами. Наличие реальных равенств во вторых неравенствах (7.8) не имеет в данном случае какого-либо значения. Но все это означает, что нера­венство (7.4) для рассматриваемого / будет равенством при всех N, принад­лежащих некоторому многограннику А/. На его формирование уже влияют равенства из вторых соотношений (7.8). Этот многогранник описывается выпуклой оболочкой типа (7.6). Суммирование ведется по тем k, q, которые соответствуют равенствам в обоих соотношениях (7.8). Теоретически, по-видимому, возможно (на практике такая ситуация не встречалась), когда для разных / многогранники А/ различны. В этом случае область задания внеш­них переменных можно разбить на такие многогранники, в каждом из кото­рых при каждом / неравенство (7.4) всегда является либо строгим неравенст­вом, либо равенством. Структуру графа по отношению к данной развертке можно исследовать отдельно на каждом многограннике. Не ограничивая существенно общность, будем считать, что это условие выполняется для всех покрывающих функций на всей области задания внешних переменных.
Условия легко контролировать. Упорядочим строки матрицы А и, соответст­венно, координаты вектора At следующим образом. Разобьем строки матри­цы А на блоки, отразив в одном блоке все неравенства (7.8), относящиеся к одной покрывающей функции. Разобьем блоки на группы, отразив в одной группе все неравенства (7.8), относящиеся к одному /. Каждую из групп ра­зобьем на две подгруппы. В первой отразим первые неравенства (7.8), во второй — вторые. В каждой подгруппе строки расположим соответственно одной и той же нумерации индексов к, q. Типичную ситуацию определяет
Утверждение 7.4
Для того чтобы неравенство (7.4) для фиксированного / при всех допустимых N было строгим неравенством (равенством), необходимо и достаточно, чтобы в каждой группе координат вектора At для того же / все координаты первой под­группы были отрицательными (все координаты группы были ненулевыми).
Таким образом, в областях определения линейных покрывающих функций всегда можно выделить такие подобласти, в общем случае представляющие
402
Часть II. Параллельное программирование
зависящие от внешних переменных многогранники, что соответствующие им дуги графа будут лежать в поверхностях уровней развертки, полученной из неравенства (7.9).
Представление лексикографически правильных графов линейными функ­циями позволяет обнаруживать ряд свойств разверток непосредственно из системы (7.9). Если развертка является расщепляющей, то на концах любой дуги она принимает одно и то же значение. Поэтому соотношение (7.2) должно быть равенством. Отсюда следует, что направляющие векторы рас­щепляющих разверток и только они удовлетворяют системе уравнений At = 0. Для любой строгой развертки соотношение (7.2) должно быть стро­гим неравенством. Легко проверить, что строгость неравенств (7.2) эквива­лентна строгости первых неравенств из (7.8). Нетрудно также установить, что развертка с векторами а вида а = (1, 0, ..., 0) является основной. Подоб­ные исследования мы будем проводить по мере необходимости.
Введенные совершенно формально как функционалы на графах развертки имеют очень прозрачный смысл. Обозначим через t(v) момент окончания выполнения операции, соответствующей вершине v пространства итераций. Если в вершину v идет дуга из вершины и, например, графа алгоритма, то очевидно, что t(v) > т(и). Равенство здесь может быть только в том случае, когда операция, соответствующая вершине v, реализуется мгновенно. По­этому любая развертка графа алгоритма в действительности описывает вре­менной режим некоторого способа выполнения самого алгоритма, реального или гипотетического. Реальным режимам соответствуют строгие развертки. В математических исследованиях удобнее считать, что все операции выпол­няются мгновенно. Если же необходимо учесть время их реализации, то это можно учитывать через времена передачи информации "по дугам графа".
Вопросы и задания
1.   Рассмотрим следующие операции над развертками, определенными на точках v одного и того же пространства итераций и соответствующими одному и тому же графу С:
•    Ф) = /i(v) ©/2(v) = max \fy{v), f2(v)};
•    ¥(v) = /i(v) ®fi(v) = min {/[(v), /2(v)};
. 9(v) = XV/1(v)=/1(v)VX=/1(v) + X.
Будем называть эти операции "сложением", "умножением" и "умножением на число". Докажите, что функции (p(v), ^i(v), 0(v) являются развертками графа G.
2.   Покажите, что операции Ф, <8>, V не выводят из множества неотрицательных раз­верток, если X > 0.
3.   Докажите, что на множестве разверток выполняются следующие соотношения:
•   f®f=f,
Глава 7. Эквивалентные преобразования программ
403
•    /i©/2=/2©/i;
•    /i®/2=/2®/i;
•     (Л®/2)®/з=/1®(£®/з);
•     (/1®/2)®/з=/1®(/2®/з);
• /1®(Л®/2)=/ь
•    /i®(/l®/2)=/i;
•     (ЛФ/2)®/з = (Л®/з)Ф(/2®/з).
Любое множество, в котором относительно пары операций выполняются такие соотношения, называется дистрибутивной решеткой.
4.   Функционал 0(v) = 0 для всех точек v называется "нулевой разверткой" и обозна­чается символом 0. Докажите, что на множестве неотрицательных разверток/это есть единственная развертка, обладающая, свойствами
/©0=0©/=/ 0®/=/®0=0
для всех/
5.   Существует ли такая развертка l(v), что для всех неотрицательных разверток / выполняются соотношения:
1®/=/®1=/?
6.   Рассмотрим множество неотрицательных разверток и для любой пары разверток введем вещественный функционал
(/,/)= max(/l(v)+/2(v)).
V
Будем называть его "скалярным произведением". Докажите, что выполняются сле­дующие соотношения
. О>0, если/* 0, ДО, 0) = 0;
•     (A/2) = (/2,/i);
•     (^V/,/2) = XV(/1,/2);
•     (Л®/2,/з) = (/ь/з)®(4/з).
7.   На множестве неотрицательных разверток/введем вещественный функционал
||/||= max Av).
V
Будем называть его "нормой". Докажите, что выполняются следующие соотношения: . H/II > 0, если/* 0, || 0 || = 0;
•     ll/i©/2|| = ll/ill®ll/2||;
•     II/i®/2II ^11/1II® II/2II;
. || X V/|| = XV||/||.
8.   Является ли множество всех разверток или неотрицательных разверток линейным векторным пространством с операциями V, ©?
9.   Докажите, что операции ©, <8> на множестве разверток не имеют обратные операции.
404
Часть II. Параллельное программирование
10.   Рассмотрим множество неотрицательных разверток с ограничениями. Именно, если из вершины и идет дуга в вершину v, то будем считать, что выполняются неравенства
/у) -Дм) > wuv,
где wuv — любые заданные неотрицательные числа, одни и те же для всех раз­верток /
Докажите, что множество разверток с ограничениями замкнуто относительно операций Ф, <8>, V.
11.   Пусть в каждой вершине, не имеющей входные дуги, все развертки принимают одни и те же неотрицательные значения. В общем случае они разные в разных вершинах. Назовем их граничными значениями. Докажите, что множество неот­рицательных разверток с заданными ограничениями и граничными значениями замкнуто относительно операций Ф, <8>.
12.   Докажите, что в условиях п. 11 существует развертка Opt, принадлежащая рас­сматриваемому классу и такая, что
Opt®/=/® Opt=/ Opt<g>/=/<8> Opt = Opt
для любой развертки из этого же класса.
13.   Предложите интерпретацию развертки Opt, связанную с временными характери­стиками процесса реализации алгоритма на каком-то компьютере.
14.   Докажите, что если все ограничения wuv положительные, то любая развертка с такими ограничениями является строгой.
15.   Для каких типов графов зависимостей реализации алгоритмов на реальных компь­ютерах автоматически приводят к положительным ограничениям в развертках?
§ 7.2. Макрографы зависимостей
Представление графов зависимостей линейными функциями открывает большие возможности в изучении параллелизма в программах как на мик­ро-, так и на макроуровне. Исследования на микроуровне дают много де­тальной информации, но они достаточно трудоемки. Исследования на мак­роуровне менее сложные. Однако и сведения они дают менее подробные. При изучении структуры реальных программ приходится сочетать оба типа исследований. На макроуровне выделяются фрагменты программы (или, другими словами, множества точек в пространстве итераций), которые не связаны между собой по графу. На микроуровне детальному исследованию подвергаются только эти фрагменты (только эти множества). Опишем один из подходов проведения исследований на макроуровне. Он связан с по­строением макрографов.
Пусть в пространстве итераций программы задан ориентированный лекси­кографически правильный граф G. Выберем в линейном пространстве ите­раций систему замкнутых областей. Будем считать, что эти области попарно не пересекаются, в совокупности включают в себя все начальные вершины
Глава 7. Эквивалентные преобразования программ
405
дуг графа Си их число не зависит от значений внешних переменных. Об­ласти могут быть многосвязными и каждая из них может принадлежать раз­ным опорным многогранникам.
Будем называть выбранные области макровершинами. Назовем макродугой всю совокупность дуг графа G, начальные вершины которых принадлежат одной макровершине, а конечные другой. Будем считать, что направление макродуги совпадает с направлением образующих ее дуг графа G. Множест­во макровершин и макродуг задает ориентированный макрограф, порожден­ный графом G.
Напомним, что ориентированный граф (подграф) называется сильно связан­ным, если в нем существует замкнутый путь, проходящий через все верши­ны. Сильно связанный подграф назовем изолированным, если никаким до­бавлением к нему новых вершин и новых дуг из графа его нельзя оставить сильно связанным. Очевидно, что любой ориентированный граф можно разбить по множеству вершин на изолированные сильно связанные подгра­фы. В частных случаях подграф может содержать и одну вершину, и все вершины графа.
Теперь разобьем макрограф на изолированные сильно связанные подмак-рографы. Множество макровершин каждого подмакрографа объявим новой макровершиной. Две новые макровершины свяжем новой макродугой, если были связаны старой макродугой какие-нибудь две старые макровершины, принадлежащие новым макровершинам. Тем самым будет построен новый макрограф. Он не имеет изолированных сильно связанных подмакрографов, содержащих более одной макровершины. Построим для него параллельную форму. Тогда множество точек пространства итераций, которые входят в один ярус и принадлежат отдельным макровершинам, являются параллель­ными по графу G. Если какая-то из макровершин не имеет петли, то ее вершины также могут образовывать параллельные множества. Проводить детальный анализ нужно только для тех фрагментов программ, которые оп­ределяют новые макровершины.
Пусть граф G представлен линейными функциями, заданными на замкнутых линейных многогранниках. Рассмотрим два наиболее простых способа по­строения макрографа. В первом в качестве областей возьмем опорные мно­гогранники. Установление макродуг здесь осуществляется тривиально. Во втором в качестве областей возьмем многогранники, на которых заданы линейные функции. Для установления макродуг теперь нужно проверять не­пустоту пересечения пар многогранников. В каждой паре один из многогран­ников представляет область определения одной из линейных функций. Дру­гой многогранник является областью значений какой-то другой функции.
Каждый из рассмотренных макрографов имеет конечное число макровер­шин, не зависящее от значений внешних переменных. Поэтому отыскание
406
Часть II. Параллельное программирование
изолированных сильно связанных подмакрографов можно осуществить лю­бым подходящим методом, в том числе, основанным на переборе. Проводя детальный анализ отдельных макровершин, можно получать более подроб­ные макрографы. В том числе можно строить макрографы, число вершин которых зависит от значений внешних переменных, но которые, тем не ме­нее, устроены достаточно регулярно. Такая ситуация возникает, например, в том случае, когда какая-то макровершина описывается циклом ParDO.
Один из самых эффективных и многообещающих способов построения макрографов осуществляется с помощью разверток. Отвлечемся на время от линейного класса программ и будем считать, что граф алгоритма задан ку­сочно-непрерывными функциями в произвольной односвязной области D. В соответствии с интерпретацией, данной в § 7.1, рассмотрим строгую раз­вертку т = t(v), определенную в точках v е D, и исследуем ее поверхности уровней. Они могут иметь сложное строение, в том числе, могут быть мно­госвязными. Однако, если с изменением х поверхности уровней меняются непрерывно, то по одну их сторону всегда будут находиться вершины, для которых выполнение соответствующих операций происходит до момента т, а по другую сторону — вершины, для которых выполнение операций проис­ходит после момента т. Вершины, лежащие на самой поверхности, можно отнести к одной из этих групп вершин. Тогда перенос информации от вер­шин одной группы к вершинам другой группы осуществляется лишь теми дугами графа алгоритма, которые пересекают поверхности уровней разверт­ки x(v). Поверхности уровней показывают в области D распределение пото­ков информации, перенос которой осуществляется в процессе реализации алгоритма.
Предположим, что граф алгоритма имеет хотя бы одну подходящую разверт­ку. Она определяет систему поверхностей уровней или, что то же самое, систему ориентированных разрезов. Эти поверхности могут быть односвяз-ными, как на рис. 7.1, а, или не быть таковыми, как на рис. 7.1, б. Здесь цифрами отмечены поверхности уровней, соответствующие последователь­ным значениям т. Так как х есть время, то систему поверхностей уровня можно трактовать как систему фиксированных состояний некоторой пере­мещающейся во времени поверхности. Внешне движение этой поверхности напоминает движение волны в области D. По этой причине аналогичным объектам дают такие названия, как волновой фронт, фронт вычислений и т. п.
Система поверхностей уровней одной строгой развертки определяет некото­рую параллельную форму алгоритма, с точки зрения решения задачи отобра­жения алгоритмов на архитектуру вычислительных систем поверхности уровня одной развертки дают не очень много информации. Можно лишь сказать, что эти поверхности позволяют расщепить все операции алгоритма на подмноже­ства. Вершины графа алгоритма, соответствующие операциям одного под­множества, и только они лежат на одной поверхности уровня. Все подмноже-
Глава 7. Эквивалентные преобразования программ
407
ства операций могут быть выполнены последовательно друг за другом, а опе­рации одного подмножества могут быть выполнены параллельно.
Рис. 7.1. Поверхности уровней одной развертки
Положение существенно меняется, если число имеющихся независимых разверток совпадает с размерностью области D, как это показано на рис. 7.2. Выбирая из системы поверхностей уровней любые подсистемы, легко свести граф алгоритма к макрографу. Для этого нужно в качестве макровершин взять подграфы, ограниченные соседними поверхностями уровней из вы­бранных подсистем. Параллельная структура макрографа очевидна. В каче­стве примера на рис. 7.2 разной штриховкой отмечены для него несколько ярусов параллельной формы. Выбирая подходящие подсистемы поверхно­стей, можно при некоторых условиях добиться малости отношения числа дуг, связанных в среднем с одной макровершиной, к числу вершин графа алгоритма, приходящихся в среднем на одну макровершину. Это означает, что алгоритм эффективно реализуется на вычислительной системе с много­уровневой памятью.
Рис. 7.2. Поверхности уровней двух разверток
408
Часть II. Параллельное программирование
Заметим, что в обсуждаемых вопросах построения макрографов строгие раз­вертки вполне можно заменить обобщенными. В рассуждениях почти ни­чего не меняется. Просто в этом случае допускается, что некоторые из дуг могут лежать на самих поверхностях уровней.
Вопросы и задания
1.   Пусть задан ориентированный ациклический граф высоты я и ширины т. Дока­жите, что для любых натуральных чисел щ < п, т§ < т граф можно разбить на подграфы, образующие ориентированный ациклический макрограф высоты я0 и ширины от0-
2.   Можно ли гарантировать, что все макровершины макрографа из п. 1 будут со­держать примерно одинаковое число вершин исходного графа?
3.   Рассмотрим управляющий и информационный графы, в которых вершины суть операторы. С точки зрения отдельных срабатываний операторов эти графы мож­но считать макрографами. Теперь рассмотрим граф алгоритма и построим для него макрограф в соответствии с п. 1. Каково принципиальное различие между данными макрографами в отношении реализации алгоритма по макровершинам?
4.   Предположим для простоты, что алгоритм записан в виде тесно вложенного гнезда циклов и граф алгоритма имеет s > 2 независимых разверток. Постройте для графа алгоритма макрограф с использованием всех s разверток и предложите на его основе параллельную реализацию алгоритма.
5.   Как зависит от s число процессоров, которые можно в п. 4 использовать парал­лельно?
6.   Как зависит от s интенсивность обмена информацией между процессорами, если алгоритм реализуется согласно п. 4?
§ 7.3. Эквивалентные программы
Чтобы использовать обнаруженный в программе параллелизм, его нужно прежде всего каким-то образом записать. Обычно параллелизм описывают с помощью специальных циклов. Мы уже отмечали это на примере циклов ParDO. Чтобы параллелизм в программе описать циклами, программу прихо­дится преобразовывать. Остановимся сначала на общих принципах выпол­нения таких преобразований.
Будем называть два арифметических выражения эквивалентными, если их за­писи совпадают с точностью до обозначения переменных. Перенумеруем оди­наковым образом входные переменные эквивалентных выражений. Рассмот­рим две линейные программы. Предположим, что между их пространствами итераций установлено такое взаимно однозначное соответствие, при котором соответствующие точки задают срабатывания операторов с эквивалентными выражениями в правых частях. Подобные пространства итераций будем назы­вать эквивалентными, а указанное соответствие — соответствием эквивалент-
Глава 7. Эквивалентные преобразования программ
409
ности. Пусть фиксировано правило, по которому для линейной программы однозначно строится какой-нибудь граф зависимостей. Возьмем две програм­мы с эквивалентными пространствами итераций и предположим, что соответ­ствие эквивалентности является изоморфным для графов. Кроме того, будем считать, что сопоставляемые при изоморфизме дуги графов зависимостей относятся к входным переменным с одними и теми же номерами. Такие программы будем называть эквивалентными по графам зависимостей, а сами графы — графами эквивалентности. Любое преобразование программы в экви­валентную ей программу будем называть эквивалентным.
Введенное понятие эквивалентности программ зависит от того, какие графы мы хотим принять во внимание при преобразовании программ. Все эквива­лентные программы выполняют одно и то же множество операций, описан­ных арифметическими выражениями. Однако при последовательной реали­зации программ порядки выполнения операций могут различаться. В общем случае разные порядки могут приводить к разным результатам. При специ­альном выборе графов эквивалентности эквивалентные программы дают в одинаковых условиях реализации одни и те же результаты, включая всю со­вокупность ошибок округления. Одинаковые условия предполагают, в част­ности, что ошибка округления при выполнении любой операции на кон­кретном компьютере определяется только входными данными операции. Этому требованию удовлетворяют все используемые на практике компьюте­ры. Множества используемых переменных в эквивалентных программах могут быть организованы в массивы по-разному и могут различаться по их числу. В частности, по-разному могут быть организованы переменные, через которые задаются входные данные и которые формируют результаты.
В реальных условиях приходится выполнять также неэквивалентные преоб­разования. Типичным примером является замена одного оператора последо­вательностью операторов, реализующих в точных вычислениях ту же самую операцию. С формальной точки зрения преобразование не является эквива­лентным, т. к. меняется пространство итераций. В практическом отношении такая замена из-за влияния ошибок округления чаще всего приводит к из­менению результатов вычислений, что также не дает основание считать по­добное преобразование эквивалентным.
Программы могут быть неэквивалентными даже в случае эквивалентности их пространств итераций. Рассмотрим, например, суммирование элементов массива. Пусть в одной из программ осуществляется последовательное сум­мирование, в другой — принцип сдваивания. Допустим далее, что графами эквивалентности являются графы алгоритма. Пространства итераций совпа­дают как по числу точек, так и по содержанию соответствующих им опера­ций. Тем не менее, программы не эквивалентны, т. к. их графы алгоритмов не изоморфны. Заметим, что и в этом случае при точных вычислениях про­граммы будут давать одинаковые результаты, если суммируются элементы
410
Часть II. Параллельное программирование
одного и того же массива. Но в условиях влияния ошибок округления ре­зультаты будут различными.
Сохранение результатов вычислений имеет безусловный приоритет при пре­образовании программ. Поэтому очень важно иметь критерий такого сохра­нения. Мы уже отмечали особую роль графа алгоритма среди минимальных графов. Посмотрим, что объединяет программы, эквивалентные по этим графам.
Построим каноническую параллельную форму графа алгоритма одной из программ. По построению начальная вершина любой дуги будет всегда на­ходиться в ярусе с меньшим номером, чем конечная вершина. В силу изо­морфизма графов эквивалентности каноническую параллельную форму другого графа можно взять такой же, включая такое же расположение соот­ветствующих вершин в ярусах. При этом одинаково расположенным верши­нам параллельных форм будут соответствовать одни и те же операции. Оди­наково расположенные дуги будут относиться к входным переменным с одними и теми же номерами. Тогда все различия в последовательных реали­зациях программ сводятся к различиям в переборах вершин параллельных форм графов, определяемых лексикографическими порядками в пространст­вах итераций.
При любом порядке перебора вершин результаты выполнения операций, соответствующих первым ярусам, зависят только от входных данных про­грамм. Поэтому для обеих программ они будут одинаковыми при одних и тех же входных данных в операциях, соответствующих одинаково располо­женным вершинам. Предположим, что для обеих программ одинаковыми будут результаты выполнения всех соответствующих операций из всех яру­сов от 1-го до к-го, к > 1. Рассмотрим любую вершину /из (к + 1)-го яруса. Если в вершину / входит дуга из вершины /, то для каждой из программ / -< J по определению графа алгоритма. Вершина / обязательно находится в ярусе, номер которого не превосходит к. Следовательно, при любом допус­тимом переборе вершин наборы значений аргументов для операции, соот­ветствующей вершине /, будут одними и теми же. Это означает, что и для {к + 1)-го яруса результаты выполнения соответствующих операций будут для обеих программ одинаковыми. Заканчивая перебор всех ярусов, заклю­чаем, что справедливо
Утверждение 7.5
Пусть программы эквивалентны по графам алгоритма. Тогда при последова­тельной реализации на одном и том же компьютере при одних и тех же вход­ных данных они будут давать одни и те же результаты, включая всю совокуп­ность ошибок округления.
По существу программы, эквивалентные по графам алгоритма, представляют различные записи одного и того же алгоритма. Единственное, чем они могут
Глава 7. Эквивалентные преобразования программ
411
принципиально отличаться друг от друга, — это объемом памяти, необходи­мым для хранения результатов промежуточных вычислений, а также формой организации переменных в массивы. Кроме этого, входные и выходные данные таких программ могут отличаться друг от друга какой-либо их пере­сортировкой. Мы будем предполагать, что при реализации эквивалентных программ необходимая пересортировка делается. Эквивалентные программы обладают одним и тем же параллелизмом. Однако записан он может быть по-разному. Например, в одной программе какой-то параллелизм может быть описан явно в форме циклов ParDO. В другой программе этот же па­раллелизм может оказаться неявно описанным в форме скошенного.
Основная цель преобразования программы заключается в приведении ее к такому виду, в котором весь ее массовый параллелизм представлен явно в форме циклов ParDO. Обязательным условием любого преобразования явля­ется сохранение уровня точности получаемых результатов. Запись преобра­зованной программы на алгоритмическом языке имеет большое значение. Но в ближайших исследованиях мы не будем заниматься этим вопросом. Будем понимать сейчас под преобразованием программы преобразование ее про­странства итераций с одновременным изменением используемых переменных. В этих условиях реализация программы означает выполнение операций, со­ответствующих точкам пространства итераций, в той последовательности, которая диктуется лексикографическим порядком в данном пространстве.
Пусть выполняется преобразование программы. Чтобы показать, что исход­ная и преобразованная программы дают одни и те же результаты, вроде бы нужно построить для них графы алгоритмов и сравнить эти графы на изо­морфизм. Однако этот способ проверки эквивалентности программ не все­гда возможно осуществить на практике. Сравнение графов на изоморфизм является довольно трудоемкой задачей. Кроме этого, очень часто принимать решение о том, какое преобразование нужно делать, приходится по ходу построения самого преобразования. В этом случае мы просто не можем по­лучить граф алгоритма преобразованной программы. Поэтому мы поступим иначе. Будем выполнять специальное преобразование. Оно гарантирует в ря­де случаев эквивалентность по графам алгоритма и позволяет осуществлять промежуточный контроль. Это преобразование охватывает большую группу конкретных преобразований, объединенных общим названием переупорядо­чивание пространства итераций. Пусть для исходной программы построен граф зависимостей. Специальное преобразование таково:
□  выполняется эквивалентное преобразование пространства итераций;
□  для новой программы выбираются такие переменные, чтобы при преоб­разовании пространства итераций разные переменные переходили в раз­ные, одинаковые — в одинаковые;
□  в новом пространстве итераций устанавливается лексикографический по­рядок, при котором образ графа зависимостей будет лексикографически правильным.
412
Часть II. Параллельное программирование
Рассмотрим сначала случай, когда в специальном преобразовании использу­ется граф зависимостей, являющийся объединением всех четырех мини­мальных снизу графов. Будем помечать образы точек пространства итераций штрихами. Пусть Г, /' являются начальной и конечной вершинами дуги графа алгоритма новой программы. Точка / на выходе и точка / на соответ­ствующем входе имеют одинаковые переменные. Предположим сначала, что I < J. Эти точки истинно зависимы. Поэтому, согласно утверждению 6.2, существует точка Р такая, что I < Р < J, пара точек /, Р или связана путем
графа зависимостей по выходу или I = Р, а пара точек Р, / связана дугой графа алгоритма исходной программы. В силу третьего свойства специаль­ного преобразования заключаем, что Г < Р' < /'. В силу второго свойства в
точках /' и Р' пересчитывается одна и та же переменная. Следовательно должно быть /' = Р', I = Р, т. к. в противном случае пара точек Г, /' не мо­жет задавать дугу графа алгоритма. Итак, в этом случае дуга графа алгоритма новой программы есть образ дуги графа алгоритма исходной программы.
Пусть теперь I >- J. Аналогично заключаем, что существует точка Р такая, что I у Р у J, пара точек /, Р связана дугой графа антизависимостей, а пара точек Р, / или связана путем графа зависимостей по входу или Р = J. Это означает, что Г >- Р' у /'. Однако на самом деле /' -< /'. Поэтому случай
/ у /невозможен.
Рассмотрим далее пару вершин /, / исходной программы, связанную дугой графа алгоритма. Пусть I < J. Согласно свойствам специального преобразо­вания, точка /' на выходе и точка У на соответствующем входе имеют оди­наковые переменные. Кроме этого, Г < /'. Существует точка Q' такая, что Г < Q' < /', пара точек /', Q' связана путем графа зависимостей по выходу,
а пара точек Q', /'связана дугой графа алгоритма. Согласно сказанному вы­ше, пара точек Q, / задает дугу графа алгоритма исходной программы и Q < J. Но по заданной конечной вершине / и заданному ее входу начальная вершина дуги графа алгоритма определяется однозначно. По предположе­нию это есть /. Поэтому 1= Q, Г= Q'. Это означает, что образ дуги графа алгоритма исходной программы есть дуга графа алгоритма новой программы.
Суммируя сказанное, заключаем, что при выполнении специального преоб­разования образ графа алгоритма исходной программы является графом ал­горитма преобразованной программы. Аналогичные утверждения справедли­вы и в отношении всех остальных минимальных графов зависимостей. Следовательно, справедливо общее
Утверждение 7.6
Пусть над любой программой выполняется специальное преобразование, в ко­тором в качестве графа зависимостей используется объединение всех четырех минимальных снизу графов. Тогда образ любого из минимальных снизу графов
Глава 7. Эквивалентные преобразования программ
413
зависимостей исходной программы есть минимальный снизу граф зависимо­стей того же типа преобразованной программы.
Чтобы обеспечить эквивалентность по графу алгоритма, наблюдая лишь за лексикографической направленностью образов, нам пришлось учитывать все четыре минимальных снизу графа зависимостей. К сожалению, число наблюдаемых таких графов нельзя уменьшить. Можно лишь уменьшить число наблюдаемых дуг.
Утверждение 7.7
Пусть граф задан линейной функцией Ф на многограннике А. Для того чтобы граф был лексикографически правильным, необходимо и достаточно, чтобы лексикографически правильными были дуги, относящиеся лишь к угловым точ­кам многогранника А.
Рассмотрим для определенности случай, когда функция Ф задает входящие дуги. Случай исходящих дуг рассматривается аналогично. Необходимость очевидна. Для доказательства достаточности обозначим через у произволь­ную точку многогранника А, через у1его угловые точки и воспользуемся известным соотношением [4], что
y = Yja'yl' а/-°> Ха/=1' уеА-i                                             i
Если функция Ф задает входящие (исходящие) дуги, то Ф(у1) -< 0 (>- 0) для всех /. Поэтому
Ф(^) = £а/Ф(У)^0(^0)
/
для всех у е А в силу того, что а/ > 0 для всех /.
В наших случаях все графы будут задаваться линейными функциями на ли­нейных многогранниках, и будем мы совершать, как правило, линейные преобразования. Из утверждения 7.7 следует, что образ такого графа при линейном преобразовании будет лексикографически правильным тогда и только тогда, когда лексикографически правильными будут образы дуг, вхо­дящих в вершины многогранников. Специальное преобразование сохраняет объем используемых входных, выходных и промежуточных переменных. Ес­ли увеличить объем используемых переменных, то все графы, кроме графа алгоритма, можно существенно упростить.
Рассмотрим следующее элементарное преобразование, не обсуждая сейчас возможность его записи. Возьмем любую точку пространства итераций и весь пучок дуг графа алгоритма, выходящих из этой точки. Заменим связан­ную с данными дугами переменную на любую другую, не совпадающую ни с одной из используемых в программе переменных. Очевидно, что полученная программа будет эквивалентна исходной по графу алгоритма. Однако в гра-
414
Часть II. Параллельное программирование
фе зависимостей по выходу пропадет дуга, входящая в выбранную точку, и дуга, выходящая из нее. В графе антизависимостей пропадет дуга, входящая в эту точку. В нем также пропадут все дуги, которые были связаны со ста­рой переменной и выходили из конечных вершин рассматриваемого пучка дуг графа алгоритма. Могут пропасть и некоторые дуги графа зависимостей по входу. В общем случае возможно появление новых дуг. Но если выпол­нять элементарные преобразования, например, последовательно, начиная с лексикографически самой младшей точки, то новых дуг не будет. Поэтому заключаем, что имеет место
Утверждение 7.8
Для любой программы существует эквивалентная по графу алгоритма про­грамма, у которой графы зависимостей по выходу, антизависимостей и обрат­ных зависимостей пустые. Дуги графа зависимостей по входу эквивалентной программы связывают только те точки пространства итераций, которые явля­ются конечными для дуг графа алгоритма, выходящих из одной точки.
Мы уже отмечали в § 6.7важность факта, когда итерации цикла, имеющего тип ParDO по графу алгоритма, не связаны дугами других минимальных гра­фов. Обеспечить это может выполнение рассмотренных элементарных пре­образований. Конечно, совсем не обязательно исключать все возможные дуги. Обычно достаточно исключить лишь те из них, которые связаны с временными переменными.
Математические записи алгоритмов не допускают пересчета значений ис­пользуемых переменных. В программах такой пересчет допускается. Если граф зависимостей по выходу пустой, то это означает, что в программе нет пересчета переменных. Поэтому получение эквивалентной программы из утверждения 7.8 можно трактовать как восстановление по программе мате­матического описания алгоритма.
В заключение обратим внимание на одно важное обстоятельство. Рассмот­рим специальное преобразование для программы, у которой все минималь­ные снизу графы являются максимально пустыми. Совершенно очевидно, что требование лексикографической правильности образа графа зависимо­стей по входу является для данного случая излишним при доказательстве эквивалентности по графу алгоритма исходной и преобразованной про­грамм. Это объясняется тем, что четыре минимальных снизу графа дают в совокупности избыточную информацию о структуре алгоритма, описанного программой. На данном конкретном случае указанная избыточность и про­является. Напомним, что минимальные снизу графы являются отражением традиционно используемых графов зависимостей. Минимальные сверху графы не градиционны. Точная структура описанного программой алгорит­ма задается некоторой комбинацией минимальных снизу и сверху графов.
Рассмотрим теперь случай, когда в качестве наблюдаемого графа зависимо­стей используется объединение графа алгоритма, графа зависимостей по вы-
Глава 7. Эквивалентные преобразования программ
415
ходу и графа обратных зависимостей. В полной аналогии с утверждением 7.6 устанавливается
Утверждение 7.9
Пусть над любой программой выполняется специальное преобразование, в ко­тором в качестве графа зависимостей используется объединение графов алго­ритма, зависимостей по выходу и обратных зависимостей. Тогда образ любого из этих графов является аналогичным графом преобразованной программы.
Вопросы и задания
1.   Докажите, что каким бы образом не менять местами координаты /, j, к в про­странстве итераций примера 6.1, получаются эквивалентные пространства.
2.   Докажите, что в условиях п. 1 получаются программы, эквивалентные по графу алгоритма.
3.   Докажите, что если поменять местами координаты /, j пространства итераций при­мера 6.2, представленного на рис. 6.11, получается эквивалентное пространство.
4.   Докажите, что в условиях п. 3 получается программа, эквивалентная исходной по графу алгоритма.
5.   Докажите, что если поменять местами координаты /, j пространства итераций при­мера 6.3, представленного на рис. 6.12, получается эквивалентное пространство.
6.   Получается ли в условиях п. 5 программа, эквивалентная исходной по графу ал­горитма?
§ 7.4. Наиболее распространенные преобразования программ
Рассмотрим кратко некоторые преобразования, получившие наиболее ши­рокое применение на практике. Проведенные выше исследования позволя­ют значительно упростить изучение возможности их использования. Если для преобразуемой программы известны нужные графы зависимостей, то в большинстве случаев удается точно ответить на вопрос, можно или нельзя выполнять то или иное преобразование. Здесь мы коснемся лишь математи­ческих особенностей преобразований и не будем обсуждать особенности, связанные с учетом деталей архитектуры параллельного компьютера.
Прежде чем переходить к рассмотрению конкретных преобразований, сде­лаем одно уточнение. Общие исследования, проведенные в § 7.3, не связы­вали преобразование пространства итераций и установление в новом про­странстве лексикографического порядка перебора точек. Это определялось тем, что в общих исследованиях не фиксировалась форма записи преобразо­ванной программы. При рассмотрении конкретных преобразований ситуа­ция иная. Теперь форма записи преобразованной программы фиксируется.
416
Часть II. Параллельное программирование
Она определяет лексикографический порядок в новом пространстве итера­ций. Поэтому при обсуждении эквивалентности преобразований мы будем принимать во внимание только одно соответствие между пространствами итераций преобразуемой и преобразованной программ. Именно то, которое определяется конкретным рассматриваемым преобразованием. На преобра­зованном пространстве итераций мы будем считать заданным вполне опре­деленный лексикографический порядок. Именно тот, который диктуется формой записи преобразованной программы.
Перестановка циклов
Преобразование состоит в перестановке местами каких-либо двух циклов в тесно вложенном гнезде циклов. Не ограничивая существенно общность, можно считать, что первый (самый внешний) цикл переставляется с к-ым. Для установления эквивалентности преобразования по графу алгоритма можно воспользоваться критериями, сформулированными в утверждени­ях 7.6, 7.9. Чтобы применить эти критерии, нужно быть уверенным в том, что перестановка 1-ой и к-ок координат точек пространства итераций пере­водит лексикографически правильную дугу в лексикографически правиль­ную. Легко проверить, что для выполнения данного условия достаточно, чтобы к-ая координата конечной вершины дуги была больше к-ок коорди­наты начальной вершины. Если графы зависимостей для гнезда циклов представлены линейными функциями на линейных многогранниках, то со­отношение между координатами устанавливается весьма просто.
В частности, всегда можно переставлять рядом стоящие циклы, имеющие по всем зависимостям тип ParDO. После перестановки свойство ParDO сохраня­ется у обоих циклов. Если возможна перестановка 1-го цикла с к-ым и 1-ый цикл имеет тип ParDO, то после перестановки тип ParDO будет иметь к-ъш цикл. Пусть самый внешний цикл имеет тип ParDO по всем зависимостям. Его всегда можно переставить со вторым циклом. Новый второй цикл будет также иметь тип ParDO по всем зависимостям. Поэтому его можно переста­вить с третьим циклом и т. д. Это означает, что любой цикл ParDO всегда можно поставить в тесно вложенном гнезде на любое более глубокое место. При этом свойство ParDO сохраняется. В противоположность этому, в об­щем случае переставлять внутренний цикл ParDO "наружу" нельзя.
Область изменения параметров циклов в гнезде описывается треугольными соотношениями. В случае перестановки 1-го и к -го циклов новая область, вообще говоря, будет иметь более сложное описание. Единственным исклю­чением является ситуация, когда границы изменения к-го параметра не за­висят от других параметров. Тогда новая область также описывается тре­угольными соотношениями.
Слияние циклов
Рассмотрим две подряд идущие циклические конструкции с одинаковыми (с точностью до обозначения параметров) самыми внешними циклами. Преоб-
Глава 7. Эквивалентные преобразования программ
417
разование заключается в слиянии этих конструкций в одну. Она представля­ет прежний внешний цикл, тело которого образовано из подряд идущих тел внешних циклов исходных конструкций. Для эквивалентности преобразова­ния снова потребуем, чтобы лексикографически правильная дуга графа за­висимостей преобразовывалась в лексикографически правильную. Очевид­но, что нужно рассмотреть только те дуги, которые идут из точек пространства итераций первой циклической конструкции в точки простран­ства итераций второй циклической конструкции. Для всех остальных дуг это требование выполняется при преобразовании автоматически. Для того что­бы оно выполнялось и для рассматриваемых дуг, достаточно, чтобы первые координаты начальных вершин не превосходили первые координаты конеч­ных вершин. Имея явные формулы для дуг, это проверить несложно.
Допустим, что внешние циклы сливаемых конструкций имеют тип ParDO. Если для дуг, которые идут из точек пространства итераций первой цикли­ческой конструкции в точки пространства итераций второй циклической конструкции, первые координаты начальных и конечных вершин совпада­ют, то внешний цикл преобразованной конструкции будет иметь тип ParDO.
Переупорядочивание операторов
Преобразование состоит в выполнении последовательности перестановок местами пар операторов или циклов в пределах ближайшего объемлющего их цикла. Допустим, что построен граф зависимостей для тела объемлющего цикла. Зафиксируем любую пару переставляемых объектов и рассмотрим все дуги, у которых хотя бы один из концов принадлежит опорным пространст­вам (или опорному пространству, если в объекте имеется только один опе­ратор) одного из объектов. Для того чтобы перестановка объектов была пре­образованием, эквивалентным по графу алгоритма, достаточно, чтобы при ее выполнении не менялось соотношение между номерами опорных про­странств, содержащих концевые вершины дуг. Другими словами, если дуга шла, например, из пространства с большим номером в пространство с меньшим номером, это соотношение должно сохраняться и после переста­новки, и т. п. Данное преобразование сохраняет циклы ParDO.
Распределение цикла
Это преобразование является обратным по отношению к преобразованию слияния циклов. Пусть задан некоторый цикл. Построим для него граф за­висимостей. Допустим, что после переупорядочивания в теле цикла его можно представить в виде двух подряд идущих фрагментов, и нет ни одной дуги, идущей из опорных пространств второго фрагмента в опорные про­странства первого фрагмента. Тогда цикл можно представить в виде двух подряд идущих циклических конструкций. Самые внешние циклы у них совпадают. Телом первого цикла является первый фрагмент, телом второ­го — второй фрагмент. Если преобразуемый цикл имел тип ParDO, то тип
418
Часть II. Параллельное программирование
ParDO будут иметь оба внешних цикла преобразованной программы. Оба внешних цикла или один из них могут иметь тип ParDO и в том случае, ко­гда таковым не является преобразуемый цикл.
Скашивание цикла
Это преобразование направлено на выявление и использование параллелизма, отличного от задаваемого циклами типа ParDO. Как правило, оно связывается с параллельными множествами, расположенными в линейных многообразиях пространства итераций. В специальной литературе можно найти много раз­личных предложений по реализации таких преобразований. Однако все они ориентированы на какие-то частные случаи.
Расщепление пространства итераций
Преобразование заключается в представлении цикла в виде двух подряд иду­щих циклов. Тела обоих циклов совпадают с телом преобразуемого цикла. Нижняя (верхняя) граница изменения параметра первого (второго) цикла также совпадает с соответствующей границей исходного цикла. Поэтому нуж­но найти лишь верхнюю (нижнюю) границу первого (второго) цикла. Эти границы должны удовлетворять следующим условиям: нижняя граница вто­рого цикла должна быть строго больше верхней границы первого цикла и обе границы должны находиться между границами преобразуемого цикла. Во всем остальном выбор границ произволен. Очевидно, что достаточно найти только верхнюю границу первого цикла. Если исходный цикл имел тип ParDO, то при любом способе нахождения границы оба получаемых цикла будут иметь тип ParDO. Оба получаемых цикла или один из них могут иметь тип ParDO и в том случае, когда таковым не является преобразуемый цикл.
Выполнение итераций цикла в обратном порядке
Формально преобразование заключается в перемене местами верхних и нижних границ изменения параметра какого-нибудь цикла программы и замене шага изменения параметра этого цикла с +1 на —1. Снова воспользу­емся критериями утверждений 7.6, 7.9. Допустим, что существует хотя бы одна дуга истинных зависимостей, антизависимостей или зависимостей по выходу, связывающая две итерации преобразуемого цикла исходной про­граммы. При рассматриваемом преобразовании образы этих дуг не будут лексикографически правильными. Поэтому все такие дуги должны отсутст­вовать. Это означает, что преобразуемый цикл должен иметь тип ParDO по объединению графов алгоритма, антизависимостей и зависимостей по выхо­ду. Согласно исследованиям, проведенным в §6.7, итерации такого цикла вообще можно выполнять в любом порядке, а не только в обратном.
Глава 7. Эквивалентные преобразования программ
419
Выделение стандартных операций
Преобразование состоит в поиске в тексте программы одинаковых фрагмен­тов и подстановки на их места обращений к оптимизированной процедуре. Основная трудность связана с установлением факта эквивалентности фраг­ментов. Для решения этого вопроса можно воспользоваться критериями ут­верждений 7.6, 7.9.
Среди преобразований программ, связанных с изменением координат точек пространства итераций, наиболее простыми являются те, которые не нару­шают лексикографического отношения. Основой построения таких преобра­зований служит утверждение 6.5.
Треугольные преобразования
Будем считать, что каждое опорное пространство преобразуется само в себя. Пусть в /-ом пространстве преобразование имеет вид
Z = BiX+bi.                                             (7.10)
Здесь Bj есть левая треугольная целочисленная матрица с единичными диа­гональными элементами. Она не зависит от внешних переменных програм­мы. Координаты вектора bt могут быть линейными неоднородными функ­циями от внешних переменных, имеющими целочисленные коэффициенты. Вектор х задает координаты точки в старой системе координат, вектор z — в новой. Согласно утверждению 6.5, преобразование (7.10) сохраняет лекси­кографическое отношение между точками пространства. Дополнительные условия целочисленности гарантируют, что образы и прообразы целочис­ленных точек для преобразования (7.10) будут целочисленными.
Если /-ое и у'-ое опорные пространства не имеют общих параметров циклов, то матрицы и свободные члены в преобразованиях этих пространств могут быть не связаны между собой. Если же общие параметры имеются, то после преоб­разования программы одинаковые значения общих параметров должны пе­рейти в одинаковые, разные — в разные. Пусть /-ое и у'-ое опорные простран­ства имеют г общих параметров. Будем считать, что первые г строк матриц Д и Bj попарно совпадают, так же как и первые г координат векторов bj и bj.
Построим для всех опорных пространств преобразования вида (7.10) с ука­занными особенностями матриц Bj и векторов bj. Назовем такие преобразо­вания треугольными. Выразим из соотношений (7.10) старые параметры хк через переменные Zj и подставим всюду в текст программы вместо старых параметров соответствующие выражения. Приведем границы изменения но­вых параметров цикла к стандартной форме. Очевидно, что новый текст представляет описание программы из линейного класса. Соответствие меж­ду пространствами итераций исходной и преобразованной программ сохра­няет по построению лексикографическое отношение. Выполненное преоб­разование программы удовлетворяет всем условиям описанного выше специального преобразования. Поэтому новая программа будет эквивалент-
420
Часть II. Параллельное программирование
на исходной по графу алгоритма и, следовательно, будет сохранять результа­ты реализации программы при одних и тех же входных данных.
Формальная запись новой программы получается из старой с помощью заме­ны всех вхождений параметров циклов их выражениями, полученными из со­отношений (7.10). Однако лишь этого факта было бы недостаточно для обес­печения эквивалентности преобразования программы. Чтобы формальная замена параметров циклов привела к эквивалентной программе, нужно на преобразования (7.10) наложить какие-то ограничения. Они могут быть таки­ми, какие имеют треугольные преобразования, но могут быть и другими.
Любое треугольное преобразование переводит независимые точки простран­ства итераций в независимые, зависимые — в зависимые того же типа. От­сюда, в частности, следует
Утверждение 7.10
Любое треугольное преобразование переводит цикл ParDO в цикл ParDO и со­храняет его тип. Никаких новых циклов ParDO при треугольном преобразовании появиться не может.
Само по себе треугольное преобразование не несет большой самостоятельной смысловой нагрузки. Однако оно имеет важное значение как предварительное преобразование перед выполнением каких-то других преобразований. С по­мощью треугольных преобразований можно сделать более явными некоторые структурные свойства программы. Например, в программе не виден паралле­лизм, задаваемый с помощью разверток. Не видно даже, какие точки про­странства итераций относятся к одной поверхности уровня развертки, а какие к разным. Выполняя специальным образом подобранное треугольное преобра­зование, можно сделать подобную информацию более явной.
Множество используемых на практике преобразований постоянно расширя­ется. Этому процессу в немалой степени способствует появление компьюте­ров новой архитектуры. Многие преобразования не требуют для своей реа­лизации серьезного математического обоснования. К ним относятся, например, следующие: вынесение за пределы цикла нескольких первых или последних итераций, объединение нескольких циклов тесно вложенного гнезда в один, дублирование тела цикла несколько раз с одновременным уменьшением во столько же раз числа повторений цикла, полная раскрутка цикла по всем итерациям, преобразование одномерного цикла в двумерную циклическую конструкцию, генерация нескольких вариантов для какого-то фрагмента с выбором конкретного варианта во время исполнения програм­мы и многие другие.
С некоторыми другими преобразованиями программ, в особенности с теми, которые требуют математического обоснования, можно познакомиться в книге [6].
Глава 7. Эквивалентные преобразования программ
421
Вопросы и задания
1. Для различных программ или их фрагментов постройте графы зависимостей и выполните эквивалентные преобразования. Не правда ли, после нескольких ус­пешных и, тем более, безуспешных примеров захотелось иметь автоматизирован­ную систему, помогающую осуществлять такую работу?
§ 7.5. Две сопутствующие задачи
Проведенные исследования являются основой для решения самых различ­ных задач, так или иначе связанных с алгоритмами и их структурами, фор­мами записи, использованием и т. п. Рассмотрим кратко две из них.
Графы зависимостей и развертки позволяют выявлять параллелизм вычис­лений в алгоритмах. Всегда полезно знать, весь ли параллелизм обнаружен или нет. Ответ на этот вопрос дают нижние и верхние оценки длины крити­ческого пути графа алгоритма. Особенно важно иметь оценки величины массового параллелизма или, другими словами, параллелизма, определяемого значениями внешних переменных. Следовательно, в первую очередь, хоте­лось бы иметь оценки длины критического пути, в которых точно отражен порядок зависимости именно от этих переменных. Допустим, что в резуль­тате проведенных исследований получена программа, в которой какие-то циклы отмечены типом ParDO. Для этой программы легко оценить порядок длины критического пути графа алгоритма, считая остальные циклы после­довательными. Вообще говоря, такую оценку следует считать оценкой свер­ху, т. к. какой-то массовый параллелизм может оказаться не выявленным. Оценить уровень параллелизма в тех фрагментах, где он еще не обнаружен, можно с помощью разверток.
Пусть задан граф алгоритма G. Если для него известна строгая развертка, то число различных поверхностей уровня, уменьшенное на 1, дает оценку сверху длины критического пути графа G. При соответствующей нормировке раз­вертки эта оценка равна разности между максимальным и минимальным ее значением на точках пространства итераций. Всегда существует развертка, для которой эта разность равна длине критического пути. Под именем макси­мальной параллельной формы она описана в § 4.2. Находить строгие разверт­ки достаточно трудно. Гораздо проще находить обобщенные развертки. По­этому мы поступим следующим образом. Выберем подходящий класс разверток и найдем в нем развертку, которая строго возрастает вдоль макси­мально возможного числа дуг. Вообще говоря, она необязательно будет стро­гой. Но в выбранном классе она будет "самой строгой". Можно надеяться, что с ее помощью также удастся получить нужные оценки.
Процесс нахождения "самой строгой" развертки будем осуществлять рекур­сивно. Допустим, что для выбранного класса разверток мы умеем решать следующую задачу: для заданного графа найти развертку, которая строго возрастает хотя бы вдоль одной дуги, или установить, что такая развертка не
422
Часть II. Параллельное программирование
существует. Сначала решаем эту задачу для графа G Предположим, что най­дена нужная развертка g. Пусть она строго возрастает вдоль дуг из множест­ва Е, необязательно строго возрастает вдоль дуг из множества Е' и множест­ва Е и Е' не пересекаются. Пространство итераций и множество дуг Е' образуют граф G'. Теперь для него решаем основную задачу. Предположим, что найдена нужная развертка g'. Пусть она строго возрастает вдоль дуг из множества Е\ с Е\ необязательно строго возрастает вдоль дуг из множества Е\ с Е' и множества Е\ и Е\ не пересекаются. Число дуг в графе G конечно, хотя оно может зависеть от внешних параметров. Поэтому минимальное приращение функции g' вдоль дуг из множества Е может быть отрицатель­ным, но обязательно ограниченным снизу. Минимальное приращение функции g вдоль дуг из множества Е положительно по ее выбору. Следова­тельно, существует такое число а > 0, возможно зависящее от значений внешних переменных, что функция
g\=a.g + g'                                             (7.11)
будет иметь положительные приращения вдоль всех дуг из объединения Е и Е\ и не будет иметь отрицательные приращения вдоль всех дуг из множе­ства Е\. Это означает, что функция gi из (7.11) является разверткой для графа G и строго возрастает вдоль большего числа дуг, чем исходная развертка g. Продолжая этот процесс, мы приходим к развертке, которая строго возрастает вдоль максимально возможного числа дуг графа G Конечно, мы надеемся, что общее число шагов в рекурсивном процессе не будет большим.
В качестве подходящего класса возьмем кусочно-линейные развертки, опи­санные в § 7.1. Пусть куски линейности совпадают с опорными многогран­никами Vj. Все такие развертки определяются решениями неравенства (7.9). Будем считать, что это неравенство одно и соответствует оно одному мно­гограннику, задающему значения внешних переменных. В данном классе разверток легко найти "самую строгую". Допустим, что мы умеем решать следующую задачу: для заданной матрицы А найти какое-нибудь решение неравенства At < 0, не являющееся решением равенства At = О, или доказать, что такое решение не существует. Это можно осуществить, например, с по­мощью методов линейного программирования [4]. Пусть мы нашли такое решение для неравенства (7.9). Представим матрицу А, руководствуясь ут­верждением 7.4, в виде суммы двух матриц
А = В+ С,                                              (7.12)
составленных из нулевых строк и строк матрицы А. Именно, в матрице В оставим только те строки матрицы А, для которых координаты вектора At отрицательные; остальные строки возьмем нулевыми. В матрице С оставим только те строки матрицы А, для которых координаты вектора At нулевые; остальные строки возьмем нулевыми. Далее решаем аналогичную задачу для матрицы С. Она проще, чем для матрицы А, т. к. матрица С содержит меньше ненулевых строк. Пусть мы нашли решение t'. Согласно (7.11), со­ставим вектор Ц = at + t', где а > 0. Очевидно, что множитель а всегда мож-
Глава 7. Эквивалентные преобразования программ
423
но подобрать так, что вектор At\ будет неположительным и будет иметь больше отрицательных координат, чем вектор At. В отличие от общего слу­чая множитель а не зависит явно от значений внешних переменных и опре­деляется лишь векторами At и Bt'. Продолжая этот процесс, мы найдем такое решение t неравенства (7.9), что вектор At будет иметь максимально воз­можное число отрицательных координат. Соответствующую развертку будем называть самой строгой обобщенной кусочно-линейной разверткой.
В разложении (7.12) для подобной развертки матрица С либо нулевая, либо обладает следующим свойством: любое ненулевое решение неравенства Ct < 0 удовлетворяет равенству Ct = 0. Самая строгая кусочно-линейная раз­вертка не единственная. Однако для всех таких разверток разложение (7.12) для матрицы А будет одним и тем же. Если матрица С нулевая, то самая строгая развертка будет просто строгой разверткой. Матрица А целочислен­ная. Если ограничиться нахождением целочисленных векторов t, то при со­ответствующей нормировке разверток длину критического пути графа мож­но оценить разностью между максимальным и минимальным значениями развертки на линейном пространстве итераций. Так как это пространство состоит из опорных многогранников, то вычисление разности сводится к вычислению значений развертки в вершинах многогранников. Поэтому оценка длины критического пути как функция внешних переменных в дан­ном случае будет линейной, в крайнем случае, кусочно-линейной.
Если матрица С в разложении (7.12) для самой строгой развертки с направ­ляющим вектором t не нулевая, то сначала поступим следующим образом. Ру­ководствуясь утверждением 7.4, пересортируем в матрицах В и С строки мат­рицы А согласно их упорядочиванию, описанному в конце § 7.1. Оставим в матрице В максимальное число строк, которые, во-первых, содержат все стро­ки отдельных блоков и, во-вторых, имеют в каждом блоке отрицательными все координаты вектора At, относящиеся к первым подгруппам. Если после этого матрица С окажется нулевой, то самая строгая развертка снова будет просто строгой. Если же и теперь матрица С ненулевая, то построенная самая строгая развертка по существу является обобщенной. В этом случае исследование графа надо продолжить. Оно осуществляется рекурсивно по такой же схеме. Но покрывающие функции берутся не на всех многогранниках Vy, а только на тех многогранниках типа А/, о которых говорилось в § 7.1. Конечно, рекурсию необязательно доводить до конца. Оценивание можно остановить тогда, когда многогранники А/ по объему станут меньше, чем длина критического пути строгой части развертки, определяемой матрицей С.
Этот способ может дать оценку длины критического пути графа алгоритма, достаточно хорошо отражающую уровень "массового" параллелизма в алго­ритме. Сравнивая полученную оценку с прямой оценкой на основе анализа циклов ParDO, можно сделать выводы относительно истинного уровня па­раллелизма.
При решении задач на массивно-параллельных компьютерах или распреде­ленных вычислительных системах время доступа к памяти чужого процессо-
424
Часть II. Параллельное программирование
ра значительно превосходит время доступа к своей памяти. Поэтому перед решением задачи нужно так распределить данные по модулям памяти, что­бы по возможности минимизировать общее время доступа к ним. Успешно решать задачу на массивно-параллельном компьютере можно лишь в том случае, когда в программе имеются циклы ParDO. Более того, эти циклы должны быть как можно более внешними и охватывать основную часть вы­полняемых операций. Итерации каждого из них не должны использовать общие переменные. Следовательно, с практической точки зрения наиболее важно рассмотреть распределение данных именно в этих условиях.
Исследуем сначала случай использования одного массива в самом внешнем цикле. Будем считать, что массив А есть множество элементов, отождеств­ляемых с точками /--мерного арифметического пространства, где г — раз­мерность массива. Выделим в пространстве элементов какой-нибудь ненуле­вой вектор у. Назовем секцией множество элементов массива, лежащих в гиперплоскости с направляющим вектором у. Все пространство элементов разбивается на непересекающиеся секции. Номером секции, которой принад­лежит элемент и, можно считать значение скалярного произведения (у, и). Основная задача состоит в нахождении такого вектора у, чтобы при реали­зации одной итерации цикла все ссылки на массив А обращались к одной и той же секции. При реализации разных итераций цикла ссылки должны об­ращаться к разным секциям. Если вектор у существует, то распределение массива А по модулям памяти осуществляется следующим образом: в память процессора, выполняющего какую-то итерацию цикла, помещается именно та секция массива А, к которой происходят обращения при реализации этой итерации.
Рассмотрим какое-нибудь вхождение массива. Предположим, что индексы имеют вид и = Bklk + р^. Здесь Ikточка пространства итераций, соответ­ствующая данному вхождению, и — элемент массива, Вкчисловая матри­ца, р^ — вектор, линейно зависящий от внешних переменных. Обозначим через р значение параметра цикла. Гиперплоскость (ak, Ik) = р, где ак = (1, 0, ..., 0), содержит все точки пространства итераций, соответствую­щие итерации цикла со значением параметра, равным р. Параллельные не­совпадающие гиперплоскости относятся к разным итерациям цикла. Про­странство точек Ik с помощью линейного преобразования и = BkIk + р^ отображается на пространство элементов и. Согласно поставленной задаче, мы хотим найти такой вектор у, чтобы при данном преобразовании парал­лельные несовпадающие гиперплоскости с направляющим вектором ак либо отображались на параллельные несовпадающие гиперплоскости с направ­ляющим вектором у в целом, либо отображались на какие-то части таких гиперплоскостей. Как показано в [6], для этого необходимо и достаточно, чтобы была совместной система линейных алгебраических уравнений
в£у = ак.                                           (7.13)
Глава 7. Эквивалентные преобразования программ
425
Если система (7.13) несовместна, то искомое распределение массива не су­ществует.
Теперь рассмотрим всю совокупность вхождений массива. Вектор у должен удовлетворять системам (7.13) для всех к. Кроме этого, при всех преобразо­ваниях и = Вф + р^ гиперплоскости (а^,1к) = р для каждого р должны ото­бражаться на одну и ту же гиперплоскость. Также в [6] установлено, что существует матрица Bq, которая вычисляется по всем вхождениям массива и такая, что выполняется равенство
% = 0.                                               (7.14)
Соберем вместе все системы (7.13) и систему (7.14). Тем самым мы получа­ем для определения вектора у систему линейных алгебраических уравнений. Если эта система совместна, то любое из ее решений дает искомое распре­деление массива А по модулям памяти. Если же система несовместна, то, вообще говоря, нужного распределения нет. В этом случае можно попытаться ослабить требования на распределение массивов по модулям памяти.
Во-первых, можно попытаться решить лишь системы (7.13). Если это уда­лось, то при реализации отдельной итерации процессор будет обращаться в "чужие" секции. Однако таких секций будет немного, и они находятся от "своей" секции на одних и тех же "расстояниях" для всех процессоров. Такая ситуация представляет интерес и может быть использована.
Во-вторых, можно попытаться уменьшить число принимаемых во внимание вхождений массива. Рассмотрим какое-нибудь из неучтенных вхождений. Вполне возможно, что для него система (7.13) имеет решение для какого-то другого вектора у. В этом случае секции массива, соответствующие этому вектору у, можно продублировать. Чтобы уменьшить число дублирований секций, новый вектор у нужно выбирать так, чтобы он был общим для наи­большего числа неучтенных ссылок.
Допустим, что хотя бы для одного к система (7.13) несовместна и матрица Bj{ имеет полный столбцовый ранг. Тогда чаще всего это означает, что на
каждой итерации цикла к-ое вхождение обращается ко всем или почти ко всем элементам массива. Обеспечение независимой работы процессоров для реализации данной программы на массивно-параллельном компьютере тре­бует дублирования всего массива в каждом модуле памяти. Если цикл имеет тип ParDO по всем четырем минимальным графам, то независимая работа процессоров снова возможна.
Если система (7.13) или несовместна, или имеет полный столбцовый ранг, то никакая линейная замена переменных в опорном пространстве не может изменить ситуацию. Поэтому, если дублирование массива в каждом модуле памяти неприемлемо по каким-либо причинам, приходится радикально из­менять программу.
426
Часть II. Параллельное программирование
Необходимость дублирования какой-то части массива не означает, что в ка­ждом модуле обязательно нужно отводить столько же памяти. Как правило, один и тот же элемент массива на разных итерациях используется в разное время. Довольно часто дублирование почти полностью можно заменить пересылками информации между процессорами, осуществляемыми в специ­альным образом выбранные моменты времени, например, так, как это дела­ется в систолических массивах [9]. Переход к блочным вариантам алгорит­мов значительно снижает временные затраты на пересылки. К сожалению, использование пересылок несколько уменьшает параллельные свойства вы­числительных процессов, т. к. приходится учитывать их синхронизацию во времени. К тому же, в этих случаях нередко приходится изменять порядок выполнения операций, что, строго говоря, приводит к неэквивалентным по вычислениям преобразованиям программ.
Проведенные исследования показывают, что распределение данных по мо­дулям памяти осуществляется независимо для каждого массива. Следова­тельно, использование в программе нескольких массивов не усложняет принципиально рассматриваемую задачу. Не усложняет принципиально ее и одновременное рассмотрение нескольких внешних циклов.
Формально мы почти нигде не использовали тот факт, что цикл может иметь тип ParDO. Однако он самым тесным образом связан со свойствами систем линейных алгебраических уравнений для определения вектора у. Если описанную технику применить для циклов, тип которых неизвестен, то, скорее всего, соответствующие системы (7.13), (7.14) для какого-нибудь массива будут несовместны. Если же совокупность систем (7.13), (7.14) ока­жется совместной для какого-то массива, то цикл будет иметь тип Pa г do по всем четырем минимальным графам, построенным именно для этого массива.
Итак, для существования нужного распределения любого массива по итера­циям цикла необходимо, чтобы цикл имел тип ParDO по всем графам зави­симостей, связанным только с этим массивом. Какой тип имеет цикл по графам зависимостей для других переменных, не имеет никакого значения. Наличие типа ParDO у цикла не гарантирует существование нужного распре­деления.
§ 7.6. Примеры
Пример 7.1. Поясним процесс построения разверток на примере 6.2. Будем считать, что пространство итераций представлено на рис. 6.11. Обозначим через V линейное пространство итераций. Это есть треугольник с вершина-
Глава 7. Эквивалентные преобразования программ
427
ми в точках (1,0), (я, 0), (я, п — 1). Четыре покрывающие функции из при­мера 6.2 могут быть объединены в две
Ф,
лл
0Л ■1
-1
2 < / < я, 1 < у < / - 1; 2 < / < я, 1 < у < / - 1.
01 01
Г/Л
ф0
v^/
В треугольнике К будем искать обобщенные развертки вида (а, х) + у, где a = (a\, a2), у = уо + у\ п. В нашем случае функции Ф] и Ф2 действуют из V в К Поэтому в соответствии с обозначениями § 7.1 в соотношениях (7.2) и др. будем иметь a = b, у = 5. Очевидно, что представление (7.6), (7.7) для внешней переменной п имеет вид
я = 2 + р, р>0.                                          (7.15)
С учетом сказанного соотношение (7.2) для функции Ф] становится таким
а2 > 0.                                                            (7.16)
Функция Ф2 определена на треугольнике, вершинами которого являются точки (2, 1), (я, 1), (я, п — 1). Подставляя координаты этих вершин в нера­венства (7.4), получаем неравенства (7.5):
ai — a2 < 0, (ai — a2) — na\ < 0.
Теперь, принимая во внимание (7.15), находим, что неравенства (7.8) будут выглядеть следующим образом:
а\ — «2 < 0, — а\ < 0.
Присоединяя к ним неравенство (7.16), заключаем, что для координат а\, а2 направляющего вектора а развертки {а, х) + у должны иметь место нера­венства
а, > 0, а2 > 0 .                                                     (7.17)
Легко проверить по рис. 4.4, а, что никаких других линейных на многогран­нике Кразверток граф алгоритма из примера 6.2 не имеет.
Если проделать аналогичные вычисления для графа алгоритма примера 6.3, то можно установить, что в этом случае соотношения для координат а\, а2 будут такими:
«1 > 0, а2 = 0.                                                     (7.18)
Формальное сравнение соотношений (7.17), (7.18) также показывает разли­чие между примерами 6.2 и 6.3. Неравенства (7.17) имеют два независимых решения. Например, щ = I, а2 = 0 и а\ = 0, а2= 1. Соотношения (7.18) имеют только коллинеарные решения. Поэтому граф на рис. 4.4, а имеет две независимых развертки, граф на рис. 4.4, б только одну, как и должно быть,
428
Часть II. Параллельное программирование
основную. Оба графа не имеют расщепляющих разверток. Для графа на рис. 4.4, а любая развертка с положительными координатами направляю­щего вектора будет строгой. Отсюда, в частности, следует, что операции алгоритма, соответствующие вершинам, лежащим на прямой вида / + j = const, можно выполнять параллельно.
Пример 7.2. Посмотрим на примере 6.4, что дает построение макрографов зависимостей для анализа макроструктуры, например, графа алгоритма.
Будем считать области на рис. 6.13 макровершинами. Чтобы установить, из каких макровершин выходят макродуги, входящие в макровершину с номе­ром /, нужно определить каким областям принадлежат значения функции Ф/ в угловых точках /-ой области. Такая проверка показывает, что макрограф имеет следующий вид (рис. 7.3).
13 2 4 5
Рис. 7.3. Общий макрограф
Макровершины с номерами 1, 4, 5 не имеют петли, поэтому операции, со­ответствующие их вершинам, можно выполнять параллельно. Макроверши­ны с номерами 2, 3 принадлежат сильно связанному подмакрографу. По­этому их параллельную структуру надо исследовать совместно и более тщательно. Прямой анализ программы примера 6.4 в целом не позволил об­наружить параллелизм, который давал бы возможность существенно сокра­тить время ее реализации.
Теперь построим макрограф для внутреннего цикла программы примера 6.4. Имеются три покрывающие функции Ф', Ф" и Ф'". Снова в качестве макро­вершин возьмем их области определения. Аналогичная проверка показыва­ет, что макрограф внутреннего цикла имеет вид, приведенный на рис. 7.4.
*^                И
Рис. 7.4. Макрограф внутреннего цикла
Глава 7. Эквивалентные преобразования программ
429
Все макровершины не имеют петли. Поэтому операции, соответствующие их вершинам, можно выполнять параллельно. Это означает, что программу примера 6.4 можно записать следующим образом
DO i = 1, n
DO j = 1, n- i
ui+j~ U2n+l-i-j END DO                                                                                                                                        (7.19)
DO j = n - i + 1, и
Uj+j = Ii2n+l-l-j
END DO END DO
Очевидно, что оба внутренних цикла имеют тип ParDO. Заметим, что с по­мощью традиционно используемых методов программу примера 6.4 не уда­ется привести к виду (7.19), в котором параллелизм имеется в циклах.
Пример 7.3. Покажем, как строится макрограф с помощью разверток. Рас­смотрим решение краевой задачи для уравнения теплопроводности. Пусть требуется найти функцию u(t, х), где
э7=э^'           ' °-'-г'                      (7-2°)
и(0, х) = ф(х), u(t,0) = «о(0, М(?Д) = Mi(0-Построим равномерную сетку с шагом h по х и шагом ? по t. Предположим, что по тем или иным причинам выбрана явная схема
u'j - u'j~l u'j~_\ - 2u'j~l + u'j~+\
h2 где u'j = u(h, jh). Пусть алгоритм реализуется в соответствии с формулой
^"Г++^^м)-              (7'21)
Для построения графа алгоритма введем прямоугольную систему координат с осями /, j. Переменные t, х связаны с переменными /, j соотношениями
t=h,x = jh.
Поместим в каждый узел целочисленной решетки вершину графа и будем считать ее соответствующей скалярной операции
2т т ® = a(l-—) + —{b + c),
h h
430
Часть II. Параллельное программирование
выполняемой для разных значений аргументов а, Ь, с. Предположим, что переменные /, j изменяются в пределах
О < /< т, 0 </'< п.
i
1 ,
1 -ч
У
ЧУ
ЧУ!
Ч У
\/
^\/^
^\/^
pt-—«
>-
и
►/'
/
У
i '/ 1 -./ .
ЧУ
ЧУ
\/
^\/^
4,/У
L
►У
Рис. 7.5. Поверхности уровней разверток
Непосредственно из (7.21) следует, что покрывающие функции для графа алгоритма будут такими:
fi\
Ф,
По}
01
v /
(-
, \<i<m, 1 < j < n - 1;
1
V /
(-Л
, 0,
, 1 < / < /и, 1 < j < n - 1;
(7.22)
Ф,
yh
Ф,
,01,
vO
1
V /
, 1 < / < m, 1 < j < n - 1.
Для случая x = 2/6, /г = 1/8 граф алгоритма представлен на рис. 7.5, а. Пунктирными линиями показаны вершины, относящиеся к одному вре­менному слою.
Теперь опишем множество обобщенных разверток этого графа. Будем ис­кать их в следующем виде:
/= ш + (3/ + у.
Не привлекая общего метода их нахождения, заключаем из рис. 7.5, а, что в соответствии с покрывающими функциями (7.22) коэффициенты а, р долж­ны удовлетворять системе неравенств
а + р > 0, а > 0, а-р>0.
Глава 7. Эквивалентные преобразования программ
431
Эта система заведомо имеет два независимых решения. Например,
Л = >+J,f2 = i-j-Некоторые из их поверхностей уровней показаны на рис. 7.5,6.
Поверхности уровней обобщенных разверток разбивают линейное простран­ство итераций на прямоугольники. В качестве макровершин берутся верши­ны, попадающие в отдельные прямоугольники. Кроме граничных, любая мак­ровершина получает информацию от двух соседних, находящихся под ней макровершин и передает информацию двум соседним, находящимся над ней макровершинам. Количество получаемой и передаваемой по макродуге ин­формации пропорционально числу вершин, лежащих на соответствующей "стороне макровершины" и непосредственно примыкающих к этой "стороне".
В связи с этим примером заметим следующее. При решении нестационар­ных задач явными методами почти всегда расчеты ведутся по временным слоям, последовательно слой за слоем. В нашем случае — по пунктирным линиям на рис. 7.5, а. Если вся информация о соседнем слое помещается в оперативную память, то особых проблем не возникает. Кстати, на каждом временном слое операции не связаны между собой и могут выполняться па­раллельно. Поэтому счет по временным слоям особенно эффективен на па­раллельных вычислительных системах с общей памятью.
Если задача настолько большая, что информация о соседнем слое не поме­щается в оперативную память, то приходится пользоваться меленной памя­тью и тогда возникают проблемы. Время переноса информации о слое из медленной памяти в оперативную пропорционально числу точек в слое. Время нахождения решения задачи на очередном слое также пропорцио­нально числу точек в слое. Но время выполнения одной операции значи­тельно меньше среднего времени пересылки единицы информации из мед­ленной памяти в оперативную. Поэтому при счете по временным слоям большая часть времени будет уходить на организацию пересылок, т. е. будет тратиться непроизводительно.
Теперь представим, что алгоритм реализуется по макровершинам. В этом случае время на пересылки информации для каждой макровершины будет пропорционально длине (в общем случае, площади) границы содержащего ее прямоугольника. Время выполнения макровершины будет пропорцио­нально площади (в общем случае, объему) прямоугольника. При увеличении прямоугольников длины границ растут медленнее площадей. Следовательно, разбиение линейного пространства на прямоугольники всегда можно вы­брать таким, что время пересылок информации из медленной памяти в опе­ративную будет составлять сколь угодно малую долю.
Как показывают исследования, проведенные в § 7.2, возможность реализа­ции по макровершинам оказывается реальной для широкого круга алгорит­мов. В частности, эта возможность широко использовалась при решении
432
Часть II. Параллельное программирование
задач линейной алгебры блочными методами [5]. Общий случай впервые рассмотрен в [11].
Пример 7.4. Рассмотрим снова пример 6.5. Среди циклов ParDO в нем наи­большим по объему вычислений является цикл с параметром j. Выясним, можно ли распределить массив А таким образом, чтобы на разных итераци­ях этого цикла не было обращений к одним и тем же секциям массива. От­вет очевиден: секции массива должны совпадать с его столбцами. Но мы получим этот ответ формальным способом.
В цикле с параметром j примера 6.5 вхождения элементов массива А имеют следующий вид
(0\ (Л
'0"
1
V /
J +
0
V J
1
V J
j +
О
V J
10
i
\ j
о
10
v j
В соответствии с ними системы линейных алгебраических уравнений (7.13) для вектора у = (уь Уг) будут такими:
0 у, + 1 у2 = 1, 0 у, + 1 у2 = 1, 0 yi + 1 у2 = 1,           1 У1 + 0 у2 = 0,        1 у! + 0 у2 = 0, 0 У1 + 1 уз = 1.
Отсюда находим, что если исходное распределение существует, то оно един­ственно и определяется вектором у = (0,1).
Пример 7.5. Теперь рассмотрим пример 6.1 вычисления матричного произ­ведения А = ВС, где все матрицы плотные квадратные порядка п. Два внеш­них цикла программы имеют тип ParDO по всем графам зависимостей. По­этому потенциального параллелизма в программе вполне достаточно.
Исследуем возможность распределения массивов А, В, С. Ограничимся сна­чала параллелизмом самого внешнего цикла. Вхождение элементов массива А единственное и имеет вид
(10 0"! 010
(i\
Система (7.13) будет такой:
1 Yi + 0 у2 = 1;
0 yi + 1 у2 = 0;
0 у, + 0 у2 = 0.
Она имеет единственное решение у = (1,0). Вектор у = (1,0) соответствует распределению массива А по строкам. Вхождение массива В также единст­венное и имеет вид
(10 0"! 001
(i\
Глава 7. Эквивалентные преобразования программ
433
Ему соответствует следующая система (7.13)
1 Yi + О Y2 = 1;
О у, + 0 уз = 0;
0 Yi + 1 Y2 = 0. Эта система имеет единственное решение у = (1,0), что означает распреде­ление массива В по строкам. Единственное вхождение массива С таково:
(0 0 1\ 010
Г[\
Ему соответствует система (7.13) вида
0 у, + 0 у2 = 1;
0 у, + 1 у2 = 0;
1 Yi + 0 у2 = 0.
Она несовместна. Так как матрица системы имеет полный столбцовый ранг, то отсюда вытекает, что на каждой итерации внешнего цикла будет использо­ваться весь массив С. Факт, который легко проверить по тексту программы.
Дублирование массива С целиком в каждом модуле памяти может быть не­целесообразным из практических соображений. Поэтому посмотрим, как используется массив С на итерациях второго цикла. Для этого цикла вхож­дение массива С имеет вид
fOlVy^
10 U
и ему соответствует такая система (7.13)
0 Yi + 1 Y2 = 1;
1 Yi + 0 Y2 = 0.
Она уже совместна и имеет единственное решение у = (0,1). Это означает распределение массива С по столбцам.
Если используется параллелизм вычислений лишь самого внешнего цикла, то при каждом значении параметра / оба внутренних цикла будут выпол­няться на одном и том же процессоре. Проведенные исследования показы­вают, что без ограничения общности можно считать, что каждый процессор для вычисления одной строки массива А требует того же номера строку мас­сива В и весь набор столбцов массива С. Теперь вспомним, что второй цикл по параметру j также имеет тип ParDo. И хотя мы не будем использовать этот факт для распараллеливания вычислений, его можно принять во вни­мание для выбора нужного порядка использования столбцов массива С.
434
Часть II. Параллельное программирование
Пусть в собственной памяти /-го процессора отведено место для четырех векторов размерности п: вектор ah где вычисляется /-ая строка массива А, вектор bj, где хранится /-ая строка вектора В и вектор q, где первоначально хранится /-ый столбец массива С, а также рабочий вектор dj. Для процессо­ров, связанных, например, в кольцо, программу вычисления матричного произведения А = ВС можно переписать следующим образом:
DOj=l,n
Par DO i=\,n
с, -> d,                                                                                                                  (723)
d;_\ > Cj
ay =a,j + (bh dj)
END DO END DO
Здесь do = d„. Порядок вычислений в этой программе иной по сравнению с исходной. Поэтому, строго говоря, она не эквивалентна ей по вычислени­ям. Тем не менее, в условиях точных вычислений обе программы будут да­вать одни и те же результаты. Существует много программ типа (7.23). Каж­дая их них приспособлена к какому-то одному типу связей процессоров между собой.
Заметим, что весь анализ программы примера 6.1 был выполнен формально. Простота программы сказалась лишь на объеме вычислений, необходимых для проведения этого анализа.
Пример 7.6. Следующий пример особенно интересен с точки зрения иллюст­рации применения описанной в этой книге теории исследования информаци­онной структуры алгоритмов и программ. В 1992 г. нам представилась воз­можность провести ряд экспериментов на крупнейших компьютерах фирмы Cray Research Inc., USA. В арсенале у нас была уже упоминавшаяся система V-Ray [65], предназначенная для изучения параллельной структуры последо­вательных программ. Применение этой системы не требует никакой предва­рительной информации ни о структуре программы, ни о ее математическом содержании. Не привязана V-Ray System и к какой-либо архитектуре вычис­лительной системы. Поэтому было очень интересно понять, чего же можно достичь с помощью данной системы на самых современных компьютерах.
В качестве тестовых примеров были выбраны программы из пакета Perfect Club Benchmarks [46]. Мы остановились на этом пакете, главным образом, из-за его широкой известности. Программы пакета Perfect Club Benchmarks тестировались и оптимизировались практически на всех крупных компьюте­рах, причем самыми разными людьми. Нам было важно не только удовле­творить собственное любопытство, но и увидеть в сравнении, что можем мы и что могут другие.
Глава 7. Эквивалентные преобразования программ
435
В табл. 7.1 приведены значения реальной производительности компьютеров Cray М90 и Cray С90 на программе TRFD из пакета Perfect Club Benchmarks, полученные в различных режимах. Заметим, что пиковая производитель­ность одного процессора Cray М90 составляет 333 Мфлопс, одного процес­сора Cray С90 — 960 Мфлопс.
Таблица 7.1. Результаты оптимизации для компьютеров Cray М90 и С90
Cray М90, CPUs
Базовая произв., Мфлопс
Ручная опт., Мфлопс
V-Ray опт., Мфлопс
1 4 8
56.19* 54.86 54.34
81.721 261.84 481.03
247+
822
954+t
Cray С90, CPUs
Базовая произв., Мфлопс
Ручная опт., Мфлопс
V-Ray опт., Мфлопс
1 8
89.5* 89.6
139.71! 962.68
579.7f 2440.5*
Здесь графа "Базовая производительность" означает производительность на программе TRFD, полученную с помощью штатного компилятора без ка­кой-либо предварительной оптимизации. Графа "Ручная оптимизация" озна­чает наибольшую производительность, полученную специалистами США с помощью ручной оптимизации программы. Графа "V-Ray оптимизация" оз­начает производительность, полученную с помощью оптимизации, выпол­ненной на основе информации о структуре программы, которая была выяв­лена V-Ray System. Дополнительные символы означают следующее:
□  (+) — значение производительности получено не в монопольном режиме и может быть улучшено;
□  (*) — результат занимает седьмое место среди значений производительно­сти на всех тринадцати программах пакета Perfect Club Benchmarks;
□  (!) — результат занимает девятое место;
□  (у) — результат занимает первое место среди значений производительно­сти на всех программах из пакета Perfect Club Benchmarks, оптимизиро­ванных вручную;
□  (ф) — результат занимает второе место среди значений производительно­сти на всех программах из пакета Perfect Club Benchmarks, оптимизиро­ванных вручную, уступая лишь хорошо распараллеливаемой программе MG3D.
436
Часть II. Параллельное программирование
Приведенные в таблице результаты говорят сами за себя. Тем не менее, мы дадим к ним небольшой комментарий. Программа TRFD относительно про­стая. Тем интереснее результаты, приведенные в первых двух графах. Первая графа говорит о том, что нет никакой надежды на то, что штатный компиля­тор параллельной вычислительной системы сможет обеспечить эффектив­ную реализацию прикладной программы без дополнительной информации о ее структуре. Получение этой информации целиком возложено на пользова­теля. Так же как и ответственность за результаты ее применения. Вторая графа показывает, что получить и эффективно использовать нужную ин­формацию трудно даже для специалистов высокой квалификации, знающих практически все о компьютере и компиляторе, и даже в том случае, когда программа относительно проста и о ней вроде бы все известно.
Искушенный читатель может возразить, что программа TRFD записана на последовательном языке, а использованные компьютеры параллельные, что для эффективного применения параллельных компьютеров специально раз­работаны параллельные языки программирования и т. п. Все это верно. Од­нако заметим, что все параллельные языки помогают только записать в нужном виде дополнительную информацию о структуре реализуемого про­граммой алгоритма и не имеют никакого отношения к способам ее получе­ния. К тому же компиляторы с параллельных языков не дают гарантий о степени эффективности реализации тех или иных конструкций параллель­ных языков. Поэтому независимо от того, написана ли программа на после­довательном или параллельном языке, пользователю приходится многократ­но пропускать разные варианты программы на компьютере, чтобы обнаружить ее узкие места и понять, как их устранить. Этот процесс очень плохо формализован и именно он вызывает наибольшие трудности при руч­ной оптимизации программ.
Третья графа таблицы говорит о том, что, по-видимому, с помощью V-Ray System удается выявить такую информацию о структуре программы, которую трудно получить каким-либо другим способом.
Так оно и есть. С самого начала V-Ray System задумывалась как автономная инструментальная система для автоматизации процесса исследования про­грамм и их преобразования к виду, удобному для целевого параллельного компьютера. Поводом для ее создания послужили фундаментальные резуль­таты, полученные авторами книги в области изучения структуры алгорит­мов, записанных в виде программ на последовательных языках. Однако по мере развития системы возникло большое число специфических проблем, связанных с повышением эффективности ее функционирования, обеспече­нием наглядности получаемых результатов, представлением различных сер­висных услуг и многими другими сторонами процесса взаимодействия с па­раллельными вычислительными системами. Поэтому спустя какое-то время V-Ray System стала совершенствоваться, главным образом, под влиянием интересов потенциальных пользователей системы. Тем не менее, в нее по-
Глава 7. Эквивалентные преобразования программ
437
стоянно вводились и вводятся сейчас новые методы исследования программ. В свою очередь, опыт анализа реальных программ с помощью V-Ray System привел к появлению ряда новых задач, интересных в математическом отно­шении.
Но вернемся к программе TRFD и рассмотрим в общих чертах те ее преоб­разования, которые привели к результатам в третьей графе табл. 7.1.
В программе TRFD большая часть вычислений и основной временной вы­игрыш от преобразований приходится на подпрограмму OLDA. Структуру данной подпрограммы можно представить в виде циклического профиля на рис. 7.6, в котором каждая скобка соответствует циклу do.
jj .iiyj&uib^ij^
Рис. 7.6. Циклический профиль
После первого анализа подпрограммы OLDA были выявлены следующие факты:
□  все самые внутренние циклы имеют тип ParDO по всем графам зависимо­стей и этот факт может быть обнаружен с помощью самых простых тра­диционных методов анализа;
□  все самые внешние циклы имеют тип ParDO по графу алгоритма; этот факт трудно обнаружить с помощью формального применения традици­онных методов анализа, но легко обнаружить визуально;
□  никакие традиционно используемые методы анализа структуры программ не позволяют установить принадлежность или непринадлежность типу ParDO хотя бы какого-нибудь из внутренних циклов, не являющегося са­мым внутренним;
□  число итераций в каждом цикле относительно невелико;
□  в целях экономии памяти основной многомерный массив хранится ком­пактно в виде одномерного, в силу чего индексные выражения вычисля­ются по сложным нелинейным (относительно параметров цикла) выра­жениям;
□  вторая циклическая конструкция описывает только пересылку данных и не содержит арифметические операции.
Ручная оптимизация основана на использовании первых двух фактов. Одна­ко четвертый факт говорит о том, что этого недостаточно. Принимая во внимание третий факт, становится понятно, почему результаты второй гра­фы таблицы в предисловии невысоки.
438
Часть II. Параллельное программирование
Первое, что нужно установить, — это принадлежность или непринадлеж­ность типу ParDO внутренних циклов подпрограммы OLDA. Для этого нуж­но построить для них минимальные графы зависимостей. Сложная цикли­ческая конструкция программы и большая глубина вложенности отдельных циклов не являются препятствием для применения V-Ray System. Но пятый факт не дает возможность ее использовать, т. к. подпрограмма OLDA не принадлежит линейному классу.
Чтобы обойти это препятствие, была построена другая программа. Она от­личается от OLDA только тем, что в ней основной одномерный массив сно­ва представлен как многомерный с линейными индексными выражениями. На рис. 7.7 представлен циклический профиль новой программы. Он не­сколько отличается от циклического профиля программы OLDA, показан­ной на рис. 7.6.
----------------------------------------------------------------------------1 i------------------------------------------' i-----------------------------------------------------------------------------------------------------
Рис. 7.7. ParDO-циклический профиль
Восстановление полноразмерного массива привело к появлению нескольких новых циклов. Для хранения полноразмерного массива требуется несколько больше памяти, чем для одномерного. Однако увеличение памяти не столь существенно, чтобы отказаться от использования построенной программы. Тем более, что ее параллельные свойства превосходны. С помощью V-Ray System были построены все графы зависимостей и определены все циклы ParDO. На рис. 7.7 они отмечены точками. Из рис. 7.7, в частности, видно, что если в программе использовать параллелизм лишь самых внутренних и самых внешних циклов исходной программы, то большая часть параллелиз­ма не будет принята во внимание.
Из рис. 7.7 следует общая стратегия преобразований, которые необходимо выполнить с новой программой:
□  объединить в один два самых внешних цикла в первой и третьей цикли­ческих конструкциях, тем самым значительно увеличив длину внешних циклов;
□  переместить эти длинные циклы внутрь и использовать их для вектори­зации;
□  самые внутренние циклы переместить наружу и использовать их для па­раллельного выполнения независимых ветвей вычислений процессорами компьютера;
□  последовательные циклы, определяющие перевычисление одних и тех же элементов массивов, использовать для повышения локальности обраще­ния к данным.
Глава 7. Эквивалентные преобразования программ
439
JJ 1Й
Jj 11--------^
iJi____ii
распределение цикла 1
UI____h
ijj LLi
перестановка циклов 2,1
перестановка циклов 2,1
перестановка циклов 2,3
^] ^
iJI____l|
J
У Ilk
слияние циклов 2
^] ^
J * h
L
^ LL
i ' L
перестановка циклов 2,1
4$ ^
J * h
L
^ LL
i * L
распределение цикла 1
i] ^
±JI_____Ii
J
Й1 Ilk
перестановка v циклов 3,1
i] 1Ш
iJI____h
J
JJ] Ilk
Рис. 7.8. Преобразования циклов
Цепочка последовательных преобразований для первой циклической конст­рукции на рис. 7.7 представлена на рис. 7.8. Меткой 1 в исходном виде кон­струкции отмечен цикл, объединяющий два самых внешних цикла той же конструкции на рис. 7.7.
Все эти и подобные им преобразования позволили достичь тех впечатляю­щих результатов, которые представлены в третьей графе табл. 7.1. Важно отметить, что вся оптимизация программы TRFD была выполнена на уровне языка Fortran без использования ассемблерных вставок и обращений к вы­сокоэффективным библиотечным подпрограммам.
ЧАСТЬ III
Смежные проблемы и применение
Глава 8
Вычислительные системы и алгоритмы
Неизбежным результатом расширяющихся
связей между различными уровнями иерархии
является возрастающая область непонимания.
Из законов Мерфи
Бурное развитие микроэлектроники позволило достичь к концу 70-х годов прошлого столетия невиданного ранее скачка в развитии вычислительной техники. Стали функционировать вычислительные системы с производитель­ностью в несколько сотен миллионов операций в секунду, мощность проек­тируемых систем определялась миллиардами операций. Появились многочис­ленные устройства, позволяющие очень быстро решать различные простые задачи, такие как матричные и векторные преобразования, быстрое преобра­зование Фурье, обработка сигналов, распознавание простейших изображений и т. п. Основной целью создания этих устройств было ускорение и упрощение процесса решения конкретных задач. Каждое из них имело свою собственную архитектуру, и не было почти ничего общего между различными устройства­ми. Тем не менее, эти очень скромные по современным понятиям успехи уже тогда привели к появлению весьма дерзкой мысли о возможности в будущем построения заказных специализированных вычислительных систем, ориенти­рованных на эффективное решение конкретных задач.
Кроме впечатляющих результатов и радужных надежд успехи микроэлек­троники принесли немало серьезных проблем в деле освоения вычислитель­ной техники, в особенности больших параллельных систем. Очень скоро стало ясно, что построение для таких систем эффективных численных мето­дов является делом и трудным, и малоизученным. Трудности определялись, главным образом, значительным разнообразием архитектур самих систем и, как следствие, таким же разнообразием способов организации вычислений. Различные способы организации вычислений влекли за собой различные организации данных, требовали создания различных численных методов и алгоритмов, различного программного обеспечения, новых средств и языков общения с вычислительной техникой.
Глубокое понимание перспектив развития, а также проблем использования вычислительной техники привело академика Г. И. Марчука к формирова­нию в те годы нового фундаментального научного направления, названного "отображение задач вычислительной математики на архитектуру вычисли-
444
Часть III. Смежные проблемы и применение
тельных систем". Его стержнем должно было стать совместное исследование численных методов и структур вычислительных систем. Тогда это направле­ние, кратко называемое "проблемой отображения", оказалось на острие многих вопросов, связанных с вычислениями. Таким оно остается и сейчас. Наверное, оно будет таким же и в будущем, по крайней мере, ближайшем.
Основные трудности развития нового направления связаны с отсутствием строгих математических постановок нужных задач. Обсуждения, проведен­ные в главах 2, 3 этой книги, говорят об огромном числе различных факто­ров, влияющих на выбор структуры компьютеров. В свою очередь, данное обстоятельство приводит к большому разнообразию самих компьютеров. Только число названий классов вычислительной техники измеряется десят­ками: векторные, конвейерные, многопроцессорные, систолические, про­граммируемые и т. п. В каждом классе имеется немало существенно различ­ных представителей. Поэтому нельзя надеяться на разработку в ближайшее время математической модели, сколько-нибудь адекватно отражающей про­цессы функционирования хотя бы основного множества компьютеров. Тем не менее, во всех представителях из всех классов можно увидеть примене­ние нескольких идей, решающим образом влияющих на производитель­ность. Это, в первую очередь, — параллелизм и конвейерность вычислений, иерархическая структура памяти, использование коммутаторов и коммуни­кационных сетей для связи функциональных устройств между собой. Ясно, что как минимум, эти идеи должны находить свое отражение в структуре численных методов.
Вычислительная техника и алгоритмы — это две опоры, на которых строится проблема отображения. Но как бы не велики были достижения в области развития вычислительной техники, она является всего лишь инструментом для решения прикладных задач. Инструментом, который создается и совер­шенствуется по своим законам, определяемым успехами микроэлектроники, авторскими идеями, чьими-то амбициями и много чем другим, не всегда даже полезным при проведении практических расчетов. Инструментом, ко­торый почти всегда претендует на универсальность своего использования и поэтому почти всегда используется с трудом при решении конкретных за­дач. Как уже отмечалось, создание общей математической модели процессов функционирования компьютеров кажется делом маловероятным. Поэтому представляется вполне естественным, что продвижение в проблеме отобра­жения должно начинаться с разработки фундаментального математического аппарата, позволяющего описывать и исследовать детальную структуру алго­ритмов. Структуру, показывающую, как в процессе реализации алгоритма отдельные его операции связаны между собой и с памятью компьютера.
Об этом аппарате мы уже говорили в главах 6 к 7. Однако изучение пробле­мы отображения начиналось не с него. По мере ее осмысления большое беспокойство вызывало отсутствие базового математического формализма, помогающего оценивать качество работы многих функциональных устройств
Глава 8. Вычислительные системы и алгоритмы
445
и соответственно этому предлагать те или иные схемы реализации алгорит­мов. Дело в том, что одновременное использование многих функциональ­ных устройств является одной из центральных идей, позволяющих получить дополнительное ускорение процесса решения задач. Для оценки их работы были введены различные характеристики, такие как пиковая производи­тельность, реальная производительность, ускорение, эффективность, загру­женность и т. п. Работа [43] и подобные ей говорят о том, что в понимании этих характеристик существует большой произвол, а это, в свою очередь, делает невозможным проведение строгих исследований. Поэтому всем та­ким характеристикам необходимо было придать четкий математический смысл. Точные определения и аккуратные выводы в базовом формализме даны в § 2.3. Они позволили заложить математическую основу в изучение процессов функционирования сложных вычислительных систем. Точное определение основ делает возможным получение точных выводов. Мы пока­зали это в § 2.3 на примере сравнения оценок Амдала и Густавсона— Барсиса для ускорения.
Была и другая веская причина для аккуратного рассмотрения процесса функционирования многих функциональных устройств. На рубеже 70—80-х годов прошлого столетия в США, а под их влиянием и в Европе, начались активные исследования в области так называемых систолических массивов. Систолические массивы представляют простейшие вычислительные систе­мы с многими функциональными устройствами. Они не имеют памяти и коммуникационной сети, реализуются на одном кристалле и, как утвержда­лось, исключительно дешевы в изготовлении. Более детально с истинными причинами проявления особого интереса к этим устройствам можно позна­комиться в работе [54]. Систолические массивы заинтересовали нас как возможный элемент проблемы отображения. К этому времени уже стала вы­рисовываться следующая схема ее решения: раскладываем задачу на про­стейшие, простейшие реализуем с помощью спецпроцессоров, а вычисли­тельная система в целом получалась как объединение этих спецпроцессоров с помощью подходящей коммуникационной сети. Казалось, что на роль спецпроцессоров вполне могут подойти систолические массивы. Однако определенные сомнения оставались. Каждый отдельный систолический мас­сив мог реализовывать только один алгоритм, причем относительно про­стой. Набор алгоритмов, подлежащих реализации, был совершенно не ясен. Его мы надеялись установить, анализируя программы пользователей с по­мощью системы V-Ray, которая к тому времени уже начинала создаваться. Но отсутствовала конструктивная методология построения систолических массивов, соответствующих заданному алгоритму. По существу, ее еще предстояло создать. Для решения всех этих вопросов также необходимо бы­ло изучать работу большого числа устройств.
Таким образом, ключевым моментом в решении проблемы отображения оказался анализ структуры алгоритмов. Ответ на вопрос, какие алгоритмы
446
Часть III. Смежные проблемы и применение
необходимо изучать в первую очередь, дан в §§ 4.3, 4.4. Это алгоритмы, за­писанные на языках программирования. Аппарат исследования программ, описанный в главах 6, 7 а система V-Ray, реализующая данный аппарат, являются основным инструментом выделения в прикладных программах тех структур, которые должны будут реализовываться специальным образом. Остается только решить, какие именно структуры надо выделять.
Практика написания прикладных программ говорит о том, что наибольший объем вычислений почти всегда концентрируется в подпрограммах. Поэтому подпрограммы вроде бы вполне могли быть кандидатами на быструю реали­зацию. Для их выделения не нужно привлекать сложный аппарат исследо­ваний, а достаточно всего лишь просматривать тексты программ. По перво­начальной идее именно подпрограммы должны были реализовываться в виде систолических массивов. Однако заметим, что различных подпрограмм очень много. К тому же сами подпрограммы могут содержать в своих телах вызовы других подпрограмм. Все это, в конечном счете, приводило к тому, что с помощью систолических массивов оказывалось возможным реализо-вывать только отдельные алгоритмы из библиотек стандартных подпро­грамм. Более того, по самой сути конструирования систолических массивов эти алгоритмы обязаны были иметь фиксированный размер (размерность, число шагов и т. п.), что вносило определенные трудности в решение задач переменного размера. Несмотря на высказанные замечания, введение спец­процессоров типа систолических массивов в состав высокопроизводитель­ных вычислительных систем безусловно было бы полезным. Об этом мы будем еще говорить.
При разработке методологии построения математических моделей систоли­ческих массивов [14] было установлено, что на подобных вычислительных системах успешно реализуются алгоритмы, графы которых устроены регу­лярно. С другой стороны, анализ информационной структуры большого числа алгоритмов с помощью системы V-Ray показал, что в очень многих случаях их графы сводятся именно к регулярным. При этом важно отметить, что часто разные алгоритмы имеют либо одни и те же, либо очень похожие графы. Это наблюдение позволило нам выдвинуть гипотезу, что в реально используемых программах основные вычислительные ядра имеют информа­ционные связи, принадлежащие небольшому по численности набору типовых структур. Пока эта гипотеза не опровергнута. Более того, она по­стоянно находит новые подтверждения, особенно при анализе сходных за­дач. Любые свойства типовых информационных структур, в том числе па­раллельные, возможно исследовать заранее. Поэтому, зная состав типовых информационных структур в конкретных программах и их связь между со­бой, можно заранее сказать, какова наиболее подходящая архитектура вы­числительной системы в данном случае и насколько эффективно данные программы будут реализованы на конкретном компьютере. Перспектива стоит того, чтобы подобными исследованиями заниматься серьезно.
Глава 8. Вычислительные системы и алгоритмы
447
Помимо достижения очевидной цели, направленной на повышение эффек­тивности использования вычислительной техники, исследование информа­ционной структуры алгоритмов постепенно стало формировать и другую цель, значительно более фундаментальную. Хотелось понять, какое место занимают параллельные вычисления среди тех наук, которые так или иначе определяют процессы функционирования сложных компьютерных систем. Конечно, в первую очередь мы интересовались математическими науками. Выяснилось любопытное обстоятельство [10]. Оказалось, что некоторые за­дачи, связанные с алгоритмами, но внешне не имеющие никакого отноше­ния к параллельным вычислениям, могут быть, тем не менее, точно описа­ны таким образом, что информационная структура алгоритмов становится определяющим элементом описания. Например, задачи восстановления ли­нейного функционала, вычисления значений градиента, оценивания вели­чины ошибок округления результатов промежуточных вычислений и многие другие могут быть сформулированы в терминах нахождения решения систе­мы линейных алгебраических уравнений. По структуре ненулевых элементов матрицы этих систем очень похожи на матрицу смежностей графа алгорит­ма. Так как граф алгоритма не имеет петель, то перестановкой строк и столбцов матрицы систем можно привести к треугольному виду. Исследо­вать такие системы очень просто. Но из них получаются и нетривиальные выводы. В случае задачи вычисления градиента решение системы "очевид­ным" способом приводит к методу, сложность которого линейно зависит от размерности пространства. Если ту же систему решать другим способом, то появляется метод быстрого вычисления градиента, сложность которого не зависит от размерности пространства. Многие задачи изучения процессов работы функциональных устройств, в том числе оптимальных процессов, описываются системой линейных неравенств, матрицы которых совпадают с матрицей инциденций графа алгоритма. Это также открывает путь к новым исследованиям.
Параллельные вычисления стали сейчас не только нужной, но и модной областью исследований. По необходимости или ради любопытства ими за­нимаются многие специалисты. Большое число вроде бы полезных результа­тов и фактов здесь лежит на поверхности. Для их получения довольно часто не требуется глубоких познаний. Достаточно более или менее "войти в предмет" и уже можно делать содержательные выводы, возможно, даже ни­кем не опубликованные. Такого рода результаты мало помогают в деле ре­шения действительно серьезных проблем. К тому же они создают впечатле­ние, что в параллельных вычислениях нет ничего другого, кроме каких-то околонаучных манипуляций с простыми понятиями. В конечном счете, это связано с тем, что как раздел науки параллельные вычисления до сих пор находятся в стадии своего становления.
448
Часть III. Смежные проблемы и применение
Мы надеемся, что проводимое исследование информационной структуры алго­ритмов и проблемы отображения убедят читателя в том, что он имеет дело с весьма сложным междисциплинарным предметом, обладающим разветвленны­ми связями с самыми различными областями знаний. А параллельные вычисле­ния являются всего лишь одним из акцентов в изучении этого предмета.
§ 8.1. Расширение и уточнение линейного класса
Любая программа на стадии предварительного анализа с помощью авто­номных систем типа V-Ray может быть приведена к некоторому канони­ческому виду, в котором все ее фрагменты размечены по принадлежности или непринадлежности к линейному классу. Нелинейность фрагментов может быть либо устранимой, либо принципиальной. Устранимая нели­нейность означает, что с помощью каких-то эквивалентных преобразова­ний фрагмент может быть приведен к линейному виду. Как показывает практика, большая часть нелинейностей оказывается устранимой. Доволь­но часто она возникает от стиля программирования и желания сэкономить ресурсы памяти компьютера. Но может возникать и по существу как, на­пример, при использовании стандартных функций и подпрограмм, циклов типа go to, ветвлений и т. п. Обнаружить принципиально нелинейные фрагменты и сделать программу максимально линейной — сложная задача. Ее решение целиком определяется уровнем интеллектуальности процесса канонизации. Исследуя проблему отображения, мы будем, как правило, считать программы и алгоритмы линейными. В подтверждение обосно­ванности такого решения рассмотрим сначала некоторые типичные случаи преобразования нелинейных или трудно анализируемых фрагментов в ли­нейные и, возможно, более простые.
Прямая подстановка
Не так уж и редко индексы используемых переменных вычисляются подоб­но тому, как в следующем фрагменте:
р = 0
DO / = 1, Я
... = ... ар ...
------------                                                                                                   (8.1)
ар+1 = ... р=р + \
END DO
Формально этот фрагмент не является линейным, т. к. значения индексов р и р + 1 переменной а вычисляются. Однако прямая подстановка вместо р
Глава 8. Вычислительные системы и алгоритмы
449
эквивалентного ему выражения / — 1 и вычеркивание оператора р — р + 1 делают фрагмент (8.1) линейным:
DO / = 1, Я
... = ... а,-_!...
в,- = ...
END DO
Вычисляемый цикл до to
Допустим, что программа реализует итерационный метод. Пусть на каждой итерации пересчитывается некоторая величина t и процесс заканчивается, когда t < е, где е — заданная внешняя переменная, символизирующая, на­пример, точность. Вполне возможно, что программа будет иметь такой вид:
1     тело цикла
if (t>e) go to 1                                                                                                      (8-2)
Даже если тело цикла представляет линейный фрагмент, программа в целом не является линейной, т. к. цикл не имеет тип цикла do. Обычно из каких-то соображений известно, что количество итераций не превосходит некото­рого целого числа ш. Выберем параметр, не используемый в теле цикла. Предположим, что это есть /. Тогда программа (8.2) будет эквивалентна программе
do i= 1, m
Тело цикла
if (t<s) go to 2                                                                                                         '
END DO
CONTINUE
Предположим теперь, что программа, реализующая тело цикла, выполняется на разных итерациях совершенно одинаково. В этом случае любой граф за­висимостей тела цикла программы (8.3) не будет зависеть от параметра /. Следовательно, граф программы (8.3) будет представлять многослойную конструкцию. Все слои одинаковы и являются графами зависимостей тела цикла. Число слоев равно числу итераций и не превосходит числа т. Ясно, что в данном случае выбор числа т никак не сказывается на особенностях графов зависимостей программы (8.3) в целом.
Вычисляемые ветвления
Предположим, что некоторая функция / вычисляется по разным формулам в зависимости от полученного в программе значения переменной q.
450
Часть III. Смежные проблемы и применение
Рассмотрим, например, следующий фрагмент
±f(q>r) go to 1
/= ф(сс, Р)
go to 2                                                                                                                     (8_4)
1    /=v|/(y)
2      CONTINUE
Здесь q — вычисляемая переменная, г — внешняя переменная, ср(а, Р) и ц/(у) представляют какие-то функции, определяющие значение/в зависимости от значения переменной q. Этот фрагмент не может быть линейным хотя бы по­тому, что условие q > г является вычисляемым. Пусть мы изучаем граф алго­ритма. При одних значениях переменной q в вершины, соответствующие функции f, будут входить дуги, определяемые переменными а, р, при дру­гих — переменной у. Какими бы ни были q а г, фрагмент (8.4) всегда перевы­числяет значение переменной / Ничто не мешает рассматривать фрагмент (8.4) как способ вычисления некоторой функции Q(q, г, а, р, у), зависящей от всей совокупности входящих в (8.4) переменных. Поэтому фрагмент (8.4) можно заменить одним оператором присваивания
/= 9(<7, г, а, р, у).                                          (8.5)
Если все аргументы в правой части представляют простые переменные или переменные с линейными индексными выражениями, фрагмент (8.5) будет принадлежать линейному классу.
Допустим снова, что для программы в целом строится граф алгоритма. При наличии в программе фрагмента (8.4) этот граф будет заведомо зависеть от значений переменной q, по крайней мере, в части тех дуг, которые связаны с вычислением / После замены фрагмента (8.4) фрагментом (8.5) зависи­мость графа алгоритма от значений переменной q пропадает. Однако теперь в графе будет несколько больше дуг. Некоторые из них будут соответство­вать фиктивным связям, относящимся к тем функциям ср(а, Р) или у (у), ко­торые не срабатывают при конкретных значениях переменной q.
Подчеркнем, что данный пример является типичным образцом введения и использования расширенного графа. Расширенный граф будет часто появ­ляться в тех ситуациях, когда исходный граф имеет какие-то плохо иссле­дуемые особенности, но существует другой граф, не имеющий такие осо­бенности и содержащий исходный граф в качестве своего подграфа. Этот другой граф и называется расширенным. При выборе расширенного графа важно не потерять слишком много из нужных свойств исходного графа.
Нелинейные индексные выражения
На практике нередко встречаются программы, в которых какая-то часть ин­дексных выражений оказывается нелинейной.
Глава 8. Вычислительные системы и алгоритмы
451
Пусть фрагмент имеет вид
DO j = 1, Я - 1
Ю/=У'""1                                                                                            (8.6)
anj-ii- 11//2 ~ anj-(j- iy/2 + Щ +{/- 1)(я-y/2)
END DO
END DO
Все опорные многогранники в нем линейные и нелинейность имеется только в индексах используемых переменных. Методика построения графов зависи­мостей в любых нелинейных случаях остается такой же, как в линейных. В частности, для всех элементарных графов нужно решать уравнение (6.13) и на множестве его решений находить лексикографически максимальное при выполнении условий (6.14). Будем строить граф алгоритма. Для первого эле­ментарного графа из (8.6) уравнение (6.13) относительно у', /' таково
nj'-0'-l)J'/2 = nj-(/-iy/2.
Но функция nj — (/' 1)у/2 на множестве 1 <у < п — 1, у < i < п — 1 не зави­сит от / и монотонно возрастает по/ Поэтому решение задачи (6.13), (6.14) имеет вид
j'=j,i' = i-l,                                                (8.7)
если 1 < j < п — 1, j < i < п — 1. В вершины, для которых / = у, дуги элемен­тарного графа не входят. Для второго элементарного графа из (8.6) уравне­ние (6.13) относительно у', /' имеет вид
nj' ~ О - 1)У'/2 = /+(/- 1)(л -у/2).                               (8.8)
Из (6.14) следует, что j'<j. Как уже отмечалось, левая часть равенства моно­тонно возрастает по у'. Легко проверить, что приу' =j разность между левой и правой частями (8.8) равна п — /, что на множестве 1 <у < п — 1, у < / < п — 1 больше или равно 1. Приу' = j — 1 та же разность равна у— /— 1, что на том же множестве 1 <у < п — 1,у < /< п не превосходит — 1. Следовательно, на допустимом множестве изменения параметров у', i',j, i уравнение (8.8) не имеет ни одного решения.
Таким образом, граф алгоритма нелинейной программы (8.6) определяется только соотношениями (8.7), т. е. является линейным. Для случая п = 6 он представлен на рис. 8.1.
Теоретически ясно, что любой линейный алгоритм можно описать програм­мой из линейного класса. Тогда возникает вопрос о том, почему же автор программы (8.6) захотел использовать нелинейную форму записи. Интерес­но докопаться до ответа на данный вопрос еще и потому, что из выбранной формы записи совсем не видны многие свойства программы. Например, из соотношений (8.7) или рис. 8.1 очевидно, что внешний цикл програм-
452
Часть III. Смежные проблемы и применение
мы (8.6) имеет тип ParDO. Но это почти невозможно понять непосредствен­но из текста (8.6).
::       ::       :: :: •
::       ::       :: ^
::       ::       ^ ::
1-------•}
Рис. 8.1. Граф алгоритма нелинейной программы
Из программы (8.6) вытекает, что алгоритм заключается в многократном пересчете некоторых элементов одномерного массива. Назовем эти элемен­ты опорными. Они имеют индексы, равные nj — (/' 1)у/2. Согласно только что проведенным исследованиям, разным значениям параметра j соответст­вуют разные опорные элементы. Конкретное же содержание алгоритма состоит в последовательном прибавлении к опорным элементам других эле­ментов, находящихся между выбранными опорными элементами и опорны­ми элементами, предшествующими выбранным. Преобразовать нелинейную программу к линейной можно многими способами. Одна из линейных программ, эквивалентная (8.6), такова
DO j
= 1, n -
-1
DO
i=j,n
-1
anj
= anJ +
a,j
END
DO
END
DO
(8.9)
если считать, что a
у
a
i + (j - \)(n - j/2)
для всех допустимых i,j. Теперь по-
нятно, что участвующие в процессе вычислении элементы одномерного массива могут быть представлены как элементы нижнего левого треугольни­ка квадратной матрицы порядка п. В этой трактовке алгоритм (8.6) сводится к суммированию элементов столбцов матрицы, за исключением наддиаго-нальных элементов. Результат помещается на месте элементов последней строки. Все свойства алгоритма и реализующей его программы (8.9) стано­вятся прозрачными.
Так все же зачем вместо ясной программы (8.9) была написана трудно ана­лизируемая программа (8.6)? Дело в том, что почти все языки программиро-
Глава 8. Вычислительные системы и алгоритмы
453
вания допускают оформление данных только в виде прямоугольных или квадратных матриц. В этом случае в программе (8.9) не будет использоваться верхняя половина массива и, следовательно, для реализации програм­мы (8.9) потребуется примерно вдвое больше памяти, чем необходимо на самом деле. Чтобы избежать этого и была написана программа (8.6).
Рассмотренный пример очень типичен. При решении многомерных задач в сложных областях трудно эффективно использовать память компьютера, при­меняя лишь многомерные матричные массивы данных. В этих случаях довольно часто данные оформляют как одномерные массивы. Но тогда неизбежно появ­ляются нелинейные индексные выражения. Они затрудняют анализ структуры программ. Поэтому, в частности, для обнаружения свойств параллельности по­лезно восстановить линейный вид программ. В случае использования стандарт­ных "упаковок" многомерных массивов в одномерные это восстановление осу­ществить достаточно просто. Однако в общем случае работа с нелинейными индексными выражениями оказывается сложной. Некоторые аспекты восста­новления линейных программ рассмотрены в работе [40].
Уточнение описания внешних переменных
Следует отметить, что в некоторых случаях без дополнительной информа­ции о значениях внешних переменных успешный анализ информационной структуры программы может оказаться крайне затрудненным. Допустим, что составляется программа для реализации разложения ленточной матрицы порядка п и ширины m на две треугольные с помощью компактной схемы метода Гаусса [5]. При малых по сравнению с п значениях m ненулевые элементы ленточной матрицы занимают лишь незначительную часть всех элементов квадратной матрицы или, другими словами, массива размера п. Поэтому в этом случае ленточную матрицу часто представляют в виде одно­мерного массива. Естественно, появляются нелинейные индексные выраже­ния. Однако за счет специального расположения элементов ленточной мат­рицы в прямоугольном массиве программу можно сделать линейной и при этом не потерять существенно память. Рассмотрим следующий ее фрагмент
DOi = n-k + l, n-m
t = -1/A(i, k + 1)
DO j = i + 1, n
A(j, k+l+i-j) = A(j, k+l+i-j) xt
DO 1 = 1, m
(8.10)
A(j, k+l+i-j+l)= A(j, k+l + i-j+l) +
+ A(j, k+l+i-j) x A(i, k + 1 + 1) END DO END DO
END DO
454
Часть III. Смежные проблемы и применение
По отношению к данному фрагменту переменные n, m, к являются внешни­ми. Если для него строить графы зависимостей без использования какой-либо априорной информации о значениях этих переменных, то возникнут две дополнительные трудности. Во-первых, пространство переменных i, j, l будет разбито на очень большое число областей, т. к. формально нужно принять во внимание все возможные соотношения между значениями внешних параметров. Во-вторых, истинная информационная структура фрагмента будет сильно искажена из-за появления "ложных" связей, возни­кающих при "нереальных" соотношениях между внешними переменными. В то же время известно, что п есть порядок матрицы. Следовательно, п > 1. Ширина m ленты матрицы не может превышать ее порядка п. Поэтому п > m > 1. Переменная к, как видно из программы в целом, означа­ет номер основного шага вычислительного алгоритма, т. е. к > т. Если при построении графов зависимостей фрагмента (8.10) принять во внимание, что n>k > m > 1, то весь процесс уже никаких затруднений не вызовет.
Вот почему при проведении анализа тонкой информационной структуры фрагментов программ необходимо максимально учитывать возможные соот­ношения между внешними переменными. Заметим, кстати, что далеко не всегда все соотношения можно найти из текста программ. Нередко какие-то из них используются по умолчанию. Тем не менее, следует помнить, что чем полнее описана область определения внешних переменных, тем точнее будет выполнен анализ структуры.
Подпрограммы, функции
и неанализируемые фрагменты
В процессе анализа структуры программ нередко встречаются ситуации, когда какие-то фрагменты, состоящие из отдельного оператора или группы опера­торов, выводят программы из линейного класса. Но в то же время по тем или иным причинам детальное исследование таких фрагментов может или ока­заться невозможным, или просто не представлять интереса. Например, нали­чие обращения к подпрограмме заведомо выводит из линейного класса. Одна­ко почти всегда текст подпрограммы бывает недоступен и, следовательно, с ним нельзя что-либо сделать. Аналогичное положение имеет место в отноше­нии вызовов функций. Может встретиться фрагмент, который устроен очень сложно. Но из каких-то соображений, прямых или косвенных, становится понятно, что параллельная его структура не играет заметную роль в организа­ции вычислительного процесса и поэтому ее можно не изучать.
Перечислять подобные примеры можно довольно долго. Для всех них харак­терно то, что знание подробной структуры фрагментов не требуется. А раз так, то сами фрагменты можно заменить фиктивными линейными програм­мами и изучать строение исходной программы с этими фиктивными встав­ками. Для получения правильных сведений о структуре необходимо иметь сведения о входных и выходных данных заменяемых фрагментов.
Глава 8. Вычислительные системы и алгоритмы
455
Предположим, что в программе происходит вызов подпрограммы gauss, осуществляемый оператором
CALL GAUSS (А, В, N)                                                                                                    (8.11)
Пусть известно, что в массиве а размером n х n и массиве в размером n х 1 задаются входные данные, а в массиве в, к тому же, получаются результаты. Выберем любую простую переменную, которая не используется в анализи­руемой программе. Допустим, что это есть переменная с идентификатором fiction. Теперь рассмотрим такую программу
DO i = 1, N DO j = 1, N fiction = A(i, j) END DO
fiction = B(i)                                                                                                          (8-i2)
END DO DO k = 1, N B(k) = fiction END DO
Программа gauss могла решать систему линейных алгебраических уравне­ний порядка n с матрицей и правой частью, расположенными, соответст­венно, в массивах айв. Программа (8.12) никакого отношения к решению системы линейных алгебраических уравнений не имеет. Более того, она почти целиком состоит из избыточных вычислений. Тем не менее, обе программы имеют одинаковые входные и выходные данные. Теперь предпо­ложим, что с исходной программой выполнены следующие действия: вместо оператора (8.11) подставлено тело подпрограммы gauss и вместо того же оператора (8.11) подставлена подпрограмма (8.12). Пусть для обеих полу­ченных программ построены графы зависимостей одного и того же типа. Далее в графах каждой из программ при всех значениях внешних по отно­шению к оператору (8.11) параметров циклов сольем в одну вершину пространства итераций соответственно тела подпрограммы gauss и под­программы (8.12), сохранив при этом все дуги. Легко проверить, что с точ­ностью до числа дуг, представляющих петли, оба построенных таким обра­зом графа полностью совпадают. Подпрограмма (8.12) линейная. Если исходная программа была во всем остальном, кроме оператора (8.11), ли­нейной, то будет линейной программа, полученная после подстановки про­граммы (8.12) вместо оператора (8.11).
Аналогично заменяются вызовы функций, не анализируемые или трудно анализируемые фрагменты, а также фрагменты, структурой которых мы не интересуемся.
456
Часть III. Смежные проблемы и применение
Использование функций min и max
Среди многочисленных функций функции min и max заслуживают особого внимания. Во-первых, по данным статистики данные функции встречаются наиболее часто. А, во-вторых, при преобразовании программ, основанных на знании скошенного параллелизма, пределы изменения параметров цик­лов почти всегда выражаются именно через них. Рассмотрим функцию min. Функция max исследуется аналогично. Пусть она имеет какое-то вхождение
- min (fbf2) ... .
Заменим это вхождение таким фрагментом
if (Л>£) go to 1
-Л -
до to 2                                                                                                                 (8ЛЗ)
CONTINUE
CONTINUE
где выражения f\ nf2 находятся в программе точно в том же положении, как и функция mia(fi,f2). Если/j и f2 были линейными функциями параметров циклов, что является типичной ситуацией, и линейность исходной про­граммы нарушалась только из-за функции min, то замена этой функции согласно (8.13) делает программу линейной.
Прямое вычисление графов зависимостей
Существует немало других способов сведения программ к линейным. Но если уж ничто не помогает, можно построить граф зависимостей непосредственно по программе для какого-то частного случая и проанализировать его вручную с целью обнаружить общую закономерность. Основанием к выбору такого подхода исследований являются свойства графов зависимостей, описанные в вопросах и заданиях к § 6.5.
Рассмотрим снова пример 6.4. Мы уже отмечали, что с анализом его струк­туры не справился ни один из параллелизирующих компиляторов. На рис. 8.2 представлен граф алгоритма для п = 10.
На первый взгляд — в дугах сплошная путаница. Однако более вниматель­ное рассмотрение рис. 8.2 позволяет обнаружить нечто интересное. Именно, при каждом значении / существует только одна дуга, которая связывает вершины с тем же значением координаты /. Более того, эти вершины явля­ются соседними по координате у. Поэтому внутренний цикл программы примера 6.4 заведомо можно представить как последовательность двух цик­лов типа ParDO. Сначала идет цикл, в котором параметр j меняется от 1 до
Глава 8. Вычислительные системы и алгоритмы
457
координаты j начальной точки указанной дуги включительно. Затем идет цикл, в котором параметр j меняется от координаты j конечной точки дуги до п. Легко установить, что начальные точки дуг лежат на прямой / + j = п, и мы сразу получаем преобразование (7.19). На рис. 8.2 также видно, что обсуждаемые параллельные оси у дуги лежат на критическом пути графа алгоритма. Его длина равна 2п — 2. Следовательно, преобразование (7.19) передает уровень параллелизма, имеющегося в исходной программе приме­ра 6.4, абсолютно точно.
Рис. 8.2. Граф алгоритма "простейшей" программы
Анализ графа на рис. 8.2 позволяет понять причины, по которым исследо­вание примера 6.4 вызывает большие трудности. Рис. 8.3 демонстрирует ярусы канонической параллельной формы графа. Видно, что они устроены достаточно сложно. При изменении номеров ярусов изменяются не только местоположение и размеры самих ярусов, но даже их "размерность" и кон­фигурация.
458
Часть III. Смежные проблемы и применение
1                                                                                                                               о
О 3
2 5
4 7
6 9
8 11
10 13
12 15
14 17
j
L
Рис. 8.3. Ярусы канонической параллельной формы
Вопросы и задания
1.   Пусть параметр р, входящий в индексное выражение, многократно пересчитыва-ется в программе по линейному закону. В каком случае прямая подстановка всех результатов пересчета параметра р даст линейное индексное выражение?
2.   Что меняется в прямой подстановке, если какие-то операторы пересчета пара­метра р охватываются условными передачами управления?
3.   Пусть программа имеет перекрывающиеся до to-циклы. Как исключить такое перекрытие?
4.   Пусть тело цикла не меняется при повторных срабатываниях. Как построить раз­вертку цикла, если известна развертка его тела?
5.   *Рассмотрите решение общей задачи (6.13), (6.14) для случая, когда индексные выражения являются многочленами (выпуклыми, возрастающими по каждой пе­ременной, лексикографически возрастающими, представленными в виде суммы одномерных возрастающих многочленов).
6.   Пусть для программы со вставкой (8.12) вместо (8.11) известна строгая развертка. Приведите примеры зависимости операций, не относящихся к вставке (8.12), но лежащих на одном ярусе развертки.
7.   Как надо видоизменить процесс построения разверток для программ со вставка­ми типа (8.12), чтобы результат был гарантированно правильным?
Глава 8. Вычислительные системы и алгоритмы
459
8.   Покажите, что длина критического пути графа линейного алгоритма оценивается сверху полиномом от внешних переменных.
9.   *Докажите или опровергните гипотезу, что для достаточно больших значений внешних переменных длина критического пути графа линейного алгоритма есть полиномиальная функция от внешних переменных.
10. *Если гипотеза п. 9 верна, то как определять коэффициенты полинома, исполь­зуя прямое вычисление графа алгоритма?
§ 8.2. Граф-машина
Рассмотрим произвольный ациклический ориентированный граф G. Помес­тим в каждую его вершину простое функциональное устройство, имеющее столько входов, сколько дуг входит в вершину. Будем считать дуги графа на­правленными линиями связи, обеспечивающими передачу информации от устройства к устройству. Предположим, что ФУ, помещенные в вершины без входящих дуг, являются устройствами ввода, помещенные в вершины без вы­ходящих дуг, — устройствами вывода. Заставим работать построенную систему по правилам, описанным в § 2.3. Будем называть такую модельную вычисли­тельную систему граф-машиной. Граф G будет графом этой системы. На граф-машину распространяются все полученные в § 2.3 результаты. Граф-машина не имеет собственной памяти и может сохранять результаты промежуточных вычислений только в самих ФУ до следующих срабатываний.
В процессе своего функционирования граф-машина будет реализовывать какой-то алгоритм. Естественно, он будет зависеть как от графа G, так и от состава ФУ, помещенных в его вершины. В рамках зафиксированных огра­ничений граф G может быть любым. Но поскольку наше внимание сейчас направлено на решение проблемы отображения, интерес представляют вы­числительные системы, реализующие заданный алгоритм. Поэтому в качест­ве графа G мы возьмем граф анализируемого алгоритма, а в каждую его вершину поместим такое ФУ, которое может выполнять операцию, соответ­ствующую именно этой вершине.
Возможны два типа функционирования граф-машины: режим однократного срабатывания устройств и режим многократного срабатывания. При одно­кратном срабатывании каждое ФУ включается только один раз, в том числе, один раз включаются все входные и выходные устройства. Поэтому входных и выходных устройств должно быть ровно столько, сколько входных данных и результатов связано с алгоритмом. Естественно, включение осуществляется лишь после того, как готовы все входные данные устройства. В режиме од­нократного присваивания хорошо видно одно из важнейших достоинств граф-машины — возможность локального управления работой отдельных ФУ. В самом деле, источники получения входных данных для каждого ФУ из­вестны. Поэтому само ФУ может следить за их появлением и, следователь-
460
Часть III. Смежные проблемы и применение
но, само ФУ может определять момент начала своего срабатывания. Поэто­му для граф-машины внешнее глобальное управление работой всей сово­купности ФУ не является необходимым. В какие бы из допустимых момен­тов не начали срабатывать ФУ, результат работы граф-машины будет одним и тем же и будет соответствовать рассматриваемому алгоритму.
При многократном срабатывании каждое ФУ включается столько раз, сколько раз будут готовы для него новые входные данные. Очередное вклю­чение может осуществляться только в те моменты, когда результат преды­дущего срабатывания либо уже был использован, либо уже начал использо­ваться. При однократном срабатывании на выходных устройствах граф-машины будут находиться результаты реализации того алгоритма, граф ко­торого взят в качестве графа вычислительной системы. При многократном срабатывании на выходных устройствах будут последовательно появляться результаты реализации этого же алгоритма, но соответствующие последова­тельно вводимым входным данным. Так как граф алгоритма ациклический, то при соблюдении правил работы ФУ, описанных только что, а также в §2.3, никакое "перемешивание" процессов вычислений, соответствующих разным по порядку ввода входным данным, невозможно. Это означает, что при изучении граф-машины можно ограничиться изучением лишь режима однократного срабатывания. Ему и будут посвящены наши ближайшие ис­следования.
Одной из важнейших характеристик, определяющих эффективность алго­ритма, является время его реализации на вычислительной системе. Конечно, время реализации зависит не только от алгоритма. В немалой степени оно определяется также структурой и временными характеристиками вычисли­тельной системы.
Совместное влияние параметров вычислительной системы и структуры ал­горитма на время реализации алгоритма является довольно сложным и не­однозначным. Об этом убедительно свидетельствует вся история развития вычислительной математики и вычислительной техники. Алгоритмы, счи­тавшиеся эффективными на однопроцессорных ЭВМ, нередко становятся очень неэффективными на существующих сегодня параллельных вычисли­тельных системах. Однако эти же алгоритмы могут снова оказаться эффек­тивными в будущем, если вычислительная техника начнет развиваться в благоприятном для них направлении.
Как же оценивать время реализации алгоритма? Очевидно, что прямое его измерение на конкретном компьютере дает возможность сравнивать вре­менные характеристики различных алгоритмов только по отношению к дан­ному компьютеру. По отношению к другому компьютеру аналогичное срав­нение, вообще говоря, дает другие результаты. Поэтому необходимо ввести некоторое абстрактное сравнение времени реализации алгоритмов или, другими словами, необходимо сравнивать времена реализации алгоритмов
Глава 8. Вычислительные системы и алгоритмы
461
на некоторой абстрактной вычислительной системе. Эта вычислительная система не должна зависеть от особенностей развития технологии на теку­щий момент, но должна, тем не менее, отражать общие тенденции в прин­ципах построения вычислительных систем.
Последовательная реализация алгоритмов как при ручном счете, так и при использовании однопроцессорных компьютеров давно привела к созданию вполне определенной процедуры сравнения алгоритмов по времени реали­зации. Эта процедура хорошо известна и состоит в сравнении числа опера­ций, выполнение которых необходимо для получения решения задачи с за­данной точностью. Она достаточно хорошо отражает основные особенности последовательной реализации алгоритмов и почти не зависит от технологи­ческих изменений в самом процессе реализации. Именно эти обстоятельства сделали число операций тем критерием эффективности алгоритмов, кото­рый оказался приемлемым для широкого круга вычислительных средств от человека до однопроцессорных компьютеров различных типов. Формально можно считать, что число операций есть время реализации алгоритма на абстрактном однопроцессорном компьютере, у которого время выполнения любой операции равно 1, а время выполнения всех других процедур (ввод/ вывод данных, взаимодействие с памятью, передача данных по каналам свя­зи и т. п.) равно 0. При этом предполагается, что операции выполняются подряд без какого-либо простоя в работе компьютера.
Как было показано раньше, сравнение времен реализации алгоритмов по числу операций становится совершенно неприемлемым ни для практики, ни для теории, если реализацию самих алгоритмов осуществлять на параллель­ных вычислительных системах. Поэтому и абстрактный последовательный компьютер, используемый для сравнения времен реализации алгоритмов, также оказывается неприемлемым для этой цели. Теперь в качестве подхо­дящей модельной вычислительной системы выступает граф-машина.
Есть одно принципиальное различие между абстрактной последовательной ЭВМ и граф-машиной. Именно, на первой реализуются все алгоритмы, на второй — только один. Конечно, не представляет никакого труда сразу вве­сти абстрактную параллельную вычислительную систему. Предположим, что она имеет бесконечно много процессоров, время выполнения любой опера­ции на любом процессоре равно 1, а время выполнения всех других проце­дур, включая установление нужных связей между процессорами, равно 0. Тогда сравнение алгоритмов по скорости реализации на параллельных сис­темах можно заменить сравнением времен реализации алгоритмов на по­строенной абстрактной параллельной вычислительной системе. Однако с введением подобной системы не стоит торопиться.
Прежде чем изучать, чем различаются различные реализации различных алгоритмов, необходимо понять, чем различаются различные реализации одного и того же алгоритма. Граф-машина вполне подходит для решения
462
Часть III. Смежные проблемы и применение
последней задачи. Более того, на ней решать эту задачу значительно проще, чем на абстрактной параллельной системе, т. к. теперь фиксирована комму­никационная сеть, а также номенклатура и число процессоров. После изу­чения множества реализаций одного и того же алгоритма нетрудно перейти к сравнению реализаций различных алгоритмов, т. е. перейти к использова­нию абстрактной параллельной вычислительной системы в качестве инст­румента моделирования вычислительных процессов.
Если алгоритм реализуется на граф-машине, то каждая его операция выпол­няется в какое-то определенное время. Обозначим через t(v) момент окон­чания срабатывания операции, соответствующей вершине v графа. Перену­меруем подряд цифрами 1, ..., N все вершины. Нумерация пока может быть произвольной и никак не связанной со строением пространства итераций. Рассмотрим вектор t = (Ц, ..., ty). Если в вершину v идет дуга из вершины и, то очевидно, что t(v) > t(u). Поэтому вектор t есть не что иное, как вектор­ное представление развертки. Таким образом, множество режимов одно­кратного срабатывания устройств граф-машины или, что то же самое, множество реализаций заданного алгоритма на любых реальных или гипоте­тических вычислительных системах полностью описывается множеством разверток графа алгоритма. Следовательно, при изучении граф-машины можно использовать любые свойства разверток, описанные в главе 7. В ча­стности, обнаружив какие-то новые свойства, мы можем на их основе по­пытаться построить и новые типы вычислительных устройств или систем.
Принимая во внимание реалии действительности, с вершинами и дугами графа необходимо связать дополнительные характеристики. Каждая опера­ция выполняется за какое-то время. Обозначим его через hj > 0 для у'-й вер­шины. Вектор h = (hi, ..., /z/v) назовем вектором реализации. Если из верши­ны / идет дуга в вершину j, то передача информации вдоль этой дуги также требует времени wy > 0. Вектор w, составленный из чисел wy, назовем векто­ром задержек. Среди всех вершин граф-машины особую роль играют вход­ные вершины. Там находятся устройства, поставляющие входные данные. На практике именно они определяют момент начала вычислительного про­цесса. Поэтому полезно рассмотреть те реализации алгоритма, которые свя­заны с фиксированными моментами подачи входных данных. Обозначим через gj множество номеров вершин, из которых идут дуги в вершину с но­мером j. Пустое множество будем обозначать стандартным символом 0. Ясно, что gj= 0 только для входных вершин. Будем считать, что если gj= 0, то для времен tj заданы значения sj. Вектор s с координатами Sj, gj= 0 будем называть вектором граничных значений. По смыслу этот же вектор можно было назвать вектором начальных условий или вектором на­чальных значений. Название выбрано с учетом геометрической интерпрета­ции фактов. Как показывает практика, входные вершины чаще всего разме­щаются на границе пространства итераций. Развертки, при определении
Глава 8. Вычислительные системы и алгоритмы
463
которых принимаются во внимание все векторы s, w, h или только какие-нибудь из них, будем называть развертками с ограничениями.
Обозначим через R = R(s, w, И) класс разверток с одними и теми же векто­рами s, w, h. Имеет место очевидное
Утверждение 8.1
Развертки с фиксированными ограничениями s, w, h описываются следующими соотношениями
t- > max(?! + W//) + h:, если g,- * 0;
leg,-
(8.14) tj = sj, если gj = 0.
Как и над любыми другими, над развертками с ограничениями из класса R, можно выполнять все ранее введенные операции. Результат всегда будет разверткой, но он может не принадлежать R. Так, например, операции сло­жения разверток и прибавления к развертке неотрицательного числа X все­гда выводят из класса R, если только вектор s и число X не являются нуле­выми. Это подтверждается тем, что уравнения s + s = s и s + X = s, X >0 имеют решения лишь когда s = О, X = 0. Операция умножения развертки на положительное число X остается в классе R только в случае X = 1. Это под­тверждается тем, что уравнения s = Xs, h = Xh, h ф 0, имеют решения лишь при X = 1.
Утверждение 8.2
Класс R(s, w, К) замкнут относительно операций Ф, <8>.
Рассмотрим любые две развертки t', t" из класса R и исследуем сначала раз­вертку t = t' ® t". На множестве вершин, для которых gj = 0, развертки ? и t" принимают одни и те же значения. По смыслу операции Ф на том же множестве вершин развертка t будет иметь такие же значения. Теперь возь­мем какую-то вершину у с условием gj ф 0. Находим, учитывая (8.14), что
t3 ={t' ® t"}j =max(tptj)>
> max maxU/ + wn) + h,-, maxU/ + wn) + h,-= max(max((// + и>/,-) + hh (tj + vc//) + /*,)) = = maxUmax^/, t") + wlt) + h,) = max(?/ + wlt) + h,.
legj                                                                                     legj
Следовательно, развертка t'@ t" принадлежит классу R в силу того, что она описывается теми же векторами ограничений s, w, h. Теперь рассмотрим
464
Часть III. Смежные проблемы и применение
развертку t = t' ® t". Она имеет тот же вектор s граничных значений, что и развертки t'и t". Для вершину с условием gj^ 0 заключаем, что
tj={t'®f}j=mm{t'j,fj)>
> min maxU/ + w'n) + h,-, maxUf + wn) + h,-
> max(min((?; + wtj ) + hj, (tj + we) + hj)) =
= max(min(?/', t") + w/;) + h, = max!/, + vc//-) + h,. legj                                                                               legj
Это означает, что развертка t'<2> t" описывается теми же векторами ограни­чений s, w, h, как и развертки t\ t", т. е. она принадлежит классу R.
Утверждение 8.3
В любом классе R(s, w, h) существует развертка 0 = Q(s, w, /г), которая описы­вается соотношениями
е =max(e/ +w,i) +h■ если gj *0;                                                 ...
leg/                                                                                                                                    [р. \0)
Qj = Sj, если gj= 0. Она удовлетворяет равенствам
Q®t=t@Q = t, 0® ?=/<S>0 = 0                                     (8.16)
для любой развертки t из R.
Развертку 9 построим конструктивно. Рассмотрим каноническую параллель­ную форму графа алгоритма. На первом ярусе и только на нем находятся вершины, для которых gj = 0. Никаких других вершин на данном ярусе нет. В этих вершинах развертке 9 припишем значения, соответствующие коор­динатам вектора s. Далее, во всех вершинах второго яруса развертке 9 при­пишем значения, вычисленные согласно первому равенству из (8.15). После прохождения аналогичным образом всех ярусов канонической параллельной формы графа алгоритма развертка 9 будет построена. Очевидно, что по спо­собу ее нахождения она принадлежит классу R(s, w, И) и для нее выполня­ются равенства (8.16).
Развертка 9 является наименьшей разверткой в классе R. Более того, значение развертки 9 в любой вершине не превосходит значения любой развертки из класса R в той же вершине. По этой причине развертку 9 будем называть минимальной в классе R. Сказанное означает, что в классе R(s,w, И) минималь­ная развертка соответствует реализации алгоритма за минимальное время.
Итак, R(s, w, И) представляет множество разверток с фиксированными огра­ничениями s, w, h. На этом множестве действуют две операции <8>, Ф. Обе
Глава 8. Вычислительные системы и алгоритмы
465
операции коммутативные и ассоциативные, причем операция "умножения" <8> дистрибутивна относительно операции "сложения" ©. На множестве R(s, w, И) существует минимальная развертка Q(s, w, К), которая, согласно (8.16), по отношению к операциям <8>, © играет роль нулевого элемента. Если бы операция Ф имела на множестве R(s, w, И) обратную, то само множество бы­ло бы кольцом. Но операция Ф обратную не имеет. Поэтому класс разверток с фиксированными ограничениями и операциями <8>, Ф представляет полу­кольцо с нулевым элементом.
Среди ограничений s, w, h ограничения w, h описывают "техническую" сто­рону граф-машины, ограничение s — математическую. Обозначим через R(w, И) класс разверток с одними и теми же векторами w, h. Он представля­ет объединение классов R(s, w, И) для всех возможных векторов s.
Утверждение 8.4
Класс R(w, К) замкнут относительно операций Ф, <8>, V.
Доказательство замкнутости класса R(w,h) относительно операций Ф, <8> поч­ти дословно повторяет доказательство утверждения 8.2. Замкнутость относи­тельно операции V очевидна.
В классе R(w, И) для каждого вектора начальных значений s существует ми­нимальная развертка 9. Ее можно считать результатом воздействия на вектор s оператора Q(s), определяемого соотношениями (8.15). Для нас интересно отметить, что этот оператор является "линейным" по отношению к паре операций Ф, V.
Утверждение 8.5
Для любых векторов s^ s2, и любых чисел аь а2 выполняется соотношение
9 (cXiV^) Ф (a2Vj2)) = (cciVG (sj) Ф (a2V0 (s2)).                              (8-17)
Обозначим через 9', 9 " и 9 минимальные развертки, соответствующие векто­рам граничных значений s\, Sj и (ai V s\) Ф (осг V si)- Векторное соотноше­ние (8.17) эквивалентно координатным равенствам
9/ = тах(ои + 9} , сс2 + 9' )
для всех j = 1, ..., N. Снова построим каноническую параллельную форму графа алгоритма. Для вершин первого яруса равенства (8.18), очевидно, вы­полняются, т.к. Q't- =s't-,Q"j =s", а 0у = max(ai + s'-,a.2+ s"). Здесь s'-,s"
суть у'-ые координаты векторов s\, Sj- Предположим, что равенства имеют место для всех вершин, находящихся в ярусах с номерами 1, ..., к, к> 1.
466
Часть III. Смежные проблемы и применение
Для вершин из яруса с номером к + 1 находим, что
9/ = max (9] + wy) + hj = max(max(a, + 9/, a2 + Q") + wy) + hj =
= max(max(a] + 6/ + wtj + h •, a2 + 0' + % + h •)) = = max(max(a] + 6/ + W/- + /z ■), max(a2 + 0" + wlf + h ■)) =
= max(a! + (max(0^ + wtj) + h,),a2 + (max(0" + wlf) + h •)) =
= max(oc, + 0'-, a2 + 0').
Перебрав все ярусы, заключаем, что равенства справедливы для всех j = 1, ..., N к, следовательно, справедливо соотношение (8.17).
В завершение общих исследований отметим монотонность оператора 9(s). Для двух векторов a, b будем писать а > b (>, <, <), если это соотношение выполняется для всех соответствующих координат векторов а, Ъ.
Утверждение 8.5*
Пусть рассматриваются минимальные развертки из любого класса R{w, h). Ес­ли Sl > s2 (>, <, <), то 9(Sl) > Q(s2) (>, <, <).
Для всех видов неравенств доказательства проводятся одинаково. Поэтому ограничимся рассмотрением знака >. Опять построим каноническую парал­лельную форму графа алгоритма. На первом ярусе и только на нем находятся вершины, на которых заданы начальные значения. Следовательно, неравен­ство 9(^i) > 9(^2) на вершинах первого яруса выполняется просто потому, что ^i > S2 по условию. Предположим, что неравенство Q(si) > 9(52) имеет место для всех вершин, находящихся в ярусах с номерами 1, ..., к, к> 1. Для вер­шин из яруса с номером к + 1 неравенство Q(s\) > 9(^) выполняется в соот­ветствии с (8.15), т. к. все вершины с номерами / расположены в первых к ярусах.
Итак, описано множество режимов функционирования граф-машины, обес­печивающих минимальное время реализации алгоритма при заданных вре­менах ввода входных данных. Заметим, что хотя множество минимальных разверток линейно по операциям Ф, V, оно не является по ним линейным пространством. В первую очередь, из-за того, что операция © не имеет об­ратную. Операция V также может не иметь обратную. Это будет, например, в том случае, когда допустимыми оказываются только неотрицательные раз­вертки. Для нахождения минимальных разверток необходимо решать отно­сительно величин 0J, ..., 9ту уравнения (8.15). Они называются дискретными уравнениями Беллмана. Неравенства (8.14) называются дискретными неравен­ствами Беллмана.
Глава 8. Вычислительные системы и алгоритмы
467
Граф-машина как модельная вычислительная система имеет немало досто­инств, среди которых, пожалуй, самое главное — возможность любых вре­менных реализаций алгоритма, в том числе за минимальное время. Но сразу же видны ее серьезные недостатки. Число используемых ФУ и число линий связи зависят в целом от числа выполняемых операций алгоритма. Как уже неоднократно отмечалось, в реальных задачах это число очень велико. В ре­жиме однократного срабатывания каждое ФУ граф-машины выполняет только одну операцию за время реализации алгоритма. Следовательно, за­груженность ФУ будет недопустимо малой. К тому же граф-машина не по­зволяет эффективно использовать конвейерные ФУ. Тем не менее, именно из граф-машины можно построить математические модели многих типов вычислительных систем. Среди них имеются и такие, которые также реали­зуют алгоритм за минимально возможное время, но обладают лучшими "техническими" характеристиками. В основе преобразования граф-машины лежит гомоморфная свертка графа [36].
Рассмотрим произвольный ориентированный граф G с множеством вершин V и множеством дуг Е. Сейчас граф может не быть ациклическим и может содержать петли. Выберем в К любые две вершины u, v и сольем их в одну вершину z- Новое множество вершин обозначим V. Перенесем на V без из­менения те дуги из G, для которых концевые вершины не совпадают ни с и, ни с v. Если же какая-то из концевых вершин совпадает с и или v, то такие дуги перенесем с заменой этих вершин на z- И, наконец, в новом графе все петли, относящиеся к одной вершине, заменим одной петлей. Также заме­ним одной дугой все кратные дуги. Множество дуг на V обозначим £'. Граф с множеством вершин V и множеством дуг is" обозначим G'. Преобразование графа G в граф G' называется простым гомоморфизмом, а многократное пре­образование простого гомоморфизма называется гомоморфной сверткой гра­фа. При гомоморфной свертке графа G множество его вершин распадается на непересекающиеся подмножества. Каждое из подмножеств состоит из тех и только тех вершин, которые в конечном счете сливаются в одну вершину. Вершины подмножества называются гомоморфными прообразами полученной вершины, а сама эта вершина — гомоморфным образом своих прообразов. Ясно, что любой ориентированный граф всегда можно гомоморфно свер­нуть в граф, состоящий из одной вершины и одной петли. Пример опера­ций простого гомоморфизма приведен на рис. 8.4. Сливаемые вершины обо­значены на нем "звездочками".
О
Рис. 8.4. Операции простого гомоморфизма
468
Часть III. Смежные проблемы и применение
Простым и конструктивным приемом осуществления гомоморфной свертки является операция проектирования. Предположим, что граф G расположен в конечномерном пространстве, т. е. его вершины являются точками простран­ства, дуги — векторами. Спроектируем граф вдоль любой прямой на перпен­дикулярную ей гиперплоскость. Пусть при этом какие-то вершины спроекти-руются в одну точку. Если в ту же точку спроектируются некоторые вектор-дуги, то поставим около точки петлю. Очевидно, что подобная операция есть гомоморфная свертка графа G. Чем больше точек-вершин графа расположено по направлению проектирования, тем больше вершин спроектируются в одну точку. Ничто не мешает повторять операцию проектирования многократно, пока не получится граф нужного строения. После числа шагов, равного раз­мерности пространства, всегда в проекции получится одна точка. Если в гра­фе G была хотя бы одна дуга, то около точки будет петля.
Гомоморфная свертка имеет очень прозрачный смысл. Пусть граф G пред­ставляет граф-машину. Совершим операцию простого гомоморфизма. Вы­бирая вершины и, v, мы определяем две операции алгоритма и два ФУ, ко­торые эти операции реализуют. Сливая вершины и, v, мы связываем с вершиной z не одну, а пару операций. ФУ, отвечающее вершине z, должно иметь возможность выполнить обе операции последовательно. После мно­гократного применения операции простого гомоморфизма полученный граф можно рассматривать как граф новой модели вычислительной системы. ФУ, связанное с любой его вершиной, обязано последовательно выполнять все операции алгоритма, связанные со всеми вершинами-прообразами. Дуги по-прежнему символизируют направленные передачи информации. Наличие петли около вершины говорит о том, что соответствующее ФУ будет сраба­тывать многократно.
Имеется одно принципиальное отличие граф-машины от вычислительной системы, полученной при гомоморфной свертке. Граф-машина не имеет память. Роль ее ячеек успешно выполняют сами ФУ в силу того, что каждое из них срабатывает только один раз. При многократном срабатывании ФУ результаты предшествующих срабатываний могут оказаться не использован­ными полностью. Для их сохранения уже нужна память. Зная граф алгорит­ма и временной режим срабатываний ФУ новой системы, можно подсчитать величину требуемой памяти и даже изучить процесс ее использования. Но мы не будем сейчас заниматься подобными подсчетами и исследованиями. Наша ближайшая задача связана с изучением возможностей осуществления наискорейших реализаций алгоритма.
В общем случае на вычислительной системе, полученной после гомоморф­ной свертки, нельзя реализовать все временные режимы, допустимые для граф-машины. Например, при слиянии всех вершин в одну мы получаем образ однопроцессорного компьютера. На нем можно реализовать только последовательные режимы. Пусть режим реализации алгоритма на граф-машине описывается разверткой / Если при этом режиме какие-то опера-
Глава 8. Вычислительные системы и алгоритмы
469
ции выполняются одновременно, то сливая соответствующие вершины в одну, мы будем вынуждены выполнять такие операции последовательно. Следовательно, на полученной после свертки системе нельзя будет реализо­вать данный режим. Отсюда, в частности, вытекает, что слияние любых вер­шин граф-машины, не связаных путем графа алгоритма, заведомо сужает множество возможных временных реализаций. Тем интереснее выделить та­кие свертки, при которых сохраняется все множество допустимых режимов.
На время реализации алгоритма влияют как времена выполнения операций, так и времена задержек при передаче информации. Во многих ситуациях предполагатся, что все задержки являются нулевыми. В этом случае почти очевидно
Утверждение 8.6
Пусть все задержки являются нулевыми и при гомоморфной свертке сливаются лишь вершины, находящиеся на одном пути графа алгоритма. Тогда на по­строенной системе реализуется весь спектр временных режимов граф-маши­ны, в том числе наискорейшие.
Достаточная разнесенность во времени моментов включения ФУ построен­ной системы гарантируется здесь тем, что сливаемые вершины находятся на одном пути. Поэтому соответствующие им операции как обязаны были раньше, так и имеют возможность теперь выполняться последовательно друг за другом. Подчеркнем также, что совсем не обязательно, чтобы образ сли­ваемых вершин имел в качестве своих прообразов все вершины, находящиеся на одном пути. Важно лишь, чтобы прообразы были связаны одним путем. Это обстоятельство имеет существенное значение, т. к. чаще всего объеди­няются вершины, соответствующие однотипным операциям, а они обычно в вычислениях перемешиваются с операциями других типов. Что же касается установления соответствия между вершинами графа алгоритма и срабатыва­ниями ФУ, помещенными в вершины графа вычислительной системы, по­лученной после гомоморфной свертки, то теперь оно очень простое. Имен­но, если из двух вершин графа алгоритма одна достижима из другой, то из двух соответствующих срабатываний ФУ ей соответствует более позднее.
Таким образом, разбивая вершины графа алгоритма на подмножества, ле­жащие на одном пути, и объединяя их с помощью операций простого гомо­морфизма, мы получаем конструктивный способ построения математиче­ских моделей вычислительных систем. Естественно, что таких систем может быть много, и они, вообще говоря, неодинаковы с точки зрения состава ФУ, их загруженности, размера присоединенной памяти, сложности коммуника­ционной сети и т. п. Но все эти системы по своим основным параметрам, кроме размера памяти, лучше, чем граф-машина. Они содержат меньшее число ФУ, загруженность каждого ФУ больше, число линий связи между ФУ меньше и при этом часто реализуется весь спектр временных режимов, включая наискорейшие. Снова можно ставить задачу оптимизации, пытаясь
470
Часть III. Смежные проблемы и применение
разбить вершины графа алгоритма на наименьшее число подмножеств, ле­жащих на одном пути. И снова возникает противоречивая ситуация: умень­шение числа ФУ может привести к усложнению коммуникационной сети и увеличению объема памяти.
Вопросы и задания
1.   Рассмотрим режим многократного срабатывания граф-машины. Пусть каждый раз все ФУ, включая входные и выходные, срабатывают за одно и то же время и максимально быстро после предыдущего срабатывания. Будет ли для всех набо­ров входных данных реализован наискорейший режим?
2.   Пусть в условиях п. 1 граф-машина работает достаточно долго. Можно ли утвер­ждать, что асимптотическая загруженность каждого ФУ равна 1?
3.   Пусть граф-машина работает в режиме однократного присваивания. Чему равно минимальное время между моментом ввода первого входного данного и момен­том получения последнего результата?
4.   Докажите, что класс разверток R(s, w, h) является множеством, ограниченным снизу и замкнутым покоординатно.
5.   Докажите, что класс разверток R(s, w, К) является множеством, выпуклым отно­сительно обычных векторных операций сложения и умножения на число.
6.   Докажите, что среди всех классов R(s, w, h) только класс R(0, w, h) замкнут отно­сительно обычных векторных операций сложения и умножения на число.
7.   Пусть множество векторов граничных значений содержит "нулевой" относитель­но операции © вектор л°. Докажите, что, во-первых, любую развертку из класса R(s, w, К) можно представить в виде "суммы" по операции © развертки 0(,s, w, h) и некоторой развертки из класса Дл°, w, К). И, во-вторых, "сумма" по операции © развертки 0(,s, w, h) и любой развертки из R(sP, w, К) дает развертку из R(s, w, К). Символически это выражается так:
R(s, w, И) = Q(s, w, И) Ф Д/», w, И).
8.   Пусть аь а2, ..., аи и |3Ь |32, ..., Р„ — любые две последовательности веществен­ных чисел. Докажите, что всегда справедливы соотношения:
max max a{, max /3; = max (max(ai, Д))
I 1< i < n          l<i<n I l<i<n
min max a{, max /3; > max (min^, /3;))
^l<i<ll         l<i<n        J l<i<n
9.   Рассмотрите алгоритмы последовательного и попарного суммирования чисел. Графы этих алгоритмов представлены на рис. 4.2. С помощью операции гомо­морфной свертки получите графы вычислительных систем, сохраняющих наи­скорейшие режимы и содержащие минимальное число вершин. Не правда ли, полученные графы различаются очень сильно?
Глава 8. Вычислительные системы и алгоритмы
471
10. **Для заданного графа алгоритма предложите метод осуществления гомоморф­ной свертки, обеспечивающий на модельной вычислительной системе сохране­ние наискорейших режимов и гарантирующий минимальность числа ФУ (минимальность числа связей, минимальность объема требуемой памяти, мак­симальность загруженности системы ФУ и т. п.).
§ 8.3. Регулярные и направленные графы
Возможность построения эффективных моделей вычислительных систем на основе объединения вершин графа алгоритма, лежащих на одном пути, сти­мулирует выделение класса алгоритмов, где такие пути устроены наиболее просто.
Пожалуй, чаще всего в вычислительной математике используются алгорит­мы, записываемые в виде рекуррентных соотношений с линейными индек­сами. Рассмотрим конечномерное пространство целочисленных вектор-индексов. Будем считать, что в этом пространстве введены естественный базис, обычное понятие ортогональности и лексикографический порядок. Пусть дана область В, содержащая непустое множество вектор-индексов. Соотношения вида
uf= Fj(uf-fi , ..., uf-fr), fe В                                  (8.18)
называются рекуррентными соотношениями с линейными индексами, если вектор-индексы/, ...,fr фиксированы, целочисленные и не зависят от/ Функции Ff могут быть произвольными, в том числе как линейными, так и нелинейными. Выполняются соотношения (8.18) в порядке лексикографи­ческого роста вектор-индекса / Чтобы данный процесс был возможен, не­обходимо, чтобы при всех / каждый из векторов/—/!, •••■>f~fr либо не принадлежал области В, либо был лексикографически меньше / При этом предполагается, что если какой-либо из векторов/—/ не принадлежит об­ласти В, соответствующая переменная и у - f считается заданной и пред­ставляет одно из входных данных алгоритма. Известно, что для осуществи­мости процесса (8.18) достаточно, чтобы первая по лексикографическому старшинству ненулевая координата каждого из векторов/, ...,/ была поло­жительной. Мы не будем сейчас обсуждать детали использования лексико­графического порядка на множестве векторов.
Будем размещать вершины графа алгоритма в точках области В с целочис­ленными координатами. Вершине, задаваемой вектором/ поставим в соот­ветствие функцию Ff. Если/е D, то из (8.18) вытекает, что в вершину/бу­дут входить дуги из вершин /— /, ...,/—/ и только из этих вершин. В случае, когда какой-то из векторов /—/ не принадлежит области Д вектор/—/ будет символизировать функцию ввода переменной «/- / •
Построенный таким образом граф имеет очень простую структуру. Если
472
Часть III. Смежные проблемы и применение
дуги задавать векторами, то в каждую вершину из области В будет входить один и тот же пучок дуг, который переносится параллельно от одной вер­шины к другой. Заметим, что при других размещениях вершин графа алго­ритма регулярная структура дуг может нарушаться. Данный пример нагляд­но подтверждает важность согласования формы записи алгоритма с формой представления его графа.
Специфические особенности графа алгоритма должны привести к появле­нию специфических свойств разверток. Специфические свойства будет иметь и граф алгоритма. Свойства графа и разверток мы будем изучать од­новременно.
Граф алгоритма (8.18) полностью определяется областью В и набором век­торов f\....,fr. В некоторых задачах, не описываемых формально рекуррент­ными соотношениями, также появляются аналогичные графы или графы, отличающиеся от подобных не слишком сильно. Однако относительно них не всегда бывает известно, что они могут рассматриваться как графы каких-нибудь алгоритмов, т. к. априори не ясно, имеют ли графы контуры. Такая ситуация возникает, например, тогда, когда мы пытаемся "аппроксими­ровать" граф алгоритма некоторым другим графом. Относительно графов, определяемых фиксированным набором векторов fh...,fr, приходится ре­шать немало задач, не связанных непосредственно с отысканием разверток. Поэтому мы начнем с того, что опишем класс исследуемых графов, не при­вязывая его к каким-то конкретным алгоритмам. К рекуррентным соотно­шениям (8.18) будем обращаться только с целью иллюстрации получаемых результатов.
Пусть задано «-мерное арифметическое пространство. Зафиксируем векторы /ь ...,fr с целочисленными координатами и будем называть их базовыми. Построим бесконечный граф, возьмем в качестве множества вершин множе­ство всех точек с целочисленными координатами. Будем считать, что в каж­дую вершину входит один и тот же пучок дуг, совпадающий с векторами /ь ...,fr Назовем такой граф бесконечным регулярным графом степени г. Лю­бой его конечный подграф будем называть регулярным графом степени г. Ес­ли регулярный граф является графом некоторого алгоритма, то соответст­вующий алгоритм также будем называть регулярным.
Очевидно, что граф алгоритма (8.18) является регулярным. Он будет оставаться регулярным и в том случае, если каждая из функций Ff зависит не от всего множества переменных и f ■_ у■,..., и /■ _ / , а лишь от какого-то его подмножества.
Основной смысл введения понятия регулярности состоит не только в том, чтобы выделить графы, которые устроены так же красиво, как граф алго­ритма (8.18). Нужно также описать графы, которые могут быть расширены до подобных графов. Реальные графы нередко имеют локальные нерегуляр­ности, особенно вблизи границы области задания вершин. В целом эти не­регулярности могут значительно портить структуру графа. Однако очень
Глава 8. Вычислительные системы и алгоритмы
473
часто они слабо влияют на изучаемые объекты, в частности, на множество подходящих разверток. Заметим, что при расширении графа множество раз­верток сужается. Структура же графа после расширения может стать вполне хорошей. Более того, может появиться возможность легко найти нужные развертки. С точностью до различий в области задания эти развертки будут также развертками для исходного графа. Желание не изучать влияние ло­кальных нерегулярностей и привело нас к введению бесконечного регуляр­ного графа. Его использование особенно оправдано в тех случаях, когда мы ничего не можем заранее сказать о структуре нерегулярностей. Основные свойства регулярных графов являются отражением соответствующих свойств бесконечных регулярных графов. Если бесконечный граф расщепляется на не связанные между собой подграфы, то аналогичное расщепление имеет место и для любых подграфов бесконечного графа. Если бесконечный граф не имеет контуров, то не имеет контуров и любой его подграф. Если для бесконечного графа можно найти развертку, то она порождает развертку для каждого подграфа, и т. п. Все эти обстоятельства побуждают начать деталь­ное исследование бесконечных регулярных графов.
Бесконечный регулярный граф может содержать бесконечные подграфы, также устроенные регулярно. Вершинами одного из них являются линейные комбинации базовых векторов со всевозможными целочисленными коэф­фициентами линейных комбинаций. Назовем такой подграф главным регу­лярным подграфом. В общем случае главный подграф не совпадает со всем бесконечным регулярным графом.
Утверждение 8.7
Рассмотрим два подграфа бесконечного регулярного графа, каждый из которых получен путем параллельного переноса главного подграфа. Имеет место аль­тернатива: или эти подграфы совпадают, или они не связаны.
Пусть G\ и С?2 — два указанных подграфа. Предположим, что они имеют общую вершину g. Рассмотрим произвольную вершину и из G\. Так как G\ получен из главного подграфа путем параллельного переноса, то вектор и — g может быть представлен в виде линейной комбинации базовых векто­ров с целочисленными коэффициентами. Но Gi также получен из главного подграфа путем параллельного переноса. Поэтому, прибавляя к вектору g эту линейную комбинацию, мы должны получить некоторую вершину из G^. Это означает, что вершина и принадлежит G^, т. е. подграфы G\ и Gj совпа­дают. Пусть подграфы соединены хотя бы одной дугой. Так как вершины главного подграфа определяются всевозможными комбинациями базовых векторов с целочисленными коэффициентами, то подграфы, связанные ду­гой, обязаны иметь общие вершины. Следовательно, они должны совпадать.
474
Часть III. Смежные проблемы и применение
Утверждение 8.8
Любой бесконечный регулярный граф можно представить как объединение подграфов, полученных из главного подграфа путем параллельного переноса на векторы с целочисленными координатами.
Если бесконечный регулярный граф не совпадает со своим главным подгра­фом, то в графе есть хотя бы одна вершина g, не принадлежащая подграфу. Перенесем главный подграф параллельно, совместив его нулевую вершину с вершиной g. Если два подграфа не покрывают полностью бесконечный ре­гулярный граф, то в нем есть хотя бы одна вершина и, не принадлежащая ни одному из подграфов. Снова перенесем главный подграф параллельно, совместив его нулевую вершину с вершиной и. Продолжая этот процесс, убеждаемся в справедливости утверждения.
Заметим, что подграфы, указанные в утверждении 8.8, не могут быть связа­ны между собой дугами.
Рассмотрим линейные комбинации базовых векторов, имеющие следующий
вид:
г
Здесь все числа а, удовлетворяют соотношениям 0 < аг < 1. Это множество векторов образует полуоткрытый многогранник, который назовем базовым. В базовый многогранник попадает некоторое число вершин регулярного графа. Назовем их опорными.
Утверждение 8.9
Пусть размерность линейной оболочки базовых векторов совпадает с размер­ностью пространства. Тогда бесконечный регулярный граф расщепляется на не связанные между собой подграфы, полученные путем параллельного переноса главного подграфа в некоторые опорные вершины.
Согласно утверждению 8.7, подграфы, полученные путем параллельного пе­реноса главного подграфа в любые две опорные вершины, или совпадают, или не связаны. Рассмотрим объединение всех подграфов, полученных пу­тем параллельного переноса главного подграфа в опорные вершины. Пред­положим, что этому объединению не принадлежит какая-то вершина g бес­конечного регулярного графа. Вектор g имеет целочисленные координаты и лежит в линейной оболочке базовых векторов. Очевидно, что всегда можно прибавить к нему такую линейную комбинацию базовых векторов с цело­численными коэффициентами, при которой результирующий вектор обяза­тельно попадет в базовый многогранник. Это означает, что наряду с верши­ной g в построенное объединение не входит одна из опорных вершин, что невозможно по построению.
Глава 8. Вычислительные системы и алгоритмы
475
Если базовые векторы линейно независимы, то никакие две различные опор­ные вершины не могут принадлежать одному и тому же подграфу, получен­ному из главного путем параллельного переноса. Если же базовые векторы линейно зависимы, то такие пары опорных вершин могут существовать.
Таким образом, любой бесконечный регулярный граф всегда расщепляется на не связанные между собой подграфы, изоморфные главному подграфу. Этот изоморфизм описывается параллельным переносом. Поэтому при изу­чении параллельной структуры бесконечного регулярного графа почти все­гда можно ограничиться изучением параллельной структуры его главного подграфа.
В общем случае базовые векторы могут быть такими, что регулярный граф будет иметь контуры. Подобные графы не могут быть графами каких-либо алгоритмов. Поэтому важно иметь критерий, показывающий отсутствие контуров в регулярном графе.
Утверждение 8.10
Бесконечный регулярный граф не имеет контуры тогда и только тогда, когда существует целочисленный вектор, образующий острые углы со всеми базо­выми векторами.
Пусть/], ...,frбазовые векторы. Предположим, что указанный вектор q существует, но регулярный граф имеет контур. Проходя этот контур в поло­жительном направлении дуг, заключаем, что имеет место равенство
1 = 1
где все числа at целые, неотрицательные и не все равны нулю. Умножая это равенство скалярно на вектор q и принимая во внимание, что (ft, q) > 0 для всех /, приходим к противоречию. Следовательно, существование вектора, образующего острые углы со всеми базовыми векторами, исключает сущест­вование у любого регулярного графа, в том числе бесконечного, хотя бы од­ного контура.
Предположим теперь, что для заданных базовых векторов /j, ..., fr не суще­ствует ни одного вектора q с указанными свойствами. Выберем среди базо­вых векторов максимальную подсистему, для которой такой вектор сущест­вует. Не ограничивая общности, можно считать, что это будут векторы f\, ..., У/, где Кг. Пусть К есть открытый конус векторов q, определяемый системой неравенств (/J, q) > О для 1 < / < /. По построению при j > I выпол­няется неравенство (//, q) < 0 для всех q е К. Следовательно, (— Jj, q) > 0 для всех q, принадлежащих не только открытому конусу К, но и его замыканию К. Согласно утверждению 6.6, каждый из векторов f/ + i, ...,fr представляется в виде линейной комбинации с отрицательными коэффициентами некото-
476
Часть III. Смежные проблемы и применение
рых из векторов/], ...,/. Пусть, например, / + j = plt/j + ... +$sfs, где s < I u все числа (Зг- отрицательные. Это означает, что вектор и = (— (Зь ..., — рЛ 1), все координаты которого строго положительны, является решением одно­родной системы линейных алгебраических уравнений Фи = О, где столбцы матрицы Ф составлены из координат векторов/], ...,/,/+ ].
Рассмотрим систему Фи = О более детально. Матрица Ф имеет целочислен­ные коэффициенты. Воспользовавшись формулами Крамера для нахожде­ния фундаментальной системы решений, заключаем, что все векторы фун­даментальной системы можно выбрать с рациональными и, следовательно, с целыми координатами. Как было установлено, система Фи = 0 имеет реше­ние с положительными значениями неизвестных. Это решение есть линей­ная комбинация векторов фундаментальной системы решений. В силу непрерывной зависимости линейной комбинации от коэффициентов и це­лочисленное™ векторов фундаментальной системы следует, что сколь угод­но малыми изменениями коэффициентов линейной комбинации можно по­лучить решение системы Фи = О с рациональными положительными значениями неизвестных. Поэтому система Фи = 0 обязательно будет иметь положительное целочисленное решение.
Если у], ..., ys, у/+ 1 — значения соответствующих неизвестных, то векторы Yi/ъ ■■■> Isfsi li+ ifi+ 1 образуют контур. Следовательно, отсутствие вектора q, образующего острые углы со всеми базовыми векторами, неизбежно вле­чет существование контуров в бесконечном регулярном графе. Отсутствие контуров гарантирует, что указанный вектор существует.
Вектор q является внутренней точкой конуса, определенного системой нера­венств (fj, q) > О для 1 < / < г. Из соображений непрерывности его всегда можно выбрать рациональным, а в силу однородности скалярных произве­дений и целочисленным.
Зафиксируем базовые векторы/, ...,/. Предположим, что никакая их ли­нейная комбинация с неотрицательными целочисленными координатами не равна нулю. Согласно утверждению 8.10, существует вектор q, образующий острые углы со всеми базовыми векторами, т. е. выполняются неравенства (fi, q) > 0 для 1 < / < г. Обозначим через х вектор пространства, в котором находятся вершины графа, и рассмотрим линейные функции вида t(x) = (х, q) + у, где у — любая константа. Поверхности уровня t(x) = а для этих функций являются гиперплоскостями.
Пусть G— произвольный регулярный граф с базовыми векторами/, ...,/•. Предположим, что какие-то вершины и, v связаны дугой /, вершина и явля­ется для дуги начальной, вершина v — конечной.
Тогда имеем
t(v) ~ t(u) = (v, q) ~ (и, q) = (v- и, q) = (fh q) > 0.
Глава 8. Вычислительные системы и алгоритмы
477
Это означает, что функция t(x) является линейной разверткой для графа G. Более того, если положить задержку для дуги ft равной (fh q), то функция t(x) будет минимальной разверткой. Следовательно, справедливо
Утверждение 8.11
Пусть регулярный граф имеет в качестве базовых векторы /ь ...,fr Выберем любой вектор q , образующий острые углы с базовыми векторами, и каждой ду­ге, задаваемой вектором fh установим задержку (/•, q). Тогда функция вида t{x) = (х, q) + у будет для графа минимальной разверткой при q = q .
Отметим, что в данном утверждении ничего не говорится о начальных усло­виях для развертки. Они легко восстанавливаются по самой развертке.
Развертка t(x) определяет параллельную форму графа. Ее ярусы задаются теми поверхностями уровня, которые содержат хотя бы одну вершину. Но поверхность уровня функции t(x) = (х, q) + у есть гиперплоскость с направ­ляющим вектором q. Поэтому вся совокупность ярусов параллельной формы описывается совокупностью различных гиперплоскостей, имеющих в каче­стве направляющего один и тот же вектор q и содержащих в себе все вер­шины графа. Если граф является графом алгоритма, то число покрывающих его различных гиперплоскостей определяет время реализации алгоритма. Так как все гиперплоскости имеют один и тот же направляющий вектор, то они параллельны. Поэтому число гиперплоскостей определяется расстоя­ниями между ними. Чтобы не рассматривать частные особенности регуляр­ных графов, снова обратимся к бесконечному регулярному графу. Проведем через каждую целочисленную точку пространства гиперплоскость с направ­ляющим вектором q и рассмотрим расстояние между соседними гиперпло­скостями.
Утверждение 8.12
Гиперплоскости с целочисленным направляющим вектором q, проходящие через целочисленные точки пространства, образуют семейство равноотстоя­щих гиперплоскостей. Расстояние между соседними гиперплоскостями равно
d ML1, где d— наибольший общий делитель модулей ненулевых координат вектора q, | ■ | —евклидова норма вектора.
Рассмотрим гиперплоскость (х, q) = 5. Вектор q = (q\, ..., qn) имеет целочис­ленные координаты. Так как гиперплоскость проходит хотя бы через одну целочисленную точку, то число 5 — целое. Пусть z = (Z\, ..., Zn) — целочис­ленный вектор. Известно, что уравнение q\Z\ + ... + qnzn = § имеет решение в целых числах тогда и только тогда, когда 5 делится на наибольший общий делитель d модулей координат q\, ..., qn. При этом число решений всегда бесконечно.
478
Часть III. Смежные проблемы и применение
Обозначим qt = dq', где q\ — целое, и рассмотрим невязку
r = d(q'lZi+...+q'„Z„)-S.                                       (8.19)
Числа         q\, ..., q'n         взаимно        простые,        поэтому        уравнение
q\l\ + ... + q'nzn = 1 обязательно имеет решение в целых числах. Следователь­но, выражение в круглых скобках в (8.19) может принимать всевозможные целочисленные значения. Это означает, что на множестве целочисленных векторов с координатами Z\, ..., Zn модуль минимального ненулевого значе­ния невязки г в (8.19) равен d, а расстояние между соседними гиперплоско-
j и и-1 стями равно d щ .
Желание минимизировать время реализации алгоритма приводит к миними­зации числа гиперплоскостей, покрывающих граф. Если не принимать во внимание частные особенности графов, то вектор q следует выбрать так,
чтобы величина d jqj была максимальной.
Таким образом, параллельная структура любого регулярного графа опреде­ляется, причем конструктивно. Конечно, для конкретного графа найденные таким образом развертки могут оказаться неминимальными. Однако почти всегда из них можно получить либо минимальные развертки, либо разверт­ки, близкие к минимальным.
Собственно говоря, есть две причины, мешающие получить минимальные развертки. Во-первых, пока не было учтено, что регулярный граф может расщепляться на не связанные между собой подграфы из-за расщепления бесконечного регулярного графа. В этом случае и вся система гиперплоско­стей может расщепляться на несвязанные подсистемы гиперплоскостей со­гласно расщеплению бесконечного регулярного графа. Во-вторых, регуляр­ный граф может иметь какие-то конкретные особенности. Они, вообще говоря, могут изменить строение минимальных разверток. Однако чаще всего подобное изменение приводит к разверткам, близким к минимальным.
Обратим внимание на следующее обстоятельство. Выбор вектора q, образую­щего острые углы с базовыми векторами регулярного графа, означает выбор вектора, образующего острые углы со всеми дугами графа. Если граф не явля­ется регулярным, то он все же может иметь вектор, образующий острые углы со всеми дугами. В этом случае исследование параллельной структуры графа осуществляется по той же самой схеме, что и для регулярного графа. Особен­ности графа сказываются на определении вектора q, но не на способе по­строения разверток. В общем случае нельзя только доказать близость развер­ток к минимальным. Графы, у которых существуют векторы, образующие острые углы со всеми дугами, оказываются существенно более простыми с точки зрения изучения их параллельных свойств. Такие графы называются строго направленными, а соответствующие векторы q — направляющими.
Глава 8. Вычислительные системы и алгоритмы
479
Рассмотрим любой строго направленный граф, вершины которого находятся в целочисленных точках. С помощью специальных преобразований его можно свести не только к регулярному графу, но даже к координатному ре­гулярному графу, дуги которого совпадают с координатными векторами. В частности, к координатному графу можно свести любой регулярный граф без контуров, т. к. он является строго направленным.
Пусть векторы s\, ..., sp описывают все множество дуг графа. Предположим, что для некоторого вектора q выполняются условия (sj, q) > О для всех /. Эти условия позволяют выбрать различные целочисленные системы векторов g\, ..., gn такие, что будут справедливы неравенства (лу, gj) > О для всех i,j. Подобных систем существует много, и среди них можно выбрать наиболее подходящую. Например, систему векторов g\, ...,g„ всегда можно выбрать как базис пространства.
Для каждого / построим систему параллельных гиперплоскостей с направ­ляющим вектором gj. Согласно утверждению 8.12, расстояние между различ­ными гиперплоскостями, проходящими через целочисленные точки, не мо­жет быть сколь угодно малым. Поэтому, не ограничивая общности, можно считать, что ни одна из гиперплоскостей не проходит ни через одну цело­численную точку пространства. Построенная система гиперплоскостей рас­секает пространство на полуоткрытые параллелепипеды, грани которых перпендикулярны g\, ..., gn. Теперь построим новый граф. В качестве его вершин возьмем параллелепипеды вместе с попавшими в них вершинами исходного графа и частями его дуг. Отождествим вершины нового графа с узлами прямоугольной регулярной решетки. Будем считать, что две соседние вершины нового графа связаны дугой, если через грань, разделяющую два соответствующих параллелепипеда, проходит хотя бы одна дуга исходного графа. Все дуги исходного графа, пересекающие одну грань, образуют с ее направляющим вектором острые углы. Поэтому дуга нового графа определя­ется корректно. Ее направление возьмем в соответствии с направлением на­правляющего вектора грани. Построенный граф будет координатным регу­лярным графом.
Заметим, что аналогичные преобразования графа алгоритма мы рассматри­вали в § 7.2 в связи с исследованием возможности реализации алгоритма на вычислительной системе с многоуровневой памятью. Регулярные коорди­натные графы устроены настолько просто, что о них известно практически все. Рассмотрим, например, вопрос о выборе максимальной параллельной формы. Если не принимать во внимание частные особенности графов, то это сводится согласно утверждению 8.12, к следующей задаче. Пусть fy, ...,fr являются координатными векторами единичной длины. Тогда в кону­се (/J, q) > О, 1 < / < г нужно найти вектор q, максимизирующий величину
rf|q|E . Почти очевидно, что решением данной задачи является вектор
q = (1, ..., 1). Так же просто решаются и многие другие задачи.
480
Часть III. Смежные проблемы и применение
Исследуя графы алгоритмов, мы почти нигде не делали различия между от­дельными вершинами и дугами. Однако в действительности разным верши­нам могут соответствовать разные операции, а разные дуги могут означать пе­редачу информации разной природы. Это обстоятельство отчетливо видно на рассмотренном преобразовании графа. Объявляя параллелепипеды новыми вершинами, мы по существу вводим новые операции. Размеры параллелепи­педов могут быть разными, что влечет за собой разную мощность как опера­ций, так и передаваемой между ними информации. В некоторых случаях при­ходится учитывать различие содержаний, вкладываемых в вершины и дуги.
В интересах практического применения утверждения 8.6 необходимо для регулярных графов найти какие-нибудь простые множества вершин, гаран­тированно лежащие на одном пути. Естественно, возникает мысль о верши­нах, находящихся на прямой линии. Оказывается, что далеко не каждая прямая обладает нужным свойством.
Рассмотрим главный регулярный подграф бесконечного регулярного графа. Предположим, что базовые векторы /j, ..., fr линейно независимы. Возьмем любую прямую линию, лежащую в линейной оболочке базовых векторов. Обозначим через / направляющий вектор прямой. Без ограничения общно­сти можно считать, что / >- 0. Если / -< 0, то вместо / берем вектор —/. До­пустим, что на прямой имеются две вершины u, v. Пусть для определенно­сти и < v. Так как мы хотим, чтобы эти вершины лежали на одном пути, то должно выполняться равенство
v - и = оц/i + ... + arfr,
где все числа oq, ..., ar целые неотрицательные. С другой стороны, справед­ливо соотношение
v - и = а/,
где а > 0 в силу условий и -< v, I >- 0. Вектор /, как вектор линейной обо­лочки векторов f]_, ...,fr, раскладывается по этим векторам как по базису, т. е.
/=Pl/l + ... + Рг/л
Следовательно, всегда должно выполняться равенство
оц/i + ... + arfr= a(Pi/i + ... + pr/r).
Из независимости векторов /j, ..., fr следует, что
cci = aPi, ..., ar = apr.
Поэтому числа oq, ..., ar и pl5 ..., pr могут быть неотрицательными только одновременно. Таким образом, имеет место
Утверждение 8.13
Пусть в линейной оболочке базовых векторов /ь ...,fr главного регулярного подграфа задана прямая линия с направляющим вектором /. Предположим, что
Глава 8. Вычислительные системы и алгоритмы
481
базовые векторы линейно независимы и на прямой находится хотя бы одна вершина. Если в разложении вектора /по векторам/ь ...,fr все ненулевые ко­эффициенты разложения имеют одинаковые знаки, то на такой прямой все вершины подграфа связаны одним путем.
Отметим в заключение, что графы на рис. 4.3, 4.6, 7.5 регулярные. Граф на рис. 4.4, а не регулярный, но направленный. В качестве направляющего можно взять вектор, например, с координатами 1,1.
Вопросы и задания
1.   Приведите примеры алгоритмов, графы которых регулярные.
2.   Пусть все дуги графа, рассматриваемые как векторы, имеют неотрицательные координаты. Докажите, что граф является направленным и в качестве направ­ляющего может быть взят любой вектор с положительными координатами.
3.   Предположим, что вершины графов расположены в точках я-мерного арифме­тического пространства с целочисленными координатами и все дуги имеют не­отрицательные координаты. Рассмотрим для графов линейные развертки. Дока­жите, что гиперплоскости с направляющим вектором q= (1, 1, ..., 1) являются поверхностями уровней строгой развертки для любого из рассматриваемых гра­фов и обеспечивают максимальное расстояние между соседними поверхностями уровней линейных разверток.
4.   Приведите пример, когда для графа из п. 3 существует строгая линейная раз­вертка с направляющим вектором, имеющим как минимум одну отрицательную координату.
5.   Обозначим через еь ..., еп координатные векторы я-мерного арифметического пространства. Выберем любой граф из п. 3 и добавим к нему любые дуги, на­правленные так же, как векторы еь ..., еп. Докажите, что любая линейная обоб­щенная (строгая) развертка исходного графа с неотрицательным (положитель­ным) направляющим вектором остается обобщенной (строгой) разверткой но­вого графа.
6.   Остается ли справедливым утверждение п. 5, если не требовать неотрицательно­сти (положительности) направляющего вектора развертки?
7.   Рассмотрите любой граф из п. 3. Любую его дугу можно представить в виде ли­нейной комбинации векторов еь ..., еп с неотрицательными целочисленными коэффициентами. Эти разложения называются укладкой графа по координатно­му базису. Предложите геометрическую интерпретацию укладки, позволяющую свести граф к координатному.
8.   *Попробуйте решить задание 10 из § 8.2 для координатного графа.
9.   Докажите, что проекция регулярного графа вдоль любой прямой на любую ги­перплоскость есть граф, изоморфный регулярному.
10.   Приведите примеры, когда проекция регулярного графа, не имеющего контуры, будет иметь контуры.
11.   Приведите условия, гарантирующие отсутствие контуров в проекции.
482
Часть III. Смежные проблемы и применение
12.   Пусть базовые векторы/ь ...,fr линейно независимы. Докажите, что с точно­стью до умножения на —1 любое ненулевое решение q системы линейных ал­гебраических уравнений
SiOb 4) = 82(4 q)= ...= 8,(/n q) является направляющим вектором графа, если только все числа 5Ь 82, ..., 5Г ве­щественные, ненулевые и имеют одинаковые знаки.
13.   Докажите, что вектор q из п. 12 образует одинаковые углы со всеми базовыми векторами, если
_II г ц~1 е _у г ц~1         е _|| г \\~l
i ~ IkiIIe ' 2 ~ 1к2Не ' •••' °r ~ ||//-|1е
§ 8.4. Математические модели систолических массивов
Уровень технологических возможностей микроэлектроники во многом оп­ределяет уровень развития вычислительной техники. Повышение плотности расположения элементов на кристалле, увеличение скорости их переключе­ния приводят не только к повышению быстродействия компьютеров и уменьшению их размеров, но и позволяют разрабатывать вычислительные системы с принципиально новыми архитектурными решениями. Достиже­ния микроэлектроники дают возможность уже в настоящее время создавать достаточно сложные сверхминиатюрные вычислительные устройства, распо­ложенные на одном кристалле. При массовом производстве такие устройст­ва оказываются, к тому же, относительно дешевыми. Все эти обстоятельства не только открывают новые перспективы конструирования вычислительных систем, но и порождают многочисленные новые проблемы, в том числе ма­тематические.
Одной из самых трудных является проблема создания коммуникационных сетей, обеспечивающих быстрые необходимые связи между отдельными функциональными устройствами. Пока скорости срабатывания устройств были не очень большими, основными факторами, препятствующими созда­нию нужных коммуникационных сетей, являлись число линий связи и сложность коммутаторов. Однако с ростом скорости срабатывания устройств наряду с этими факторами стали иметь большое значение также и длины линий связи.
В определенном отношении самой простой и эффективной является комму­никационная сеть, в которой нет коммутаторов, все устройства соединены непосредственно друг с другом и длины всех линий связи, а потому и вре­мена задержек при передаче по ним информации минимальные, в некото­ром смысле, нулевые. С конца семидесятых годов прошлого столетия стали появляться работы, активно призывающие строить специализированные вы­числительные системы, получившие название систолические массивы. Эти
Глава 8. Вычислительные системы и алгоритмы
483
системы устроены предельно просто и в то же время имеют предельно про­стую коммуникационную сеть. Поэтому они являются исключительно эф­фективными по быстродействию, но каждая из них ориентирована на реше­ние весьма узкого класса задач [54].
Общий подход к конструированию математических моделей систолических массивов основан на следующей идее. Пусть в нашем распоряжении имеется достаточно большое число функциональных устройств. Все ФУ являются простыми и срабатывают за одно и то же время. Математическое содержа­ние и техническая конструкция ФУ сейчас не имеют особого значения. Важно, чтобы выходная и входная информация устройств соответствовали друг другу. Если по каким-либо причинам информация не подается на вхо­ды ФУ, ничто не мешает считать, что она в этих случаях вырабатывается самими ФУ. Допустим, что конструктивно каждое ФУ выполнено в виде некоторого плоского правильного четырех- или шестиугольника. Пусть вхо­ды и выходы ФУ выведены на границу многогранников. Будем называть эти ФУ систолическими ячейками (процессорными элементами, элементарными процессорами, чипами и т. п.).
Теперь начнем складывать из ФУ-многоугольников различные фигуры, при­соединяя без наложения последовательно многоугольник за многоугольни­ком и предполагая, что соседние многоугольники соприкасаются друг с другом. В местах соприкосновения соединим входы и выходы соседних ФУ. В результате такого построения получится некоторая фигура, у которой ос­танутся свободными какие-то входы и какие-то выходы. Мы получим мо­дель специализированной вычислительной системы, если заставим всю со­вокупность собранных ФУ работать по правилам, описанным в §2.3, например, в синхронном режиме с одинаковым временным тактом. Подавая на свободные входы в том же синхронном режиме входные данные, будем получать какие-то результаты, начиная с некоторого момента, на свободных выходах. Созданная подобным образом модельная вычислительная система называется систолической системой.
Весьма заманчиво хотя бы теоретически рассмотреть общий случай систо­лической системы. Однако мы не будем это делать просто в силу того, что практическая необходимость пока не требует таких исследований. Будем считать, что при построении систолических систем используются только плоские ФУ-многоугольники. Они имеют одинаковые конфигурации и раз­меры, пристыковываются друг к другу без зазоров и все вместе находятся в одной плоскости, образуя некоторый плоский массив. Подобные систоличе­ские системы и называются систолическими массивами.
Прежде чем обсуждать различные аспекты конструирования и функциони­рования систолических массивов, рассмотрим простой пример. Это один из первых систолических массивов, построенных с момента возникновения самой идеи создания вычислительных систем такого типа. Предположим,
484
Часть III. Смежные проблемы и применение
что необходимо построить специализированную вычислительную систему, на которой достаточно быстро реализуется операция вычисления матрицы D = С + АВ, где
an an
О
*п Ъп Ъ\ъ
0"
в =
Ь2\ ъг1... ... ЪЪ1...
0
-
a2\ a2l
аЪ\ аЪ2
л 42
'11 42 Ч;
'14
'21 "-22
с
'31 "-32
с41
о
Здесь все матрицы — ленточные порядка п. Матрица А имеет одну диаго­наль выше и две диагонали ниже главной. Матрица В имеет одну диагональ ниже и две диагонали выше главной. Матрица С имеет по три диагонали выше и ниже главной.
Пусть в нашем распоряжении имеется достаточно большое число функцио­нальных устройств, выполняющих скалярную операцию с + ab и осуществ­ляющих одновременно передачу данных. Такие операции как раз и реали­зуются либо на четырех-, либо на шестиугольных систолических ячейках. Они представлены на рис. 8.5.
b а
aN
с
Sb
с —►
—► с
У ч
W
b а
ъ"
У
с
^
Рис. 8.5. Систолические ячейки с операцией с + ab
Здесь каждое ФУ имеет три входа a, Ь, с и три выхода а, Ь, с. Входные (in) и выходные (out) данные в общем случае связаны соотношениями
Lout
С|д "Г C
гиАш ^out = b-m, аоЫ = ain.                            (8.20)
Глава 8. Вычислительные системы и алгоритмы
485
Если в момент выполнения операции какие-то данные не поступают, то бу­дем считать, что они заменяются нулями. Точка указывает ориентацию сис­толической ячейки на плоскости.
На рис. 8.6 представлен систолический массив, реализующий указанную выше матричную операцию С + АВ. Здесь же указаны расположение и схема подачи входных данных.
Рис. 8.6. Систолический массив для перемножения ленточных матриц
При построении систолического массива использованы шестиугольные сис­толические ячейки. Сделаем по этому рисунку некоторые пояснения.
Вся плоскость покрыта регулярной косоугольной решеткой, образованной двумя семействами равноотстоящих параллельных прямых, пересекающихся под углами соответственно 60° и 120°. В узлах этой решетки размещены ФУ и входные данные. Система работает по тактам. За каждый такт все данные перемещаются в соседние узлы по направлениям, указанным стрелками.
486
Часть III. Смежные проблемы и применение
На рисунке показано состояние в некоторый момент времени. В следующий такт все данные переместятся на один узел и элементы a\\, Ь\\, с\\ окажутся в одном ФУ, находящемся на пересечении штриховых линий. Следователь­но, будет вычислено выражение сц + a\\b\\. В этот же такт, в частности, данные д]2 и Ь2\ приблизятся вплотную к ФУ, находящемуся в вершине сис­толического массива. В следующий такт все данные снова переместятся на один узел в направлении стрелок и в верхнем ФУ окажутся элементы «12, #21 и результат срабатывания ФУ, находящегося снизу, т. е. сц + оцйц. Следовательно, будет вычислено выражение сц + оц^п + an^2\- Это есть элемент d\ \ матрицы D.
Продолжая потактовое рассмотрение процесса, можно убедиться, что на вы­ходах ФУ, соответствующих верхней границе систолического массива, пе­риодически через три такта будут выдаваться элементы матрицы D. На каж­дом выходе будут появляться элементы одной и той же диагонали. Примерно через Ъп тактов будет закончено вычисление всей матрицы D. Загруженность каждой систолической ячейки асимптотически равна 1/3.
Трудно предположить, что после рассмотрения данного примера у читателя сложилось четкое представление в отношении возможностей систолических массивов как специализированных вычислительных систем. Скорее возник­ло много вопросов. В самом деле, этот пример показывает, что для данной задачи систолический массив может быть построен. Но почему он выглядит именно так? Существуют ли другие систолические массивы, позволяющие решать ту же задачу? С чем связана относительно низкая загруженность ФУ? Как разработать общую методологию отображения алгоритмов на сис­толические массивы? Такие вопросы можно задавать долго.
К настоящему времени построено много других систолических массивов для решения различных задач, в основном матрично-векторных. Тем не менее, мы не будем сейчас рассматривать новые примеры. Это связано с тем, что анализ конкретных систолических массивов не дает ответы на многие во­просы, касающиеся потенциальных возможностей вычислительных систем данного типа. На конкретных примерах легко объяснить, как заданный сис­толический массив решает заданную задачу, но почти невозможно понять, как построить систолический массив, чтобы на нем можно было решать нужную задачу.
Для разработки методологии построения систолических массивов исследуем сначала вид графов алгоритмов, реализуемых подобными вычислительными системами. Не будем пока принимать во внимание, что некоторые или даже все выходные данные ФУ могут совпадать с какими-то входными данными. Не важно и то, что теперь каждое ФУ имеет более одного выхода, а не один, как чаще всего предполагалось до сих пор. Вообще говоря, сейчас важнее не то, какие операции выполняют ФУ, а то, как они будут управляться и в ка­ком временном режиме смогут работать. Разрабатывая методологию по-
Глава 8. Вычислительные системы и алгоритмы
487
строения систолических массивов, мы будем стремиться как можно полнее перенести на эти вычислительные системы две важнейшие особенности граф-машины. Во-первых, возможность локального управления работой системы ФУ и, во-вторых, возможность реализации всего спектра времен­ных режимов, включая наискорейшие. Первую особенность мы сохраним безусловно и будем строить лишь такие систолические массивы, в которых систолические ячейки сами управляют своей работой и моментами срабаты­вания. Принимая во внимание вторую особенность, мы будем строить сис­толические массивы, реализующие алгоритм по возможности максимально быстро. Кроме этого, мы сохраним еще одну особенность, подмеченную в реально созданных систолических массивах. Именно, любая систолическая ячейка, начав работать в какой-то момент времени, продолжает срабатывать каждый такт или с постоянным периодом, пока не закончится весь процесс ее функционирования. Другими словами, в работе систолических ячеек не бывает как больших, так и неконтролируемых перерывов.
Очевидно, что плоскость можно покрыть без зазоров как четырех-, так и шестиугольниками одного размера. Если задан какой-то лежащий в плоско­сти систолический массив, то он вырезает из покрытия плоскости соответ­ствующую ему односвязную фигуру. Выберем в плоскости массива два се­мейства параллельных равноотстоящих прямых линий таким образом, чтобы центр любой систолической ячейки находился в точке пересечения двух прямых. Эти семейства выбираются однозначно в случае четырехугольников и неоднозначно в случае шестиугольников. На основе выбранных семейств построим аффинную систему координат. Пусть она такова, что центры всех систолических ячеек описываются двумерными векторами с целочисленны­ми координатами. Соседние ячейки всегда имеют равными одну из коорди­нат и различающуюся на 1 другую координату. Обозначим оси координат х, у. Теперь возьмем любую ось t, не лежащую в плоскости х, у, и установим на ней направление. При t=0, 1, 2, ... проведем плоскости, параллельные плоскости х, у. Предположим, что t = 0 соответствует плоскости систоличе­ского массива.
Будем считать t осью времени. Пусть систолический массив начинает рабо­тать при / = 0и продолжает функционировать с тактом, равным 1. Постро­им ориентированный граф. Вершинами являются точки трехмерного про­странства, в которых координаты х, у соответствуют систолической ячейке, срабатывающей в момент t. Дуги символизируют передачу информации от ячейки, которая произвела ее в момент t, к ячейке, которая получит ее в момент t + 1. Очевидно, что число различных дуг, рассматриваемых как век­торы в трехмерном пространстве, не превосходит числа различных по на­правлению передач информации от одной систолической ячейки к другой соседней в самом систолическом массиве. При сделанных предположениях координаты векторов-дуг по осям х, у равны 0, 1 или —1, а по оси t коорди-
488
Часть III. Смежные проблемы и применение
ната всегда равна 1. Вспоминая исследования, проведенные в § 8.3, заклю­чаем, что справедливо
Утверждение 8.14
Если ячейки систолического массива работают в синхронном режиме, то сис­толический массив реализует алгоритм, граф которого изоморфен подграфу бесконечного регулярного графа.
Таким образом, систолические массивы как специализированные вычисли­тельные системы могут реализовывать лишь вполне определенный класс алгоритмов. Это — регулярные алгоритмы. Реально используемые алгорит­мы часто являются регулярными изначально. Многие алгоритмы легко сво­дятся к регулярным. Иногда процесс сведения оказывается достаточно труд­ным. Конечно, имеются алгоритмы, не сводимые к регулярным без значительного ухудшения временных характеристик. Обо всем этом мы еще будем говорить позднее. Сейчас же только отметим одну особенность.
По-видимому, самой характерной чертой систолических массивов является отсутствие каких-либо дополнительных линий связи при соединении входов и выходов систолических ячеек. Соединение отдельных ячеек между собой лишь в местах соприкосновения приводит к тому, что в систолических масси­вах все связи имеют минимально возможные длины и, следовательно, оказы­ваются минимальными временные затраты на передачу информации от одних функциональных устройств к другим. Подразумевая именно эту особенность, говорят, что в систолических массивах реализуется принцип близкодейспгвия. Это, в свою очередь, приводит к тому, то в графах связей систолических мас­сивов дуги никогда не пересекаются. Точнее, дуги могут иметь общими только концевые вершины. Такие графы называются плоскими.
Как видно из рис. 8.5 и формул (8.20), систолические ячейки могут выпол­нять функции переносчика информации. Это очень важная их черта. Она помогает в вычислительных системах организовывать транспортные связи. Если некоторое данное должно использоваться во многих устройствах, то необходимо применение специальных способов его рассылки. Один из спо­собов реализует шинную связь. Рассылаемое данное поступает на шину и од­новременно принимается несколькими устройствами. Для систолических массивов такой способ неприемлем. В подобных вычислительных системах транспортные связи оказываются более подходящими. В этом случае данное поступает в ФУ, используется в нем как операнд и, не изменяясь, пересыла­ется по транспортной магистрали в соседнее ФУ, где используется, пересы­лается дальше и т. д.
При преобразовании графов алгоритмов многократная передача информа­ции через систолические ячейки означает замену "длинных" дуг графа пу­тем, составленным из "коротких" дуг. Данный путь не обязан быть прямо­линейным. Обратим особое внимание на то, что такая замена может
Глава 8. Вычислительные системы и алгоритмы
489
увеличить длину критического пути графа алгоритма и, следовательно, уве­личишь время наискорейшей реализации. Вводя транспортные связи, об этом следует помнить.
Теперь ключевыми моментами методологии создания систолических масси­вов становятся умение строить и исследовать графы алгоритмов, а также утверждения 8.6, 8.13, 8.14 [14]. Для регулярных графов операция гомо­морфной свертки может быть выполнена как операция однократного или даже многократного ортогонального проектирования. Выбор направления проектирования ограничен лишь требованием, чтобы последняя проекция представляла собой плоский граф. В целом при увеличении числа вершин, лежащих на прямых с выбранным направлением, повышается загруженность систолических ячеек. Если на каждой такой прямой все вершины связаны одним путем, то сохраняется весь спектр временных реализаций алгоритма. Во всех остальных случаях возможность наискорейших реализаций не га­рантируется.
с,
л »,
к
1
1 *!
') »'
Рис. 8.7. Одномерные систолические массивы
Рассмотрим следующий пример. Предположим, что граф алгоритма имеет вид, показанный на рис. 8.7. Подобные графы свойственны многим вычис­лительным методам линейной алгебры и математической физики. С одним из них мы встречались, например, в § 4.4 при рассмотрении алгоритма ре­шения систем линейных алгебраических уравнений с блочно-двухдиа-гональной матрицей. Этот граф регулярный. На горизонтальных или верти­кальных прямых, а также на прямых, параллельных прямой, проходящей через вершины с кружками, либо нет ни одной вершины, либо все вершины связаны одним путем. Проектируя граф вдоль таких прямых на перпендику­лярные им прямые линии, получаем различные схемы одномерных систоли­ческих массивов. Они тоже показаны на рис. 8.7. Согласно утвержде­нию 8.6, все систолические массивы имеют возможность реализовать весь
490
Часть III. Смежные проблемы и применение
спектр временных режимов, в том числе, наискорейшие. Тем не менее, они имеют разное число устройств и, следовательно, разную их загруженность. Схемы передач информации также различные. В двух из них осуществляется односторонняя передача, в одной — двухсторонняя. Наиболее простой пред­ставляется схема систолического массива, расположенная на рис. 8.7 справа. Однако учет дополнительных факторов, таких как подача к систолическим ячейкам входных данных алгоритма, может в некоторых случаях сделать бо­лее предпочтительными другие схемы.
В каждый такт работы систолического массива срабатывают какие-то его ячейки. Исходя из принципов построения подобных вычислительных сис­тем, можно утверждать, что соответствующие этим ячейкам операции не могут быть связаны между собой информационно. Следовательно, процесс функционирования систолического массива в действительности реализует некоторую строгую параллельную форму графа алгоритма. Ярусы парал­лельной формы описываются работающими одновременно систолическими ячейками, номера ярусов — временами срабатывания ячеек.
Напомним, что граф систолического массива мы получаем путем ортого­нального проектирования графа алгоритма вдоль прямой линии. Обозначим ее направляющий вектор через р. Если граф алгоритма регулярный, то, со­гласно утверждению 8.10, существует вектор q, который образует острые уг­лы со всеми базовыми векторами. Поэтому на любой гиперплоскости с на­правляющим вектором q не могут находиться вершины, связанные дугами. Другими словами, совокупность гиперплоскостей, проходящих через вер­шины графа алгоритма и имеющих в качестве направляющего вектор q, раз­бивает все вершины на ярусы строгой параллельной формы. Если существу­ет хотя бы одна дуга, параллельная вектору р, то векторы р и q не могут быть ортогональными.
Выберем подходящий вектор q и будем реализовывать на систолическом массиве соответствующую параллельную форму. Проведем через вершины графа алгоритма гиперплоскости с направляющим вектором q. Перенумеру­ем их подряд в направлении этого вектора. Пусть наименьший номер ги­перплоскости, содержащий хотя бы одну вершину, равен 1. Эти и только эти вершины определяют систолические ячейки, которые будут срабатывать в 1-й такт. Гиперплоскость с номером 2 определяет те и только те ячейки систолического массива, которые будут срабатывать во 2-й такт, и т. д. Тем самым фиксируется программа работы каждой систолической ячейки. В процессе работы систолического массива в его плоскости в соответствии с номерами гиперплоскостей распространяется фронт вычислений, показывая, как систолические ячейки вступают в процесс функционирования и как они его заканчивают. Каждая систолическая ячейка будет срабатывать не обяза­тельно каждый такт, но обязательно периодически. Период для всех ячеек один и тот же. Он на 1 больше числа гиперплоскостей, помещающихся ме­жду соседними точками на прямой с направляющим вектором р.
Глава 8. Вычислительные системы и алгоритмы
491
Мы уже рассматривали в §4.4 задачу вычисления произведения А = ВС двух квадратных матриц порядка п. На рис. 4.3, а для я = 2 показан граф алгоритма без учета ввода входных данных, т. е. элементов матриц В и С. На рис. 4.3, б эти данные показаны рядом с графом алгоритма. Видна их множественная рассылка. Это означает, что при построении систолического массива обязательно появится транспортная связь.
Построим модель систолического массива для более часто встречающейся задачи вычисления матрицы А + ВС, где А, В, С — заданные матрицы по­рядка п. Если элементы матрицы А + ВС находятся по обычным формулам, то для п = 3 граф алгоритма будет таким, как показано более жирными стрелками на рис. 8.8. Легко проверить, что один и тот же элемент матрицы В или С требуется для выполнения операций, соответствующих вершинам, лежащим на прямой, параллельной оси j или /. Элементы матрицы А требу­ются для выполнения лишь операций, соответствующих начальным верши­нам путей графа алгоритма. На рис. 8.8 линиями без стрелок обозначены будущие транспортные связи. Направление передачи информации определя­ется выбором порядка выполнения операций.
Рис. 8.8. Граф алгоритма и транспортные связи
Если спроектировать граф алгоритма на плоскость, перпендикулярную оси к, то проекция будет состоять только из изолированных вершин. В этом случае реализовать алгоритм за минимально возможное время можно лишь при рассылке начальных данных В, С с помощью шинных связей, парал­лельных осям у, /. Если для реализации операции А + ВС мы хотим постро­ить систолический массив, то должны фигуру на рис. 8.8 превратить в регу­лярный граф или, другими словами, установить направления передач информации. Пусть эти направления совпадают с направлениями осей i,j. Тем самым фигура на рис. 8.8 становится регулярным координатным гра­фом. Заметим, что при этом длина критического пути графа сразу увеличи­вается в три раза.
Спроектируем полученный граф на плоскости, перпендикулярные оси /, вектору с координатами (1, 1, 1) и оси у. Соответствующие схемы систоли­ческих массивов представлены на рис. 8.9, а—е. Сразу видны различия
492
Часть III. Смежные проблемы и применение
множеств систолических ячеек и конфигураций связи. Кроме этого, нетруд­но заметить общность схем на рис. 8.9, б и 8.6. Более жирными линиями на рис. 8.9 указаны проекции ребер куба, содержащего вершины графа. Бу­дем считать, что реализуется параллельная форма, определяемая гиперпло­скостями с направляющим вектором q = (1, 1, 1). В систолических массивах на рис. 8.9, а, 8.9, в любая систолическая ячейка, начиная с некоторого мо­мента, срабатывает каждый такт, на рис. 8.9, б — один раз в три такта. Штриховыми линиями отмечено распространение фронта вычислений. На рис. 8.9, а и 8.9, в фронт распространяется по диагонали квадрата от уг­ла, ближайшего к началу координат, к наиболее удаленному углу, на рис. 8.9, б от центра к границе. Во всех трех случаях функции систоличе­ских ячеек описываются, в основном, соотношениями (8.20) с переменой местами букв а и с.
Рис. 8.9. Систолические массивы для операции А + ВС
Разные проекции приводят не только к различиям в числе вершин и кон­фигураций связей, но и к различиям в организации процессов. Так, в про­екции на рис. 8.9, б имеется достаточное число связей для выполнения опе­раций (8.20) без использования какой-либо памяти. В проекциях на рис. 8.9, а, 8.9, в связей меньше. Поэтому здесь каждая систолическая ячейка должна иметь по одной ячейке памяти для хранения соответственно элементов скр btk матриц С, В. Это приводит, в свою очередь, к различиям в процессах загрузки и разгрузки систолических массивов.
Заметим, что ни один из систолических массивов не позволяет реализовать операцию А + ВС за минимально возможное для нее время. Данный факт является платой за использование транспортных связей и он не противоре­чит, например, утверждению 8.6. Справедливость утверждения 8.6 связана с предположением, что любое ФУ, осуществляющее все необходимые опера­ции после слияния вершин, может работать в тех же условиях передачи ин-
Глава 8. Вычислительные системы и алгоритмы
493
формации, что и в граф-машине. Но введение транспортных связей как раз и нарушает данные условия, что диктуется, в конечном счете, потребностью организации множественных рассылок.
Рассмотрим более содержательный пример 6.5 из § 6.8, описывающий про­цесс решения системы линейных алгебраических уравнений методом Жордана без выбора ведущего элемента. Ясно, что граф алгоритма должен состоять из графов опорных гнезд с метками 1—5 и связующих графов. Анализируя по­крывающие функции, видим, что графы гнезд 1—3, 5 пустые. В опорном гнезде 4 дуги имеются. В этом гнезде заведомо есть путь длиной я — 1. Следо­вательно, нельзя рассчитывать на то, что алгоритм можно реализовать быст­рее, чем за п параллельных шагов. Связи между гнездами таковы:
□  для гнезда 1 дуги заходят из гнезда 4 и выходят в гнездо 3;
□  для гнезда 2 дуги заходят из гнезда 4 и выходят в гнездо 4;
□  для гнезда 3 дуги заходят из гнезд 1,4 и выходят в гнездо 4;
□  для гнезда 4 дуги заходят из гнезд 2—5 и выходят в гнезда 1—4;
□  для гнезда 5 дуги заходят из гнезда 3 и выходят в гнездо 4.
Связи из гнезда 1 в гнездо 3, из гнезда 2 в гнездо 4, а также из гнезда 3 в гнездо 4 — множественные. Поэтому здесь придется организовывать транс­портные магистрали. Все это хорошо видно из явного представления по­крывающих функций графа алгоритма.
Суммируя сказанное, разместим вершины опорных гнезд 1—3, 5 вокруг опорного гнезда 4. Область, занимаемая вершинами графа алгоритма, изо­бражена на рис. 8.10. Она представляет усеченную пирамиду, из которой исключено ребро 6. Вершины опорного гнезда 1 размещены на ребре 1, гнезда 2 — на грани 2, гнезда 3 — на грани 3, гнезда 5 — на грани 5, верши­ны опорного гнезда 4 размещены в остальной части пирамиды.
(п, л+1, п+1) (п, п, п+1)
-5
(1, п+1, п+1) (1, 1, 1) -------770. 1,4+1)
Рис. 8.10. Расположение вершин метода Жордана
Основной объем вычислений приходится на оператор 4, который является телом тройного цикла. Неоднородности вычислений, связанные с операто-
494
Часть III. Смежные проблемы и применение
рами 1—3, 5 локализованы на границе. Структура связей в графе алгоритма вместе с транспортными связями показана на рис. 8.11. В каждую вершину графа входят и выходят дуги, определяемые векторами (1, 0, — 1), (О, 1, 0), (0, 0, 1) в системе координат (k,j, i). В вершинах, лежащих на гра­нице пирамиды рис. 8.10 отсутствуют дуги, связанные с внешним простран­ством. Транспортные магистрали для рассылки элементов Uj расположены на прямых, параллельных оси /. Они начинаются на грани 3 и заканчивают­ся на грани 5, проходя через соответствующие точки пирамиды. Транспорт­ные магистрали для рассылки элементов 1р начинаются на грани 2 и ребре 1 и проходят через соответствующие точки пирамиды на прямых, параллель­ных оси j. При этом мы полагаем, что р совпадает с /. Входные данные, т. е. элементы матрицы А, поступают через нижнюю грань.
(/с+1,у+1,/-2)
(/с+1,у+1, /+1)
(/с+1,у-1,/-2)
(Аг, у-1, /-2)
(/с,у-1,/+1)
Рис. 8.11. Граф метода Жордана
Граф алгоритма с учетом введенных транспортных связей легко сводится к координатному с помощью линейного преобразования
k'=k,j' = j, i'= i + к- 1.
Область, занимаемая графом в пространстве (k',j", /') изображена на рис. 8.12. Соответствие между операторами и вершинами координатного графа остает­ся аналогичным рассмотренному выше. Все неоднородности вычислений в новом графе также локализованы на границе. Параллельная форма мини­мальной высоты снова задается вектором q = (1, 1, 1).
ч.
1
S
(п. п+1. 2п) /7 (л, п, 2л)
^(1, п+1, п+1) п+1)
(1, 1, 1)
(1,1,
Рис. 8.12. Новое расположение вершин
Глава 8. Вычислительные системы и алгоритмы
495
Рассмотрим две проекции координатного графа для л = 4. Ортогональ­ная проекция по направлению (1, 1, 1) представлена на рис. 8.13. Введем в плоскости проектирования новые координаты £ = / — к, ц = j — к. В этих координатах ребро 1 перешло в точку (0, 0), грань 2 — в линию (1, 0)— (п - 1, 0), грань 3 — в линию (0, 1)—(0, п), грань 5 — в линию (л, 1)—(я, л). При таком проектировании все неоднородности снова остались на границе.
Рис. 8.13. Схема систолического массива
Функции систолических ячеек определяются содержанием операций, кото­рым они соответствуют, а также необходимостью пересылки элементов uj, I, и входных данных. Ячейки осуществляют полностью или частично опера­ции типа (8.20). Именно,
□  ячейка (0, 0): /out = l/a-m, oOHt = ain;
□  ячейки (1, 0)—(л — 1, 0): /out = — «in;
□  ячейки (0, l)-(0, n): uont = ain/in, /out = /in;
□  ячейки (n, 1)—(л, n): aoul = % + um;
П остальные ячейки: aoul = ain + win/in, wout = win, /out = /in.
Всю совокупность ячеек можно свести к устройствам двух типов. Функции ячейки (0, 0) таковы:
^out ^in> "out V^in> '-out Qm Qn !■•
Остальные ячейки можно взять с функциями
Q>ut С'ш ^in"in> "out "im ^out ^in-
Предполагается, что недостающие операнды всегда доопределяются нулями, а на вход Ьш ячейки (л, 1) всегда подается — 1.
496
Часть III. Смежные проблемы и применение
Схема систолического массива и порядок подачи данных и выдачи результа­тов для п = 4 приведены на рис. 8.14. Общее время решения системы по­рядка п приблизительно равно 5п. Каждая ячейка срабатывает один раз в три такта. При этом средняя загруженность ячеек систолического массива будет около 0,1.
Рис. 8.14. Ввод данных и получение результатов
Глава 8. Вычислительные системы и алгоритмы
497
Рассмотрим теперь ортогональную проекцию координатного графа алгорит­ма для п = 4 по направлению (0, 0, 1). Она представлена на рис. 8.15. В но­вых координатах Z, = j, г\ = к ребро 1 и грань 2 перешли в линию (1, 1)— (я, п), грани 3, 5 и внутренность пирамиды перешли на всю область, за ис­ключением этой линии. Каждая ячейка систолического массива теперь бу­дет срабатывать каждый такт после начала функционирования. Схема сис­толического массива, порядок подачи данных и выдачи результатов приведены на рис. 8.16. Результаты вычислений будут выдаваться из ячейки (п + 1, п) в последовательные такты, начиная с (Зя + 2)-го. Все ячейки мас­сива меняют выполняемые ими функции во время счета, хотя в целом они делают примерно то же, что и в случае первой проекции. Загруженность ячеек систолического массива приблизительно равна 0,25.
Рис. 8.15. Схема другого систолического массива
Эти примеры показывают, что на одиночной задаче загруженность систоли­ческих массивов может оказаться не очень высокой. Особенно заметно она уменьшается из-за достаточно длинных этапов загрузки и разгрузки массивов. На загруженность влияет также выбор прямой, вдоль которой осуществляет­ся проектирование графа алгоритма. Чем больше длина пути, связывающего соседние сливаемые вершины, тем реже срабатывает соответствующая сис­толическая ячейка и, следовательно, тем ниже ее загруженность. Опреде­ленную помощь в выборе прямой при проектировании оказывает утвержде­ние 8.12. Максимизируя величину djqj , мы в общем случае минимизируем
время реализации алгоритма. В частности, для проекции графа на рис. 8.12 вдоль вектора (1, 1, 1) эта величина равна У1/2, а вдоль вектора (0, 0, 1) она равна 1. Кроме этого, загруженность уменьшается из-за введения транс­портных связей. Загруженность можно повысить, решая одновременно не­сколько одинаковых задач, отличающихся только входными данными. Воз­можность такого режима гарантируется тем, что все систолические ячейки срабатывают с одним и тем же периодом. Систолические массивы без суще-
498
Часть III. Смежные проблемы и применение
ственной потери своих качеств могут успешно работать не только в общем синхронном, но и в асинхронном режиме. Управление процессом легко осуществить, например, с помощью тегирования, т. е. переноса от ячейки к ячейке управляющей информации.
х1
х2
хз
Х4
•I
•I
—>
•I
•I
—>
—>
•I
•I
->
Л
-►
Л
-►
•I
•I
—►
^
—>
^
—>
^
—>
•I
т т т т т
а11 ............
Э21 Э12 .........
а31 а22 а13
а41 а32 а23 а14
Э42 Э33 Э24 ^1
а43 а34 ^2
Э44 Й3
ь4
Рис. 8.16. Другое перемещение информации
Вопросы и задания
1.   Почему систолические ячейки делают в форме правильных четырех- или шести­угольников, а не, скажем, трех- или пятиугольников?
2.   Рассмотрим бесконечный регулярный граф и семейство прямых с направляющим вектором, принадлежащим линейной оболочке базовых векторов. Докажите, что на всех прямых, на которых имеется хотя бы одна вершина, находится бесконеч-
Глава 8. Вычислительные системы и алгоритмы
499
но много вершин, расстояние между соседними вершинами постоянно и не за­висит от расположения прямых.
3.   Докажите, что любой плоский граф делит плоскость на односвязные области, ограниченные дугами. Если граф конечный, то среди этих областей одна будет неограниченной, остальные — ограниченными.
4.   Пусть плоский односвязный граф имеет п вершин, m дуг и делит плоскость на / односвязных областей. Докажите формулу Эйлера, определяемую равенством п — - т + 1=2.
5.   Покажите, что трехмерный регулярный граф с базовыми векторами (1, 0, 0), (0, 1, 0), (1, 1, 1), (1, —1, 1) при ортогональном проектировании вдоль вектора (0, 0, 1) дает не плоский граф.
6.   Докажите, что для трехмерного координатного графа существует только 13 на­правлений, ортогональное проектирование вдоль которых дает плоские графы.
7.   "Попробуйте получить по возможности конструктивно проверяемое необходи­мое, достаточное или, лучше всего, необходимое и достаточное условие изо­морфное™ ориентированного ациклического графа регулярному графу.
8.   "Попробуйте получить то же самое для графов линейных алгоритмов.
9.   "Попробуйте разработать конструктивную методологию сведения графов линей­ных алгоритмов к регулярным.
§ 8.5. Математическая модель алгебраического вычислителя
Проведенные исследования позволяют высказать некоторые соображения о построении моделей вычислительных систем, специализированных для ре­шения классов задач. Мы рассмотрим в качестве примера одну из возмож­ных моделей для решения задач линейной алгебры с плотными матрицами.
Для задач данного класса характерным является исключительно большая их трудоемкость. В общем случае решение практически любой из них требует выполнения порядка я3 арифметических операций, где п есть параметр, оп­ределяющий порядок используемых матриц или размерность векторов. В настоящее время нередко возникает необходимость в решении задач ли­нейной алгебры с плотными матрицами порядка 103—105. По существу единственный путь быстрого их решения связан с применением специали­зированных систем с большим числом одновременно работающих функцио­нальных устройств.
Чтобы вычислительная система могла работать наиболее эффективно, не­достаточно лишь наличия в ней большого числа ФУ. Столь же важно иметь подходящие алгоритмы, которые позволили бы обеспечить необходимый уровень загруженности всех или, по крайней мере, большей части ФУ сис­темы. Для повышения общего быстродействия существенно так же, чтобы
500
Часть III. Смежные проблемы и применение
структура алгоритмов не требовала от вычислительной системы сложной коммуникационной сети.
Одним из самых эффективных типов вычислительных систем с многими ФУ являются систолические массивы. Весьма заманчиво сконструировать один или несколько массивов, с помощью которых можно решать различ­ные задачи линейной алгебры с плотными матрицами. На первый взгляд, эта задача не кажется такой уж нереальной, т. к. опыт создания систоличе­ских массивов, реализующих различные алгоритмы решения задач линей­ной алгебры, говорит о том, что в подобных массивах есть много общего. Более того, иногда один и тот же массив может решать различные задачи всего лишь после незначительного изменения функций систолических ячеек или способа подачи данных. Тем не менее, по-видимому нецелесообразно идти непосредственно по этому пути.
Число ячеек систолического массива, предназначенного для решения кон­кретной задачи линейной алгебры с матрицей порядка п, обычно имеет по­рядок п2. Хотя в теоретическом плане систолические массивы легко нара­щиваются, зависимость числа ячеек от п не дает возможности эффективно использовать все функциональные устройства при фиксированном п. К тому же, при больших п общее число систолических ячеек оказывается настолько значительным, что начинают возникать серьезные технические трудности. В частности, требуется очень большое число каналов связи с памятью для обеспечения режима максимального быстродействия. К сожалению, невоз­можно построить систолические массивы с фиксированными числами яче­ек, обеспечивающие решение сколько-нибудь сложной задачи линейной алгебры с матрицами произвольного порядка без многократного обращения к памяти для записи и считывания результатов промежуточных вычислений. Созданию унифицированных систолических массивов мешает и то обстоя­тельство, что массивы для разных задач все-таки отличаются друг от друга. Суммарный объем этих различий весьма велик и затрагивает не только функции систолических ячеек, но и коммуникационную сеть.
Модель специализированной вычислительной системы для решения задач линейной алгебры с плотными матрицами может быть предложена, если ориентироваться на использование клеточных или блочных методов [5, 13]. Основанием к этому предложению служат следующие факты:
□  разработаны клеточные аналоги для всех основных численных методов линейной алгебры;
□  основная часть вычислений в клеточных методах приходится на выпол­нение операций сложения и умножения матриц небольшого фиксиро­ванного размера;
□  клеточные методы являются экономичными с точки зрения числа и вре­мени обменов с памятью;
Глава 8. Вычислительные системы и алгоритмы
501
П для реализации матричных операций с матрицами небольшого фиксиро­ванного размера можно построить систолические массивы.
Детальный анализ различных клеточных методов показывает, что основная часть вычислений приходится в действительности на выполнение матрич­ных операций вида
s
D = A + YJBlCh                                               (8.21)
/ = /
где s либо равно 1, либо является достаточно большим числом. При этом в обоих случаях имеется много независимых операций вида (8.21). Во всех методах матрицы А, В/, Q квадратные и имеют такой же порядок, как клет­ки, на которые разбиваются входные, выходные и промежуточные данные. Остальные матричные операции, необходимые для реализации клеточных методов, так же разнообразны, как разнообразна вся совокупность числен­ных методов линейной алгебры. Однако общий объем приходящихся на них вычислений относительно невелик и все нестандартные операции, как пра­вило, относятся к матрицам размера одной клетки.
Если 5=1, то мы имеем уже рассмотренную задачу вычисления матрицы А + ВС. Наличие потока независимых задач этого типа позволяет загрузить систолический массив почти полностью. Если s является достаточно боль­шим числом, то ситуация новая и мы рассмотрим ее более подробно.
Пусть порядок всех матриц из (8.21) равен ш. Обозначим В= (В\, ..., Bs) и С = (C[,...,C'S). Матрицы В, С имеют размеры m х ms и очевидно, что
D = А + ВС. Поэтому в данном случае систолический массив должен реали-зовывать матричную операцию А + ВС для прямоугольных матриц В, С и квадратной матрицы А. При этом число столбцов (строк) матрицы В (С) кратно числу ее строк (столбцов). Коэффициент кратности может быть про­извольным, в том числе достаточно большим.
Предположим, что матричная функция А + ВС вычисляется стандартным способом, и пусть s фиксировано. В этом случае граф алгоритма аналогичен представленному на рис. 8.8, но увеличенному по оси к в s раз. Вершины графа заполняют прямоугольный параллелепипед, содержащий по m вершин вдоль осей /, j и ms вершин вдоль оси к. После введения транспортных свя­зей граф оказывается координатным. Конечно, мы хотим построить систо­лический массив, размеры которого не зависит от числа s. Единственная проекция, которая удовлетворяет данному условию, есть проекция вдоль оси к. Граф систолического массива будет выглядеть так же, как на рис. 8.9, а и 8.9, е.
В качестве направляющего вектора, определяющего реализуемую параллель­ную форму, опять можно взять q = (1, 1, 1). В целом новый систолический
502
Часть III. Смежные проблемы и применение
массив будет работать так же, как рассмотренный ранее для случая квадрат­ных матриц А, В, С, только примерно в s раз дольше.
Универсальный не очень быстрый
процессор ------------?------------
Небольшая быстрая память
-----------?-----------
Массовая память
Систолический массив А+ВС
Рис. 8.17. Схема алгебраического вычислителя
Общая схема вычислительной системы для решения задач линейной алгебры с плотными матрицами блочными методами показана на рис. 8.17. Систоли­ческий массив предназначен для быстрого выполнения потока операций вида (8.21) при фиксированном размере блоков. Быстрая память необходи­ма, в основном, для подачи данных на систолический массив и считывания результатов его работы. Универсальный процессор предназначен для выпол­нения всех остальных операций, кроме операций (8.21). Он необязательно должен обладать большим быстродействием. Вся его работа может осущест­вляться параллельно с работой систолического массива. Общий объем вы­полняемой им работы относительно невелик. Нетрудно вывести различные соотношения, связывающие общее время решения задачи со скоростью вы­полнения операций универсальным процессором, числом ячеек систоличе­ского массива, скоростью их срабатывания, размером быстрой памяти и числом каналов связи с массовой памятью.
Мы не будем подробно обсуждать модель данной вычислительной системы, т. к. это обсуждение зависит от многих конкретных деталей. Заметим толь­ко, что при подходящем согласовании всех ее узлов на ней можно решать задачи со скоростью, пропорциональной скорости реализации матричной операции А + ВС на систолическом массиве. Если по каким-либо причинам сдерживающим фактором является время выполнения нестандартных опе­раций на универсальном процессоре, то для повышения общего быстродей­ствия в его состав также можно ввести систолические массивы, но уже существенно меньших размеров. Конечно, для быстрого выполнения мат-
Глава 8. Вычислительные системы и алгоритмы
503
ричной операции А + ВС можно использовать и другие систолические мас­сивы, а также принципиально другие устройства, если таковые будут пред­ложены.
Вопросы и задания
1.   Промоделируйте на рассмотренном алгебраическом вычислителе процессы ре­шения различных алгебраических задач блочными методами.
2.   Постройте модель алгебраического вычислителя для реализации "блочных" ана­логов методов, использующих преобразования вращения или отражения.
3.   Постройте модель вычислителя для решения нестационарных линейных уравне­ний математической физики с постоянными коэффициентами явными разност­ными методами.
4.   Какие различия между систолическими массивами, используемыми в вычислите­лях пп. 1—3?
5.   Выведите соотношения, связывающие число ячеек систолического массива, ско­рость выполнения операций универсальным процессором, скорость выполнения операций систолическими ячейками и скорость обмена информацией с массовой памятью в вычислителях пп. 1—3.
6.   *Болыпинство методов, подходящих для реализации на рассматриваемых вычис­лителях, имеют достаточно регулярную структуру. Нельзя ли использовать этот факт для разработки специальной структуры массовой памяти, в том числе, на уровнях иерархии?
7.   *Каковы узкие места в рассматриваемых вычислителях, мешающие достижению максимально возможной скорости решения задач?
§ 8.6. Матрицы и структура алгоритмов
Для достижения различных целей в изучении алгоритмов используются раз­личные формы их описания. Записи в форме программ удобны для осуще­ствления процесса реализации. Общие графовые формы предпочтительны для выбора подходящей архитектуры вычислительной системы. Функцио­нальная форма представления графов позволяет эффективно проводить ис­следование информационной структуры. Мы уже неоднократно убеждались в том, что все эти формы тесно связаны между собой, выгодно оттеняя от­дельные стороны либо самих алгоритмов, либо процесса их изучения. Мат­ричные формы также находят свою сферу применения.
Рассмотрим любой алгоритм, граф которого не меняется при изменении входных данных. Как уже неоднократно отмечалось, большинство вычисли­тельных алгоритмов или их основных фрагментов обладает подобным свой­ством. Поэтому все реализации такого алгоритма можно описать последова­тельностями операций, порядок выполнения которых также не меняется
504
Часть III. Смежные проблемы и применение
при изменении входных данных. При подходящем соответствии между опе­рациями и вершинами графа алгоритма все последовательности будут опре­делять исходный граф. Следовательно, не уменьшая существенно общности, можно на каком-то этапе исследований ограничиться рассмотрением лишь одной последовательности операций.
Будем считать, что алгоритм описывается последовательностью операций над числами. Обозначим через иу, ..., up его входные данные. Пусть процесс реализации алгоритма заключается в выполнении действий
uk = Fkiuk^ ■■■■> uksl), Р <k<n; к{, ...,kSt < к,                      (8-22)
где все Fk являются достаточно гладкими функциями своих аргументов. В качестве результатов реализации алгоритма не может рассматриваться что-либо иное, кроме какой-то совокупности величин %. Не накладывая серь­езных ограничений, можно предполагать, что результат состоит только из величины и„. Это означает, что рассматриваются лишь алгоритмы, которые описывают процессы вычисления значений числовых функций в точках. Значительная широта такого класса алгоритмов очевидна.
Итак, будем предполагать, что рекуррентные соотношения (8.22) задают процесс вычисления значений некоторой функции
v = F(uh ..., up),                                               (8.23)
зависящей от р переменных u\, ..., up. В различных теоретических исследова­ниях и практической деятельности кроме вычисления значений функций F в точках требуются многие другие сведения, касающиеся функции F. характери­стики процесса распространения ошибок округления, значения отдельных производных и градиента в точках и т. п. Все эти сведения легко получаются лишь в случаях, когда имеется явное представление функции F через пере­менные щ, ..., Up, и само представление является достаточно простым. Если процесс вычисления функции (8.23) задан алгоритмом (8.22), то при больших п получить явное представление самой функции через входные данные удается очень редко. В этом случае не остается ничего другого, кроме исследования функции через определяющие ее рекуррентные соотношения.
Соотношения (8.22) можно рассматривать как систему уравнений для опре­деления величин %. Запишем эту систему в следующем виде:
Fk(ukl>--->ukSk)-uk=0' P<k<n; k{,...,ks<k.                     (8.24)
В общем случае она является нелинейной. При исследовании процес­са (8.22) или, что то же самое, множества решений системы (8.24) прихо­дится исследовать близкие решения. Они удовлетворяют такой системе:
Fk{uk + Auk ,..., ик + Аик ) - к + Auk) = О,
, ,                                                           (8-25)
р < к < n; kl,...,ks < к,
Глава 8. Вычислительные системы и алгоритмы
505
где все вариации Аик малые в каком-то смысле. Вычитая из уравнений (8.25) уравнения (8.24), получаем с точностью до малых второго порядка линей­ную систему для вариаций Аик:
&dFk(uk,...,uk )
> -------------------'-*— Au/, - Aut = О,
1                  "uki
p < к < n; ky,..., ks < k.
(8.26)
Матрица *F линейной системы (8.26) есть функциональная матрица Якоби функций Fk(uk,...,uk ) — uk по переменным и\, ..., un. Так как число
1                         sk
функциональных соотношений в (8.24), (8.25) равно п — р, а число пере­менных равно п, то матрица системы (8.26) имеет размеры (п — р) х п. Она, как правило, сильно разреженная в силу того, что каждая из функций Fk зависит обычно лишь от небольшого числа переменных. Матрица *Р имеет полный ранг. Это видно хотя бы из того, что она является левой треуголь­ной относительно диагонали, проходящей через правый нижний угол мат­рицы. При этом на диагонали стоят элементы, равные — 1. Будем называть матрицу ¥ вариационной матрицей алгоритма (8.22). Естественно, что ее элементы в общем случае зависят от переменных и\, ..., ип- \. В целом они таковы:
■1.
если j = i + р; , если j есть одно из чисел (/ + р)у,...,(/ + p)s
(8.27)
dF
i + р
¥»
dUj
О — в остальных случаях.
Как мы увидим в дальнейшем, именно вариационная матрица будет возни­кать в различных задачах, связанных с изучением алгоритма (8.22).
Вариационная матрица алгоритма тесно связана с графом алгоритма. Сопос­тавим к-к вершине графа получение величины ик. Ясно, что первые р вер­шин будут символизировать ввод начальных данных щ, ..., ир, а остальные вершины — вычисление величин ик как значений функций Fk из (8.22). Бу­дем считать, что дуга идет из /-Й вершины ву'-ю в том и только в том случае, когда при вычислении величины uj величина щ используется в качестве ар­гумента. В соответствии с записью (8.22) дуги не будут входить в к-ю вер­шину, если к < р. Если же к > р, то в к-ю вершину будут входить дуги из вершин с номерами к\, ..., ks.
Теперь опишем полученный граф некоторой матрицей Ф размеров (п с элементами фу.
■ р) х п
506
Часть III. Смежные проблемы и применение
Положим
— 1, если j = i + р; <?ц=\ 1, если j есть одно из чисел (/+ р){,...,(/ + p)s ',            (о.la)
О — в остальных случаях.
Легко видеть, что к-& столбец матрицы Ф соответствует величине %, а к-я строка — величине ик + р. В к-к строке элемент —1 стоит в том столбце, но­мер которого соответствует номеру вычисляемой величины и^ + р. Элементы + 1 стоят в тех столбцах, номера которых соответствуют номерам аргументов вычисляемой величины и^ + р. Матрица Ф описывает информационную связь величин ик между собой. По этой причине будем называть ее матри­цей информационной связности алгоритма (8.22). Очевидна связь матрицы информационной связности с вариационной матрицей. Первая получается из второй путем замены всех элементов, равных частным производным функции Fk, единицами. Поэтому структуры ненулевых элементов обеих матриц полностью совпадают.
Из матрицы информационной связности алгоритма можно получить целое семейство матриц, относящихся по существу к тому же графу алгоритма. Для этого заменим все ее ненулевые элементы какими-нибудь числами. Лю­бую такую матрицу будем называть взвешенной матрицей информационной связности алгоритма. Вариационная матрица алгоритма есть одна из подоб­ных матриц. Здесь мы будем иметь дело лишь с этой матрицей. Все подоб­ные матрицы позволяют однозначно восстановить граф алгоритма. Поэтому структура расположения их нетривиальных элементов отражает в действи­тельности структуру алгоритма.
Мы уже неоднократно встречались с таким фактом, что с помощью подхо­дящей перенумерации вершин графа алгоритма можно упростить описание самого графа. Следовательно, можно попытаться за счет перенумерации операций в алгоритме (8.22) привести матрицу информационной связности алгоритма и, соответственно, его вариационную матрицу к какому-нибудь более удобному виду. Рассмотрим любую параллельную форму алгорит­ма (8.22). Перенумеруем все его операции по ярусам: сначала операции пер­вого яруса, затем второго и т. п. В этой нумерации любая взвешенная мат­рица информационной связности будет блочной левой почти треугольной. Все блоки над главной диагональю суть единичные матрицы со знаком ми­нус. Их порядки равны ширине соответствующих ярусов параллельной формы. Первый блочный столбец и только он относится к операциям, сим­волизирующим ввод начальных данных.
Матрицы традиционно используются для описания графов. Пусть задан ориентированный граф G= (V, Е) без петель и кратных дуг. Предположим,
Глава 8. Вычислительные системы и алгоритмы
507
что он имеет п вершин и m дуг. Квадратная матрица В порядка п с элемен­тами by называется матрицей смежностей графа, если
[1, если из /-и вершины в у-ю идет дуга;
Ь„= ,
[О — в остальных случаях.
Отметим, что в матрице смежностей главная диагональ нулевая. Нулевыми являются также строки, соответствующие выходным вершинам, и столбцы, соответствующие входным вершинам. Матрица смежностей графа алгоритма тесно связана с его матрицей информационной связности. Именно, в нуме­рации вершин, согласованной с записью (8.22), матрица информационной связности Ф есть подматрица, состоящая из последних п — р строк матрицы В'— Е. Здесь В— матрица смежностей графа алгоритма, Е— единичная матрица, знак "штрих" означает транспонирование.
Прямоугольная матрица D размеров п х т с элементами dy называется матрицей инциденций графа, если
1, если у-я дуга выходит из /-и вершин; — 1, если /-я дуга заходит в i-ю вершин; О — в остальных случаях.
В каждом столбце матрицы инциденций графа лишь два элемента отличны от нуля. Отсутствие петель говорит о том, что они равны —1 и 1. Так как граф по предположению не имеет кратных дуг, то матрица инциденций не имеет одинаковых столбцов.
Некоторые факты очень легко формулируются в матричных терминах. На­пример, почти очевидно следующее
Утверждение 8.15
Для того чтобы ориентированный граф без петель и кратных дуг не имел конту­ры, необходимо и достаточно, чтобы с помощью перенумерации вершин графа матрицу смежностей можно было сделать правой треугольной.
Предположим теперь, что алгоритм с матрицей D его графа реализуется на некоторой вычислительной системе. Будем считать, что относительно усло­вий реализаций выполняются требования, высказанные в §§ 2.3, 8.2. Снова рассмотрим вектор t= (Ц, ..., t„) развертки. Рассмотрим также вектор задер­жек w. Поскольку времена выполнения операций всегда можно включить во времена задержек, то без ограничения общности допустим, что вектор реа­лизации нулевой.
Имеет место
Утверждение 8.16
Пусть задан вектор задержек w, вектор реализации является нулевым и D есть матрица инциденций графа алгоритма. Для того чтобы вектор t был вектором
508
Часть III. Смежные проблемы и применение
развертки, соответствующей вектору задержек w, необходимо и достаточно вы­полнение неравенства
-D' t > w.
Пусть задан вектор задержек w. Выберем какую-нибудь последовательность вершин графа, образующую цикл. В такой последовательности связаны ду­гой любые две соседние вершины, а также первая и последняя вершины. Установим в цикле направление обхода вершин. Будем считать, что дуга проходится в положительном направлении, если сначала встречается ее на­чальная вершина, а затем конечная. В противном случае будем считать, что дуга проходится в отрицательном направлении. Пусть при обходе цикла ij-я дуга проходится Гу раз в положительном направлении и by раз в отрицатель­ном. Сумму величин (гу — by)wy по всем /, j, соответствующим дугам цикла, будем называть ориентированной задержкой цикла. Цикл, у которого ориен­тированная задержка равна нулю, будем называть уравновешенным.
Утверждение 8.17
Все циклы графа алгоритма уравновешены или граф не имеет циклы тогда и только тогда, когда совместна система линейных алгебраических уравнений
-D' t > w.                                                         (8.29)
Предположим, что система (8.29) совместна и вектор t задает ее решение. Выберем в графе алгоритма любой цикл, если он имеется, и установим на­правление обхода вершин. Каждому проходу у-й дуги в положительном на­правлении поставим в соответствие равенство tj — Ц = wy, каждому проходу той же дуги в отрицательном направлении поставим в соответствие равенст­во Ц — tj= —wy. Просуммируем все равенства согласно обходу дуг цикла. В правой части суммы будет стоять ориентированная задержка цикла. Левая же часть суммы будет равна нулю. Действительно, при каждом проходе со­седних дуг цикла инцидентная им вершина учитывается дважды. Соответст­вующая этой вершине величина tj всегда будет встречаться в двух соседних или последнем и первом уравнениях также дважды: сначала со знаком "плюс", а затем со знаком "минус". Следовательно, при суммировании каж­дая из величин t'j сократится.
Допустим теперь, что граф алгоритма либо не имеет циклы, либо все его цик­лы уравновешены. Не ограничивая общности, можно считать граф алгоритма односвязным. Возьмем, например, первую вершину и припишем t\ какое-нибудь значение. Предположим, что построен односвязный подграф, и его вершинам приписаны такие значения th что уравнения (8.29) выполняются для всех дуг подграфа. Добавим новую дугу, у которой хотя бы одна вершина принадлежит построенному подграфу. Если вторая вершина не принадлежит подграфу, то соответствующее ей значение ц однозначно определяется из того уравнения (8.29), которое можно написать для добавленной дуги.
Глава 8. Вычислительные системы и алгоритмы
509
Пусть вторая вершина тоже принадлежит подграфу. В силу сделанного предположения об односвязности подграфа это означает, что добавленная дуга образует с некоторыми дугами подграфа какие-то циклы. Выберем лю­бой из таких циклов и установим в нем направление обхода, при котором добавленная дуга проходится в положительном направлении. Пусть началь­ная вершина добавленной дуги имеет номер г, конечная — номер /. Соот­ветствующие значения tr и t/ определены раньше. Обойдем все дуги цикла, начиная с 1-й вершины и кончая r-й вершиной. Снова поставим в соответ­ствие каждому проходу ij-й дуги в положительном направлении равенство tj — tj = Wy, в отрицательном направлении — равенство Ц — tj = —Wy. Про­суммируем эти равенства. Согласно сделанному выше замечанию, сумма левых частей даст выражение tr — Ц. Сумма правых частей, принимая во внимание уравновешенность цикла, даст — wrt. Следовательно, уравне­ние (8.29) для г/-й дуги уже выполняется.
Продолжая процесс присоединения новых дуг, припишем в конце концов всем вершинам графа алгоритма такие значения ц, что будут выполняться все уравнения (8.29). Тем самым получено какое-то решение систе­мы (8.29), т. е. установлена совместность этой системы.
Следствие
Если граф алгоритма односвязный и система (8.29) совместна, то множество ее решений представляет прямую линию с направляющим вектором
(1, 1, ...,!)■
Данное следствие вытекает из того, что решение системы (8.29) в случае од­носвязности графа однозначно определяется при фиксации значения любой из его координат ц и остается решением при добавлении вектора (1, 1, ..., 1). Довольно часто граф алгоритма является уравновешенным с вектором задержек w = е, где все координаты вектора е равны 1.
Утверждение 8.18
Ациклический ориентированный граф без петель и кратных дуг с матрицей смежностей В не имеет не уравновешенные по вектору е циклы тогда и только тогда, когда существует матрица перестановок Р такая, что матрица Р'ВР яв­ляется блочной правой двухдиагональной с нулевыми квадратными диаго­нальными блоками.
Будем считать граф графом некоторого алгоритма. Рассмотрим решение системы (8.29). Вектор t можно трактовать как вектор развертки при h = 0. Сопоставим ему параллельную форму графа, объединяя в ярус те вершины, у которых совпадают времена выполнения соответствующих операций. Каж­дая дуга связывает вершины, расположенные только на соседних ярусах, т. к. соответствующие им времена различаются на 1. Выбор матрицы Р оп­ределяется только нумерацией вершин. Если вершины графа занумеровать
510                                                                    Часть III. Смежные проблемы и применение
по ярусам, то матрица смежностей будет иметь указанный в утверждении вид. Но уж если матрица смежностей имеет такой вид, то у графа не могут существовать не уравновешенные по вектору е циклы.
Утверждения, касающиеся матрицы смежностей и матрицы инциденций, могут быть полезными при изучении матрицы информационной связности алгоритма и его вариационной матрицы. Они показывают, как нужно нуме­ровать операции алгоритма (8.22), чтобы исследование его структуры осуще­ствлялось более просто.
Вопросы и задания
1.   Может ли вариационная матрица алгоритма быть нулевой?
2.   Предположим, что все элементы вариационной матрицы алгоритма нулевые, кроме равных —1 элементов на диагонали. Что представляет собой алгоритм?
3.   Могут ли быть нулевыми вне диагональные элементы какой-либо одной строки вариационной матрицы алгоритма?
4.   Что представляет собой алгоритм, если его вариационная матрица не зависит от входных данных?
5.   Пусть алгоритм представляет последовательность операций сложения/вычи­тания двух чисел, умножения двух чисел и вычисления обратной величины числа. Докажите, что дополнительные операции, которые нужно выполнить при одновременной реализации алгоритма и нахождении его вариационной матрицы по сравнению с только реализацией алгоритма, составляют такое ко­личество умножений, сколько имеется в алгоритме операций вычисления об­ратной величины.
6.   Пусть для граф-машины все координаты вектора задержек равны 1, вектор реа­лизаций — нулевой, т. е. w = е, h = 0. Докажите, что для того, чтобы в режиме многократного срабатывания каждое ФУ могло быть асимптотически загружено полностью, необходимо и достаточно, чтобы граф был уравновешенным по данным векторам задержек.
7.   Предположим, что в граф-машине имеется цикл, при обходе которого число дуг, проходимых в положительном направлении, не совпадает с числом дуг, проходимых в отрицательном направлении. Может ли в такой граф-машине быть достигнута асимптотически полная загруженность всех ФУ в режиме мно­гократного срабатывания?
8.   Докажите, что координатный граф является уравновешенным по векторам
w = е, h = 0.
9.   Приведите пример регулярного графа, не уравновешенного по векторам
w = е, h = 0.
10. Что означают пп. 8, 9 в свете того, что любой регулярный граф можно свести к координатному?
Глава 8. Вычислительные системы и алгоритмы
511
§ 8.7. Новое применение сведений о структуре
Использование матриц в предыдущем параграфе связано с фактами, отно­сящимися к структуре алгоритмов. Раньше мы уже получили достаточно много подобных фактов с помощью других средств. Матричный аппарат не очень удобен для изучения собственно структуры алгоритмов, главным об­разом, из-за необходимости представлять любую информацию в "плоском" виде. Однако он оказался исключительно полезным инструментом для уста­новления связи структурных свойств алгоритмов с различными их матема­тическими свойствами. Покажем эту связь на нескольких примерах.
Восстановление линейного функционала
Рассмотрим более подробно частный случай алгоритма (8.22), где все функ­ции Fk линейные. Это значит, что процесс реализации алгоритма заключа­ется в выполнении действий
uk а**и**> Р<к^п'> кь...,к<к.                             (8.30)
; = 1
Будем считать, что все числа к известны к моменту начала осуществления
процесса (8.30). Очевидно, что данный процесс определяет некоторый спо­соб вычисления значений какой-то числовой функции (8.23), которая долж­на быть линейной относительно совокупности переменных иу, ..., up. Следо­вательно, она имеет вид:
р v = F(ux,...,up) = Y$jUj.                                       (8-31)
Конечно, непосредственное вычисление значений функции F по форму­ле (8.31) значительно эффективнее, чем реализация процесса (8.30). Однако коэффициенты Ру могут быть неизвестны. Именно поэтому может возник­нуть необходимость осуществлять вычисление значений функции /"из (8.31) косвенным образом через процесс (8.30). Естественно, возникает вопрос о том, как это сделать наиболее эффективно.
Однократная реализация процесса (8.30) требует выполнения операций ум­ножения и сложения/вычитания чисел, общее число которых равно
N = 2 ^.ч-(п-р).                                 (8.32)
к = р + 1
Однократное вычисление значений функции из (8.31) непосредственно че­рез коэффициенты ру требует выполнения операции умножения и сложе­ния/вычитания чисел, общее число которых равно
М=2р-1.                                             (8.33)
512
Часть III. Смежные проблемы и применение
Ясно, что при больших п имеем N » М. Если процесс (8.30) приходится осуществлять многократно при одних и тех же коэффициентах ак и разных
входных данных и\, ..., up, то с точки зрения уменьшения общего объема вычислительной работы может оказаться выгоднее сначала вычислить ко­эффициенты Ру, а затем уже многократно вычислять значение функ­ции (8.31) непосредственно через коэффициенты Ру. И снова возникает во­прос о том, как наиболее эффективно вычислить коэффициенты Ру.
Казалось бы ответ очевиден. Действительно, обратимся к процессу (8.30). Величина up + \ уже представлена в виде линейной комбинации величин щ, ..., Up. Предположим, что мы имеем аналогичные представления для всех величин Up + 1, ..., uk- \. Подставляя в (8.30) вместо всех ик их представле­ния в виде линейных комбинаций величин и\, ..., ир и приводя подобные члены, получим необходимое представление (8.31) как линейное представ­ление величины un. Если величины s^ ограничены, то для больших п оты­скание явного представления (8.31) потребует выполнения порядка
п
К = 2р 5%,                                                (8.34)
k = p + l
операций умножения и сложения/вычитания чисел. Это примерно в р раз больше, чем требуется для однократной реализации процесса (8.30). Срав­нивая (8.32)—(8.34), можно сделать вывод, что если при одних и тех же ко­эффициентах ак процесс (8.30) необходимо осуществлять более р раз, то
выгоднее сначала получить коэффициенты Ру, а затем вычислять значения линейной функции (8.31) непосредственно.
Однако оказывается, что этот очевидный вывод далеко не лучший. В дейст­вительности коэффициенты ру- можно вычислить с существенно меньшими затратами по числу операций.
Рекуррентные соотношения (8.30) можно рассматривать как систему линей­ных алгебраических уравнений
ч
2^cckuk —uk=Q, p<k<n; k\,...,ks <к                           (8.35)
относительно переменных ир + \, ..., un. Более того, в действительности необ­ходимо найти лишь одно переменное un. При этом переменные щ, ..., ир можно считать свободными. Отметим, что матрица системы (8.35) есть вариа­ционная матрица алгоритма (8.30). В данном случае она имеет такой вид:
— 1, если j = i + р; \|/;/ = -j oij, если j есть одно из чисел (/ + р\,...,(/ + р\. ',            (о.Jo)
0 — в остальных случая.
Глава 8. Вычислительные системы и алгоритмы
513
Теперь запишем систему (8.35) следующим образом:
Рх + Qy = 0.                                                 (8.37)
Здесь Р — прямоугольная матрица размеров (п — р) х р, составленная из первых р столбцов матрицы *F; Q — квадратная матрица порядка п — р, со­ставленная из последних п — р столбцов матрицы *Р; х — вектор-столбец размерностир из элементов щ, ..., ир; у — вектор-столбец размерности п — р из элементов up + h ..., un. Из (8.37) находим, что
y=-Q~xPx.                                       (8.38)
Согласно (8.36), матрица Q левая треугольная с диагональными элементами, равными —1. Поэтому она невырожденная, и представление (8.38) возмож­но. По условию задачи необходимо найти лишь последний элемент вектора-столбца у. Коэффициенты Ру из (8.31) составляют последнюю строку матри­цы — Q~lP. Следовательно, сложность вычисления коэффициентов Ру есть сложность вычисления последней строки матрицы — Q~]P.
Матрица Q левая треугольная. Известно, что для матрицы, обратной к левой треугольной, существует специальное мультипликативное представление [5]. Получить его можно, например, на основе применения к матрице Q метода исключения Гаусса с целью приведения самой матрицы к единичной. С уче­том конкретного вида матрицы Q представление матрицы — Q _1 будет таким:
-Q-i = R„-p-1Rn-p-2...Ri.                                  (8.39)
Здесь матрица Rt отличается от единичной матрицы только i-ш столбцом. Его наддиагональные элементы равны нулю, диагональный элемент ра­вен + 1, поддиагональные элементы равны соответствующим поддиагональ-ным элементам /-го столбца матрицы Q.
Будем получать последнюю строку матрицы — Q~], используя представле­ние (8.39). Последняя строка матрицы Rn-p-\ известна. Пусть известна последняя строка произведения Rn-р- 1 ... Rt. Тогда последняя строка про­изведения Rn -р - i ... RjRj- х будет равна произведению последней стро­ки матрицы R„ -р- i ... Rj на матрицу Ri- \. Принимая во внимание специ­альный вид матрицы Rj- j, в последней строке нужно пересчитать только (/— 1)-й элемент. Если в матрице Rj- i имеется 5г- i ненулевых поддиаго-нальных элементов, то для этого пересчета нужно выполнить 25г -1 1 опера­ций умножения и сложения/вычитания чисел. При подсчете числа операций учтен тот факт, что при любом / в последней строке матрицы Rn _ р _ j ... Rj первые / — 1 элементов обязательно будут равны нулю. Используя данный алгоритм, можно определить последнюю строку матрицы — Q~] за число операций умножения и сложения/вычитания, равное
п — р — 2
L' = 2 £б,-(и-/7-2)                                        (8.40)
r = i
514
Часть III. Смежные проблемы и применение
Предположим, что в 1-м столбце матрицы Р находится а/ ненулевых элемен­тов. Не ограничивая общности, можно считать, что а/ > 0 для всех /, т. к. в противном случае результат реализации алгоритма (8.30) не будет зависеть от какого-то входного данного. Ясно, что для получения последней строки матрицы — Q ~]Р необходимо выполнить дополнительно
р L" = 2^a,-p                                   (8.41)
операций умножения и сложения-вычитания чисел.
Суммируя затраты (8.40), (8.41), заключаем, что вычисление последней стро­ки матрицы — Q ~]Р или, что то же самое, вычисление коэффициентов ру из представления (8.31) можно осуществить за
п - р - 2               р
X 5<- + Х°1
L = L' + L'
(п-2)
операций умножения и сложения/вычитания. Но
и - р - 2               р                      п
ъп_р_х+ х 5/+ Хо/= Xs*'
/ = 1                 1 = 1            к = р + 1
так как выражения в левой и правой частях равенства означают число всех коэффициентов ак, участвующих в процессе (8.30) или, что то же самое, чис­ло всех нетривиальных элементов вариационной матрицы алгоритма. Так как 5„ - р - j равно либо 0, либо 1, то в действительности с учетом (8.32) имеем:
N-p<L<N-p + 2.                                     (8.42)
Таким образом, вычисление коэффициентов Ру по описанному алгоритму практически требует таких же вычислительных затрат, как и однократная реализация процесса (8.30). Этот алгоритм примерно в р раз более экономи­чен, чем очевидный алгоритм, соответствующий оценке (8.34). Поэтому, если при одних и тех же коэффициентах ак процесс (8.30) необходимо реа­лизовать более одного раза, то вместо многократной реализации данного процесса значительно выгоднее вычислить коэффициенты ру по описанному алгоритму и затем многократно вычислять значение линейной функции, используя представление (8.31). Если число р и число требуемых реализаций процесса (8.30) невелики по сравнению с п, то суммарные вычислительные затраты по числу операций будут незначительно отличаться от затрат одно­кратной реализации процесса (8.30).
Проведенным исследованиям можно дать следующую формулировку. Функ­ция (8.31) есть линейный функционал. Пусть он неизвестен. Допустим од­нако, что имеется возможность вычислять значения функционала с помо-
Глава 8. Вычислительные системы и алгоритмы                                                              515
щью некоторого линейного процесса. Этот процесс имеет вид (8.30). Спра­шивается, можно ли восстановить линейный функционал, и, если можно, то ценой каких вычислительных затрат? Традиционный способ восстановления линейного функционала имеет сложность /7-кратного вычисления значения функционала. Рассмотренный здесь способ говорит о том, что справедливо
Утверждение 8.19
Пусть значения линейного функционала вычисляются с помощью некоторого скалярного линейного алгоритма. Тогда линейный функционал может быть вос­становлен со сложностью, не превосходящей сложность однократного вычис­ления значения функционала по заданному алгоритму.
Подчеркнем, что при построении эффективного способа восстановления линейного функционала в значительной мере была учтена структура графа алгоритма, реализующего вычисления значений самого функционала, или, что то же самое, структура вариационной матрицы.
Вычисление градиента
Снова рассмотрим алгоритм (8.22) в общем виде. Одной из важнейших за­дач, связанных с исследованием функции F из (8.23), является вычисление ее градиента. Традиционный подход к решению этой задачи состоит в сле­дующем. Все величины ик являются неявными функциями входных данных. Продифференцировав рекуррентные соотношения (8.22) как неявные функ­ции, получим:
Ё д Fk(uki,...,uk ) ---------^---------— gradMyt;
, = i           дик<                                                         (8.43)
р <к <п; кь ..., ks <к;
gradukк, к = 1,2, ...,р.
Здесь е^ есть к-к единичный координатный вектор. Если процес­сы (8.22), (8.43) проводить параллельно, то вместе с вычислением функций Fk можно одновременно вычислять и все их частные производные. Вся ин­формация для этого имеется.
Градиенты величин ик являются векторами размерности р. Не обсуждая сей­час сложность реализации процесса (8.43) подробно, заметим лишь, что об­щее число операций над числами, которое необходимо выполнить для вы­числения градиента величины ип непосредственно по формулам (8.43), будет пропорционально р.
На первый взгляд в этом выводе нет ничего неожиданного. Вычисляемый объект есть вектор размерности р. Поэтому вполне естественно, что он оп­ределяется через рекуррентный процесс над векторами размерности р. От­сюда вроде бы очевидно, что общие затраты на вычисление градиента вели-
516
Часть III. Смежные проблемы и применение
чины un также должны быть пропорциональны р. В действительности делать этот вывод преждевременно.
Сложность вычисления величин и^ и частных производных функций F^ при больших п практически не зависит от р. Зависимость от р появилась лишь потому, что процесс (8.43) описан и рассмотрен как процесс над векторами размерности р. Однако процесс вычисления градиента величины и„ можно осуществлять иначе.
Будем считать, что процесс (8.22) проведен. Это означает, что вычислены все величины ик, и, следовательно, можно вычислить все частные производ­ные всех функций Fk, которые не зависят от градиентов величин ик. Обо­значим
dFk(uk,...,uk )
ak =------------------к—, vk=graduk                               (8.44)
о "*,
для всех возможных к, и к. Теперь процесс (8.43) будет выглядеть следую­щим образом:
ч vk =X04TV Р<кйп; к{,...,кч<к,                           (8.45)
r = i
где vi, ..., vp заданы. В нашем случае vi, ..., vp равны соответственно
в\, ..., вр.
С учетом обозначений (8.44) процесс (8.45) по существу совпадает с процес­сом (8.30). Если считать v\, ..., vp свободными переменными, то при заданных ocj. переменная vn есть линейная комбинация переменных vi, ..., v„, т. е.
'Л=£РЛ-                                             (8.46)
./=1
Коэффициенты ру- неизвестны и их необходимо определить. Так как правую часть равенства нужно вычислить при единичных координатных векторах Vj, то коэффициенты Ру будут координатами искомого градиента величины ип.
Таким образом, задача вычисления градиента функции по существу совпа­дает с рассмотренной ранее задачей восстановления линейного функциона­ла. Поэтому сложность вычисления градиента функции должна быть срав­нимой со сложностью реализации скалярного процесса типа (8.45).
Снова будем рассматривать рекуррентные соотношения (8.45) как систему линейных алгебраических уравнений
^oc^v*. -vk =0, p<k<n; ки...,кч,                              (8.47)
Глава 8. Вычислительные системы и алгоритмы
517
но теперь относительно векторных переменных vp + \, ..., v„ размерности р. И снова нужно найти лишь одно переменное v„. Векторные переменные V], ..., vp будем считать свободными. Матрица системы (8.47) есть кронеке-рово произведение х Ер. Здесь *Р есть вариационная матрица алгорит­ма (8.22). С учетом обозначений (8.44) она имеет вид (8.36). Ерединичная матрица порядка р. Запишем систему (8.47) следующим образом:
(PxEp)X+(QxEp)Y=0,                               (8.48)
где Р и Q с точностью до обозначений (8.44) являются теми же матрицами, что и в равенстве (8.37); X— вектор-столбец размерности р2, составленный из элементов векторов v\, ..., vp; Y — вектор-столбец размерности (п — р)р, составленный из элементов векторов vp+ \, ..., v„. Принимая во внимание свойства кронекерова произведения матриц, находим из (8.48), что
Y=((-Q-] Р)хЕр                                   (8.49)
В векторе /необходимо найти лишь последние р его координат. Как следу­ет из формулы (8.49), для этого достаточно знать последнюю строку матри­цы — Q ~]Р. Ее элементы и являются коэффициентами Ру из (8.48) или, что то же самое, координатами искомого градиента величины ип. Задача вычис­ления последней строки матрицы — Q~] Р была подробно рассмотрена вы­ше. Там же было показано, что эту задачу можно решить за число операций, по существу не зависящее от р.
Рассмотрим более подробно случай, когда функции Е^ реализуют одну из следующих операций: сложение/вычитание двух чисел, умножение двух чи­сел и вычисление обратной величины. Будем считать для простоты все эти операции эквивалентными по сложности реализации. Вычисление величины и„ требует выполнения п — р таких операций. Вычисление градиента этой величины согласно описанному алгоритму требует дополнительных затрат на вычисление нетривиальных элементов вариационной матрицы алгоритма и вычисление последней строки матрицы — Q ~1 Р.
Пусть Ек = щ + щ. В соответствии с обозначениями (8.44), (8.45) имеем
«*, %2 =±1, sk=2. Никаких затрат на вычисление коэффициентов к не требуется. Пусть
Ек = иК х uh . Тогда
akt = ик2 5 ак2 = икх 7 sk = 2-
Если процесс (8.22) реализован, то величины uk , ик известны, и никаких дополнительных затрат на вычисление коэффициентов ак не требуется.
Пусть теперь Ек = и~[.
518
Часть III. Смежные проблемы и применение
Имеем
f' т .
си-                  , sk - [.
Если процесс (8.22) реализован, то для вычисления коэффициента к. в дан­ном случае нужно выполнить только одну операцию умножения.
Итак, для всех рассмотренных операций при каждом к сумма числа коэф­фициентов к. и числа дополнительных операций, требуемых на вычисление
этих коэффициентов, равна 2. Если функции Fk реализуют операции сложе­ния/вычитания с константой или умножения на константу, то можно по­ступать по-разному: либо считать константы входными данными, либо рас­смотреть отдельно эти частные случаи. Однако во всех случаях, в том числе и в этих частных, сумма числа коэффициентов к и числа дополнительных
операций, требуемых на вычисление коэффициентов, при каждом к не пре­восходит 2.
Из соотношений (8.32), (8.42) вытекает, что для вычисления последней стро­ки матрицы — Q _1 Р требуется выполнить L операций, где
п
L<2 5>,-(-2).
к = р + 1
Кроме этого, согласно сделанному выше замечанию для вычисления эле­ментов вариационной матрицы алгоритма требуется дополнительно выпол­нить Sопераций,где
п к = р + 1
Следовательно, для вычисления градиента величины un требуется выполнить дополнительно не более, чем
п                                            п                                   п
L + S<2- £ Sk-(n-2)+ Y, (2-sk)= ^к+п-2р + 2
k=p+\                               k=p+\                       k=p+\
операций. И, наконец, если учесть, что для рассматриваемого множества операций sk < 2 для всех к, то будем иметь
L + S<3(n~ р) - р + 2.
Заметим, что для вычисления градиента во всех случаях предварительно нужно реализовать процесс (8.22), т. е. выполнить п — р операций.
Глава 8. Вычислительные системы и алгоритмы
519
Поэтому проведенные исследования говорят о том, что справедливо
Утверждение 8.20
Пусть значение функции в ^-мерной точке вычисляется как последовательность операций сложения/вычитания двух чисел, умножения двух чисел и взятия об­ратной величины числа. Тогда независимо от величины р одновременное вы­числение значения функции и ее градиента в одной и той же точке может быть осуществлено с затратами по числу операций, не превосходящими четырех­кратного вычисления значений функции.
Основной примечательностью полученного результата является независи­мость затрат на вычисление градиента от его размерности. К сожалению, рассмотренный алгоритм требует памяти довольно большого объема. В це­лом она пропорциональна числу операций, затрачиваемых на вычисление функции. Хотя требуемая память велика, используется она в очень простом режиме. С точностью до не очень существенных деталей в нее один раз в каком-то порядке записывается некоторый массив данных и он же списыва­ется один раз в противоположном порядке. Поэтому при решении не очень сложных задач большой размерности описанный алгоритм вычисления гра­диента может оказаться весьма эффективным.
Утверждение 8.20 доказано в условиях, когда в качестве базовых взяты опе­рации сложения/вычитания, умножения и взятия обратной величины. Можно было бы вместо операции обратной величины взять операцию деле­ния. В этом случае общая схема ускоренного вычисления градиента остается без изменения. Нужна лишь определенная аккуратность при вычислении последней строки матрицы — Q~] Р. При этом будет иметь место утвержде­ние, аналогичное утверждению 8.20. Лишь слова "взятие обратной величины числа" и "четырехкратного" заменяются словами "деления двух чисел" и "пятикратного". Это совпадает с известным результатом [44]. Заметим, что операция деления отсутствует почти на всех вычислительных машинах.
Если р = 1, то вычисление градиента превращается в вычисление производ­ной. Теперь нет необходимости использовать описанный выше алгоритм, а можно сразу применять рекуррентные соотношения (8.43). Заметим, что для вычисления правой части в (8.43) нужны производные от тех же самых ве­личин uk , которые используются для вычисления частных производных
функций Fk. Следовательно, при вычислении производной по форму­лам (8.43) необходимо иметь память для хранения величин ик точно такого
же объема, как и для хранения %. При этом не учитывается память, требуе­мая для вычисления самих правых частей в (8.43). Режимы записи величин % и и'к в память и их считывание из памяти с точностью до незначительных
деталей совпадают по времени. Если алгоритм состоит из последовательно­сти операций сложения/вычитания, умножения и взятия обратной величи­ны, то дополнительные затраты по числу операций на вычисление произ-
520
Часть III. Смежные проблемы и применение
водной согласно формулам (8.43) не будут превосходить трехкратного вы­числения значений функции. Мало что изменяется, если нужно вычислить не первую производную, а производную более высокого порядка. Пропор­ционально увеличенному на единицу порядку вычисляемой производной растет объем требуемой памяти.
Анализ ошибок округления
Одним из наиболее трудных вопросов, связанных с изучением и конструиро­ванием численных методов, является оценивание влияния на вычислительный процесс ошибок округления. Несмотря на значительные успехи в этой облас­ти, исследование ошибок округления в каждом конкретном алгоритме остает­ся до сих пор тяжелой и скучной работой, требующей к тому же большой изобретательности и виртуозности в проведении самого исследования.
Причина подобного положения кроется в отсутствии общей теории оцени­вания влияния ошибок округления. В настоящее время нет даже отдельных теоретических положений, объясняющих, например, в чем заключается по­хожесть процессов оценивания ошибок для разных алгоритмов, где находятся узкие места этих процессов, когда возможно или невозможно их осуществ­ление и т. п. Идеи прямого и обратного анализа не конструктивны и не да­ют никакого указания на то, как лучше проводить процессы оценивания. Не очень даже ясно, какая математическая задача решается в процессе оцени­вания ошибок, и как эта задача связана с той задачей, решение которой обеспечивает вычислительный процесс. Если с этих позиций взглянуть на все достижения в изучении ошибок округления, то нетрудно видеть, что от­дельные результаты объединены не столько общностью способов получения тех или иных фактов, сколько общностью формы их представления [5, 38].
Предположим, что оператор F, в общем случае нелинейный, действует из про­странства размерности р в пространство размерности г. Пусть для некоторого элемента и вычисляется его образ v = F(u). В реальных условиях элемент v редко будет получен. Основной причиной этого факта являются ошибки округления. Вместо элемента v мы будем, как правило, иметь некоторый другой элемент v. Именно его естественно считать приближенным образом элемента и.
Конечно, возникает вопрос: "Как оценить, насколько хорошо элемент v приближает элемент v?" Ответ кажется очевидным. Нужно оценить какую-нибудь норму ошибки v — v и на основе ее величины сделать тот или иной вывод. Точный элемент v неизвестен. Поэтому не остается ничего другого, кроме как оценивать ошибку v — v рекуррентно, следуя вычислительному процессу. Проводя рекуррентное оценивание шаг за шагом, в конце концов, можно получить оценку нормы ошибки v — v. Это и есть основная идея прямого анализа ошибок. Ничего более глубокого в этой идее нет.
Глава 8. Вычислительные системы и алгоритмы
521
Интуитивно ясно, что прямой анализ можно осуществить всегда. Другое дело, какая получится при этом оценка нормы ошибки v — v. Если она дос­таточно мала, то прямой анализ ошибок можно считать удачным. Если же она окажется большой, то это еще не означает, что прямой анализ ошибок проведен плохо. Большая оценка может появиться просто из-за того, что оператор /'является неустойчивым в окрестности элемента и. В этом случае никакое изменение вычислительного процесса не улучшит ситуацию. Более того, становится ясно, что нужно обратить внимание на оператор F.
Желание выделить описанную ситуацию и привело, собственно говоря, к обратному анализу ошибок. Его идея так же очень проста. Она заключается в попытке представить реально полученный элемент v как точный образ при преобразовании F некоторого элемента и . Если это удается сделать, то влияние ошибок округления можно оценить нормой разности и — и . Раз­ность и — и стали называть эквивалентным возмущением. Ничего более глу­бокого в идее обратного анализа тоже нет.
Несмотря на простоту общей идеи, обратный анализ дал удивительные ре­зультаты. Оказалось, что для большого числа алгоритмов он позволяет полу­чить оценки эквивалентных возмущений, не зависящие от меры устойчиво­сти или неустойчивости оператора F. Выделение таких алгоритмов имеет огромное значение для вычислительной математики. Оно дает уверенность, что по сравнению с ними никакие другие алгоритмы не могут дать принци­пиально лучшие по точности результаты.
С формальных позиций кажется, что вопрос существования обратного ана­лиза достаточно ясен. В самом деле, пусть оператор F отображает некоторую /7-мерную окрестность элемента и в r-мерную окрестность элемента v = F(u). Такая окрестность заведомо существует если якобиан оператора F на эле­менте и имеет ранг, равный г. Если в этих условиях элемент v попадает в окрестность элемента v, то в окрестности элемента и обязательно существует хотя бы один прообраз элемента v. Вроде бы можно сделать вывод об усло­виях существования обратного анализа ошибок, по крайней мере, в малом. В действительности высказанные соображения говорят только о теоретиче­ских условиях возможности осуществления обратного анализа. На практике условиях его выполнения обрастают многими деталями, связанными с кон­кретным способом вычисления значений оператора F. Более того, именно эти детали нередко создают узкие места в процессах получения оценок эк­вивалентных возмущений. Поэтому заранее не ясно, можно ли предложить такой процесс осуществления обратного анализа ошибок, чтобы практиче­ские условия его существования совпали с теоретическими.
Для того чтобы получить ответы на возникающие вопросы, нужно понять, решением каких задач являются ошибка v — v и эквивалентное возмущение и — и , как эти задачи связаны с алгоритмом вычисления значений операто­ра F, как они связаны со свойствами самого оператора Fa многое другое.
522
Часть III. Смежные проблемы и применение
Рассмотрим процесс распространения ошибок округления в алгоритме (8.22) вычисления функции (8.23). Формулы (8.22) отражают точные вычисления. При реальных вычислениях имеют место равенства
uk=Fk(uk^...,uk ), p<k<n; кх ,...,к <к,                   (8.50)
или эквивалентные им равенства
"/с = Fd%,—,\) + Цк, р<к<п;кх,...,кц <к.
Здесь Fk"близкая" к Fk функция, реальное вычисление которой осуще­ствляется в процессе реализации алгоритма вместо вычисления F^, uk
реально заданная или вычисленная величина ик; г\кэквивалентная абсо­лютная ошибка, которая вносится в результат вычисления Fk. Будем пока считать, что ошибки г\к известны. В общем случае они могут каким-то об­разом зависеть от ик ,..., щ , но могут и не зависеть от них.
Попытаемся представить вычислительный процесс (8.50) в следующем виде: Щ +4 =Fk(\ к1,...,икчкч), р<к<п; кь...,кч <к,               (8.51)
где 8£ — некоторые числа. Если представление (8.51) возможно, то оно име­ет очень простой смысл. Возмущения е^ вводятся во все величины ик , в том числе и во входные данные. Возьмем возмущенные входные данные щ + S], ..., Up + ър и будем в соответствии с ними проводить точный вычис­лительный процесс (8.22). Тогда на каждом шаге этого процесса в качестве точного результата будем получать величину йк + е^.
Таким образом, имеем три последовательности: последовательность ик, соответствующая точным вычислениям по точным входным данным U]_, ..., Up, реально вычисленная последовательность йк, соответствующая
точным входным данным и\, ..., up, и, наконец, последовательность ик + е^, соответствующая точным вычислениям по входным данным щ + б£ , ..., Up + ък. Эти последовательности определяются рекуррентными соотношениями (8.22), (8.50), (8.51). Последовательности ик, ик, е^ сущест­вуют, т. к. предполагаем, что существуют как реализация алгоритма в усло­виях точных вычислений, так и реализация алгоритма в условиях появления ошибок округления.
Возмущения е^ величин ик делают величины йк + е^ результатами точной
реализации процесса (8.22), но лишь при возмущенных входных данных. В этом процессе уже нет никаких ошибок округления. Поэтому величины е^ можно рассматривать как возмущения реально вычисленных величин йк, компенсирующие влияние ошибок округления, или, другими словами, эк-
Глава 8. Вычислительные системы и алгоритмы
523
Бивалентные ошибкам округления. По установившейся традиции величины 8£ будем называть эквивалентными возмущениями.
Заметим, что в отличие от общепринятого понятия эквивалентных возму­щений, здесь рассматриваются эквивалентные возмущения не только вход­ных данных, но и всех промежуточных данных, а также окончательных результатов. Это — принципиальное отличие, позволяющее понять особен­ности процессов распространения ошибок в реальных вычислительных про­цессах.
По крайней мере одна последовательность е^ из (8.51) всегда существует. В самом деле, положим S] = ... = ър = 0. Сравнивая (8.22) и (8.51), заключа­ем, что для всех остальных е^ имеется однозначное решение е^ = ик — ut
Следовательно, в данном случае е^ есть не что иное, как абсолютная ошибка вычисленной величины ик. Такая последовательность е^ определяет прямой анализ ошибок.
Если е„ велико, то нужно выяснить причины появления большой ошибки и искать способы ее устранения. Большая ошибка может появиться либо из-за плохих свойств алгоритма (8.22) вычисления значений функции (8.23), либо из-за неустойчивости самой задачи вычисления функции (8.23), либо из-за обеих причин вместе. Задача изучения влияния ошибок округления в алго­ритмах исключительно сложна. Следовательно, очень важно иметь гарантии, что большая ошибка в результате не появится из-за каких-то особенностей алгоритма.
Предположим, что удалось найти такую последовательность е'к из (8.51), что е'„ =0. Процесс (8.51) является точным для возмущенных входных данных. Поэтому реально вычисленную в условиях влияния ошибок округления (8.50) величину и„ можно представить так:
и„ = Д«1 + e'i, ..., up + е'р).                                    (8.52)
Если эквивалентные возмущения ej, ..., е'р малы, то независимо от вели­чины ошибки в ип эта величина является результатом точного вычисления
функции (8.23) со слабо возмущенными входными данными. Теперь боль­шая ошибка в и„ однозначно свидетельствует о неустойчивости задачи вы­числения функции (8.23). Появляется возможность сравнить эквивалентные возмущения е\, ..., е'р в (8.52) с ошибками задания входных данных
в (8.23). Если возмущения не очень сильно превосходят эти ошибки, то данный факт означает, что никакой другой алгоритм вычисления функ­ции (8.23) не может привести к существенно лучшим по точности результа­там. Такой подход к изучению влияния ошибок округления, как уже отме­чалось, получил название обратного анализа ошибок. Отметим, в частности,
524
Часть III. Смежные проблемы и применение
что именно обратный анализ ошибок дал возможность построить исключи­тельно эффективные по точности алгоритмы для решения самых различных задач линейной алгебры [5, 38].
Итак, последовательность е^ из (8.51), у которой эквивалентные возмущения входных данных равны нулю, описывает прямой анализ ошибок. Все после­довательности е/с, У которых эквивалентные возмущения выходных результа­тов равны нулю, описывают обратный анализ ошибок. Другие последова­тельности 8£ описывают смешанный анализ ошибок. В этих случаях влияние ошибок округления в вычислительных алгоритмах оценивается одновремен­но через эквивалентные возмущения входных данных и ошибки результатов, полученных на основе возмущения данных.
Для получения соотношений, позволяющих определять эквивалентные воз­мущения, исключим величину ик из (8.50), (8.51). Тогда будем иметь сле­дующую систему уравнений:
£а =Fk@kl +eil,...,Mi + ек )-Fk(uk ,uk )-r\k,
,,. ■         . ,:'        *                         *                             (8.53)
р <k<n; kl7..., ks < к.                                                                v '
Величины uk и ошибки г\к определяются вычислительным процессом (8.50).
Поэтому, задавая любым способом эквивалентные возмущения еь ..., ер для входных данных, можно однозначно определить, по крайней мере, в теоре­тическом плане, все остальные эквивалентные возмущения е^ последова­тельно для к> р. При заданных ик, Л/t система уравнений (8.53) является в
общем случае нелинейной относительно величин е^. Чтобы описать множе­ство решений системы (8.53), необходимо сделать какие-то предположения. Таких предположений будет два.
Первое из них основано на том, что локальные ошибки щ, как правило, яв­ляются малыми. Это дает основание к тому, чтобы рассматривать лишь малые эквивалентные возмущения е#. Второе предположение связано с тем, что функции Ffr являются достаточно гладкими в малой окрестности величин uk ,...,ик , получаемых при точном вычислительном процессе. На практике
в качестве F^ выступают функции, реализующие операции сложения, умно­жения, деления и т. п., которые, очевидно, обладают нужной гладкостью.
Принимая во внимание сделанные предположения, заменим нелинейную систему (8.53) линейной системой
АЭ^(м^,...,М4 ) гк=2_,--------=:---------l—-e^-r\k, p<k<n; ки...,к <к.
Эта система всегда совместна, т. к. ранг ее матрицы равен п — р, т. е. равен числу уравнений в системе. Поэтому при малых Л/t все е^ действительно
Глава 8. Вычислительные системы и алгоритмы
525
можно выбрать малыми. Следовательно, мы сделаем ошибку высшего по­рядка малости, если реально вычисленные величины uk ,...,ик заменим на
точные величины щ, ..., ик . Теперь будем рассматривать такую систему:
lLdFk(uk,...,uk )
2j--------д--------- £^< -£к = rU' Р<к<п; ku...,kSk <к.              (8.54)
, = \           биК
Если величины r|,t являются малыми по сравнению с единицей, то все ма­лые решения е^ линейной системы (8.54) с точностью до малых второго по­рядка описывают все малые решения нелинейной системы (8.53), т. е. с точностью до малых второго порядка описывают все множество малых эк­вивалентных возмущений из (8.51). Конечно, при малых ц^ могут существо­вать большие эквивалентные возмущения е^. Однако такие возмущения не представляют особого интереса для исследования. Отметим, что матрица системы (8.54) есть вариационная матрица именно того алгоритма (8.22), в котором изучаются процессы распространения ошибок округления.
Выполняя тот или иной вид анализа ошибок, мы будем вынуждены накла­дывать какие-то ограничения на эквивалентные возмущения. Это приводит, в свою очередь, к появлению ограничений на возможность проведения ана­лиза, т. е. ограничений на условия совместности системы. Рассмотрим более подробно обратный анализ ошибок.
Утверждение 8.21
Чтобы при любых малых локальных ошибках цк существовал обратный анализ ошибок с малыми эквивалентными возмущениями для величин иь ..., ип алго­ритма (8.22), необходимо и достаточно, чтобы градиент вычисляемой функ­ции (8.23) в точке иь ..., ир был отличен от нуля.
Чтобы при малых локальных ошибках г\к существовал обратный анализ ошибок с малыми эквивалентными возмущениями, необходимо и достаточ­но, чтобы линейная система (8.54) имела решение, в котором е„ = 0. При­чем такое решение должно существовать при любых малых локальных ошибках. Это эквивалентно условию, что ранг подматрицы, составленной из первых п — 1 столбцов вариационной матрицы алгоритма, равен п — р. Данное условие корректно, т. к. всегда р > 1, и, следовательно, всегда п — р < п — I, т.е. всегда число строк подматрицы не больше числа ее столбцов. Обозначим указанную подматрицу через Ф.
Установить ранг матрицы Ф можно по матрице BW, где D любая невырож­денная матрица. Матрицу D можно подобрать так, чтобы матрица BW была наиболее простого вида. В частности, можно взять D= Q~l, где матрица Q та же, что и в системе (8.48). В этом случае последние я — р — 1 столбцов матрицы Q _1 Ф будут совпадать с первыми п — р — 1 столбцами единичной
526
Часть III. Смежные проблемы и применение
матрицы порядка п — р. Последние п — р — 1 элементов последней строки матрицы Q ~' Ц> равны нулю. Если равны нулю и первые р элементов по­следней строки матрицы Q _1 Ч>, то эта матрица и, следовательно, матрица Ф не могут иметь ранг п — р. Поэтому для того, чтобы матрица *Р имела ранг, равный п — р, необходимо, чтобы среди первых р элементов последней строки матрицы Q ~' Ф был хотя бы один ненулевой элемент. Легко видеть, что это условие является и достаточным. Если, например, /-и из указанных элементов отличен от нуля, то будет отличен от нуля минор матрицы Q ~1 Ф , составленный из /-го ее столбца и последних п — р — 1 ее столбцов. Остает­ся заметить следующее. Как было показано выше, первые р элементов по­следней строки матрицы Q _1 Ч> совпадают с координатами градиента вы­числяемой функции (8.23) в точке щ, ..., ир, взятыми с противоположными знаками.
Таким образом, вопрос о возможности выполнения обратного анализа оши­бок в случае алгоритма, вычисляющего значение функции, полностью ре­шен. Важно подчеркнуть, что возможность или невозможность выполнения обратного анализа никак не зависит от алгоритма. От алгоритма будет зави­сеть лишь сложность выполнения обратного анализа. Конечно, это связано с тем, что процесс выполнения обратного анализа мы трактуем здесь как процесс решения системы (8.54).
Как в теоретических исследованиях, так и на практике результат реализации алгоритма (8.22) редко состоит лишь из одной величины un. Как правило, ре­зультатом является некоторый набор величин uq ,...,uq, где р < q\, ..., qr< п.
Конечно, среди чисел q\, ..., ^.должно быть число п, т. к. в противном слу­чае нет смысла проводить процесс (8.22) до конца. Однако сейчас это об­стоятельство не будет нас интересовать. Естественно, что в этом случае пря­мой анализ ошибок осуществляется точно так же, как прежде, и вопрос об условиях его выполнения не возникает. Что же касается обратного анализа ошибок, то условия возможности его осуществления формулируются иначе.
Утверждение 8.22
Пусть результатом реализации алгоритма (8.22) является набор величин uq ,..., uq. Чтобы при любых малых локальных ошибках \\к существовал об­ратный анализ ошибок с малыми эквивалентными возмущениями для величин иь ..., и„ алгоритма (8.22), необходимо и достаточно, чтобы функциональная матрица Якоби величин uq ,...,uq в точке «ь ..., ир имела ранг, равный г.
Доказательство почти аналогично тому, что было проведено в утвержде­нии 8.21. Поэтому мы остановимся на нем очень кратко. Чтобы при малых локальных ошибках \\к существовал обратный анализ ошибок с малыми эк-
Глава 8. Вычислительные системы и алгоритмы
527
Бивалентными возмущениями, необходимо и достаточно, чтобы линейная система (8.54) имела решение, в котором г = ... =^Чг =0. Причем такое
решение должно существовать при любых малых локальных ошибках. Это эквивалентно условию, что ранг подматрицы, полученной из вариационной матрицы алгоритма путем вычеркивания ее столбцов с номерами q\, ..., qr, равен п — р. Обозначим указанную подматрицу через Ч> и рассмотрим мат­рицу Q _1 Ц>. Первые р элементов строк матрицы Q ~' Ч>, имеющих номера qi, ..., qr, совпадают с координатами градиентов величин и ,...,и , взятыми
с противоположными знаками. В совокупности эти элементы с точностью до знака образуют функциональную матрицу Якоби величин uq ,...,uq в
точке щ, ..., Up. Последние п — р — г элементов указанных строк равны ну­лю. Отсюда и вытекает справедливость высказанного утверждения.
Следствие
Если число результатов алгоритма больше числа его входных данных, то об­ратный анализ ошибок может быть осуществим не при всех даже малых ло­кальных ошибках г\к или неосуществим вообще.
Действительно, в этих условиях число строк матрицы Якоби больше числа столбцов. Поэтому ранг матрицы Якоби не может равняться числу ее строк.
До недавнего времени вопрос существования обратного анализа прямо свя­зывался с его конструктивностью. На пути выполнения анализа ошибок встречалось немало "подводных камней". Одни из них были связаны с неод­нозначностью локального обратного анализа, другие — с размножением ин­формации, имелись и другие "подводные камни", затрудняющие процесс анализа.
Утверждения 8.21, 8.22 полностью решают вопрос об осуществимости об­ратного анализа ошибок, если сам анализ рассматривать как процесс реше­ния систем (8.54). В этом случае практические условия осуществимости совпадают с теоретическими. Они никак не связаны с алгоритмом, хотя анализ ошибок выполняется именно для алгоритма. Все это согласуется с установленной связью между процессом оценивания ошибок и той задачей, решение которой реализует исследуемый алгоритм. От алгоритма зависят лишь величины эквивалентных возмущений и сложность процесса их опре­деления. Эти вопросы связаны со свойствами систем типа (8.54), т. е. со свойствами вариационной матрицы алгоритма.
Использование систем (8.54) для определения эквивалентных возмущений имеет особенности. Первая из них связана с тем, что значения элементов матрицы системы в общем случае будут известны только после выполнения алгоритма. Это означает, что системы (8.54) более подходят для апостериор­ного оценивания эквивалентных возмущений, хотя они полезны и в прове-
528
Часть III. Смежные проблемы и применение
дении априорного анализа. Вторая особенность связана с тем, что правые части систем неизвестны, а известны лишь верхние оценки для их модулей. Более того, даже эти оценки чаще всего известны апостериорно. Все это говорит о том, что для анализа ошибок округления в целом, по-видимому, более естественным является апостериорный, а не априорный процесс его проведения.
Вопросы и задания
1.   Обратите внимание, что одновременное вычисление функции и ее градиента в точках необходимо для реализации многих численных методов: различные вари­анты метода Ньютона для решения систем нелинейных уравнений, различные варианты метода скорейшего спуска для отыскания минимумов функций и т. п.
2.   *Попробуйте предложить более эффективный метод вычисления градиента функции в точках, если сама функция выписывается на нескольких страницах.
3.   *Предположим, что имеется программа вычисления значения функции в точке. Разработайте автоматизированный алгоритм написания программы вычисления градиента в точке для той же функции.
§ 8.8. Примеры
Пример 8.1. Рассмотрим пример, демонстрирующий технику ускоренного вычисления градиента функции. Пусть в сделанных ранее обозначениях алгоритм выглядит так:
uk = auk-p+ft, р < к < п,                                      (8.55)
где а, р — некоторые фиксированные числа. Легко найти точное представ­ление функции и„:
un = ааи8 + (аа " ! + аа " 2 + ... + 1)р.                              (8.56)
Здесь а есть целая часть от деления п на р, 5 — остаток. Предположим, что мы не догадались, как выглядит точное представление, и стали вычислять градиент и„, исходя из прямого дифференцирования соотношения (8.55). Тогда будем иметь:
gradw^ = a gradw^ _ р.
Если не принимать во внимание специфику данного соотношения, то для вычисления градиента un придется выполнить (п — р)р операций. Как и должно быть, сложность вычисления градиента и„ по порядку в р раз боль­ше сложности вычисления функции.
Теперь рассмотрим алгоритм вычисления градиента и„, описанный выше. Составим вариационную матрицу *Р алгоритма (8.55).
Глава 8. Вычислительные системы и алгоритмы
529
Она будет такой:
->
ц<
a :-1 а
а
1 I
Первые р и последние п — р столбцов матрицы *Р образуют соответственно матрицы Р и Q из представления (8.37). В матрице лишь элементы двух диагоналей отличны от нуля. Именно, ф,-,-=а, если /— у = О, и ф,; = — 1,
если / — у' = — /?.
Согласно алгоритму необходимо далее вычислить последнюю строку матри­цы — Q~l. Для этого воспользуемся представлением (8.39). Все матрицы Rt квадратные порядка п — р. Принимая во внимание вид матрицы Q, заключа­ем, что среди поддиагональных элементов /-го столбца матрицы Rt только один элемент может быть отличен от нуля. При / < п — 2 р он находится в строке с номером р + i и равен а. Матрицы Rt при п — 2р < i < п — р — 1 в данном алгоритме совпадают с единичными, если р > 1. Поэтому при р > 1 последняя строка произведения матриц Rn -р - \ ... Rn-2p + \ будет послед­ней строкой единичной матрицы порядка п — р. При умножении этой строки справа на матрицу Rn - 2Р в ней изменится только один элемент в позиции с номером п — 2р, который станет равным а. Далее, при умножении вновь по­лученной строки справа на матрицы Rn-2p -\ ... Rn - зр + \ в ней не будут ме­няться элементы. И лишь при умножении строки справа на матрицу Rn - т,р в ней снова изменится один элемент в позиции с номером п — Зр, который ста­нет равным а2. Окончательно заключаем, что ненулевые элементы в послед­ней строке матрицы — Q~l будут находиться только в позициях с номерами п — pi, где /= 1, 2, ..., а, и они равны al~ 1. Если не обращать внимания на то, что при последовательном умножении на матрицы Rt элементы строки изменяются не всегда, то нетрудно подсчитать, что для получения последней строки матрицы — Q _1 нужно выполнить не более п — р операций.
Для получения координат градиента ип остается вычислить произведение последней строки матрицы — Q _1 на матрицу Р. Принимая во внимание вид обоих сомножителей, заключаем, что все координаты градиента и„ равны нулю кроме одной в позиции с номером 5, которая равна сса. Непосредст­венное сравнение полученного результата с (8.56) показывает его правиль­ность. Если снова не учитывать специфику элементов последней строки
530
Часть III. Смежные проблемы и применение
матрицы — Q ], то для вычисления координат градиента как элементов ука­занного произведения необходимо выполнить не более р операций.
Суммируя все затраты, видим, что на вычисление градиента и„ было затра­чено не более п операций. На реализацию вычисления и„ согласно (8.55) должно быть затрачено 2(я — р) операций. Справедливость утверждения 8.20 на рассмотренном примере подтверждена. Одновременно подтверждена справедливость утверждения 8.19.
Пример 8.2. Пусть вычисляется сумма s чисел щ, ..., ир. Градиент s есть век­тор, все координаты которого равны 1. Согласно утверждению 8.21, это оз­начает, что обратный анализ ошибок для любого алгоритма суммирования чисел будет возможен при всех значениях чисел U]_, ..., up, если только сам алгоритм основан на использовании операций, описываемых достаточно гладкими функциями. Рассмотрим традиционный алгоритм накопления:
«р + 1 = «1 + «2, up + i = UP + i - l + "/ + ь 2 < / < р - 1.                 (8.57)
Здесь s = и - 1.
Согласно исследованиям, проведенным выше, эквивалентные возмущения ег величин щ удовлетворяют системе (8.54) при дополнительном условии 82^ - 1 = 0. Вариационная матрица *Р алгоритма (8.57) имеет вид
<- р -> <- р-\ ->
1 1
-1 1 -1
т р-\
I
¥
1
1
Дополнительное условие г - \ — 0 означает, что эквивалентные возмуще­ния s\,...,S2P-2 удовлетворяют системе, матрица Фкоторой получается из матрицы ^Р вычеркиванием последнего столбца. Матрица Ф имеет ранг р — 1. Следовательно, система (8.54) будет всегда совместна даже при усло­вии S2P - 1 = 0. Это подтверждает уже сделанный вывод о том, что обратный анализ ошибок всегда возможен. Решая систему (8.54) с вычеркнутым по­следним столбцом, находим
Ч2р-:Ц2р-
Z2p-' S2/>-
+
^2р-
S3             - Цр + 2 ~ S/> +1
si + г2 = r\p+ i + Sp+i.
+ e„ + ъ
Глава 8. Вычислительные системы и алгоритмы
531
Для оценивания точности операции суммирования нужно знать лишь сумму эквивалентных возмущений входных данных. Поэтому из полученных соот­ношений имеем
р            р -1
XSi = Х^-И-/ = 1               / = I
Это хорошо известный результат [5]. Если вычисления осуществляются с плавающей запятой, то | цр + t \ < е | щ + и2 + ... + щ + \\ . Здесь е — некото­рая малая величина, определяющая точность выполнения арифметических операций на вычислительной системе. Связана она с параметрами представ­ления чисел. Отсюда, в частности, видно следующее. Если в режиме с пла­вающей запятой суммируются неотрицательные числа, то для получения наименьшей погрешности в сумме необходимо складывать числа в порядке их неубывания. Это тоже известный результат [1]. В данном примере мы хотели обратить внимание только на тот факт, что анализ ошибок может быть выполнен по формальным правилам.
Пример 8.3. Пусть вычисляются значения функции (wi + и^щ. Градиент этой функции есть вектор с координатами 2(щ + и^щ, 2(щ + 1*2)113, (ui + U2)2. Согласно утверждению 8.21, обратный анализ ошибок будет воз­можен, если U] + U2 * 0. Предположим, что алгоритм вычисления значений функции выглядит следующим образом:
щ = И] + и2, и5 = щщ, щ = щи5.                                (8.58)
Вычисления функций и^ и щ требуют одного и того же аргумента щ. Следо­вательно, в алгоритме есть размножение информации. Вариационная мат­рица *Р алгоритма (8.58) будет такой:
"11         -1
ц<
1
-1
Эквивалентные возмущения еь ..., еь удовлетворяют системе (8.54) при до­полнительном условии 86 = 0. Матрица Фсистемы для возмущений еь ..., 55 получается из матрицы *Р вычеркиванием последнего столбца. Условием со­вместности этой системы при произвольных правых частях является равен­ство ранга матрицы Ф числу 3. Это же является условием выполнимости обратного анализа. Не равные тождественно нулю миноры третьего порядка
матрицы ¥ принимают одно из значений U4U5, и4 , W3W4 + и5- В этом можно убедиться непосредственной проверкой. Принимая во внимание (8.58), за­ключаем, что ранг матрицы *Р равен 3, если щ + и2 *■ 0. Этот результат под­тверждает уже сделанный вывод об условии выполнимости обратного анали­за ошибок.
532
Часть III. Смежные проблемы и применение
Если вычисления осуществляются с плавающей запятой, то при выполне­нии условий И] + и2 ф О, щ ф О локальные ошибки л,- можно представить в виде произведений г\ и,-. При этом модули всех величин е' будут ограниче­ны сверху малой величиной е, зависящей только от параметров представле­ния чисел в вычислительной системе и способа выполнения операции ок­ругления. Будем искать эквивалентные возмущения е, в виде аналогичных произведений е" щ для / > 3. Ясно, что S] + е2 можно так же искать в виде
произведения е"2 щ. Принимая во внимание (8.58), находим
//                               __       №                         __       t ,
£2                              £4                   ~~ £ 4->
е3 т е4 е5 - s 5>
е4 + е5 6.
Эквивалентные возмущения определяются неоднозначно. В качестве одного из решений этой системы можно взять
£24 + е6, £355, е4-еб, е5 - 0.
В данном примере мы хотели обратить внимание на тот факт, что в некото­рых случаях можно находить малые относительные, а не только абсолютные эквивалентные возмущения.
Глава 9
Пользователь
в среде параллелизма
Если ничто не помогает, прочтите, наконец, инструкцию.
Из законов Мерфи
Параллельные вычисления универсальны. Они могут использоваться в лю­бой области науки, где традиционного последовательного способа обработки данных недостаточно и необходимо существенно ускорить процесс вычис­лений. Астрофизика, аэро- и гидродинамика, вычислительная механика, квантовая химия, геофизика и экология, криптография и статистика — эти, как и многие другие области науки нуждаются в высокопроизводительных параллельных компьютерах. Однако практически все ученые, работающие в той или иной среде параллельных вычислений, сталкиваются с общей про­блемой: как эффективно использовать параллельные компьютеры? Это и неудивительно. Параллельная вычислительная среда обладает своими осо­бенностями, для работы в ней во многих случаях нужно знать технологии параллельного программирования, параллельные методы решения задач, архитектуру параллельных компьютеров, методы анализа структуры про­грамм и алгоритмов и другие смежные дисциплины, нехарактерные для предметной области ученого: химика, биолога, физика, медика. Понятно, что в такой ситуации успешно "прорваться" через круг возникающих сопут­ствующих проблем удается далеко не всегда — отсутствие помощи, допол­нительных сведений, доступа к ресурсам, полигонов для тестовых испыта­ний и других сервисных служб среды параллелизма значительно усложняет работу прикладных специалистов.
Эффективное использование параллельных вычислительных систем — это главная проблема параллельных вычислений. Сейчас нет технологических трудностей построить компьютер из тысячи процессоров, но по-настоящему серьезная проблема — сделать его эффективно используемым многими ис­следователями.
Параллельные вычисления начались с потребностей пользователей в реше­нии больших задач. Развиваясь, они дали жизнь многим направлениям, ко­торые сейчас пытаются быть самостоятельными и независимыми друг от друга. Архитектура параллельных компьютеров, технологии параллельного программирования, параллельные методы решения задач — все это примеры подобных направлений. Конечно, по каждому их них нужно проводить свои
534
Часть III. Смежные проблемы и применение
собственные исследования. Нельзя только забывать, для чего все это делается. Создали оригинальный язык программирования, а им никто не пользуется. Почему? Разработали новый метод, а для решения практических задач он непригоден. Почему? Установили уникальный компьютер, а на нем никто не считает. Почему? Чаще всего ответы на подобные вопросы следует искать у пользователей.
К сожалению, на практике часто забывают, что параллельные вычисле­ния — это не цель, а лишь средство для решения наиболее трудоемких и сложных задач. По этой причине мы решили последние три параграфа дан­ной книги посвятить описанию различных сторон работы пользователя в среде параллелизма.
Для анализа структуры исследуемых фрагментов мы будем часто пользоваться возможностями, предоставляемыми системой V-Ray. Эта система разработа­на на основе теории, изложенной в данной книге {главы 6 и 7), и прошла апробацию в ходе выполнения большого числа реальных проектов и экспе­риментов с использованием параллельных вычислительных систем.
§ 9.1. Типичные ситуации в вопросах и ответах
В данном параграфе мы обсудим целый ряд типичных проблем, с которыми в той или иной степени сталкивается каждый пользователь параллельных вычислительных систем. Изложение построено так, что каждая проблема формулируется в виде отдельного вопроса, иллюстрируется примерами ре­альных программ, и затем предлагается способ ее решения. В качестве объ­ектов исследования мы рассматриваем различные компьютеры: Cray С90, Cray T3D, IBM SP2. Во многих случаях более пристальное внимание мы уделяем многопроцессорному векторно-конвейерному компьютеру Cray С90. Интерес к данному компьютеру объясняется просто. Помимо традиционных проблем, связанных с распараллеливанием программ между несколькими процессорами, Cray С90 одновременно заставляет думать и о векторизации. Мало провести локальный анализ того или иного фрагмента. Для полного использования потенциала компьютера нужен анализ свойств всей про­граммы целиком. В такой постановке задача анализа и отображения про­грамм на архитектуру компьютера становится намного сложнее.
Безусловно, все проблемы мы обсудить не сможем. Это не реально, да и не нужно. Намного важнее понять общий подход к анализу структуры про­грамм. Различных задач на практике возникает очень много, однако большая их часть может быть успешно решена схожими методами. Важно осознать общую идею, научиться пользоваться ключевыми понятиями и ме­тодами, после чего применить эти знания на практике для вас уже не соста­вит большого труда.
Глава 9. Пользователь в среде параллелизма
535
На программе очень плохая производительность. В чем причина? Итак, самая ти­пичная ситуация, с которой сталкиваются все программисты на параллельных вычислительных системах, состоит в том, что есть правильно работающая про­грамма, но используемый компьютер выполняет ее с крайне низкой производи­тельностью. С одной стороны, пользователь знает о пиковой производительно­сти данного компьютера. С другой стороны, он видит реальные цифры, полученные на конкретной программе и отличающиеся от желаемых значений иногда на несколько порядков. Сразу же возникает вопрос: "Почему?"
Возьмем фрагмент (9.1) и предположим, что мы его выполняем на вектор-но-конвейерном компьютере Cray С90. Несмотря на то, что пиковая произ­водительность одного процессора С90 равна почти 1 Гфлопс, на данном фрагменте он показывает лишь 118 Мфлопс. В чем причина?
DO 111 NUM = 10, 40, 5                                                                                   (9.1)
DO 100 MS = 1, NUM DO 100 MR = 1, MS
DO 10 MQ = 1, NUM
DO 10 MI = 1, NUM 10 XI(MI,MQ,MR,MS) = 0.0D + 00
DO 4 0 MP = 1, NUM
DO 30 MQ = 1, MP
DO 20 MI = 1, NUM
XI(MI,MQ,MR,MS) = XI(MI,MQ,MR,MS)+XNEW(MQ,MP,MR,MS)*V(MP,MI)
XI(MI,MP,MR,MS) = XI(MI,MP,MR,MS)+XNEW(MQ,MP,MR,MS)*(MQ,MI) 20 CONTINUE 30 CONTINUE 40 CONTINUE
DO 90 MI = 1, NUM
DO 50 MJ = 1, MI 50 XIJ(MJ,MI,MR,MS) = 0.0D + 00
DO 7 0 MQ = 1, NUM
DO 60 MJ = 1, MI 60 XIJ(MJ,MI,MR,MS) = XIJ(MJ,MI,MR,MS)+XI(MI,MQ,MR,MS)*V(MQ,MJ) 70 CONTINUE
DO 80 MJ = 1, MI 80 XNEW(MJ,MI,MR,MS) = XIJ(MJ,MI,MR,MS) 90 CONTINUE
100 CONTINUE
111 CONTINUE
536
Часть III. Смежные проблемы и применение
Обратимся к структуре данного фрагмента и особенностям целевого компью­тера. С помощью системы V-Ray легко определить, что все самые внутренние циклы фрагмента имеют тип ParDO (по графу алгоритма), и, следовательно, все они могут быть векторизованы. Другими словами, 100% арифметических операций будут выполняться в векторном режиме. Векторизация осуществля­ется по ведущему измерению основных массивов, поэтому выборка данных для векторных операций идет с шагом 1 и конфликтов при обращении к па­мяти не происходит. Массив v описан как v (41, 41), поэтому выборка по его второму измерению идет с нечетным шагом 41, т. е. тоже без конфликтов. Устройства сложения и умножения загружены равномерно и всегда использу­ются в режиме с зацеплением. Все векторные операции фрагмента требуют только два входных векторных аргумента, что опять же хорошо согласуется с особенностями архитектуры Cray С90.
Остается лишь один фактор, сильно снижающий производительность векторно-конвейерных компьютеров — это очень короткие внутренние циклы. Не слож­но заметить, что во время исполнения данного фрагмента их длина не превы­шает значения num, меняющегося от 10 до 40, поэтому говорить о сколько-нибудь значительной производительности, конечно же, не приходится.
Итак, причина найдена, но этим решена только часть задачи. Мы знаем, что именно нам мешает, но не знаем, во-первых, можно ли это препятствие пре­одолеть, и, во-вторых, если можно, то как. Обратимся к следующему разделу.
Причина известна. Как ее устранить? Если мы хотим отказаться от вектори­зации самых внутренних циклов фрагмента (9.1) и найти для этого более подходящие операции, то первое, что необходимо понять, а из чего же можно выбирать. Для выполнения любой векторной операции необходим, прежде всего, набор независимых операций и самый простой способ их на­хождения — это выделение циклов с независимыми итерациями или, дру­гими СЛОВаМИ, ЦИКЛОВ ParDO.
На рис. 9.1 показан размеченный циклический профиль фрагмента (9.1) без учета самого внешнего цикла (с параметром num), в котором каждая гори­зонтальная скобка соответствует своему циклу. Структура вложенности ско­бок на профиле полностью повторяет структуру вложенности циклов, по­этому его можно рассматривать как своего рода "боковой срез" фрагмента. Все циклы с независимыми итерациями (циклы ParDO по графу алгоритма) помечены точками.
^_1 ■ ■ I'—-—' I ■ ■ U^l I'—-—' I U^l
■«---------1 I-----------------------------1 I------------------------«---------------------
Рис. 9.1. Циклический профиль фрагмента (9.1) с разметкой циклов ParDO
Глава 9. Пользователь в среде параллелизма
537
Сразу бросается в глаза большое число параллельных циклов. Это значит, что независимых операций в данном фрагменте много. Однако просто ис­пользовать какой-либо другой цикл данного фрагмента для векторизации не имеет смысла, т. к. все циклы имеют длину того же порядка, что и внутрен­ние. Естественный выход из данной ситуации состоит в замене нескольких параллельных циклов одним. В частности, для данного примера это могут быть два внешних цикла с параметрами ms и mr и суммарным числом опера­ций NUM * (NUM + 1)/2.
Пока не обговаривая детали, предположим, что циклы ms и mr мы каким-то образом будем использовать для векторизации. Но наш целевой компьютер — это Cray С90, объединяющий в максимальной конфигурации до 16-ти вектор-но-конвейерных процессоров. Если мы хотим, чтобы программа выполнялась с максимальной скоростью, то надо использовать все процессоры, для чего в программе нужно найти дополнительный ресурс параллелизма.
Обратимся снова к фрагменту (9.1). Легко определить, что все циклы с па­раметром mi, присутствующие во всех гнездах циклов, являются циклами ParDO, и именно этот ресурс параллелизма мы будем использовать для па­раллельной работы отдельных процессоров. Теперь ясно, что мы хотим сде­лать, и осталось лишь выполнить само преобразование программы.
Главная проблема, которая довольно часто возникает при объединении цик­лов для увеличения длины векторных операций, заключается в необходимо­сти изменения используемых структур данных. Чтобы минимизировать вно­симые изменения, перед выполнением анализируемого фрагмента вставим дополнительную часть кода, заполняющую новые структуры, а после него — часть для восстановления прежних структур. Для фрагмента (9.1) массивы xnew и v являются входными, но от переменных ms и mr зависят только ин­дексные выражения элементов массива xnew, поэтому заполнение и восста­новление связано только с этим массивом.
С Заполнение нового массива YNEW                                                                      (9.2)
ICOUNT = 1
DO 1 MS = 1, NUM
DO 1 MR = 1, MS
DO 2 MP = 1, NUM
DO 2 MQ = 1, MP 2 YNEW(MQ,MP,ICOUNT) = XNEW(MQ,MP,MR,MS) 1 ICOUNT = ICOUNT + 1
С-----------
DO 100 MI = 1, NUM DO 100 MQ = 1, NUM DO 100 MSR = 1, NUM * (NUM + l)/2 100 XI(MI,MQ,MSR) = 0.0D + 00
538
Часть III. Смежные проблемы и применение
DO 20 MI = 1, NUM
DO 4 0 MP = 1, NUM
DO 30 MQ = 1, MP
DO 101 MSR = 1, NUM * (NUM + 1) / 2
XI(MI,MQ,MSR) = XI(MI,MQ,MSR) + YNEW(MQ,MP,MSR) *V(MP,MI)
XI(MI,MP,MSR) = XI(MI,MP,MSR) + YNEW(MQ,MP,MSR) *V(MQ,MI)
101 CONTINUE 30 CONTINUE 40 CONTINUE 20 CONTINUE
С-----------
DO 90 MI = 1, NUM
DO 50 MJ = 1, MI
DO 102 MSR = 1, NUM * (NUM + 1) / 2
102 XIJ(MJ,MI,MSR) = 0.0D + 00 50 CONTINUE
DO 7 0 MQ = 1, NUM
DO 60 MJ = 1, MI
DO 103 MSR = 1, NUM * (NUM + 1) / 2
103 XIJ(MJ,MI,MSR) = XIJ(MJ,MI,MSR) + XI(MI,MQ,MSR) * V(MQ,MJ) 60 CONTINUE
70 CONTINUE
DO 80 MJ = 1, MI
DO 104 MSR = 1, NUM * (NUM + 1) / 2
104 YNEW(MJ,MI,MSR) = XIJ(MJ,MI,MSR) 80 CONTINUE
90 CONTINUE С Восстановление массива XNEW ICOUNT = 1 DO 3 MS = 1, NUM DO 3 MR = 1, MS DO 4 MP = 1, NUM
DO 4 MQ = 1, MP 4 XNEW(MQ,MP,MR,MS) = YNEW(MQ,MP,ICOUNT) 3 ICOUNT = ICOUNT + 1
Фрагмент (9.2) получен из фрагмента (9.1) с помощью указанного преоб­разования, но в отличие от него выполняется с производительностью
Глава 9. Пользователь в среде параллелизма
539
322 Мфлопс. Несмотря на то, что выполненное преобразование относитель­но нетривиально, оба фрагмента эквивалентны с точностью до ошибок ок­ругления и в конце получают один и тот же массив xnew. Следует специаль­но отметить, что для данного примера увеличение длины векторных операций (а значит увеличение производительности компьютера и умень­шение времени выполнения фрагмента) полностью компенсировало на­кладные расходы, связанные с поддержкой новых структур данных.
Какой же вывод можно сделать? Перед тем как смириться с низкой произ­водительностью или же вообще отказаться от существующего варианта программы, надо определить ее потенциальные свойства и найти способ их использования. Перед тем как заменять один алгоритм другим, надо убе­диться в том, что старый действительно плох и на самом деле не позволяет эффективно использовать особенности данного параллельного компьютера. Для этого и создаются инструментальные средства для исследования струк­туры программ, позволяющие ответить на вопрос, можно ли использовать какие-либо резервы данной программы или же принципиальных улучшений добиться в принципе невозможно.
Можно ли еще улучшить программу? Итак, увеличив длину векторных опе­раций, мы получили намного более эффективный вариант программы. Можно ли его еще улучшить за счет каких-либо других преобразований? Вопрос на самом деле очень важный. Обоснованный ответ позволяет по­нять, насколько далеко в настоящий момент мы находимся от оптимального варианта, можно ли вообще с помощью эквивалентных преобразований су­щественно улучшить программу или же подобная оптимизация в принципе не может дать большого выигрыша.
Вернемся к особенностям архитектуры векторно-конвейерного компьютера Cray С90 (см. § 3.2). Перед выполнением векторной операции необходимо данные занести в векторные регистры, а после завершения снова записать их в память. На перемещение данных между основной памятью и вектор­ными регистрами, т. е. на операции чтения/записи, требуется дополнитель­ное время, что неизбежно снижает общую производительность. Выход из такой ситуации может быть только один — повторное использование дан­ных, уже хранящихся на векторных регистрах. В отличие от описанного выше преобразования, для этого надо исследовать не ресурс параллелизма программы, а свойства использования памяти, чтобы ответить на вопрос, на каких итерациях идет обращение к одним и тем же элементам массивов.
Рассмотрим цикл 70 фрагмента (9.2). Выражение xij(mj,mi,msr) , стоящее как в левой, так и правой частях оператора присваивания, не зависит от параметра этого цикла. На каждой итерации mq происходит обращение к одной и той же части массива хи, поэтому, как указывалось в § 7.4, имеет смысл выполнить раскрутку данного цикла. Фрагмент (9.3) содержит рас­крученный вариант цикла 7 о с глубиной раскрутки 5. В отличие от преды-
540
Часть III. Смежные проблемы и применение
дущего тестового фрагмента нам потребовалось добавить еще одну цикли­ческую конструкцию (с меткой 7 01), т. к. значение num может не быть кратно 5.
NUNROLL = 5                                                                                                        (9.3)
NZ = MOD(NUM,NUNROLL)
DO 7 0 MQ = 1, NUM - NZ, NUNROLL
DO 60 MJ = 1, MI
DO 103 MSR = 1, NUM * (NUM + 1) / 2
T = XIJ(MJ,MI,MSR)
T = T + XI(MI,MQ, MSR) * V(MQ, MJ)
T = T + XI(MI,MQ + 1,MSR) * V(MQ + 1,MJ)
T = T + XI(MI,MQ + 2,MSR) * V(MQ + 2,MJ)
T = T + XI(MI,MQ + 3,MSR) * V(MQ + 3,MJ)
T = T + XI(MI,MQ + 4,MSR) * V(MQ + 4,MJ) 103 XIJ(MJ,MI,MSR) = T 60 CONTINUE 70 CONTINUE С Выполнить остаток цикла 70
DO 701 MQ = NUM - NZ + 1, NUM
DO 601 MJ = 1, MI
DO 1031 MSR = 1, NUM * (NUM + 1) / 2 1031 XIJ(MJ,MI,MSR) = XIJ(MJ,MI,MSR) + XI(MI,MQ,MSR) * V(MQ,MJ) 601 CONTINUE 701 CONTINUE
Почти точно так же можно преобразовать цикл 20 фрагмента (9.2), однако, в отличие от цикла 7 о, для данного цикла есть одна проблема. Дело в том, что выражение xi (mi,mq,msr) первого оператора не зависит от параметра мр цикла 40, а выражение xi (mi,mp,msr) второго оператора не зависит от па­раметра mq цикла зо. Раскручивать одновременно оба цикла, сохраняя в теле цикла два оператора, не выгодно, т. к. резко возрастает число входных век­торов. Возникает естественное желание разбить данный цикл на два, содер­жащих по одному оператору присваивания, и в каждом выполнить раскрут­ку по своему циклу. Можно ли это сделать и если можно, то как? Вопрос нетривиальный, учитывая, что в данном фрагменте есть зависимость между итерациями циклов 4 0 и 30.
Забегая немного вперед, скажем сразу: да, можно, но для этого нам понадо­бится исследовать тонкую информационную структуру данного фрагмента и выполнить специальное преобразование (данное преобразование будет опи­сано немного ниже). В результате, используя указанные свойства циклов зо, 40 и 70 фрагмента (9.2), удается поднять значения производительности до
Глава 9. Пользователь в среде параллелизма
541
511 Мфлопс. В дальнейшем дополнительный ресурс параллелизма внешних циклов с параметром mi может быть эффективно использован для распарал­леливания, что еще больше поднимет производительность в случае много­процессорных конфигураций Cray С90.
В итоге проведенного исследования получаем следующее. Отталкиваясь от 118 Мфлопс исходного варианта (9.1), мы сначала определили точную ин­формационную структуру фрагмента и выделили полный ресурс параллелизма (рис. 9.1). На основе этого мы приняли стратегическое решение для преобра­зования с учетом как векторизации, так и распараллеливания, и увеличили производительность до 322 Мфлопс (9.2). На последнем этапе мы использова­ли один из возможных подходов к тонкой настройке полученного фрагмента и подняли производительность до 511 Мфлопс. Как и всегда, следуем основ­ной идее нашего подхода: сначала определяем потенциальные свойства про­граммы, а затем принимаем решение о целевом преобразовании.
Можно ли распределить данные так, чтобы не было пересылок? Теперь пред­положим, что целевая вычислительная система совершенно другая — это массивно-параллельный компьютер с распределенной памятью, например, Cray T3D/T3E, IBM SP2, МВС-ЮООМ или обычный вычислительный кла­стер. В этом случае на каждом процессоре исполняется своя часть одной и той же программы, используя данные, расположенные в локальной памяти своего процессора. Если на каком-либо процессоре потребовались данные, расположенные в памяти другого процессора, то на передачу этих данных необходимо время, как правило, значительно превосходящее время обраще­ния к своей локальной памяти.
Основное интуитивно понятное правило получения эффективных программ для подобного рода компьютеров состоит в том, чтобы выделить побольше примерно одинаковых по вычислительной сложности независимых фраг­ментов, которые бы поменьше обменивались данными во время исполне­ния. С точки зрения анализа и преобразования структуры программ это правило можно разделить на две задачи:
□  определение потенциального параллелизма фрагмента — эта часть в ка­кой-то степени нам уже знакома;
□  нахождение множества возможных распределений данных по процессо­рам, согласованных с найденным ресурсом параллелизма.
Вторая задача предполагает поиск таких способов распределения данных по процессорам вычислительной системы, при которых обмены данными меж­ду процессорами были бы минимальными. Самая благоприятная ситуация возникает в том случае, когда удается распределить массивы так, что обме­ны данными отсутствуют вовсе.
Обратимся опять к внешнему циклу юо фрагмента (9.1). Его ресурс парал­лелизма мы уже определили в процессе оптимизации под архитектуру
542
Часть III. Смежные проблемы и применение
Cray С90. Однако мы пока ничего не знаем относительно множества воз­можных распределений массивов. Детальное исследование позволяет обна­ружить следующие свойства фрагмента:
□  существуют такие распределения массивов xi, xnew, xij и v, при которых передачи данных во время исполнения программы отсутствуют;
□  если распределение данных согласовывать с ресурсом параллелизма цик­лов ms и mr, то массивы xi, xnew и xij, требующие больше всего памяти, можно распределить по процессорам без дублирования;
□  если распределение данных согласовывать с ресурсом параллелизма цик­лов mi, то распределить массив xnew без его дублирования нельзя;
□  любое распределение массивов, при котором отсутствуют пересылки данных, требует дублирования массива v на каждом процессоре;
□  использование ресурса параллелизма какого-либо одного цикла не по­зволит получить хорошо масштабируемой программы, т. к. число итера­ций каждого из них не превышает 40.
На основе этой информации не трудно найти способ относительно легкого получения параллельного варианта данного фрагмента без взаимодействия процессоров между собой. Использование параллелизма циклов ms и mr по­зволит легко распределить основные массивы xi, xnew и xij по процессо­рам без дублирования. Входными данными для каждого процессора будут массив v и соответствующая часть массива xnew, а массивы xi и xij, ис­пользуя технику приватизации (array privatization), можно объявить локаль­ными на каждом процессоре. Суммарное число итераций циклов ms и mr меняется от 55 (шм=ю) до 820 (num=4 0), что позволит получить хорошо масштабируемую программу для упомянутых выше параллельных компью­теров с распределенной памятью.
JLO = JL                                                                                                                           (9.4)
DO 100 MS = IL, IH
JHI = MS
IF( MS .NE .IL ) JLO = 1
IF( MS .EQ. IH ) JHI = JH
DO 100 MR = JLO, JHI
... (прежний вариант программы) 100 CONTINUE
Фрагмент (9.4) содержит один из возможных параллельных вариантов данного кода. Каждый процессор получает какое-то число лексикографически после­довательных итераций, расположенных в подпространстве (ms,mr) между точ­ками (il,jl) и (ih,jh). Предполагается, что в данном случае используется модель программирования SPMD, поэтому на каждом процессоре значения
Глава 9. Пользователь в среде параллелизма
543
переменных il, jl, ih, jh различны. В табл. 9.1 показаны времена выполне­ния данного фрагмента на различных конфигурациях компьютеров Cray T3D и IBM SP2, полностью подтверждающие хорошую масштабируемость парал­лельной программы (большие конфигурации компьютера IBM SP2 на момент проведения экспериментов не были доступны).
Таблица 9.1. Время выполнения фрагмента (9.4) (с) на массивно-параллельных компьютерах Cray T3D и IBM SP2.
Компьютер
Число процессоров
2
4
8 16
32
64
Cray T3D IBM SP2
6,48 5,83
3,24 2,55
1,63 0,82 1,81
0,41
0,21
Можно ли применить данное преобразование к программе? Подобного рода вопросы довольно часто возникают во время оптимизации программ. Прак­тически все программисты слышали о том, что бывают полезными переста­новки циклов местами, распределение циклов или, наоборот, их слияние, раскрутка циклов и т. п. Но для того чтобы получить корректный преобра­зованный вариант программы, надо не только выполнить собственно преоб­разование, но и убедиться в его эквивалентности исходному варианту. Хо­рошо если преобразуемый фрагмент относительно прост или вы являетесь автором данной программы и понимаете "физический" смысл преобразова­ния. А если это не так?
Рассмотрим возможность распределения цикла 20 фрагмента (9.5). Данный цикл является частью фрагмента (9.2), во время оптимизации которого мы обещали обосновать возможность распределения циклов для последующей раскрутки. Можно ли разбить данное гнездо циклов на одну или несколько циклических конструкций так, чтобы каждая содержала только один опера­тор присваивания? Заметим, что для этого фрагмента данное преобразова­ние может быть полезно не только перед выполнением раскрутки (что мы и сделали ранее), но и для лучшего использования кэш-памяти команд и дан­ных, и для получения большего числа циклов ParDO.
DO 20 MI = 1, NUM                                                                                                        (9.5)
DO 20 MP = 1, NUM DO 20 MQ = 1, MP
DO 20 MSR = 1, NUM * (NUM + 1) / 2
XI(MI,MQ,MSR) = XI(MI,MQ,MSR) + YNEW(MQ,MP,MSR) *V(MP,MI) XI(MI,MP,MSR) = XI(MI,MP,MSR) + YNEW(MQ,MP,MSR) *V(MQ,MI) 20 CONTINUE
544
Часть III. Смежные проблемы и применение
Можно ли просто взять и разбить данный цикл на два, где каждый цикл будет содержать по одному оператору присваивания? Нет, так делать нельзя, поскольку эквивалентность фрагментов будет нарушена! Обратимся к про­странству итераций исходного фрагмента. На рис. 9.2 показана передача информации от одного оператора присваивания к другому при изменении параметров циклов мр и mq.
Рис. 9.2. Передача информации между операторами
присваивания фрагмента (9.5) при изменении
параметров циклов МР и MQ
На данном рисунке вершины нижнего треугольника (нижняя плоскость) соответствуют первому оператору присваивания, а вершины верхнего — второму. Совершенно ясно, что простое деление цикла на два нарушает ин­формационную зависимость: операция А (второй оператор), вычисляющая аргумент для операции В (первый оператор) еще не выполнена, а мы уже хотим выполнить В.
Вместе с тем, из этой же картинки не сложно найти такой способ преобра­зования данного фрагмента, при котором были бы выполнены и условия преобразования, и соблюдена эквивалентность. В самом деле, если "отре­зать" диагональные итерации (мр = mq), то выполнение второго оператора уже не зависит от выполнения первого. Следовательно, сначала можно вы­полнить всю преддиагональную часть второго оператора (в эту часть входят те операции, для которых мр > mq), затем диагональные итерации в преж­нем виде, а в самом конце преддиагональную часть первого оператора. Фрагмент (9.6) содержит преобразованный текст фрагмента (9.5).
Глава 9. Пользователь в среде параллелизма
545
С Преддиагональная часть второго оператора                                                      (9.6)
DO 20 MI = 1, NUM DO 20 MP = 1, NUM DO 20 MQ = 1, MP - 1 DO 20 MSR = 1, NUM * (NUM + 1) / 2 XI(MI,MP,MSR) = XI(MI,MP,MSR) + YNEW(MQ,MP,MSR) *V(MQ,MI)
20 CONTINUE
С Диагональная часть, MQ = MP DO 21 MI = 1, NUM DO 21 MP = 1, NUM MQ = MP
DO 21 MSR = 1, NUM * (NUM + 1) / 2
XI(MI,MQ,MSR) = XI(MI,MQ,MSR) + YNEW(MQ,MP,MSR) *V(MP,MI) XI(MI,MP,MSR) = XI(MI,MP,MSR) + YNEW(MQ,MP,MSR) *V(MQ,MI)
21 CONTINUE
С Преддиагональная часть первого оператора DO 22 MI = 1, NUM DO 22 MP = 1, NUM DO 22 MQ = 1, MP - 1 DO 22 MSR = 1, NUM * (NUM + 1) / 2 XI(MI,MQ,MSR) = XI(MI,MQ,MSR) + YNEW(MQ,MP,MSR) *V(MP,MI)
22 CONTINUE
В данном примере мы говорили только о распределении циклов. Однако практически для всех наиболее известных преобразований можно привести подобные нетривиальные примеры, когда корректность выполнения кон­кретного преобразования не очевидна и требует дополнительного анализа — аналитического (автоматического, автоматизированного) или визуального.
Опять подчеркнем, что, как и прежде, мы сначала определили точную ин­формационную структуру программы, и лишь затем приняли решение о воз­можности выполнения целевого преобразования. Ответ на поставленный во­прос: "Можно ли выполнить распределение циклов в исходном фрагменте?" — был отрицательный, но, как мы видели, это совсем не значит, что для данного фрагмента указанное преобразование в принципе не применимо.
Нужно ли попробовать какой-либо другой препроцессор или метод оптимиза­ции? Этот вопрос возникает особенно часто у начинающих программистов, когда используемые ими методы или средства оптимизации не дают ожи­даемого эффекта. Почему нет эффекта и что делать дальше? Надо ли попро­бовать какой-либо другой подход или данную программу существенно улучшить невозможно? Точно такие же вопросы возникают даже в том слу­чае, когда оптимизация программы уже позволила значительно увеличить
546
Часть III. Смежные проблемы и применение
производительность: может быть если попробовать что-то еще, то результат будет еще лучше?
Основная причина неуверенности и сомнений, явно выраженных в подоб­ного рода вопросах, кроется в том, что практически все используемые в на­стоящее время препроцессоры, оптимизаторы, анализаторы и т. п. имеют два больших недостатка:
□  они ничего не говорят о структуре программы или фрагмента целиком, о потенциальных свойствах. Пользователь не знает, может ли оптимизация еще что-либо дать или же он уже использовал весь заложенный в про­грамму потенциал и получил показатели производительности, близкие к предельно возможным;
□  они, как правило, ничего не говорят о том, что же можно ожидать в том случае, когда используемый ими метод анализа структуры программ не дает положительного результата. Почему цикл не векторизуем: метод сла­бый или данный цикл не может быть векторизован в принципе? Кэш­память используется неэффективно из-за того, что оптимизатор не знает, как ее использовать эффективно, или для данного фрагмента использо­вание кэш-памяти вообще не может дать никакого эффекта?
Недомолвки оптимизаторов естественно выливаются в неуверенность и не­доверие пользователей: раз нет полной информации, то возникает естест­венное желание попробовать что-то еще.
Следует специально подчеркнуть, что говоря об оптимизации и полной ин­формации о структуре программы, мы имеем в виду описание и использо­вание двух основных характеристик программы: потенциала параллелизма (какие операции можно выполнять независимо друг от друга, а какие нет) и свойств работы с памятью (по какому закону происходит обращение к раз­личным областям памяти). Сейчас мы не обсуждаем, например, как повы­шать производительность компьютера за счет использования более опти­мальных алгоритмов генерации кода или за счет поиска общих подвы­ражений в тексте программ. Наша основная задача — сконцентрироваться на тех общих машинно-независимых свойствах программ, которые, с одной стороны, принципиально важны для эффективной реализации на парал­лельных компьютерах, а с другой, характеризуют реализованный в програм­ме алгоритмический подход. Только в этом случае мы сможем определить, насколько полно используется потенциал программы, есть ли резерв для увеличения производительности или же надо сменить используемый алгоритм.
Тысячи строк исходного текста программ — можно ли в них разобраться?
Нужно использовать старую программу на данном параллельном компьюте­ре... Нужно понять и устранить причины низкой производительности уже существующей чужой программы... Требуется перенести программу на дру­гую параллельную платформу с сохранением разумной производительности...
Глава 9. Пользователь в среде параллелизма
547
Всякий, кто сталкивался хотя бы с одной из подобных задач, согласится с тем, насколько важно понять общую структуру той бездны строк, с которой приходится иметь дело. Основная проблема опять же идет от специфики параллельных компьютеров — надо добиться не только того, чтобы про­грамма выдавала правильные результаты, но и работала с разумной произ­водительностью .
С одной стороны, можно взять профилировщик, на обычном последо­вательном компьютере найти те фрагменты программы, на которые прихо­дится 90% общего времени выполнения (а это, как правило, около 5—10% текста), и заняться их оптимизацией. Разумно? В некоторых случаях, безус­ловно, да.
Однако вспомним закон Амдала: если 9/10 программы исполняется парал­лельно, а 1/10 по-прежнему последовательно, то ускорения более, чем в 10 раз получить в принципе невозможно вне зависимости от качества реали­зации параллельной части кода. Если подобное ускорение устраивает поль­зователя или же ускорение его вообще не интересует, то с таким подходом вполне можно согласиться. Однако в жизни, как правило, ситуация иная: никто не станет использовать 2048 процессоров для ускорения программы в 10 раз.
Посмотрим на проблему с другой стороны. Какую же часть кода надо уско­рить (а значит и предварительно исследовать), чтобы получить заданное ус­корение? Ответ можно найти в следствии из закона Амдала (см. § 2.3): для того чтобы ускорить выполнение программы в q раз, необходимо ускорить не менее, чем в q раз не менее, чем (1 — 1/#)-ю часть программы. Следова­тельно, если нужно ускорить программу в 100 раз по сравнению с ее после­довательным вариантом, то необходимо получить не меньшее ускорение не менее, чем на 99,99% кода, что почти всегда составляет существенную часть программы.
Вернемся опять к фрагменту (9.1). 99% времени занимает исполнение самых внутренних циклов, однако для того, чтобы найти способ его преобразова­ния и получить 511 Мфлопс, нам потребовалось исследовать структуру всего фрагмента целиком. Если бы мы этого не сделали, то производительность так бы и осталась на прежнем уровне.
А что значит "понять структуру программы"? Каждый исследователь вкла­дывает в эти слова свой смысл в зависимости от стоящей перед ним задачи, сложности и размера кода, своей квалификации и многих других факторов. В разных ситуациях может понадобиться граф вызовов подпрограмм и функций, структура вхождения операторов CALL в отдельных процедурах, детальная информационная структура выбранных процедур или фрагментов, исследование потенциала параллелизма и локальности использования дан­ных, структура взаимодействия подпрограмм через COMMON-блоки и многое другое. Более детально мы поговорим об этих структурах в § 9.2.
548
Часть III. Смежные проблемы и применение
Соответствует ли структура программы особенностям параллельного компью­тера? Если нет, то какой компьютер следует использовать для решения зада­чи? Довольно часто в одной сети бывают доступными параллельные компь­ютеры с совершенно разной архитектурой, среди которых наиболее типичными являются массивно-параллельные и SMP-компьютеры, класте­ры рабочих станций. Какой компьютер разумнее использовать для данной программы? Если решается вопрос о новом параллельном компьютере, то не понятно, какому классу отдать предпочтение. Можно ли определить, на­сколько хорошо структура программы или алгоритма соответствует особен­ностям архитектуры? Нет ли в данной программе фрагментов, которые заве­домо будут узким местом при ее выполнении на данном компьютере?
Ясно, что можно сэкономить много усилий и времени, если перед разработ­кой программы или ее переносом на другую машину знать ответы на по­ставленные вопросы. Можно ли их найти? Рассмотрим фрагмент (9.7).
А = ABS(SMOOPI)                                                                                                             (9.7)
R = А / (А +0.5+ SQRT(A + 0.25)) Т = 1. / (1.0 + R) DO 42 N = 1, 4 DO 10 J = 2, JL DW(1,J,N) = 0. 10 CONTINUE
DO 21 I = 2, IL DO 20 J = 2, JL DW(I,J,N) = DW(I,J,N) - R * (DW(I,J,N) - DW(I - 1,J,N))
20 CONTINUE
21 CONTINUE
DO 30 J = 2, JL DW(IL,J,N) = T * DW(IL,J,N) 30 CONTINUE
DO 41 I = IL - 1 , 2, -1
DO 4 0 J = 2, JL
DW(I,J,N) = DW(I,J,N) - R * (DW(I,J,N) - DW(I + 1,J,N))
40 CONTINUE
41 CONTINUE
42 CONTINUE
A = ABS(SMOOPJ)
R = A / (A +0.5+ SQRT(A + 0.25))
T = 1. / (1.0 + R)
DO 82 N = 1, 4
Глава 9. Пользователь в среде параллелизма
549
DO 50 I =
2, IL
DW(I,1,N)
= 0.
50 CONTINUE
DO 60 I =
2, IL
DO 61 J =
2, JL
DW(I, J,N)
= DW(I
J,N) - R * (DW(I,J,N)
- DW(I,J - 1,N
61 CONTINUE
60 CONTINUE
DO 7 0 I =
2, IL
DW(I, JL,N
= т *
DW(I, JL,N)
70 CONTINUE
DO 80 I =
2, IL
DO 81 J =
JL - 1
2, -1
DW(I, J,N)
= DW(I
J,N) - R * (DW(I,J,N)
- DW(I,J + 1,N
81 CONTINUE
80 CONTINUE
82 CONTINUE
Определим структуру данного фрагмента. Циклы 21, 41, 61, 81— последо­вательные, поскольку каждая итерация зависит от предыдущей из-за ис­пользования рекурсии первого порядка. Остальные циклы являются парал­лельными: два самых внешних цикла имеют по четыре итерации, число итераций других циклов порядка il или jl. Циклический профиль данно­го фрагмента, на котором точками отмечены все циклы ParDO, показан на рис. 9.3.
.—11 ' • UI . IL=. -----■ I | | I—.—11 ■ . JJI . IL^^
Рис. 9.3. Циклический профиль фрагмента (9.7)
Самые внешние циклы очень хорошо подходят для параллельного исполне­ния, т. к. их итерации независимы друг от друга. Единственная проблема — это малое число самих итераций (их всего четыре), поэтому данный вариант приемлем, если речь идет о распараллеливании лишь между 2—4 процес­сорами.
Если переставить местами циклы 60<->61и80<->81, то все внутренние циклы можно сделать параллельными и, следовательно, фрагмент можно полностью векторизовать. Производительность будет зависеть, прежде всего, от величин il и jl: чем они больше, тем производительность выше. Вместе с этим, следует иметь в виду два других фактора. Во-первых, на производи-
550
Часть III. Смежные проблемы и применение
тельности заведомо скажется дисбаланс операций сложения и умножения, т. к. число умножений в данном фрагменте меньше числа сложений. Во-вторых, векторизация будет проводиться как по первому измерению массива dw, так и по второму. Следовательно, чтобы избежать конфликтов при дос­тупе в память, первая размерность этого массива должна быть выбрана, ис­ходя из соответствующих требований архитектуры. В частности, для компь­ютера Cray С90 это может быть любое нечетное число.
Дополнительно, по всем последовательным циклам можно выполнить рас­крутку, используя обращения к одним и тем же векторам на соседних ите­рациях. Например, для цикла 21 вектор dw(i, j,n) на итерации i есть то же самое, что вектор dw(i-i, j,n) на итерации i+i. В табл. 9.2 показана произ­водительность компьютера Cray С90 на данном фрагменте в зависимости от значений il и jl и различной глубины раскрутки последовательных циклов.
Таблица 9.2. Производительность компьютера Cray С90
(и время выполнения в секундах) в зависимости
от значений пили различной глубины раскрутки
последовательных циклов фрагмента (9.7)
Значения переменных
Глубина раскрутки последовательных циклов
IL, JL
-
2
3
5
30,30
227(1,76-Ю"4)
251(1,59-10 "4)
259(1,54-Ю"4)
262(1,53-Ю"4)
150,150
364(2,92-10 _3)
414(2,57-10 _3)
437(2,44-10 _3)
453(2,35-10 _3)
1000,30
300(4,61 -10 -3)
341 (4,06-10-3)
362(3,82-10-3)
364(3,79-10 -3)
1000,150
413(1,73-10-2)
445(1,60-10 _2)
459(1,55-10 _2)
468(1,52-10 _2)
1000,1000
463(1,03-10 "1)
503(9,53-10 "2)
508(9,42-10 "2)
524(9,14-10 "2)
Рассмотрим многопроцессорные конфигурации векторно-конвейерных ком­пьютеров с общей памятью. Если число процессоров не превышает четырех, то можно использовать параллелизм внешних циклов, что практически ни­как не повлияет на производительность каждого процессора в отдельности. Если процессоров больше четырех, то параллелизма внешних циклов недос­таточно и единственная возможность загрузить все процессоры — это до­полнительно разделить внутренние, предназначенные для векторизации, циклы на части: для 8-ми процессоров на две части, для 16-ти процессоров на четыре части и т. д. При этом надо иметь в виду, что деление внутренних циклов на части позволит загрузить все процессоры полезной работой, од­нако производительность каждого процессора неизбежно уменьшится, из-за уменьшения длины векторных операций. Окончательное решение, т. е. раз-
Глава 9. Пользователь в среде параллелизма
551
бивать ли внутренние циклы или нет, зависит только от конкретных значе­ний IL И JL.
Наиболее сложен анализ данного фрагмента для параллельных компьютеров с распределенной памятью. Идеальный вариант заключается в использова­нии параллелизма внешних циклов, т. к. при использовании распределения массива dw по последнему измерению взаимодействий между процессорами во время работы фрагмента не будет совсем. Но точно так же ясно, что дан­ный вариант, в силу своей ограниченности, не будет широко использоваться на практике.
Отойдем от использования параллелизма внешних циклов и рассмотрим общую структуру фрагмента. Его ресурс параллелизма мы уже знаем. Что можно сказать относительно возможных распределений единственного мас­сива dw? Будем исходить из предположения, что весь фрагмент должен вы­полняться параллельно, т. к. параллельная реализация лишь одной полови­ны не может ускорить его выполнение более, чем в два раза. Детальный анализ позволяет определить следующие свойства данного фрагмента:
□  не существует единого распределения массива dw, при котором парал­лельное выполнение данного фрагмента проходило бы без взаимодейст­вия процессоров вообще;
□  координатное распределение массива dw по первому или второму изме­рению лучше, чем любое скошенное распределение;
□  если для всего фрагмента использовать координатное распределение по первому (второму) измерению, то первую (вторую) половину фрагмента процессоры будут вынуждены исполнять последовательно один за другим.
Отсюда следует, что для параллельного выполнения всего фрагмента потре­буется, по крайней мере, одна точка перераспределения массива dw для пе­рехода от одного координатного распределения к другому. Для массивно-параллельных компьютеров с распределенной памятью это очень неприят­ная операция, требующая больших накладных расходов. Однако на практике ситуация может быть еще хуже. Дело в том, что в приложениях, реализуе­мых на компьютерах подобного рода, стараются использовать одинаковое распределение данных в смежных фрагментах и подпрограммах. Если для приложения, содержащего данный фрагмент, это действительно так, то по­требуется как минимум еще одна точка перераспределения, либо в начале фрагмента, либо в его конце. Если данный фрагмент составляет вычисли­тельное ядро всего приложения, то на значительное ускорение работы по сравнению с выполнением на одном процессоре рассчитывать трудно, что полностью подтверждает табл. 9.3. Она содержит времена выполнения дан­ного фрагмента (с двумя точками перераспределения) на массивно-параллельном компьютере Cray T3D с различным числом процессорных элементов и различными значениями il и jl.
552
Часть III. Смежные проблемы и применение
Таблица 9.3. Время выполнения фрагмента (9.7) на массивно-параллельном компьютере Cray T3D с двумя точками перераспределения
Значения
Число процессоров
переменных IL, л.
1
4
16
64
256
30,30
2,39-10 "3
1,56-Ю-3
1,40- Ю-3
2,15-Ю"3
2,47-10 _3
150,150
6,94-10 "2
3,11-10"2
1,05-10 _2
6,35-10 "3
1,04-10"2
1000,30
9,47-10 "2
4,28-10 "2
1,30-Ю"2
7,66-10 _3
1,22-Ю"2
1000,150
0,49
0,23
6,60-10 "2
2,33-10 "2
1,81-Ю"2
1000,1000
3,27
1,52
0,44
0,12
4,85-10 "2
Проведенное исследование показывает, насколько реальной и важной ста­новится возможность априорного исследования качества отображения про­грамм на различные типы архитектур. Мы опять использовали два ключевых объекта технологии: потенциальный параллелизм и локальность использо­вания данных, позволивших определить все необходимые свойства про­граммы.
Можно ли оценить потенциал параллелизма алгоритма, используемого в про­грамме? Во многом на данный вопрос мы уже ответили в предыдущем раз­деле, когда исследовали свойства фрагмента (9.7). Для определения потен­циального параллелизма мы использовали понятие циклов ParDO по графу алгоритма и показывали результат в виде размеченного циклического про­филя. Однако не для всех программ этой информации достаточно. Коорди­натный параллелизм (ParDo) действительно очень важен, но с его помощью нельзя описать весь потенциальный параллелизм программ. Для полной картины необходимо добавить информацию о конечном параллелизме на уровне фрагментов или отдельных операторов и информацию о скошенном параллелизме.
Для изучения конечного параллелизма лучше всего подходит параллельная форма: все множество фрагментов исследуемой программы разбивается на непересекающиеся группы (ярусы) так, что все группы должны выполняться в строгом порядке одна после другой, но все фрагменты, попадающие в од­ну группу, независимы и могут реализовываться параллельно.
Во время анализа одной программы нам встретилась подпрограмма ROT, состоящая из 86-ти одномерных циклов, расположенных подряд друг за другом. Исследование координатного параллелизма было явно недостаточно и нужно было понять, можно ли выполнять отдельные циклы независимо друг от друга или нельзя. Для этого мы воспользовались построенной сие-
Глава 9. Пользователь в среде параллелизма
553
темой V-Ray параллельной формой, показанной на рис. 9.4 (сама подпро­грамма ROT весьма объемна, поэтому ее текст мы здесь не приводим).
о
iS
IS
о
&
iS
iS
о
IS
о
Si
$
о
S!
о
iS
S>
s>
!S
S>
S>
IS
iS
о
iS
iS
К S) s> о iS S! о
iS
о
s>
о •
о
S
iS
&
iS
&
S>
о
s
iS
iS
iS
& iS О iS iS IS &
!S
о
iS
о
S>
iS
s>
iS
5)
s>
о
t3
iS iS S> S>
iS
IS
о
iS
iS
s>
о
iS
о
S
о
iS
о
о
IS
Рис. 9.4. Параллельная форма подпрограммы ROT
Каждый цикл данной подпрограммы обозначен отдельной вершиной и все множество вершин расположено по ярусам, каждый ярус лежит на своей вертикальной линии. Все ярусы должны выполняться последовательно друг за другом слева направо, но все вершины, лежащие на одном ярусе, незави­симы и могут реализовываться параллельно.
Одновременно с построением параллельной формы мы пометили каждую вершину, являющуюся циклом ParDO, перечеркнув ее наклонной чертой. Для получения полной информации о параллельных свойствах данной под­программы осталось исследовать структуру тех циклов, которые не помече­ны как циклы ParDO. Детальный анализ средствами системы V-Ray показал, что все они очень похожи и устроены примерно так:
DO 3700 1 = 501, 2500
3700      F(l) = F(l) + F(l-500)
Каждая итерация по i от iooi до 2500 зависит от итерации с номером 1-500, поэтому данный цикл, конечно же, не обладает свойством ParDO. С другой стороны, данный цикл (как и остальные циклы, не помеченные как ParDo) может быть тривиально преобразован в последовательность из четырех цик­лов так, что все новые циклы должны исполняться последовательно друг за другом, однако каждый из них уже обладает свойством ParDo:
DO 3701 1 = 501,1000
3701   F(l) = F(l) + F(l-500) DO 3702 1 = 1001,1500
554
Часть III. Смежные проблемы и применение
3702   F(l) = F(l) + F(l-500) DO 3703 1 = 1501,2000
3703   F(l) = F(l) + F(l-500) DO 3704 1 = 2001,2500
3704   F(l) = F(l) + F(l-500)
После подобного исследования мы получили исчерпывающую информацию о параллельных свойствах исходной подпрограммы: конечный или макропа­раллелизм виден из параллельной формы, а параллелизм на уровне итера­ций циклов понятен как из разметки ParDO, так и из последнего детального исследования информационной структуры отдельных циклов. В итоге весь параллелизм, которым обладает подпрограмма, а значит и лежащий в ее ос­нове алгоритм, нам известен.
Что следует из перечисленных примеров? Всем, кто хотя бы раз пытался на­писать действительно эффективную программу для параллельного компью­тера, часть изложенных вопросов наверняка хорошо знакома. Подобные во­просы возникают в процессе его использования практически постоянно и находить на них ответы приходится всем. Это не проблемы конкретного пользователя, а общие проблемы, характерные для параллельных вычисле­ний в целом. Попробуем критически проанализировать только что изло­женный материал и выделить те основные положения и факты, на которых базировался проводимый в каждом случае анализ.
Отвечая на вопросы данного параграфа, мы практически всегда опирались на знание точной информационной структуры исследуемого фрагмента. С эф­фективностью выполнения фрагмента (9.1) было что-то не так, поэтому сначала мы определили его потенциальные свойства, поняли, какие резервы есть в нашем распоряжении, и лишь затем, согласуясь с особенностями це­левого компьютера, приняли решение о преобразовании фрагмента. Только знание точной информационной структуры позволило нам оценить возмож­ность реализации фрагмента (9.7) на параллельных компьютерах с совер­шенно разной архитектурой и описать весь ресурс параллелизма подпро­граммы ROT, параллельная форма которой показана на рис. 9.4.
Чем точнее определяется информационная структура программы, тем лучше. Тезис абсолютно правильный, однако на практике сам по себе он мало что дает пользователям. Нужна математическая гарантия результатов исследова­ния, без которой никогда не будет уверенности в качестве результатов оптими­зации программ, да и сам процесс оптимизации скорее будет похож на "блуждание в потемках", чем на обоснованную последовательность действий.
Если на вопрос пользователя, обладает ли цикл, например, свойством ParDO, анализатор ответил "нет", что это значит? Возможны два варианта: данный цикл действительно не является циклом ParDO или же используемые анали-
Глава 9. Пользователь в среде параллелизма
555
затором методы не смогли определить истинной информационной структу­ры цикла, и для безопасности он дал отрицательный ответ. А что делать пользователю? Поверить или попробовать исследовать свою программу другими средствами? В любом случае пользователь должен точно знать, по­чему было сказано "нет".
Вспомним один из предыдущих вопросов — можно ли выполнить распреде­ление циклов фрагмента (9.5) для их последующей раскрутки? Традицион­ный ответ "нет", объединяющий "нет" и "не знаю", абсолютно бесполезен для пользователя. Исследование фрагмента (9.7) показало, что для него не существует единого распределения данных, при котором отсутствовали бы пересылки данных между процессорами. Это "нет" может быть доказано, является неотъемлемым свойством данной программы, и поэтому никакой другой анализатор не даст иного ответа. Увидев отрицательный ответ, мож­но пойти дальше, чтобы определить и ликвидировать причину. В обсуждае­мом примере после выполнения анализа и дополнительного преобразования этот ответ, по существу, был сведен к "да".
Другой обязательной составной частью технологии, без которой немыслима оптимизация программ для параллельных компьютеров с распределенной памятью, является согласование потенциального параллелизма с распределением данных. Несмотря на простоту формулировки, данное положение предпола­гает решение целого ряда сложных задач: определение потенциального параллелизма, деление массивов на распределяемые, дублируемые и локаль­ные, нахождение распределений без пересылок данных, определение воз­можных точек перераспределения данных и т. п.
Со многими из перечисленных задач, но в достаточно простой форме, мы уже встречались, когда обсуждали возможность реализации фрагмента (9.7) на различных параллельных архитектурах. Единственный массив dw должен быть распределен. Распределение, согласованное с параллелизмом внешних циклов, при котором нет пересылок между процессорами, существует, одна­ко более четырех процессоров эффективно использовать нельзя. Любое скошенное распределение приведет, во-первых, к очень неравномерной за­грузке процессоров и, во-вторых, к взаимодействию процессоров на каждой итерации. Для использования координатного распределения потребуется по крайней мере одна точка перераспределения массива dw и т. д.
Следует отметить, что исследование множества возможных распределений данных программы необходимо не только для компьютеров с распределен­ной памятью, но и для эффективного использования кэш-памяти. В самом деле, для распределения массивов по процессорам мы делим массивы на блоки так, что основная масса ссылок на каждом процессоре является ло­кальными ссылками к данным своего блока. Следовательно, данные каж­дого блока будут автоматически попадать в кэш, если порции вычислений,
556
Часть III. Смежные проблемы и применение
предназначенные отдельным процессорам, выполнять последовательно друг за другом.
Очень часто из программы приходиться буквально выжимать все резервы параллелизма. В этом случае уже нельзя ориентироваться на исследование какого-либо одного типа параллелизма, например, выделение только циклов ParDO. Необходим более сложный анализ структуры программы, показы­вающий весь ресурс параллелизма программы в терминах параллелизма по дан­ным и параллелизма по вычислениям. Параллелизм по данным чаще всего соответствует параллелизму итераций циклов, а параллелизм по вычислени­ям — макропараллелизму на уровне отдельных фрагментов.
Оба вида параллелизма тесно связаны между собой. Полная картина потен­циала параллелизма всей программы образует иерархию: на самом внешнем уровне есть несколько независимых ветвей вычислений (параллелизм по вычислениям), каждая ветвь содержит циклы, часть из которых параллель­ные (параллелизм по данным), в теле каждого цикла можно опять выделить независимые ветви вычислений, в которых опять содержатся циклы, и т. д. Примерно по такой схеме мы исследовали структуру подпрограммы ROT, для которой параллелизм по вычислениям описывали с помощью парал­лельной формы, а параллелизм по данным разметкой циклов ParDO.
Какой вид параллелизма, на каком уровне и в каком виде будет использо­ваться, зависит от особенностей архитектуры целевого параллельного ком­пьютера и размера исходной задачи. Отдать предпочтение тому или иному виду параллелизма априори достаточно сложно, однако, располагая полной информацией о параллельной структуре фрагмента, можно всегда принять правильное решение в каждом конкретном случае.
Говоря об иерархии в описании потенциального параллелизма программы, становится очевидной необходимость единого подхода к анализу как простых, так и сложных фрагментов. Технология не должна ориентироваться на ка­кой-либо фиксированный набор структур циклов или графов управления, например, только на тесновложенные гнезда циклов или на ациклические графы. В разных ситуациях бывает необходимо исследовать либо всю про­граммную единицу целиком, либо самые внешние циклы, либо только их тела, либо структуру самых внутренних циклов.
Идея иерархичности пронизывает технологию анализа и преобразования программ практически на всех этапах. Анализ фрагментов программы на любом уровне вложенности циклов, иерархическое описание параллелизма в терминах параллелизма по вычислениям и параллелизма по данным, ана­лиз в терминах фрагментов различной сложности (оператор, линейный уча­сток, одиночный цикл, совокупность циклов), преобразование или описа­ние потенциала параллелизма отдельного фрагмента на любом уровне
Глава 9. Пользователь в среде параллелизма
557
вложенности — эти и многие другие действия должны применяться к мак­симально широкому классу фрагментов, вне зависимости от их расположе­ния в тексте программы.
Даже поверхностное знакомство с задачами, появляющимися в процессе оптимизации программ для параллельных вычислительных систем, пока­зывает насколько они сложны и разнообразны. В одном случае нужно ис­следовать структуру графа управления, в другом информационную зависи­мость между операторами или отдельными фрагментами, в третьем проводить анализ локальности данных, в четвертом определять возмож­ность выполнения того или иного преобразования и т. д., и т. д. Трудно рассчитывать на то, что можно создать полностью автоматическое средст­во анализа и преобразования одинаково эффективное для всего комплекса потенциальных задач и всего класса программ, написанных просто в соот­ветствии с правилами входного языка. Естественный выход из данной си­туации заключается в том, что в технологии и построенных на ее основе инструментальных средствах должна быть предусмотрена возможность организации исследования в любом режиме — от полностью автоматического преобразования "текст—текст" до интерактивного режима с получением максимально детальной информации. В зависимости от задачи исследова­ния (оценить потенциал параллелизма, выбрать целевой компьютер, пере­нести программу на новую платформу и т. п.), сложности программы и уровня своей подготовки пользователь должен иметь возможность выбрать подходящий для него режим.
Включение интерактивного режима накладывает серьезные требования на организацию программных средств, поддерживающих этот режим. Чаще всего этот режим будет использоваться тогда, когда в программе пользовате­ля что-то не так. Например, он не может понять, почему нельзя выполнить некоторое преобразование или почему система говорит о наличии инфор­мационной зависимости, хотя с его точки зрения ее быть не должно. Для этого необходимы мощные средства визуализации структуры программы, под­держивающие все этапы анализа и помогающие пользователю лучше разо­браться с особенностями его же программы. В частности, если он не может понять, почему нельзя выполнить явное распределение циклов фрагмента (9.5), то он должен иметь возможность простыми средствами получить изо­бражение структуры данной циклической конструкции, например, так, как показано на рис. 9.2.
Похоже, что сложность предстоящего пути мы обрисовали. Однако не стоит пугаться всего того обилия проблем, которое обсуждалось в данном парагра­фе. Во-первых, далеко не со всеми из них вам сразу придется столкнуться. А, во-вторых, если и придется, то теперь вы знаете, как нужно действовать.
558
Часть III. Смежные проблемы и применение
Вопросы и задания
1.   Как в программе найти те фрагменты, на которые приходится основное время вычислений?
2.   По каким признакам можно выделить наиболее значимые (с точки зрения вре­мени выполнения) фрагменты на основе только статического анализа?
3.   Приведите пример фрагмента программы, для которого формальное применение перестановки циклов невозможно, однако выделение небольшой порции итера­ций в отдельную конструкцию приводит к искомому результату.
4.   Какие графовые конструкции могут помочь при анализе структуры больших программных комплексов?
5.   Возьмите любую работающую параллельную программу, на которой реальная производительность компьютера сильно отличается от пиковой. Попробуйте спланировать ее исследование, чтобы максимально быстро найти причину низ­кой эффективности. Какие программные средства могут помочь в этой работе? Какие характеристики и свойства программы нужно уметь определять?
6.   Определите зависимость времени работы фрагмента (9.7) от способа распределе­ния массива DW и значений переменных IL и JL.
7.   *Разработайте методику исследования структуры программы, состоящей из де­сятков и сотен тысяч строк кода. Какие программные системы могут помочь ре­шить эту задачу?
§ 9.2. Программный сервис в параллельных вычислениях
Сложных вопросов при использовании параллельных вычислительных сис­тем в самом деле возникает много. Нам приходилось работать в рамках многих проектов на большом числе параллельных компьютеров, и практи­чески всегда перед нами стояла задача — помощь в создании действительно эффективных параллельных программ. Конечно же, помощь нужна только тогда, когда есть какие-то проблемы: если нет проблем, то нет и вопросов. Однако интересно то, что сама проблема каждый раз формулировалась пользователями практически одинаково: "Что-то не так с эффективностью моей программы".
Иногда проблема снималась легко — достаточно было, например, разо­браться в документации по работе с системой или компилятором (вспом­ните эпиграф к данной главе). Иногда приходилось довольно глубоко вни­кать в суть решаемой задачи. Чем дольше мы работали в этой области, тем отчетливей проявлялась многогранность исходной проблемы. Стало ясно, что мы уже не можем ограничиваться решением одной конкретной задачи, скорее речь должна идти о создании целой инфраструктуры, сопровождающей большинство прикладных работ на суперкомпьютерах.
Глава 9. Пользователь в среде параллелизма
559
В самом деле, давайте посмотрим, на какие составные части может разби­ваться исходная задача, и почему нужен комплексный подход к анализу си­туации в каждом конкретном случае. Если "с программой что-то не так", то, как показала практика, проблемы могут возникать на самых разных уровнях:
АНАЛИЗ КОНФИГУРАЦИИ КОМПЬЮТЕРА
I
АНАЛИЗ ЭФФЕКТИВНОСТИ СИСТЕМНОГО И ПРИКЛАДНОГО ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ
АНАЛИЗ ПРИКЛАДНОЙ ПРОГРАММЫ
I
АНАЛИЗ АЛГОРИТМИЧЕСКОГО ПОДХОДА
Начинать исследование, чаще всего, приходилось с анализа конфигурации конкретного компьютера: тип и число процессоров, уровни и объем памяти, параметры и топология коммуникационной среды, особенности ввода/ вывода и т. п. Даже эти, казалось бы, простые и очевидные вещи, на прак­тике могут оказаться причиной вопросов пользователей. В свое время мы разрабатывали программу для векторно-конвейерного компьютера Cray С90. Когда программа была закончена и передана пользователю, он после не­скольких запусков на своем компьютере стал утверждать, что "с программой что-то не так", т. к. время ее работы было где-то на 30% больше заявленного нами. Детальный анализ показал, что истинная причина крылась вовсе не в программе: процессор второго компьютера, в отличие от первого, имел лишь три канала, а не четыре, как предписано стандартной конфигурацией. С программой было "все так" и в рамках той конфигурации она работала идеально.
Следующий большой срез — это анализ эффективности системного и приклад­ного программного обеспечения. У многих когда-либо были нарекания на работу штатных компиляторов, поэтому проблемы данного пункта хорошо знакомы. Однако далеко не всегда удается столь легко найти виновника бед пользовате­лей и тогда приходится проводить детальный анализ характеристик про­граммного окружения. Когда в середине 90-х годов мы писали программу для компьютера Cray T3D, то обнаружили крайне низкую эффективность ряда коммуникационных процедур текущей реализации системы PVM, включен­ной в состав штатного программного обеспечения. Проблема с программой? Да нет, опять-таки далеко не в самой программе дело...
Для облегчения исследования эффективности программного окружения конкретного компьютера разработано целое множество тестов и бенчмарков. Многие вопросы, связанные с данной проблемой, мы уже обсуждали в § 3.6.
560
Часть III. Смежные проблемы и применение
Основная задача в рамках анализа прикладной программы состоит в поиске ответа на вопрос: "Можно ли не меняя алгоритма улучшить эффективность программы?" Занимаясь исследованиями в данном направлении в течение двух десятков лет, мы разработали и используем на практике комбинацию методов статического и динамического анализа. На основе проведенных ис­следований создана и успешно апробирована многоцелевая эксперимен­тальная система — V-Ray, предназначенная для изучения структуры больших программных комплексов и адаптации этих комплексов к требованиям це­левых компьютеров с параллельной архитектурой. Ее особенность заключа­ется в том, что впервые система анализа и адаптации программ под архитек­туру параллельных компьютеров базируется на использовании истории реализации программ. В отличие от существующих методов, предлагаемый подход опирается на компактное параметрическое описание информацион­ной истории реализации программы, прогнозируя динамику поведения программ по их статической форме — исходному тексту. Несмотря на ка­жущийся недостаток информации, разработанные методы, в большом числе случаев, не только дают максимально точный ответ, но и позволяют гаран­тировать неулучшаемость полученных результатов. Эта проблематика обсуж­далась во многих параграфах данной книги.
И, наконец, если анализ приложения показал, что структура программы не соответствует особенностям архитектуры компьютера, то проблемы эффек­тивности исходной программы кроются в свойствах используемых алгорит­мов. Единственное, что остается — это перейти к алгоритмическому анализу. Возможно, что в результате исследований этого этапа придется поменять алгоритм и полностью переписать некоторые части программы. В ряде слу­чаев другого пути просто нет.
Как мы видим, проблема анализа и повышения эффективности параллельных программ — комплексная. Именно поэтому мы и говорили не о каком-то од­ном средстве, системе или методе для ее решения, а о целой инфраструктуре. В полной мере это касается и программного обеспечения. Понимая сложность проблемы и острую необходимость в разного рода средствах, к настоящему мо­менту разработано немало вспомогательных систем, входящих в состав штатного программного обеспечения компьютеров. Компиляторы, отладчики, анализато­ры, конверторы, профилировщики — всем этим богатством нужно пользовать­ся, если есть желание грамотно использовать уникальные возможности парал­лельных вычислительных систем. Насколько это реально для вас, насколько развит программный сервис на доступном вам компьютере — это вопрос к сер­висной службе и администраторам вашей системы.
Как же помочь пользователю максимально быстро разобраться со структурой исследуемой программы и локализовать фрагменты, требующие аккуратного детального анализа? Мы попробуем рассказать об общих подходах к решению данной задачи, иллюстрируя конкретные шаги на примере системы V-Ray.
Этот материал можно рассматривать и как руководство при анализе кон­кретных программ, и как описание необходимой функциональности проек-
Глава 9. Пользователь в среде параллелизма
561
тируемых систем для анализа структуры программных комплексов. Обсуж­даемые структуры не являются чем-то сугубо теоретическим. Скорее наобо­рот, они продиктованы практикой и возникли в процессе работы с реаль­ными программами. Большая часть этого материала реализована в системе V-Ray и прошла апробацию во многих проектах.
Изложение будет вестись в соответствии с общей стратегией анализа: от об­суждения общих структур будем постепенно переходить к все более и более детальным. Сначала рассмотрим формы представления структуры програм­мы на межпроцедурном уровне, затем перейдем к макроструктуре отдельных процедур, и затем — к микроструктуре на уровне отдельных срабатываний операторов.
Граф вызовов процедур
Удачно организованный процесс исследования общей структуры программы во многом помогает проведению качественного детального анализа инфор­мационной структуры. Пользователь сразу видит состав процедур, распреде­ление циклов в процедуре, все особенности ее графа управления, повто­ряющиеся фрагменты с одинаковой структурой и многое другое, что в дальнейшем может оказаться очень полезным для ее преобразования.
Простейшая форма графа вызовов процедур является традиционным объек­том, помогающим получить первое представление о структуре программы. Каждой программной единице в графе вызовов соответствует отдельная вершина, причем вершины А к В соединяются направленной дугой А -> В только в том случае, если процедура А содержит вызов процедуры В.
Первую информацию о структуре программы можно получить из формы графа вызовов. В самом деле, если в некоторую вершину X входит много дуг, то данная процедура вызывается из многих мест программы. Если, кро­ме этого, вершина X является листовой, то от эффективности реализации данной процедуры может во многом зависеть и эффективность всей про­граммы. Если вершина содержит много исходящих дуг, то в теле такой про­цедуры есть много обращений к другим программным единицам, поэтому для анализа параллелизма на верхнем уровне нужен более детальный анализ именно этой процедуры.
Изменение способа изображения графа вызовов
Практика показала, что все программные проекты обладают своими специ­фическими особенностями, отражающимися в структуре графа вызовов. В одних проектах есть вершины с большим числом входных или выходных дуг, а в других подобных явно выраженных "центров" нет. В одних граф вы­зовов имеет вид дерева, а в других часть путей многократно пересекается.
Указанную специфику проектов нужно хоть каким-то образом отразить в изображении графа. Трудно рассчитывать на то, что существует один уни-
562
Часть III. Смежные проблемы и применение
версальный способ изображения подобного рода объектов, позволяющий в каждом случае получить наглядное и понятное изображение. По этой при­чине нужно поддерживать целый набор методов изображения графов вызо­вов, чтобы в каждом конкретном случае пользователь мог выбрать тот из них, который с его точки зрения наиболее пригоден для анализа.
На рис. 9.5 показаны три варианта изображения одного и того же графа вы­зова программы ARC2D из пакета Perfect Club Benchmarks. Даже для столь небольшого примера заметна разница в восприятии структуры проекта.
Рис. 9.5. Три варианта изображения одного и того же графа вызовов процедур (начало)
Глава 9. Пользователь в среде параллелизма
563
(scaldt)
QO
( tkinv )
( specf
(ypenta (ypent2
(xpenta (xpent2>* (xpentp (xpenp?
( verify )
(elapse)
calcps)
( filery
calcme) xidif ) etadif)
( filerx
(cputim)
Рис. 9.5. Три варианта изображения одного и того же графа вызовов процедур (окончание)
Расширенный граф вызовов процедур
Основной недостаток графа вызовов заключается в том, что по выходящим дугам нельзя определить ни общего числа вызовов каждой конкретной про­цедуры (между любыми двумя вершинами в классическом графе вызовов не может быть кратных дуг), ни порядка, в котором вызовы следуют по тексту программы. Чтобы решить эту проблему, система поддерживает построение расширенного графа вызовов. Данный объект сложнее обычного графа вызо­вов, но он свободен от указанных недостатков. На рис. 9.6 показан пример расширенного графа вызовов одной из анализируемых программ.
Разметка вершин графа вызовов
Для быстрого определения многих особенностей программы можно исполь­зовать разметку вершин графа. В частности, система сразу отмечает началь­ные вершины, т. е. те программные единицы, которые сами ниоткуда не вызываются. Обычно такая вершина в анализируемом проекте только одна, что соответствует единственной главной части программы. Однако, если анализируется, например, структура вызовов некоторой библиотеки, то чис­ло начальных вершин будет больше.
564
Часть III. Смежные проблемы и применение
С помощью этой же разметки можно сразу найти те процедуры, которые когда-то были включены в проект, но по какой-либо причине сейчас не ис­пользуются — они также будут помечены как начальные. В одном из проек­тов, содержащем около 490 процедур, таким образом было обнаружено бо­лее 80 (!) "забытых" и, следовательно, неиспользуемых подпрограмм.
:
:
semsumsemsum
А                А
:
Ш II Ш |Г
symd
symq
Ж
cabs2
test
Ж
:
Ш
С
semsum
Ж
energ2
Ж
nonlin energy curlbl аМ
vb1 sumal curlb2
Ж           Ж            Ж
i_t
I                 I С
timest
Ж
OOOOOOOOOOOOOOOOOOOOO
:
right
ж
iterat error new
ж                ж               Ж
|
00000
|
step
mhd2
Рис. 9.6. Расширенный граф вызовов процедур
По разметке вершин графа вызовов можно получить первую информацию о возможных наиболее значимых подпрограммах. В самом деле, разметим все вершины согласно максимальной глубине вложенности содержащихся в каждой из них циклических конструкциях. Для многих программ выпол­няется правило: чем больше глубина вложенности, тем больше времени приходится на соответствующий фрагмент. Ясно, что это ни в коей мере не является законом, однако позволяет лучше понять структуру анализи­руемой программы. На рис. 9.7 показан размеченный подобным образом граф вызовов программы FL052Q, также входящей в состав пакета Perfect Club Benchmarks. Кстати, для нее на подпрограммы с максимальной глу-
Глава 9. Пользователь в среде параллелизма
565
биной вложенности циклических конструкций действительно приходится основное время.
Рис. 9.7. Размеченный граф вызовов
Разметку вершин графа можно применять в самых разных целях. В частно­сти, не всегда исходный текст всех процедур, показанных на графе вызовов, доступен для анализа. Типичный пример — это использование внешних библиотек. Но действительно ли они используются, можно легко выяснить с помощью соответствующей разметки графа. На основе этого станет по­нятно, останутся ли после анализа в программе "темные" места или же программу можно будет исследовать целиком.
Исключительно важной характеристикой многих программ является организа­ция операций ввода/вывода, которые имеют две отличительные особенности:
□  операции ввода/вывода намного медленнее арифметических;
□  операции ввода/вывода во многих случаях препятствуют оптимизации процесса вычислений.
566
Часть III. Смежные проблемы и применение
Поскольку ввод/вывод может сильно повлиять на скорость выполнения анализируемой программы, нужно уметь определять, каким образом опера­торы ввода/вывода распределены среди программных единиц проекта, для чего опять можно использовать механизм разметки вершин графа.
Циклический профиль путей
Для более точного анализа важно знать не только глубину вложенности цик­лических конструкций в процедурах, но и уметь оценивать общее число объ­емлющих циклов, в телах которых вызывалась каждая процедура. Для этого V-Ray строит циклический профиль путей, в котором отражена вся последова­тельность циклов для каждого пути графа вызовов. По существу строится, ис­следуется и описывается все множество путей графа вызовов. Ниже показаны фрагменты циклического профиля путей некоторого проекта:
10 : (GCMA3 —?—> DYNIMP1 l-^HHSOLV3 l-^GAUSPH2 l-^SINGL5), (9.8) 8 : (GCMA3 —?—> RADFL24 —!—> RADFSW3 —!—> VARP84)—^—> DELED31, 7 : (GCMA3 —?—> DYNIMP1 —!—> DYNADD1 —5—> MMATR24).
Конструкция вида Xa —*—» 7P, используемая в качестве основного элемен­та каждого пути, означает следующее: процедура X вызывает процедуру Y в у-мерном цикле, причем максимальная глубина вложенности циклов в са­мих процедурах X и Y равна аир соответственно. Если X содержит не­сколько вызовов Y, то на месте у будет указано максимальное значение.
Ясно, что интерес представляет как значение а, так и у + р. Самая левая колон­ка циклического профиля (9.8) показывает максимальную суммарную вложен­ность циклов по каждому пути, а скобками отмечена та цепочка пути, на кото­рой достигается указанное значение. Поскольку все пути отсортированы по убыванию данного суммарного показателя, среди всех путей можно сразу выде­лить наиболее "тяжелые" в качестве кандидатов для детального анализа.
Граф использования общей памяти
Чтобы оценить возможное взаимодействие процедур проекта через объекты общей памяти (для языка Fortran — это COMMON-блоки, для С — глобальные переменные), можно построить граф использования общей памяти. Для языка Fortran этот граф можно представлять двудольным графом, в ко­тором одно множество вершин составляют процедуры, а другое — COMMON-блоки. Вершины разных множеств соединяются дугой тогда и только тогда, когда соответствующая процедура содержит в своем теле соот­ветствующий COMMON-блок. Можно расширить функциональность дан­ной формы представления, позволяя детализировать состав каждого блока и анализировать способ работы с каждым объектом COMMON-блока, т. е. отмечать использование и/или изменение составляющих его переменных.
Глава 9. Пользователь в среде параллелизма
567
Граф управления и комбинированные структуры
Основной используемый на практике метод представления структуры процеду­ры использует структуру вложенности циклов. Для того чтобы упростить про­цесс анализа и не опускаться сразу на уровень отдельных операторов, каждой процедуре ставится в соответствие иерархическая система графов. Граф верхнего уровня показывает структуру процедуры в целом и состоит из вершин двух ти­пов: вершины одного типа соответствуют самым внешним циклам процедуры, а вершины другого — линейным участкам между ними. Линейным участком будем называть такую последовательность операторов, в которой только первый опе­ратор может иметь метку и только последний оператор может нарушать после­довательный характер выполнения операторов. В любом из этих графов две вершины А а В связываются направленной дугой А -> В тогда и только тогда, когда фрагмент В может быть выполнен сразу после фрагмента А, т. е. данный вид графа является модификацией классического графа управления.
Каждый из циклов может представлять интерес для самостоятельного ана­лиза, поэтому нужно поддерживать построение аналогичных графов на всех последующих уровнях, детализируя структуру составных вершин вплоть до отдельных линейных участков. Данный способ представления структуры программы назовем иерархическим или L-деревом.
На рис. 9.8 сверху показан пример графа первого уровня. Каждая вершина с петлей представляет один внешний цикл процедуры. Число внутри показывает глубину вложенности циклов данной конструкции, что помогает в первом при­ближении оценить значимость каждого фрагмента. Ниже показана структура тела цикла той же процедуры (граф второго уровня), помеченного цифрой 3 в верхней части рисунка. Совершенно аналогично можно построить граф для любого цикла процедуры.
Для проведения более детального анализа можно использовать две анало­гичные формы представления структуры программы. В первой все макро­вершины текущего фрагмента раскрываются вплоть до отдельных линейных участков, а во второй — до отдельных операторов. Эти способы представле­ния активно используются во время исследования тонкой информационной структуры отдельных фрагментов программ.
Циклический профиль процедуры
С одной стороны, иерархический способ представления структуры програм­мы позволяет скрыть массу излишних подробностей. Однако с другой, для того чтобы понять внутреннюю структуру макровершины, надо последова­тельно просмотреть графы на всех подуровнях. На внешнем уровне мы ви­дим только максимальную глубину вложенности циклов данного фрагмента, но не знаем ни сколько всего циклов он содержит, ни каким образом они распределены внутри него.
568
Часть III. Смежные проблемы и применение
ш.
U CD
I
3
I
00 О)
о
S
о.
Глава 9. Пользователь в среде параллелизма
569
Для того чтобы облегчить восприятие циклической структуры фрагмента, система поддерживает построение циклического профиля. Каждый цикл те­кущего фрагмента на циклическом профиле условно изображен в виде гори­зонтальной скобки. Для каждого графа в нижней части рис. 9.8 показаны циклические профили исследуемых фрагментов. Заметим, что если иерархи­ческий способ рассматривать как взгляд на программу сверху, то цикличе­ский профиль можно интерпретировать как боковой срез.
Разметка графа и циклического профиля
Как и для графов вызовов процедур, самым простым способом определения свойств фрагментов программы является разметка вершин графа процедуры. Система выполняет анализ и помечает все вершины графа разными цветами в зависимости от того, выполняется ли заданное свойство для вершины или нет.
Предположим, что мы хотим проверить, есть ли до to-циклы в исследуемой процедуре. Известно, что до to-циклы, обладая нерегулярной структурой, часто являются серьезным препятствием для детального анализа свойств фрагмента. Если пользователь сделал такой цикл самым внутренним цик­лом, то трудно будет анализировать и все объемлющие циклы. Если go to-цикл внешний, то на первом этапе его анализ можно не выполнять, сосре­доточившись на хорошей реализации его тела. В любом случае первый шаг — это определение положения всех до to-циклов в данной процедуре, и проще всего это сделать с помощью разметки.
Аналогично можно разметить и циклический профиль процедуры. В этом случае, чтобы разметить весь профиль, кроме вершин текущего графа, сис­тема проверит, выполняется ли выбранное свойство у вершин всех после­дующих уровней. Наиболее активно разметка вершин и профиля использу­ется при отображении свойства ParDO циклов программы.
Процедурный граф управления
Если в графе управления некоторого фрагмента оставить только те верши­ны, которые содержат обращения к другим подпрограммам и функциям, то получим редуцированную форму графа управления, называемую процедур­ным графом управления. В отличие как от графа вызовов, так и от расширен­ного графа вызовов, данная форма представления содержит информацию о том, зависят ли срабатывания каждого вызова от срабатывания альтернатив­ных операторов или нет.
Пример такой конструкции показан на рис. 9.9. По данному графу легко опре­делить, что подпрограмма DFLUXC вызывается в зависимости от выполнения условного оператора, а вызов подпрограммы EFLUX происходит всегда.
570
Часть III. Смежные проблемы и применение
щ<------
г* *"
-J
Hi
4t
il
—*и
Щ
X
-\
q= *■ ■о
1— га —' б
£1
-\
Го"
н -JJ
m
X
-1
CO
§---------*
т ]
■&
СП Q.
1
CD
3-
Ш
i
s
■&
CO Q.
3 о
CO Q.
I
Глава 9. Пользователь в среде параллелизма
571
Расширенный граф вызовов с циклическим профилем
Практически полная картина как о структуре вызовов, так и о циклической структуре анализируемого программного комплекса может быть получена с помощью комбинации расширенного графа вызовов с циклическим профи­лем каждой процедуры. В этом случае, за основу берется расширенный граф вызовов и в каждую вершину "вписывается" ее циклический профиль. По­добная структура показана на рис. 9.10.
Циклические профили, использованные в данной форме представления, могут быть дополнительно размечены в соответствии с некоторым свойст­вом. В частности, на рис. 9.10 помечены все циклы ParDO.
Локализация наиболее значимых частей кода
Итак, каким же образом можно локализовать наиболее значимые части ана­лизируемой программы? Под "наиболее значимыми" будем понимать те фрагменты программы, на которые приходится значительная часть общего времени выполнения. Понятно, что именно эти части программы надо ана­лизировать в первую очередь.
Методы решения данной задачи можно разделить на две большие группы: методы статического и динамического анализа. Все структуры и действия, поддерживающие статическое определение наиболее значимых частей кода, мы уже обсуждали, поэтому сейчас лишь соберем полученную информацию вместе. Совместный анализ
□  размеченного графа вызовов процедур,
□  циклического профиля путей,
□  циклического профиля процедур,
□  процедурного графа управления,
□  расширенного графа вызовов с циклическим профилем
позволяет выделить наиболее значимые части программ с достаточно высо­кой точностью. Без подобных методов не обойтись, т. к. далеко не всегда есть возможность выполнить программу на целевом компьютере. Не всегда в распоряжении исследователя есть программа целиком и возможно для анализа ему предоставлен лишь какой-то фрагмент. В конце концов для программы может не быть реальных входных данных, без чего ее выполне­ние часто либо невозможно в принципе, либо бессмысленно.
Динамическое профилирование программ
Если все же есть возможность выполнить программу, то полученную на эта­пе статического анализа информацию о наиболее значимых частях програм-
572
Часть III. Смежные проблемы и применение
мы можно уточнить данными профилирования. Система поддерживает все основные этапы профилирования, начиная с разметки исходного текста и заканчивая визуализацией результатов.
Конкретный способ разметки исходного текста зависит от двух параметров. Первый — в каких терминах проводить профилирование. Поддерживается несколько стандартных режимов: профилирование процедур, самых внеш­них циклов, циклов с заданной глубиной вложенности, операторов вызовов процедур и функций и некоторые другие.
Второй параметр управляет тем, какую информацию собирать: интеграль­ную, когда не имеет значения, каким образом мы дошли до данного фраг­мента или подробную, когда информация собирается отдельно по каждому возможному пути от начальной точки программы до данного фрагмента. Например, подпрограммы А к В вызывают подпрограмму С. В первом слу­чае вся собираемая по С информация ассоциируется только с С, а во втором она распределяется между путями А -> С и В -> С.
В процессе профилирования система по умолчанию определяет пять вели­чин: общее, среднее, минимальное и максимальное времена работы фраг­мента и общее число обращений к данному фрагменту.
Для отображения результатов профилирования можно использовать либо специальные средства (разного рода диаграммы и гистограммы), либо соот­ветствующую разметку обсуждавшихся выше структур. Использование рас­ширенного графа вызовов позволяет отображать результаты профилирова­ния отдельно по каждому пути.
Информационный граф и макроструктура программ
Следуя общей методике анализа программ, исследование информационной структуры процедуры можно разделить на два этапа: макро- и микроанализ. На первом этапе определяется зависимость между частями исходного текста процедуры, т. е. циклами, линейными участками либо операторами. Второй этап предполагает детальное исследование зависимостей фрагмента на уровне отдельных итераций циклов. На этапе макроанализа мы определяем взаимо­связь элементов процедуры на макроуровне и одновременно локализуем фрагменты, требующие детального исследования на уровне пространства ите­раций, т. е. микроанализа.
Основной инструмент для исследования информационной структуры про­цедуры на макроуровне — это информационный граф. Вершинам графа со­ответствуют фрагменты текста, которые связываются направленной дугой тогда и только тогда, когда одна вершина (фрагмент) вычисляет данные для другой. Дуга направлена от вершины, производящей данные, к вершине их использующей. Ясно, что построенный таким образом граф содержит толь-
Глава 9. Пользователь в среде параллелизма
573
ко истинные зависимости и, следовательно, позволяет определить потенци­альный параллелизм процедуры на макроуровне.
Независимые фрагменты программ и компоненты сильной связности
Построение информационного графа преследует две цели. Во-первых, мы имеем возможность сразу определить те вершины (то есть фрагменты тек­ста), которые в принципе являются информационно независимыми. В даль­нейшем такие вершины можно будет исполнять в любом порядке. Во-вторых, информационный граф, показывая общую последовательность вы­числений, часто позволяет сильно упростить последующий процесс анализа. Предположим, что в графе вершина А связана с вершиной В: А -> В. Ясно, что раздельный анализ таких фрагментов и проще, и позволяет выделить практически весь потенциальный параллелизм части АВ исходной програм­мы. Если же связь между данными фрагментами циклическая: А <± В, то
ситуация принципиально иная, поскольку только совместный анализ фраг­ментов А к В позволит выделить скрытый скошенный параллелизм, прино­сящий во многих случаях существенное ускорение.
Из данного примера становится ясно, что за основу проведения последую­щего микроанализа надо брать компоненты сильной связности информаци­онного графа. Для графа А -> В это как А, так и В, а для графа А <± В это фрагмент АВ целиком. Рассмотрим следующий пример:
DO 141 i = 1, 200
ABCl(i-l) = ABC2(i-l) + 1 BCDl(i-l) = ABCl(i-l)**2 A(i) = BCD1 (i-1) + D(i) ABC2(i) = B(i+1) + B(i-l) B(i) = C(i) + 1
141     C(i+1) = B(i) + 1 С-------
DO 142 i = 1, 100
142     A(i) = A(i) + B(i) + C(i) DO 143 i = 101, 200
143     A(i) = A(i) - B(i) - C(i)
На рис. 9.11 показан информационный граф этого примера. Мы видим, что первый цикл вычисляет данные, которые затем используются вторым и третьим циклами, причем последние два цикла оказались независимыми между собой. Кандидатами для проведения микроанализа являются все цик-
574
Часть III. Смежные проблемы и применение
лы фрагмента как наиболее существенные компоненты сильной связности. Заметим, что второй и третий циклы обладают свойством ParDO по графу алгоритма и потому не требуют дополнительного детального анализа (все итерации этих циклов независимы, поэтому у этих вершин нет петель).
-ИИ D №п Ш D Ш D о
J
Рис. 9.11. Информационный граф
Для исследования первого цикла повторим еще раз данную процедуру, приняв за вершины отдельные операторы тела цикла (рис. 9.12). Данное изображение исключительно информативно. Во-первых, структура связи компонентов сильной связности сразу показывает, что фрагмент можно преобразовать в последовательность из пяти циклов, содержащих пятый и шестой, четвертый, первый, второй, третий операторы исходного цикла соответственно. Во-вторых, только один компонент имеет контур, а зна­чит лишь срабатывания двух последних операторов будут связаны между собой в процессе выполнения. Следовательно, остальные четыре цикла заведомо будут обладать свойством Pa г do и микроанализ для них опять-таки не нужен.
->а doa t>Q?D D о
Рис. 9.12. Информационный граф тела цикла
Если мы хотим оценить не только ресурс параллельности фрагмента, но и найти путь его реального преобразования, надо построить единый граф с ука­занием истинной зависимости, зависимости по выходу и антизависимости (рис. 9.13). Если мы не хотим вводить новых переменных, то для соблюде­ния эквивалентности преобразований последние не два, а три оператора на­до объединить в один цикл. В противном случае старые значения массива в надо запомнить в новом массиве. Если нужно понять влияние какой-либо конкретной переменной на структуру всего фрагмента, то по точно такой же схеме можно построить информационный граф для любой переменной или для любого набора переменных.
Глава 9. Пользователь в среде параллелизма
575
-мз dod сьтао □ о
Рис. 9.13. Информационный граф тела цикла с указанием трех типов зависимостей
Параллельная форма
Для описания и визуализации макропараллельной структуры процедуры ис­пользуется параллельная форма. По существу данная форма является объе­динением информационного графа и графа управления. Она показывает, какие части процедуры можно выполнять в любом порядке, а какие после­довательно одну за другой. Параллельная форма разбивает все вершины графа на непересекающиеся множества (ярусы) так, что все множества должны выполняться строго один за другим, но все вершины одного и того же множества информационно независимы и могут исполняться в произ­вольном порядке. В § 9.1 был показан пример построения и использования параллельной формы.
Параллельная форма определена неоднозначно. Любую вершину можно пе­реместить на ярус с большим номером вместе с перемещением зависящих от нее вершин. Среди всего множества форм система V-Ray выбирает кано­ническую параллельную форму, в которой любая вершина зависит от какой-либо вершины предыдущего яруса. Этот факт гарантирует нахождение тео­ретически наискорейшего способа выполнения анализируемого фрагмента при неограниченном числе процессоров. Перемещая избыточные вершины на ярусы с большими номерами, можно дополнительно учесть ограничения конфигурации целевого компьютера.
Микроструктура программ
Исследование тонкой информационной структуры программ опирается на построение графа алгоритма либо аналогичного графа по какому-либо дру­гому виду зависимости. Фрагмент приводится к каноническому виду и для каждого входа каждого оператора строится граф в полном соответствии с изложенной в главе 6 теорией. Рассмотрим следующий фрагмент:
DO 10 i = 1, 2*n DO 10 j = 1, n+1 10 s = s + A(i,j)
с----------
DO 20 i = 1, n+1
576
Часть III. Смежные проблемы и применение
DO 20 j = 1, 2*n DO 20 к = 1, n
IF( i.EQ.l ) THEN
D(i,j,k) = B(i,j,k)*s ELSE
D(i,j,k) = D(i-l,j,k)*t + B(i,j,k) ENDIF 20 CONTINUE
Полное описание информационной структуры данного фрагмента в терми­нах графа алгоритма выглядит следующим образом (Nj описывает ограниче­ние на значения внешних переменных, А, задает множество вершин (многогранник) в пространстве итераций, a Ft показывает вид покрывающих функций, описывающих дуги графа, / = 15):
Nl=in>l; N2=\n> 1;
[1 < / < 2л;
J7!
i = i;
f = y'-i;
/' = / - 1; j" = n + 1;
2 < j < n + 1; 2 < / < 2л;
/ = i;
/ = 1;
1 < j < 1n\
\<k<n;
i = 2;
1 < j < In;
\<k<n;
3 < / < n + 1;
1 < j < 2л; 1 < к < n;
N.
n>\;
F,
/' = In; j' = n + 1;
/' = / - 1;
У = У
К — /с,
'/' = / - 1;
У = У к' = к.
N4
n>\;
FA
N<
n>2;
Исследование этого описания позволяет определить всю необходимую ин­формацию о структуре фрагмента и, в частности, выделить весь потенци­альный параллелизм. Это же описание используется для визуализации раз­личных аспектов тонкой структуры программ. Например, на рисунке 9.14, который может быть построен автоматически, показана структура данного фрагмента при п = 3.
Глава 9. Пользователь в среде параллелизма
577
Рис. 9.14. Визуализация информационной структуры фрагмента
Циклы ParDO
Начинать исследование микроструктуры процедуры можно с нахождения са­мого простого для последующего использования типа параллелизма — циклов ParDO по графу алгоритма. Если мы обнаружили, что некоторый цикл облада­ет свойством ParDO, то для того, чтобы этот факт использовать, как правило, не требуется серьезных дополнительных преобразований программы.
Вспомним, что цикл обладает свойством ParDO, если все его итерации неза­висимы. Из данного определения сразу следует, что итерации ParDO-цикла могут быть выполнены в любом порядке, например, одновременно. По­скольку данное свойство относится к уже написанным циклам программы, то прямо в исходном тексте все циклы могут быть размечены в зависимости от того, обладают они этим свойством или нет.
В реальных программах циклы с данным свойством обладают одной осо­бенностью. Если цикл помечен как ParDO по графу алгоритма, то это озна­чает следующее: цикл в принципе обладает этим свойством, но для того, чтобы все итерации выполнять независимо, может потребоваться введение дополнительных переменных (что связано с возможной антизависимостью и зависимостью по выходу между различными итерациями этого цикла). По­этому имеет смысл уточнить разметку программы и выделить циклы, кото­рые не требуют никакого изменения. Рассмотрим следующий пример:
DO к = 2, nz - 1 DO i = 2, nx - 1
578                                                                    Часть III. Смежные проблемы и применение
DO j = 2, ny - 1
с-----------
tmpl = B(i,j,k)/C(i-l,j,k) tmp2 = A(i + l,j,k)/C(i-l,j,k)
с-----------
C(i,j,k) = C(i,j,k) - tmpl*D(i-l,j,k) D(i,j,k) = D(i,j,k) - tmpl*E(i-l,j,k) F(i,j,k,m) = F(i,j,k,m) - tmpl*F(i-l,j,k,m)
с-----------
B(i+l,j,k) = B(i+l,j,k) - tmp2*D(i-l,j,k) C(i+l,j,k) = C(i+l,j,k) - tmp2*E(i-l,j,k) F(i+l,j,k,m) = F(i+l,j,k,m) - tmp2*F(i-l,j,k,m) END DO END DO DO j = 2, ny - 1
F(nx,j,k,m) = F(nx,j,k,m)/C(nx,j,k)
F(nx-1, j , k,m) = (F(nx-1, j , k,m) - D (nx-1, j , k) *F (nx, j , k,m) ) +                     /C(nx-l,j,k)
END DO END DO
Система размечает входной текст с помощью директив ParDO и TrueParDO, показывая, какой цикл обладает данным свойством лишь в принципе, а ка­кой можно сразу выполнять параллельно без какого-либо изменения. Каж­дая из этих директив относится только к ближайшему последующему циклу. Предыдущий пример система разметит следующим образом:
с ParDO
DO k = 2, nz - 1 DO i = 2, nx - 1 с ParDO
DO j = 2, ny - 1
с-----------
tmpl = B(i,j,k)/C(i-l,j,k) tmp2 = A(i+l,j,k)/C(i-l,j,k)
с-----------
C(i,j,k) = C(i,j,k) - tmpl*D(i-l,j,k) D(i,j,k) = D(i,j,k) - tmpl*E(i-l,j,k) F(i,j,k,m) = F(i,j,k,m) - tmpl*F(i-l,j,k,m)
с-----------
B(i+l,j,k) = B(i+l,j,k) - tmp2*D(i-l,j,k)
Глава 9. Пользователь в среде параллелизма
579
C(i+l,j,k) = C(i+l,j,k) - tmp2*E(i-l,j,k) F(i+l,j,k,m) = F(i+l,j,k,m) - tmp2*F(i-l,j,k,m) END DO END DO с TrueParDO
DO j = 2, ny - 1
F(nx,j,k,m) = F(nx,j,k,m)/C(nx,j,k)
F(nx-1, j , k,m) = (F(nx-1, j , k,m) - D (nx-1, j , k) *F (nx, j , k,m) ) +                     /C(nx-l,j,k)
END DO END DO
После такой разметки параллельная структура данного фрагмента во многом становится понятной. Более того, практически для всех существующих ти­пов параллельных компьютеров этой информации о структуре фрагмента оказывается достаточно. Конкретный способ использования найденного ре­сурса параллельности зависит от архитектуры конкретного компьютера. На­пример, внешний цикл можно распараллелить, а самые внутренние циклы векторизовать. Причина того, что два цикла помечены как ParDO связана с использованием одних и тех же переменных tmpi и tmp2 на разных итера­циях. Если их заменить на двумерные массивы, то оба цикла станут TrueParDO. В данном случае этого можно и не делать, поскольку такие про­стые действия и проверки должен выполнять компилятор.
Скошенный параллелизм
К сожалению, не всегда параллелизм программы можно полностью описать, отмечая только свойство ParDO у циклов исходного текста. Рассмотрим фрагмент программы:
DO j = 1, m
U(j,0) = О END DO DO k = 1, n
U(0,k) = 0
DO i = 1, m
U(i,k) = (F(i,k) - E(i-l,k)*U(i-l,k) - D (i, k-1) *U (i, k-1) ) + /B(i,k)
END DO END DO
580
Часть III. Смежные проблемы и применение
Только первый цикл этого примера обладает свойством ParDO, однако большого практического значения этот факт не имеет. Важно знать, сущест­вует ли какой-либо другой вид параллелизма и если да, то как его показать и использовать.
Если воспользоваться теорией, изложенной в главе 7, то можно легко опре­делить, что данный фрагмент обладает скошенным параллелизмом. К сожа­лению, реальность такова, что скошенный параллелизм нельзя использовать так же просто, как координатный. Основная сложность заключается в том, что границы изменения параметров циклов в новом фрагменте описываются лишь кусочно-линейными функциями. А это значит, что либо преобразо­ванная программа будет содержать большее число циклов, либо в границах циклов будут использованы функции min и мах, а моменты срабатывания некоторых операторов присваивания будут определять условные операторы, стоящие перед ними. Тем не менее, очень часто все это влечет лишь не­большие накладные расходы, которые полностью покрываются найденным параллелизмом. Одна из возможных форм записи предыдущего фрагмента показана ниже:
DO j = 1, m U(j,0) = О END DO DO t = 1, n + m
IF( t.LE.n ) U(0,t) = 0 с \TrueParDO
DO k = MAX(1, t-m), MIN(t-l, n)
U(t-k,k) = (F(t-k,k) - E(t-k-l,k)*U(t-k l,k) -+ D(t-k,k-l)*U(t-k,k-l))/B(t-k,k) END DO END DO
Внутренний цикл фрагмента в такой записи обладает искомым свойством и может быть, например, векторизован. Если перед данным преобразованием выделить компоненты сильной связности, то оператор u(0,t) = 0 будет вынесен за пределы двойного гнезда, и условный оператор исчезнет.
Итак, надеемся, что все те сложности, которые были обозначены на пути освоения и использования параллельных вычислительных систем, постепен­но исчезают. Если и не исчезают совсем, то, во всяком случае, появляется ощущение уверенности в их преодолении. Так и должно быть. Все эти про­блемы появились не сегодня, накоплен опыт, произошло их осмысление, созданы многочисленные вспомогательные средства, появились высоко­классные специалисты в данной области. Во многих центрах, имеющих вы-
Глава 9. Пользователь в среде параллелизма
581
сокопроизводительную вычислительную технику, уже создана инфраструк­тура поддержки пользователей. Если это не так, то обратитесь к заключи­тельному параграфу данной книги.
Вопросы и задания
1.   Возьмите достаточно большую программу и постройте для нее:
1.1. Граф вызовов.
1.2. Циклический профиль путей.
1.3. Графы управления для ключевых подпрограмм и функций.
1.4. Граф использования общей памяти.
1.5. Гистограмму времени исполнения отдельных процедур.
1.6.  Информационный граф в терминах отдельных итераций для наиболее важных фрагментов.
2.   Какие инструментальные средства были (или были бы) вам полезны для выпол­нения данной работы?
3.   Какие функции для работы с графом вызовов были бы вам полезны, если граф для всей программы состоит из 1000 вершин?
4.     Как определить свойство ParDO для цикла, содержащего обращение к процедуре?
5.     "Разработайте систему, которая находит и показывает тонкую информацион­ную структуру фрагмента.
§ 9.3. Организационная поддержка пользователя
Реальность сегодняшнего дня заключается в том, что исчезли принципиаль­ные проблемы в потенциально бесконечном увеличении пиковой произво­дительности компьютеров. Действительно серьезная проблема состоит в том, как этот колоссальный потенциал использовать. Очевидно, что недоста­точно купить параллельный компьютер или объявить о создании суперком­пьютерного центра. Для того чтобы "дело пошло", для успешного решения всего множества смежных проблем необходим комплексный подход, рассмат­ривающий в совокупности вопросы теории и практики, развития науки и образования, интегрирующий знания из различных предметных областей.
Конечно же, многое определяется тем, какие цели ставятся перед тем или иным центром. Однако даже в самых различных центрах имеется немало общего. Мы хотим поделиться своим опытом организации работ в Учебно-научном центре по высокопроизводительным вычислениям Московского государственного университета им. М. В. Ломоносова. Надеемся, что выска-
582
Часть III. Смежные проблемы и применение
занные соображения помогут вам правильно сориентироваться в практиче­ской деятельности. Многие подробности организации и технические детали нашего Центра можно найти на сайте http://www.Parallel.ru.
Учебно-научный центр по высокопроизводительным вычислениям создан на базе Научно-исследовательского вычислительного центра МГУ. Спектр дея­тельности Центра широк, что определяется как спецификой самой области высокопроизводительных вычислений, так и разнообразием стоящих перед ним задач. Среди приоритетных направлений можно назвать следующие:
□  поддержка фундаментальных научных исследований;
□  поддержка учебного процесса, подготовка и переподготовка специалистов;
□  создание инфраструктуры поддержки пользователей;
□  формирование сообщества ученых, работающих в данной области, и ряд других направлений.
Вычислительные ресурсы. Развитие вычислительной базы Учебно-научного центра проходит по двум направлениям, отражающим два основных направ­ления развития суперкомпьютерной техники в мире. Это многопроцессор­ные вычислительные системы с общей и распределенной памятью. Каждый из этих классов компьютеров имеет свои достоинства и недостатки. Для ком­пьютеров с общей памятью, в частности, для SMP-серверов, гораздо проще создавать эффективные программы, однако подобные системы, во-первых, достаточно дороги, а во-вторых, содержат лишь относительно небольшое число процессоров. Компьютеры с распределенной памятью, в частности, кластерные системы, могут содержать практически неограниченное число процессоров, но писать программное обеспечение для подобных систем, безусловно, сложнее.
В Центре должны присутствовать оба класса. Это определяется как требова­ниями со стороны прикладных задач, так и потребностью учебного процес­са. Большая вычислительная мощность предоставляется кластерными сис­темами. Вместе с тем, целый ряд алгоритмов накладывает исключительно жесткие требования к скорости межпроцессорного взаимодействия, чего можно достичь только на системах с общей памятью.
Перспективное направление, активно развиваемое в Центре в последнее время, — это метакомпьютинг. Он позволяет объединить воедино колос­сальные вычислительные ресурсы компьютеров в сети, в частности, компь­ютеров в Интернете, или же объединить вычислительные ресурсы подразде­лений университета для решения отдельных задач. Приоритетная задача на ближайшее время состоит в создании унифицированной высокопроизводи­тельной вычислительной среды.
С развитием собственно вычислительных ресурсов необходимо одновремен­ное совершенствование материальной базы Центра в трех смежных направ-
Глава 9. Пользователь в среде параллелизма
583
лениях. Это создание высокоскоростной сетевой инфраструктуры, систем визуализации результатов численных экспериментов, а также средств хране­ния и обработки больших объемов данных. Как и любой хорошо сбаланси­рованный организм, Центр по высокопроизводительным вычислениям не может развиваться только в направлении увеличения вычислительной мощ­ности — необходимо согласованное развитие всей инфраструктуры.
Поддержка фундаментальных научных исследований. В том, что во многих об­ластях науки необходимо использовать высокопроизводительную вычисли­тельную технику, никого убеждать уже не надо. Однако заметим, что среди задач, решаемых на высокопроизводительных вычислительных системах, особое место занимают задачи, по существу отражающие интересы целых отраслей или областей знаний. На таких крупных областях исследований отрабатываются основные элементы технологии постановки и решения боль­ших задач. Чтобы постоянно поддерживать их программное обеспечение на должном уровне, необходимо решить ряд трудных задач междисциплинар­ного характера. Одна из них состоит в разработке универсальной автомати­зированной технологии адаптации программного обеспечения под требова­ния архитектуры параллельных компьютеров. Другая задача определяется тем, что это, в свою очередь, требует привлечения высококвалифицирован­ных специалистов очень широкого профиля. От них потребуются не только дополнительные знания по физике, механике, математике, химии и другим областям, но и хорошее владение как современной параллельной вычисли­тельной техникой, так и технологиями параллельного программирования.
Поддержка учебного процесса, подготовка и переподготовка специалистов. За­дача сложная, но необходимая. Без ее решения просто немыслимо какое-либо существенное продвижение вперед в будущем. Параллельные и высо­копроизводительные вычислительные технологии уже стали неотъемлемой частью общей информационной культуры. Поэтому крайне необходимо соз­дание соответствующей инфраструктуры учебного процесса: базовые и спе­циализированные курсы, вычислительные практикумы, учебные материалы, поддержка выполнения курсовых и дипломных работ, спецсеминары, мето­дическая и организационная деятельность и многие другие элементы. Объ­ект особого внимания — подготовка и переподготовка преподавателей вузов.
Мировой опыт развития параллельных вычислений показывает исключитель­ную важность развитой инфраструктуры поддержки пользователей. Деятель­ность по ее созданию составляет существенную часть работы Центра. Сюда входят как традиционные формы: консультационный центр, специальные представители Центра в подразделениях, hot-line, совместное выполнение проектов и т. п., так и новые способы работы с пользователями: электронная рассылка новостей Центра, разработка, внедрение и обучение инструменталь­ным средствам исследования параллельных свойств программ, online-консультации, консультационный Интернет-Совет, Web-конференции и др.
584
Часть III. Смежные проблемы и применение
Важным фактором на пути реализации этих целей является объединение Учебно-научного центра с Информационно-аналитическим центром по па­раллельным вычислениям в сети Интернет http://www.Parallel.ru. Данный ин­тернет-центр создан сотрудниками НИВЦ МГУ и уже признан научным со­обществом в качестве центра общения специалистов данной области.
Сформулированные выше задачи мы не рассматриваем как локальные зада­чи для Московского университета. У многих коллективов есть уникальный опыт и интересные разработки — делитесь опытом, объединяйтесь, ставьте новые задачи, интересуйтесь проблемами других. В данной области, нахо­дящейся на стыке многих наук, нужно продвигаться вместе, интегрировать знания и усилия как на уровне отдельных ученых и научных групп, так и на уровне институтов и вузов. Только на этом пути можно получить качествен­но новые результаты и реальную отдачу от вложенных усилий, сохранить научные кадры и серьезно продвинуться вперед в решении многих задач.
Вопросы и задания
1.   Предположим, что перед вами поставили задачу активизировать использование параллельных вычислений в научных исследованиях и учебном процессе вуза. Сильно ли изменится содержательная сторона вашей деятельности, если ориен­тироваться на собственные вычислительные ресурсы или на работу в удаленном режиме на суперкомпьютерных ресурсах другой организации?
2.   Какие изменения, по вашему мнению, нужно внести в базовые учебные курсы на естественнонаучных факультетах, чтобы дать студентам представление о параллель­ных вычислительных технологиях? Что изменится, если не "дать представление", а научить студентов владению параллельными вычислительными технологиями? Рас­смотрите варианты математических, физических, химических, биологических фа­культетов. Результаты опубликуйте на страницах http://www.ParalIel.ru.
3.   Приведите примеры задач с каждого естественнонаучного факультета, которым для своего решения необходимы суперкомпьютерные вычислительные ресурсы.
4.   Нужны ли суперкомпьютеры в гуманитарных исследованиях?
5.   Как с вашей точки зрения должен быть устроен идеальный суперкомпьютерный центр? Какова должна быть программно-аппаратная инфраструктура? Какие элементы должны обязательно присутствовать в его организационной структуре?
Заключение
Параллельные вычисления: интеграция от А до Я
Вселенная не только необычнее,
чем мы воображаем. Она необычнее,
чем мы можем вообразить.
Из законов Мерфи
Итак, книга закончена. Мы назвали ее "Параллельные вычисления", безус­ловно поставив акцент на первом слове. Так чем же все-таки параллельные вычисления принципиально отличаются от просто вычислений? И в чем же, собственно говоря, состоит новизна книги? Ведь в наше время любые вы­числения всегда имеют дело с компьютерами, программным обеспечением, численными методами, различными способами нечисловой обработки дан­ных и т. п.
Ответ очень простой. Параллельные вычисления ничего не отрицают из того, что было, есть и будет использоваться в традиционных последователь­ных вычислениях.
Однако новейшие вычислительные средства, называемые суперкомпьютера­ми, параллельными вычислительными системами или как-нибудь иначе, настолько сильно отличаются по своему устройству от привычных персо­нальных компьютеров и рабочих станций, что требуют для своего эффек­тивного использования принципиально новой программной, математиче­ской и даже организационной среды. Вот об этих вычислительных средствах и окружающей их среде и шла речь в нашей книге.
В ней мы обсуждали в основном научные проблемы решения больших задач и лишь в конце слегка коснулись проблем организационных. В параллель­ных вычислениях оба типа проблем одинаково важны, поскольку конечной целью все же является реальное решение реальных задач. Понимание этого позволяет правильно оценить как то, что еще не сделано в параллельных вычислениях, так и то, что предстоит сделать.
Параллельные вычисления долгое время не были на острие внимания науч­ной и, в том числе, вычислительной общественности. Мощности индивиду­альных однопроцессорных компьютеров росли достаточно быстро, обеспе­чивая основные потребности большинства областей в решении текущих задач и поспевая в какой-то мере за ростом их сложности. Интерес к парал­лельным вычислениям сохранялся там, где большие задачи возникли давно. Но, в конце концов, случилось то, что должно было случиться — начался
586
Заключение
резкий подъем интереса к большим задачам едва ли не всюду. И тут стали обнаруживаться настораживающие обстоятельства: специалистов в области параллельных вычислений катастрофически не хватает; в основной своей массе вузы не готовят кадры, необходимые для постановки и решения больших задач; в области параллельных вычислений нет учебников, учебных пособий и т. д.
Причин возникновения создавшегося положения много. Но, возможно, глав­ная из них связана с тем, что своевременно не был замечен и правильно оце­нен назревающий революционный с точки зрения технологии решения задач переход к использованию больших распределенных вычислительных систем. И это несмотря на то, что совершенно очевидные сигналы надвигавшихся трудностей появились уже давно. По мере развития вычислительной техники пользователю приходилось предоставлять все больший объем нетрадиционных сведений о параллельной структуре алгоритмов, распределении массивов дан­ных и т. п. При этом ему самому было необходимо все в большей степени заботиться об использовании подобных сведений и нести ответственность за эффективность их применения. Теперь, с приходом кластеров и больших рас­пределенных систем, пользователь почти весь процесс решения задач должен организовывать сам. Научиться этому, прочитав несколько страниц или даже томов инструкций, практически невозможно. Чтобы быть хорошим специали­стом в области параллельных вычислений, необходимо иметь за плечами и опыт, и обширную фундаментальную подготовку.
Параллельные вычисления по сути своей являются интегрированной нау­кой. Они объединяют в единое целое сведения из таких разных областей, как архитектура компьютеров и вычислительных систем, системное про­граммирование и языки программирования, различные методы обработки информации и т. п., фокусируя все это на построение процессов решения больших задач. Подобное объединение настолько глубоко и иерархично, что в каждой из областей, в свою очередь, также можно указать центры инте­грации. Вспомните, например, сколько смежных знаний приходится при­влекать для изучения математических моделей вычислительных процессов или информационной структуры алгоритмов и программ. Таких примеров можно привести очень много. По-видимому, эта особенность параллельных вычислений должна учитываться при составлении учебных программ, фор­мировании учебных курсов и проведении научных исследований.
Отмеченная особенность параллельных вычислений связана с их интегри­рующей ролью среди самих наук. Но у них проявляется и другая особен­ность — параллельные вычисления начинают стимулировать интеграцию научного сообщества.
Сейчас наступило время, когда большие задачи будут появляться часто и в разных областях. Там, где еще недавно достаточно было использования пер­сонального компьютера, стремление либо получить большую точность, либо
Параллельные вычисления: интеграция от А до Я
587
усложнить модель изучаемого явления, либо просто ускорить счет естест­венным образом приводит к желанию использовать более мощную вычисли­тельную технику. Но если эта техника имеет параллельную архитектуру, од­ного желания будет мало. Как мы уже знаем, программы с персонального компьютера в общем случае не переносятся эффективно на параллельные системы. Поэтому потребуется коренная их переделка, сопровождаемая до­полнительным анализом задачи и, возможно, полной заменой методов об­работки информации. Если на месте не у кого получить рекомендации, как это сделать, придется обращаться за помощью туда, где есть намеченная к использованию или похожая на нее вычислительная система.
Большие параллельные системы устанавливаются пока только в крупных организациях. Обычно здесь уже имеется достаточное количество техники меньшей мощности, накоплен опыт ее эксплуатации и подготовлен ряд больших задач. Как правило, вся техника связана локальной сетью, из кото­рой возможен выход как на большую систему, так и в глобальную сеть. Эксплуатация систем параллельной архитектуры является делом исключи­тельно сложным. Поэтому всегда создается группа сопровождения, состоя­щая из квалифицированных специалистов разного профиля, объединенных общей идеей — научиться и научить эффективно эксплуатировать новей­шую технику, решать на ней большие задачи.
Такого рода группы оказываются теми звеньями, с которых начинается ин­теграция научного сообщества в области параллельных вычислений. К ним обращаются за консультациями. У них скапливается информация о том, ка­кие пользователи и как осваивают новую технику. Они знают многое об уз­ких местах вычислительных процессов и нередко имеют готовые рецепты по их преодолению. В силу накопленного опыта именно эти группы часто ста­новятся инициаторами организации междисциплинарных семинаров по ме­тодам решения больших задач, технологиям программирования, повышению эффективности использования больших вычислительных систем. На них ложится составление всякого рода методических пособий и других материа­лов, ориентированных на применение в учебном процессе. В конце концов, эти группы оказываются в центре жизни научного общества, связанного с вычислениями. Все подготовлено для развертывания серьезного междисци­плинарного сотрудничества.
Конечно, мы нарисовали несколько идеализированную картину. Для орга­низации подобных групп сопровождения нужны не просто квалифициро­ванные, но и очень активные кадры. Найти или воспитать их не легко. Но усилия на формирование инициативных групп сопровождения затратить стоит. Отдача не заставит себя ждать.
Мы уверены, что параллельные вычисления и все, что их окружает, еще не раз порадуют научное сообщество своими достижениями. Дерзайте, дорогу осилит идущий!
Список литературы
1.  Андреев А. Н., Воеводин Вл. В., Жуматий С. А. Кластеры и суперком­пьютеры — близнецы или братья? // Открытые системы. — 2000. — № 5-6. - С. 9-14.
2.  Андрианов А. Н., Бугеря А. Б., Ефимкин К. Н., Задыхайло И. Б. Норма. Описание языка. Рабочий стандарт / Препринт ИПМ им. М. В. Келды­ша РАН. 1995. - № 120. - 50 с.
3.  Антонов А. С, Воеводин Вл. В. Эффективная адаптация последователь­ных программ для современных векторно-конвейерных и массивно-параллельных супер-ЭВМ // Программирование. — 1996. — № 4. — С. 37-51.
4.   Васильев Ф. П. Численные методы решения экстремальных задач. — М.: Наука, 1988. - 430 с.
5.   Воеводин В. В. Вычислительные основы линейной алгебры. — М.: Нау­ка, 1977. - 304 с.
6.   Воеводин В. В. Информационная структура алгоритмов. — М.: МГУ, 1997. - 139 с.
7.   Воеводин В. В. Компьютерная революция и вычислительная математика // Математика и кибернетика. — М.: Знание. — 1988. — Вып. 3. — 47 с.
8.   Воеводин В. В. Массивный параллелизм и декомпозиция алгоритмов // ЖВМ и МФ. - 1995. - Т. 35. № 6. - С. 988-996.
9.   Воеводин В. В. Математические модели и методы в параллельных про­цессах. — М.: Наука, 1986. — 296 с.
10.  Воеводин В. В. Математические основы параллельных вычислений. — М.: МГУ, 1991. - 345 с.
11.  Воеводин В. В. Параллельные структуры алгоритмов и программ. — М.: ОВМ АН СССР. 1987. - 148 с.
12.  Воеводин В. В. Полиномиальное оценивание сложности алгоритмов // ЖВМ и МФ. - 1999. - Т. 39, № 6. - С. 1032-1040.
13.  Воеводин В. В. Численные методы алгебры (теория и алгоритмы). — М.: Наука, 1966. - 248 с.
14.   Воеводин В. В., Краснов С. А. Математические вопросы проектирования систолических массивов / Препринт ОВМ АН СССР. 1985. — № 80. — 26 с.
Список литературы
589
15.  Воеводин В. В., Пакулев В. В. Определение дуг графа алгоритма / Пре­принт ОВМ АН СССР. 1989. - № 228. - 22 с.
16.  Воеводин Вл. В. Статистический анализ и вопросы эффективной реали­зации программ // Вычислительные процессы и системы. — 1993. — № 9. - С. 249-301.
17.  Воеводин Вл. В. Статистические оценки возможности выявления парал­лельной структуры последовательных программ // Программирование. — 1990. - № 4. - С. 44-54.
18.  Воеводин Вл. В. Легко ли получить обещанный гигафлоп? // Програм­мирование. - 1995. - № 4. - С. 13-23.
19.  Воеводин Вл. В. Проект профессионального центра в сети Интернет // Тезисы докладов научной конференции "Интернет и современное обще­ство" (30 ноября — 3 декабря. — 1999 г., г. Санкт-Петербург). СПб. — 1999. - 55 с.
20.  Воеводин Вл. В. Суперкомпьютеры: вчера, сегодня, завтра // Наука и жизнь. - 2000. - № 5. - С. 76-83.
21.  Воеводин Вл. В. Теория и практика исследования параллелизма последо­вательных программ // Программирование. — 1992. — № 3. — С. 38—53.
22.  Воеводин Вл. В. Точное описание входных и выходных данных про­грамм // Вестн. Моск. ун-та. Сер. 15, Вычислительная математика и ки­бернетика. — 1997. — № 1. — С. 41—44.
23.  Воеводин Вл. В., Капитонова А. П. Методы описания и классификации архитектур вычислительных систем. — М.: МГУ. 1994. — 79 с.
24.  Головкин Б. А. Параллельные вычислительные системы. — М.: Наука, 1980. - 520 с.
25.  Гэри М., Джонсон Д. Вычислительные машины и труднорешаемые за­дачи. — М.: Мир. 1982. — 416 с.
26.  Дымников В. П. Современные проблемы моделирования отклика кли­матической системы на малые внешние воздействия // Труды межд. теор. конф. "Проблемы гидрометеорологии и окружающей среды на по­роге XXI века". — М.: Гидрометеоиздат, 2000. — С. 14—34.
27.  Ершов А. П. Современное состояние теории схем программ // Пробле­мы кибернетики. — 1973. — № 27. — С. 87—110.
28.  Забродин А. В., Луцкий А. Е., Марбашев К. X., Чернов Л. Г. Численное исследование обтекания летательных аппаратов и их элементов в реаль­ных полетных режимах // Общероссийский науч.-техн. журнал "Полет". - 2001. - № 7. - С. 21-29.
29.  Задыхайло И. Б. Организация циклического процесса счета по парамет­рической записи специального вида // ЖВМ и МФ. — 1963. — Т. 3.
№ 2. - С. 337-357.
590
Список литературы
30.  Коновалов Н. А., Крюков В. А., Погребцов А. А., Сазанов Ю. Л. С-DVM — язык разработки мобильных параллельных программ // Про­граммирование. — 1999. — № 1. — С. 20—28.
31.  Крюков В. А., Удовиченко Р. В. Отладка DVM-программ // Программи­рование. - 2001. - № 3. - С. 19-29.
32.  Арапов Д. М., Калинов А. Я., Ластовецкий А. Л., Ледовских И. Н., По-сыпкин Н. А. Язык и система программирования для высокопроизводи­тельных параллельных вычислений на неоднородных сетях // Програм­мирование. - 2000. - № 4. - С. 55-80.
33.  Лебедев С. А. Электронно-вычислительные машины // Сессия АН СССР по научным проблемам автоматизации производства. Пленарные заседания. - М.: АН СССР. - 1957. - Т. 1. - С. 162-180.
34.  Котов В. Е., Марчук Г. И. Проблемы вычислительной техники и фунда­ментальные исследования // Автомат, и вычисл. техн. — 1979. — № 2. — С. 3-14.
35.  Мультипроцессорные системы и параллельные вычисления / Под ред. Ф. Г. Энслоу. - М.: Мир, 1976. - 384 с.
36.  Оре О. Теория графов. — М.: Наука, 1980. — 336 с.
37.  Михайлов А. П., Самарский А. А. Компьютеры и жизнь. — М.: Педаго­гика, 1987. - 128 с.
38.  Уилкинсон Дж. X. Алгебраическая проблема собственных значений. — М.: Физматгиз, 1963. — 564 с.
39.  Фаддеева В. Н., Фаддеев Д. К. Параллельные вычисления в линейной алгебре // Кибернетика. - 1977. - № 6. С. 28-40; 1982. - № 3. -С. 18-31, 44.
40.  Хмелев Д. В. Восстановление линейных индексных выражений для све­дения программ к линейному классу // ЖВМ и МФ. — 1998. — Т. 38. № 3. - С. 532-544.
41.  Частиков А. П. От калькулятора до супер-ЭВМ // Вычислительная тех­ника и ее применение. — М.: Знание. — 1988. — 96 с.
42.  Abramov S. М., Adamowitch А. 1., Nesterov 1. A., Pimenov S. P., Shevchuck Yu. V. Autotransformation of evaluation network as a basis for automatic dynamic parallelizing // NATUG'1993 Spring Meeting "Transputer: Research and Application", May 10—11, 1993.
43.  Bailey D. H. Twelve ways to fool the masses when giving performance results on parallel computers / RNR Technical Report RNR-91-20, NASA Ames Research Center, Moffett Field CA 94035, June 11, 1991.
44.  Baur W., Strassen V. The complexity of partial derivatives // Theor. Comput. Sci., - 1983. - 22. - P. 317-330.
Список литературы
591
45.  Berry М. et al. The Perfect Club Benchmarks: effective performance evaluation of supercomputers // Int. J. of Supercomputer Applications. — 1989. - 3(3) - P. 5-40.
46.  Cybenko G., Kipp L., Pointer L., Kuck D. Supercomputer performance evaluation and the Perfect Benchmarks / Tech. Rep. 965, CSRD, Univ. of Illinois, 1990.
47.  Davies J. et al. The KAP/S-1: An advanced source-to-source vectoriser for the S-l Mark 11 Supercomputer // IEEE Proc. of 1CPP. - 1986. - P. 833-835.
48.  Fiacco A. V., McCormick J. P. Nonlinear programming: sequential uncon­strained minimization techniques. — N. Y.; Research Analyses Corp., 1968. — 326 p.
49.  Kesselman C, Foster 1. Eds. The Grid: blueprint for a new computing infrastructure. Morgan Kaufmann, 1999.
50.  Gelernter D. Parallel programming in Linda / Technical Report 359, Yale University Department of Computer Science, Jan., 1985.
51.  Gustafson J. L., Todi R. Conventional benchmarks as a sample of the per­formance spectrum // The Journal of Supercomputing. — 1999. — V. 13. — P. 321-342.
52.  Huson C. et al. The KAP/205: An advanced source-to-source vectoriser for the Cyber 205 supercomputers // IEEE Proc. of 1CPP. - 1986. - P. 827-832.
53.  Konovalov N. A., Krukov V. A., Mihailov S. N. and Pogrebtsov A. A. Fortran DVM — a language for portable parallel program development // Proceedings of Software For Multiprocessors & Supercomputers: Theory, Practice, Experience. Institute for System Programming, RAS, Moscow, 1994.
54.  Kung H. T. Why systolic architecture? // Computer. — 1982. — 15. № 1. — P. 37-46.
55.  Lamport L. The coordinate method for parallel execution of DO loops // Proc. 1973 Samagore Comput. Conf. Parallel Process., N. Y., IEEE. — 1973. - P. 1-12.
56.  Lamport L. The hyperplane method for an array computer // Proc. 1974 Samagore Comput. Conf. Parallel Process., Lect. Notes Comput. Sci., 24. Berlin: Springer-Verlag. — 1975. - P. 113—131.
57.  Lewis T. G. Foundation of parallel programming: machine-independent approach. IEEE Computer Society Press, 1994. — 282 p.
58.  Maske T. et al. The KAP/ST-100: A Fortran translator for the ST-100 attached processor // IEEE Proc. of 1CPP. - 1986. - P. 171-175.
59.  McMahon F. H., The Livermore Fortran Kernels: a computer test of the numerical performance range / Technical Report UCRL-53745, Lawrence Livermore National Laboratory, Livermore, Calif. — Dec, 1986.
592
Список литературы
60.  Polychronopoulos С. D. Compiler optimizations for enhancing parallelism and their impact on architecture design // IEEE Trans, on Computers. — 1988. — V. 37. № 8. - P. 991-1004.
61.  Borck W.C., McReynolds R.C., Slotnik D.L. The SOLOMON computer // AF1PS Conf. Proa, 22. - 1962. - P. 97-107.
62.  "The FORGE Product Set", Applied Parallel Research, Inc.
63.  Voevodin V. V. Information structure of sequential programs // Russ. J. of Num. An. and Math. Modelling. - 1995. - V. 10. № 3. - P. 279-286.
64.  Voevodin V. V. Mathematical foundations of parallel computing. World Scientific Publishing Co., Series in computer science. 1992. V. 33. — 343 p.
65.  Voevodin V. V., Voevodin VI. V. V-Ray technology: a new approach to the old problems. Optimization of the TRFD Perfect Club Benchmark to CRAY Y-MP and CRAY T3D supercomputers // Proc. Of the High Performance Computing Symposium'95, Phoenix, Arizona, USA. — 1995. — P. 380—385.
66.  Arpaci-Dusseau R., Wong F., Culler D., Martin R. Architectural requirements and scalability of the NAS Parallel Benchmarks // Proc. of SC99 Conference on High Performance Networking and Computing. — Nov. 1999.
67.  Zhiyu Shen, Zhiyan Li, Pen-Chung Yew. An empirical study of Fortran programs for parallelizing compilers // IEEE Trans, on Parallel and Distributed Systems. — July 1990. - P. 350—364.
Интернет-ресурсы
1.    http://www.citforum.ru — сервер информационных технологий.
2.    http://www.parallel.ru — Информационно-аналитический центр по па­раллельным вычислениям в сети Интернет.
Предметный указатель
L
L-свойство матрицы 362
А
Автокод 31
Автоматическое распараллеливание
программ 222 Автономная программная система 323
V-Ray 436 Адрес слова 15
физический 27 Алгоритм:
детерминированный 193
линейный 349
недетерминированный 193
параллельный 199
Штрассена 206 Альтернативные многогранники 365 Анализ ошибок:
обратный 521
прямой 520 Арифметика:
разрядно-параллельная 51
разрядно-последовательная 51 Арифметико-логическое устройство 16 Архитектура:
ccNUMA 73
NUMA71
UMA71
Б
Байт 15
Барьерная синхронизация 144
Бенчмарк 166
Банк памяти 53 Бит 14
В
Вектор:
данных 124
граничных значений 462
задержек 462
реализации 462 Величина, заданная на области 313 Вершина:
входная 192
выходная 192 Взаимодействие процессов:
Put/Get 299
Send/Receive 299 Внутренний код 16 Волновой фронт 406 Время разгона конвейера 125 Выделение стандартных операций 419 Выражение индексное нелинейное 450 Вычисления избыточные 376 Вычислительное ядро 36 Вычислительный кластер 146 Вычисляемые ветвления 449
Г
Гиперкуб 69
Гомоморфизм простой 467 Гомоморфная свертка 467 Гомоморфный:
образ 467
прообраз 467
594
Предметный указатель
Граф:
алгоритма 192
влияния 334
вызовов процедур 561
вызовов процедур расширенный 563
зависимостей 333
максимальный 346
минимальный сверху 347
минимальный снизу 346
обратный 347
простой 353
элементарный 354
покрытие 348 информационный 329 использования общей памяти 566 лексикографически правильный 373 направленный 478
направляющий вектор 478 параллельная форма 194
каноническая 194
линейная 195
максимальная 194
обобщенная 195
строгая 194 параметризованный 193 передач управления 327 плоский 488 программы 327
простой канонический вид 371 расширенный 450 регулярный 472
бесконечный 472
базовые векторы 472
базовый многогранник 474
главный регулярный подграф 473
опорные вершины 474 решетчатый 336 сильно связанный 405 системы 84 управления 327
процедурный 569 управляющий 327 эквивалентности 409 элементарный канонический
вид 371 Граф-машина 459 Графовая модель программы 326
д
Денотационный подход 325 Динамическое порождение
процессов 298 Дискретное неравенство Беллмана 466 Дискретное уравнение Беллмана 466 Дистрибутивная решетка 403 Доля последовательных вычислений 87
3
Зависимость 345
data-flow 346
анти 346
истинная 346
по входу 346
по выходу 346 Зависимые точки графа 345 Загруженность:
системы 82
устройства 80 Закон:
Амдала 1-й 86
Амдала 2-й 87
Густавсона—Барсиса 89
Мура 43 Запятая:
плавающая 21
фиксированная 21 Значение неготовое 302
И
Иерархия памяти 58 Иллюстративная модель компьютера 38 Информационная структура программы 324 тонкая 324 Информационное ядро 197 Информационные типовые структуры 446 гипотеза 446 История реализации программы 329 Итерация 343 цикла 343
Предметный указатель
595
К
Каскадный переключатель 66 Классификация:
В. Хендлера 103
Д. Скилликорна ПО
Л. Шнайдера 107
М. Флинна 97
М. Флинна MIMD 98
М. Флинна MISD 98
М. Флинна S1MD 98
М. Флинна S1SD 97
Р. Хокни 100
Т. Фенга 101 Когерентность кэш-памяти 72 Код:
внутренний 16
машинный 16 Команда:
векторная 48
машинная 16
скалярная 48 Коммуникационный профиль
приложений 175 Коммутатор матричный 66 Компилятор 16 Компьютер:
ATLAS 53
BBN Butterfly 72
Beowulf-кластеры 147
CDC-6600 53
CDC-7600 53
Cm* 71
Connection Machine 70
Cray C90 116
Cray T3D/T3E 142
Cray-1 57
Hewlett-Packard Superdome 135
IBM 701 51
IBM 704 51
IBM 709 51
ILLIAC IV 54
STRETCH 52
БЭСМ-6 29
M-20 29
MBC-1000M 150
Сетунь 24
Стрела 56
SMP64
с архитектурой ccN UMA 73
с архитектурой NUMA 71
с архитектурой UMA 71
с общей памятью 63
с распределенной памятью 64 Конвейер:
длина 47
команд 53
ступень 47 Концепция неограниченного
параллелизма 199 Кортеж система Linda 268 Критическая секция программы 233
Л
Лексикографически:
неупорядоченные функции 370
упорядоченные функции 370 Лексикографический порядок:
в линейном пространстве итераций 344
в программе 344
в пространстве итераций 345 Линейные фрагменты 341 Линейный класс алгоритмов 349 Локальность:
вычислений 57
использования данных 57
м
Макровершина 405 Макрограф 405 Макродуга 405 Макропараллелизм 374 Массивов:
выравнивание 239
распределение 238 Матрица:
алгоритма вариационная 505
информационной связности 506 взвешенная 506
инциденций 507
смежностей 507
596
Предметный указатель
Машинный нуль 22 Метакомпьютинг 155
Condor 155
GIMPS 156
Globus 156
РАСХ-МР1 155
SETI@home 156 Микропараллелизм 374 Модель программирования:
master/slaves 274
SPMD 275
н
Норма 403
О
Область 311 Обработка:
конвейерная 46
параллельная 44 Обратное выполнение цикла 418 Общая шина 65 Общие данные DVM 242 Операнд 15
Операционная система 17 Операционно-логическая история 328 Операционный подход 325 Операция 15
коллективная 293
произведения областей 311 Опорная область 343 Опорное:
гнездо циклов 342
пространство 342 Опорный многогранник 342 Отношение:
влияния 335
зависимости 333
п
Память 15
адресуемая 26 быстрая 26
виртуальная 28
время доступа 25
конфликты 117
кэш 26
медленная 26
неадресуемая 26
оперативная 26
постоянная 26
проблема когерентности кэш 72
разрядно-параллельная 51
разрядно-последовательная 51
регистровая 26
сверхбыстрая 26 Параллелизм:
внутренний 207
координатный 396
массовый 421
скошенный 396 Параллельная форма графа 194
высота 194
каноническая 194
линейная 195
обобщенная 195
строгая 194
ширина 194
ширина яруса 194
ярус 194 Параллельные множества 373 Параллельный алгоритм 199 Передача сообщений:
асинхронная 285
синхронная 280 Переменная:
координатная 252
локальная 226
неготовая 303
общая 226
размазанная 251
распределенная по сети 250 Перестановка циклов 416 Переупорядочивание операторов 417 Покрытие графа 348 Полукольцо разверток 465 Портабельность программ 33 Порядок:
линейный 195
частичный 192
Предметный указатель
597
Правило собственных вычислений 237 Принцип близкодействия 488 Проблема отображения 444 Программа 31
иерархическое представление
структуры 567 история операционно-
логическая 328 история реализации 329 канонизация 448 линейный участок 567 нелинейность устранимая 448 преобразование:
специальное 411
треугольное 419
элементарное 413 L-дерево 567 векторизация 123 графовая модель 326 детерминированная 193 исследование:
динамическое 571
статическое 341 линейная 340 линейный класс 340 недетерминированная 193 параллельная структура 373 параметр:
младший 342
старший 342 портабельность 33 преобразователь 326 простая 353 распознаватель 326 элементарная 354 Производительность: Flops 165 MIPS 164
асимптотическая 86 пиковая 80 реальная 80 Пространство: кортежей 268 итераций 343 переупорядочивание 411 расщепление 418
линейное 343 Процесс сдваивания 200 рекуррентный 203
Процессор 17
матричный 54
с архитектурой VLIW 62
суперскалярный 62
центральный 17 Процессы параллельные
FORK/JOIN 226 Прямая подстановка 448
Р
Развертка 394 вектор: граничных значений 462 задержек 462 реализации 462 кусочно-линейная 397
направляющий вектор 399 минимальная 464 нулевая 403 обобщенная 394 обобщенная самая строгая 423 основная 395 расщепляющая 395 с ограничениями 463 строгая 394 Раскрутка цикла 131 Распределение цикла 417 Расслоение памяти 52 Рекуррентные соотношения 471
С
Свойства метакомпьютера 157 Связи фиктивные 450 Связь:
информационная 327
операционная 327
по управлению 327
транспортная 488
шинная 488 Сдваивания рекуррентного процесса 203 Сеть:
каскадная перестраиваемая 41
латентность 151
пропускная способность 151
598
Предметный указатель
Синхронизация барьерная 144 Система коммутации 65 Система программирования:
DVM 235
Linda 268
mpC 248
MPI 275
MPI-2 298
OpenMP 225
НОРМА 309
Т-система 301 Систолическая:
система 483
ячейка 483 Систолический массив 482 Скалярное произведение 403 Скашивание цикла 418 Слияние циклов 416 Слово 15
адрес 15
длина 15
содержимое 15 Спецпроцессор 61 Список Тор500 167 Срабатывание оператора 343 Стоимость:
операции 80
работы 80 Сумматор 24
т
Теневые грани массива 242 Теорема об информационном
покрытии 348 Тестирование компьютеров:
HINT 176
UNPACK 166
NPB 172
PERFECT Club Benchmarks 171
SPEC 175
STREAM 168
Top500 167
бенчмарк 166
Ливерморские циклы 170 Технологии параллельного программирования 220
Топология:
гиперкуба 69
связи процессоров 68 Точка лексикографического максимума 356
У
Укладка графа 481 Умножитель 24 Ускорение 83 Устройство 16
ввода/вывода 16
арифметико-логическое 16
загруженность 80
конвейерное 79 время разгона 125 длина 79 ступень 79
оперативное запоминающее 26
простое 79
управления 17 Уточнение внешних переменных 453
Ф
Формула Эйлера 499 Фронт:
волновой 406
вычислений 406 Функции:
базовые 249
лексикографически неупорядоченные 370
лексикографически упорядоченные 370
покрывающие 348
сетевые 254
узловые 249
чистые 302
ц
Центральный процессор 17 Цикл:
ParDo 374
TrueParDo 578
Предметный указатель
599
Циклический профиль: путей 566 фрагмента 569
ч
Число:
двоичные разряды 19
мантисса 22
машинный нуль 22
округление 19
ошибка округления 19
порядок 22
с плавающей запятой 22
с фиксированной запятой 21
э
Эквивалентное преобразование программы 409
Эквивалентные:
возмущения 521
выражения 408
программы 409 Эффективность 83
Я
Ядро:
вычислительное 36
информационное 197 Язык программирования 31
высокого уровня 31
машинно-независимый 32
низкого уровня 31
однократного присваивания 310
проблемно-ориентированный 31
универсальный 32