Вы находитесь на странице: 1из 62

Учреждение Российской академии наук УДК 681.3.

06
Институт проблем управления
им. В.А. Трапезникова РАН Сальников А.М., Ярошенко Е.А., Гребенник О.С., Спиридо-
нов С.В. Введение в параллельные вычисления. Основы
программирования на языке Си с использованием интер-
фейса MPI. – М.: ИПУ РАН, 2009. – 123 с.

Рассматривается архитектура многопроцессорных вы-


А.М. Сальников, Е.А. Ярошенко, числительных систем. Приводится обзор современных
О.С. Гребенник, С.В. Спиридонов суперкомпьютеров. Описывается работа на суперкомпью-
терах кластерного типа и особенности параллельного про-
граммирования на языке Си с использованием интерфейса
MPI. Среди рассматриваемых вопросов уделяется внима-
ВВЕДЕНИЕ ние необходимым навыкам разработки программ в опера-
В ПАРАЛЛЕЛЬНЫЕ ВЫЧИСЛЕНИЯ. ционной системе Linux. Изложенный материал адаптиро-
ван применительно к суперкомпьютеру ИПУ РАН.
ОСНОВЫ ПРОГРАММИРОВАНИЯ
Научное издание рассчитано на научных работников,
НА ЯЗЫКЕ СИ С ИСПОЛЬЗОВАНИЕМ аспирантов, студентов и разработчиков прикладных про-
ИНТЕРФЕЙСА MPI грамм.

Рецензенты: д.т.н. Лебедев В. Г.


д.ф.-м.н. проф. Исламов Г. Г.

Утверждено к печати Редакционным советом Института.

Текст воспроизводится в виде, утверждённом


Редакционным советом Института

ISBN 978-5-91450-031-0
Москва 2009
1. АРХИТЕКТУРА МНОГОПРОЦЕССОРНЫХ
ОГЛАВЛЕНИЕ ВЫЧИСЛИТЕЛЬНЫХ СИСТЕМ
1. Архитектура многопроцессорных вычислительных
систем................................................................................................ 4 1.1. Введение
1.1. Введение .................................................................................... 4 Термин «суперкомпьютер» существует со времени появле-
1.2. Традиционная классификация вычислительных систем....... 7 ния первых электронных вычислительных машин (ЭВМ) и
1.3. Классификация многопроцессорных вычислительных фактически эволюционирует вместе с термином «компьютер».
систем ............................................................................................... 9 Сфера применения компьютеров охватывает абсолютно все
1.4. Векторно-конвейерные системы ........................................... 11 области человеческой деятельности, и сегодня невозможно
1.5. Симметричные многопроцессорные системы представить себе эффективную организацию работы без приме-
(SMP и NUMA) ............................................................................... 13 нения компьютеров. Но если компьютеры в целом развиваются
1.6. Системы с массовым параллелизмом (MPP)........................ 16 разнонаправлено и самым непостижимым образом проникают в
1.7. Кластерные системы............................................................... 19 нашу жизнь, то суперкомпьютеры по-прежнему предназначены
2. Программирование для многопроцессорных для того же, для чего разрабатывались первые электронные
вычислительных систем ............................................................. 22 вычислительные машины, т.е. для решения задач, требующих
2.1. Программирование для систем с общей памятью ............... 22 выполнения больших объемов вычислений. Развитие топливно-
2.2. Программирование для систем с распределенной памятью24 энергетического комплекса, авиационной, ракетно-космической
2.3. Параллельное программирование ......................................... 25 промышленности и многих других областей науки и техники
2.4. Оценка эффективности распараллеливания программ ....... 31 требует постоянного увеличения объема производимых расчетов
2.5. Проблемы оптимизации программ ....................................... 32 и, таким образом, способствует активному развитию суперком-
пьютеров.
3. Суперкомпьютер ИПУ РАН ............................................... 42 Считается, что термин «суперкомпьютер» (англ.
3.1. Операционная система GNU/Linux ....................................... 43 supercomputer) впервые стали использовать Джордж Майкл
3.2. Инструментарий разработчика.............................................. 50 (George Michael) и Сидней Фернбач (Sidney Fernbach), занимав-
4. Основы программирования на языке Си с шиеся проблемой параллельных вычислений в Ливерморской
использованием интерфейса MPI.............................................. 61 национальной лаборатории им. Э. О. Лоуренса (англ. Lawrence
4.1. Инициализация MPI ............................................................... 63 Livermore National Laboratory, LLNL) с конца пятидесятых годов
4.2. Прием/отправка сообщений с блокировкой......................... 68 двадцатого века.
4.3. Прием/отправка сообщений без блокировки ....................... 75 Ливерморская национальная лаборатория в настоящее вре-
4.4. Объединение запросов на прием/отправку сообщений ...... 85 мя входит в структуру Калифорнийского университета (англ.
4.5. Барьерная синхронизация ...................................................... 90 The University of California, UC) и наряду с Национальной лабо-
4.6. Группы процессов................................................................... 92 раторией в Лос-Аламосе (англ. Los Alamos National Laboratory,
4.7. Коммуникаторы групп ........................................................... 99 LANL) «является главной научно-исследовательской и опытно-
4.8. Функции коллективного взаимодействия .......................... 104 конструкторской организацией для решения проблем нацио-
4.9. Типы данных ......................................................................... 106 нальной безопасности США», т.е. одной из двух лабораторий,
главной задачей которых служит разработка ядерного оружия.
Литература................................................................................... 123 Также лаборатория занимается исследованиями в области наук,

3 4
напрямую не связанных с военными технологиями, таких как очень большие объемы вычислений, т.е. более производитель-
энергетика, экология и биология (в том числе биоинженерия). ный и дорогой, чем любой серийно выпускаемый компьютер.
Именно в Ливерморской национальной лаборатории за многие Большинство современных суперкомпьютеров – это кла-
годы было создано и успешно эксплуатировалось абсолютное стерные системы, состоящие из большого числа серийно выпус-
большинство известных суперкомпьютеров, включая IBM Blue каемых компьютеров, объединенных в единую систему с помо-
Gene/L – самый быстрый в мире суперкомпьютер 2004-2008 гг. щью серийно выпускаемых сетевых интерфейсов. Границы
В общеупотребительный лексикон термин «суперкомпью- между специализированным программным обеспечением для
тер» вошел в восьмидесятых годах благодаря феноменальной суперкомпьютеров и типовым программным обеспечением
популярности в СМИ компьютерных систем Сеймура Крея сильно размыты. При этом суперкомпьютеры не ориентированы
(Seymour Cray), таких как Cray-1, Cray-2 и др. В то время в на работу с типовыми приложениями и этим они принципиально
научно-популярной литературе суперкомпьютером назывался отличаются от других компьютеров с высокой общей произво-
«любой компьютер, который создал Сеймур Крей», хотя сам дительностью. В отличие от серверов и мейнфреймов, призван-
Крей никогда не называл свои системы суперкомпьютерами, ных работать с типовыми задачами (управление базами данных,
предпочитая использовать традиционное название «компьютер». группами пользователей и т.п.), суперкомпьютеры призваны
Более того, еще при жизни Сеймура Крея его именем называли работать со сложными задачами (прогнозирование, моделирова-
различные суперкомпьютеры, созданные другими талантливыми ние и т.п.), требующими создания собственных приложений.
инженерами, среди которых был Стив Чен (Steve Chen), созда- Иногда суперкомпьютеры работают с одним-единственным
тель самого производительного суперкомпьютера начала вось- приложением, использующим всю память и все процессоры
мидесятых Cray X-MP, породившего настоящий суперкомпью- системы; в других случаях они обеспечивают выполнение боль-
терный бум в СМИ. В настоящее время имя Сеймура Крея носит шого числа разнообразных пользовательских программ.
компания Cray Inc., занимающая достойное место в ряду произ- Вычислительное направление применения компьютеров
водителей суперкомпьютеров. всегда оставалось основным двигателем прогресса в компьютер-
На волне триумфа и популярности суперкомпьютеров Сей- ных технологиях. Основным параметром, отличающим супер-
мура Крея в конце восьмидесятых годов появилось множество компьютер от обычного компьютера, является его производи-
небольших компаний, занимающихся созданием высокопроиз- тельность (вычислительная мощность) – количество
водительных компьютеров. Однако уже к середине девяностых арифметических операций за единицу времени. Именно этот
большинство из них было приобретено традиционными произ- показатель с наибольшей очевидностью демонстрирует масшта-
водителями компьютерного оборудования, такими как IBM и бы прогресса, достигнутого в компьютерных технологиях. Про-
Hewlett-Packard. изводительность одного из первых суперкомпьютеров ABC,
Из-за шумихи в средствах массовой информации, созданной созданного в 1942 году в Университете штата Айова (англ. Iowa
при активной «помощи» журналистов, термин «суперкомпью- State University of Science and Technology, ISU) составляла всего
тер» некоторое время трактовался по-разному. Например, в 1989 30 операций в секунду, тогда как пиковая производительность
году знаменитый компьютерный инженер и создатель архитек- самого мощного суперкомпьютера 2008 года IBM Roadrunner в
туры VAX Гордон Белл (Gordon Bell) в шутку предложил счи- Национальной лаборатории в Лос-Аламосе составляет 1 квад-
тать суперкомпьютером любой компьютер, весящий более риллион (1015) операций в секунду.
тонны. Сегодня термин «суперкомпьютер» вернулся к истокам и Таким образом, за 65 лет произошло увеличение производи-
по-прежнему обозначает компьютер, способный выполнять тельности суперкомпьютеров в 30 триллионов раз. Невозможно
назвать другую сферу человеческой деятельности, где прогресс

5 6
был бы столь очевиден и так велик. И такой прогресс оказался понятиях потоков команд и потоков данных. На основе числа
возможным не столько за счет тысячекратного увеличения этих потоков выделяется четыре класса архитектур:
скорости работы электронных схем, сколько за счет максималь- − SISD (англ. Single Instruction Single Data) – единственный
ного распараллеливания обработки данных. поток команд и единственный поток данных. По сути де-
Считается, что идея параллельной обработки данных как ла это классическая машина фон Неймана. К этому классу
мощного резерва увеличения производительности вычислитель- относятся все однопроцессорные системы.
ных машин была высказана еще Чарльзом Бэббиджем примерно − SIMD (Single Instruction Multiple Data) – единственный
за сто лет до появления первого компьютера. Однако уровень поток команд и множественный поток данных. Типичны-
развития технологий середины девятнадцатого века не позволил ми представителями являются матричные компьютеры, в
ему реализовать эту идею. С появлением первых компьютеров которых все процессорные элементы выполняют одну и
идеи распараллеливания неоднократно становились отправной ту же программу, применяемую к своим (различным для
точкой при разработке самых передовых и производительных каждого ПЭ) локальным данным. Некоторые авторы к
вычислительных систем. Без преувеличения можно сказать, что этому классу относят и векторно-конвейерные компьюте-
вся история развития высокопроизводительных вычислений – ры, если каждый элемент вектора рассматривать как от-
это история реализации идей параллельной обработки данных на дельный элемент потока данных.
том или ином этапе развития компьютерных технологий, естест- − MISD (Multiple Instruction Single Date) – множественный
венно, в сочетании с увеличением скорости работы электронных поток команд и единственный поток данных. М. Флинн
схем. не смог привести ни одного примера реально сущест-
Принципиально важными решениями в повышении произ- вующей системы, работающей на этом принципе. Неко-
водительности вычислительных систем были: торые авторы в качестве представителей такой архитек-
− введение конвейерной организации выполнения команд; туры называют векторно-конвейерные компьютеры,
− включение в систему команд векторных операций, позво- однако такая точка зрения не получила широкой под-
ляющих одной командой обрабатывать целые массивы держки.
данных; − MIMD (Multiple Instruction Multiple Date) – множествен-
− распределение вычислений на множество процессоров. ный поток команд и множественный поток данных. К
этому классу относится большинство современных мно-
1.2. Традиционная классификация гопроцессорных систем.
вычислительных систем Поскольку в этой классификации почти все современные
многопроцессорные системы принадлежат одному классу, то
Большое разнообразие вычислительных систем породило
вряд ли такая классификация представляет сегодня какую-либо
естественное желание ввести для них какую-то классификацию.
практическую ценность. Тем не менее, используемые в ней
Эта классификация должна однозначно относить ту или иную
термины достаточно часто упоминаются в литературе по парал-
вычислительную систему к некоторому классу, который, в свою
лельным вычислениям.
очередь, должен достаточно полно ее характеризовать. Попыток
Эффективность использования современных компьютеров в
такой классификации в разное время предпринималось множе-
решающей степени зависит от состава и качества программного
ство. Одна из первых классификаций, ссылки на которую наибо-
обеспечения, установленного на них. В первую очередь это
лее часто встречаются в литературе, была предложена М. Флин-
касается программного обеспечения, предназначенного для
ном в конце 60-х годов прошлого века. Она базируется на

7 8
разработки прикладных программ. Так, например, недостаточ- Processing, MPP). При этом, как правило, каждый из ПЭ системы
ная развитость средств разработки для систем с распределенной с MPP-архитектурой является универсальным процессором,
памятью долгое время сдерживала их широкое использование. В действующим по своей собственной программе (в отличие от
настоящее время ситуация изменилась, и благодаря кластерным общей программы для всех ПЭ матричной МВС).
технологиям такие системы стали самой распространенной и Первые матричные МВС выпускались с конца семидесятых
доступной разновидностью высокопроизводительных вычисли- годов буквально поштучно, поэтому их стоимость была фанта-
тельных систем. стически высокой. Серийные образцы подобных систем, такие
Основной характеристикой при классификации многопро- как первая коммерческая матричная МВС ICL DAP (англ.
цессорных систем является наличие общей или распределенной Distributed Array Processor), включавшая до 8192 ПЭ, появились
памяти. Это различие является важнейшим фактором, опреде- примерно в это же время, однако не получили широкого распро-
ляющим способы параллельного программирования и, соответ- странения ввиду сложности программирования МВС с одним
ственно, структуру программного обеспечения. потоком управления (с одной программой, общей для всех ПЭ).
Первые промышленные образцы многопроцессорных сис-
1.3. Классификация многопроцессорных тем появились на базе векторно-конвейерных компьютеров в
вычислительных систем середине восьмидесятых годов. Наиболее распространенными
МВС такого типа были суперкомпьютеры Сеймура Крея. Одна-
В процессе развития суперкомпьютерных технологий идею ко такие системы были чрезвычайно дорогими и производились
повышения производительности вычислительной системы за небольшими сериями. Как правило, в подобных компьютерах
счет увеличения числа процессоров использовали неоднократно. объединялось от 2 до 16 процессоров, которые имели равно-
Если не вдаваться в исторический экскурс и обсуждение всех правный (симметричный) доступ к общей оперативной памяти.
таких попыток, то можно следующим образом вкратце описать В связи с этим они стали называться симметричными многопро-
развитие событий. цессорными системами (англ. Symmetric Multiprocessing, SMP).
Экспериментальные разработки по созданию многопроцес- Как альтернатива дорогим многопроцессорным системам на
сорных вычислительных систем (МВС) начались в семидесятых базе векторно-конвейерных процессоров была предложена идея
годах двадцатого века. Одной из первых таких систем стала строить эквивалентные по мощности многопроцессорные систе-
разработанная в 1976 году в Университете Иллинойса в Урбане- мы из большого числа дешевых серийно выпускаемых микро-
Шампэйн (англ. University of Illinois at Urbana-Champaign, UIUC) процессоров. Однако очень скоро обнаружилось, что архитекту-
МВС ILLIAC IV, которая включала 64 (в проекте до 256) про- ра SMP обладает весьма ограниченными возможностями по
цессорных элемента (ПЭ), работающих по единой программе, наращиванию числа процессоров в системе из-за резкого увели-
применяемой к содержимому собственной оперативной памяти чения числа конфликтов при обращении к общей шине памяти.
каждого ПЭ. Обмен данными между процессорами осуществ- В связи с этим оправданной представлялась идея снабдить каж-
лялся через специальную матрицу коммуникационных каналов. дый процессор собственной оперативной памятью, превращая
Указанная особенность коммуникационной системы дала назва- компьютер в объединение независимых вычислительных узлов.
ние «матричные суперкомпьютеры» соответствующему классу Такой подход значительно увеличил масштабируемость много-
МВС. Более широкий класс МВС с распределенной памятью и с процессорных систем, но в свою очередь потребовал разработки
произвольной коммуникационной системой получил впоследст- специального способа обмена данными между вычислительны-
вии название «многопроцессорные системы с массовым парал- ми узлами, реализуемого обычно в виде механизма передачи
лелизмом», или МВС с MPP-архитектурой (англ. Massive Parallel сообщений (англ. Message Passing). Компьютеры с такой архи-

9 10
тектурой являются наиболее яркими представителями совре- ются процедуре векторизации с тем, чтобы они могли реализо-
менных MPP-систем. В настоящее время оба этих направления вываться с использованием векторных команд. Как правило, это
(или их комбинации) являются доминирующими в развитии выполняется автоматически компиляторами при создании ис-
суперкомпьютерных технологий. полняемого кода программы. Поэтому векторно-конвейерные
Нечто среднее между SMP и MPP представляют собой компьютеры не требуют какой-то специальной технологии
NUMA-архитектуры (англ. Non-Uniform Memory Access), в программирования, что и явилось решающим фактором в их
которых память физически разделена, но логически общедос- успехе на компьютерном рынке. Тем не менее, требуется соблю-
тупна. При этом время доступа к различным блокам памяти дение некоторых правил при написании циклов с тем, чтобы
становится неодинаковым. В одной из первых NUMA-систем компилятор мог их эффективно векторизовать.
Cray T3D, выпущенной в 1993 году, время доступа к памяти Исторически это были первые компьютеры, к которым в
соседнего процессора было в шесть раз больше, чем к памяти полной мере было применимо понятие суперкомпьютер. Как
своего собственного процессора. правило, несколько векторно-конвейерных процессоров (от двух
В настоящее время развитие суперкомпьютерных техноло- до шестнадцати) работают в режиме с общей памятью (SMP),
гий идет по четырем основным направлениям: образуя вычислительный узел, а несколько таких узлов объеди-
− векторно-конвейерные системы; няются с помощью коммутаторов, образуя NUMA- или MPP-
− SMP- и NUMA-системы; систему. Типичными представителями такой архитектуры явля-
− MPP-системы; ются компьютеры серий Cray J90 (1994 год), Cray T90 (1995
− кластерные системы. год), NEC SX-4 (1995 год), Cray SV1 (1998 год), NEC SX-5 (1999
год). Уровень развития микроэлектронных технологий долгое
1.4. Векторно-конвейерные системы время не позволял производить однокристальные векторные
процессоры, поэтому эти системы были довольно громоздки и
Первый векторно-конвейерный компьютер Cray-1 появился чрезвычайно дороги. В связи с этим, начиная с середины 90-х
в 1976 году. Архитектура его оказалась настолько удачной, что годов, когда появились достаточно мощные суперскалярные
он положил начало целому семейству компьютеров. Название микропроцессоры, интерес к этому направлению значительно
этому семейству компьютеров дали два принципа, заложенные в ослабел.
архитектуре процессоров: Суперкомпьютеры с векторно-конвейерной архитектурой
− конвейерная организация обработки потока команд; стали проигрывать системам с массовым параллелизмом. Одна-
− введение в систему команд набора векторных операций, ко в марте 2002 г. компания NEC представила систему Earth
которые позволяют оперировать целыми массивами дан- Simulator (ES) из 5120 векторно-конвейерных процессоров,
ных. которая в 5 раз превысила производительность предыдущего
Длина одновременно обрабатываемых векторов в современ- обладателя рекорда, очередной MPP-системы из Ливерморской
ных векторных системах составляет, как правило, 128 или 256 национальной лаборатории ASCI White (2000 год), состоящей из
элементов. Очевидно, что векторные процессоры должны иметь 8192 суперскалярных микропроцессоров. Это заставило многих
гораздо более сложную структуру и по сути дела содержать по-новому взглянуть на перспективы векторно-конвейерных
множество арифметических устройств. Основное назначение систем. Производительность суперкомпьютера NEC ES остава-
векторных операций состоит в распараллеливании выполнения лась самой высокой в мире до момента запуска в 2004 году
операторов цикла, в которых в основном и сосредоточена боль- первой версии суперкомпьютера IBM Blue Gene/L с MPP-
шая часть вычислительной работы. Для этого циклы подверга- архитектурой.

11 12
1.5. Симметричные многопроцессорные системы Наличие общей памяти значительно упрощает организацию
(SMP и NUMA) взаимодействия процессоров между собой и упрощает програм-
мирование, поскольку параллельная программа работает в еди-
Характерной чертой симметричных многопроцессорных ном адресном пространстве. Однако за этой кажущейся просто-
систем является то, что все процессоры имеют прямой и равно- той скрываются проблемы, присущие системам этого типа. Все
правный доступ к любой точке общей памяти. Первые SMP- они, так или иначе, связаны с оперативной памятью. Дело в том,
системы состояли из нескольких однородных процессоров и что в настоящее время даже в однопроцессорных системах
массива общей памяти, к которой процессоры подключались самым узким местом является оперативная память, скорость
через общую системную шину. Однако очень скоро обнаружи- работы которой отстает от скорости работы процессора. Для
лось, что такая архитектура непригодна для создания каких-либо того чтобы сгладить этот разрыв, современные процессоры
масштабных систем. снабжаются скоростной буферной памятью (кэш-памятью),
Первая возникшая проблема – это большое число конфлик- скорость работы которой значительно выше, чем скорость рабо-
тов при обращении к общей шине. Остроту проблемы удалось ты основной памяти.
частично снять разделением памяти на блоки, подключение к В качестве примера приведем данные измерения пропуск-
которым с помощью коммутаторов позволило распараллелить ной способности кэш-памяти и основной памяти для персональ-
обращения от различных процессоров. Однако и в таком подхо- ного компьютера образца 1999 года на базе процессора Pentium
де неприемлемо большими казались накладные расходы для III. В данном процессоре кэш-память имела два уровня:
систем более чем с 32 процессорами. − L1 (буферная память команд) – объем 32 Кб, скорость
Современные системы архитектуры SMP состоят, как пра- обмена 9976 Мб/с;
вило, из нескольких однородных серийно выпускаемых микро-
− L2 (буферная память данных) – объем 256 Кб, скорость
процессоров и массива общей памяти, подключение к которой
обмена 4446 Мб/с.
производится либо с помощью общей шины, либо с помощью
В то же время скорость обмена с основной памятью состав-
коммутатора.
ляла всего 255 Мб/с. Это означало, что для полной согласован-
ности со скоростью работы процессора скорость работы основ-
CPU CPU CPU ной памяти должна была быть минимум в 40 раз выше. Для
Кэш Кэш Кэш сравнения, скорость обмена 10 Гбайт/с обеспечивает контроллер
памяти (впервые интегрированный непосредственно в кристалл
CPU) процессора Intel Core i7 образца 2009 года.
Очевидно, что при проектировании многопроцессорных
Шина или коммутатор систем эти проблемы еще более обостряются. Помимо хорошо
известной проблемы конфликтов при обращении к общей шине
памяти возникла и новая проблема, связанная с иерархической
структурой организации памяти современных компьютеров. В
Общая память многопроцессорных системах, построенных на базе микропро-
цессоров со встроенной кэш-памятью, нарушается принцип
Рис. 1. Архитектура SMP-системы равноправного доступа к любой точке памяти. Данные, находя-
щиеся в кэш-памяти некоторого процессора, недоступны для

13 14
других процессоров. Это означает, что после каждой модифика- Время обращения к различным уровням может отличаться
ции копии некоторой переменной, находящейся в кэш-памяти на порядок, что сильно усложняет написание эффективных
какого-либо процессора, необходимо производить синхронную параллельных программ для таких систем.
модификацию самой этой переменной, расположенной в основ- Перечисленные обстоятельства значительно ограничивают
ной памяти. возможности по наращиванию производительности ccNUMA
С большим или меньшим успехом эти проблемы решаются систем путем простого увеличения числа процессоров. Тем не
в рамках общепринятой в настоящее время архитектуры менее, эта технология позволяет создавать системы, содержащие
ccNUMA (англ. Cache coherent Non-Uniform Memory Access). В до 256 процессоров с общей производительностью порядка 200
такой архитектуре память физически распределена, но логиче- млрд. операций в секунду. Системы этого типа серийно произ-
ски общедоступна. Это с одной стороны позволяет работать с водятся многими компьютерными фирмами как многопроцес-
единым адресным пространством, а с другой – увеличивает сорные серверы с числом процессоров от 2 до 128 и до недавне-
масштабируемость систем. го времени они прочно удерживали лидерство в классе малых
суперкомпьютеров. Типичными представителями данного клас-
CPU са суперкомпьютеров являются компьютеры на базе процессо-
CPU CPU CPU
Кэш Кэш ров AMD Opteron или Intel Itanium 2, например, HP Integrity
Кэш Кэш
Superdome (2002 год).
С конца 2008 года архитектура ccNUMA также поддержи-
вается микроархитектурой Nehalem, преемником Core 2, первы-
Шина Шина ми представителями которой стали процессоры Intel Core i7.
Неприятным свойством всех симметричных многопроцес-
сорных систем является то, что их стоимость растет быстрее,
чем производительность при увеличении числа процессоров в
системе. Кроме того, из-за задержек при обращении к общей
Память Память памяти неизбежно взаимное торможение при параллельном
выполнении даже независимых программ.

1.6. Системы с массовым параллелизмом (MPP)


Коммуникационная среда
Проблемы, присущие многопроцессорным системам с об-
щей памятью, простым и естественным образом устраняются в
Рис. 2. Архитектура NUMA-системы
системах с массовым параллелизмом. Компьютеры этого типа
Когерентность кэш-памяти поддерживается на аппаратном представляют собой многопроцессорные системы с распреде-
уровне, что не избавляет, однако, от накладных расходов на ее ленной памятью, в которых с помощью некоторой коммуника-
поддержание. В отличие от классических SMP-систем память ционной среды объединяются однородные вычислительные
становится трехуровневой: узлы.
− кэш-память процессора; Каждый из узлов состоит из одного или нескольких процес-
− локальная оперативная память; соров, собственной оперативной памяти, коммуникационного
− удаленная оперативная память. оборудования, подсистемы ввода/вывода, т.е. обладает всем

15 16
необходимым для независимого функционирования. При этом среды. Самым простым и наиболее эффективным было бы со-
на каждом узле может функционировать либо полноценная единение каждого процессора с каждым. Но тогда на каждом
операционная система, либо урезанный вариант, поддерживаю- узле суперкомпьютера, содержащего N процессоров, потребова-
щий только базовые функции ядра, а полноценная операционная лось бы N – 1 коммуникационных каналов, желательно двуна-
система работает на специальном управляющем компьютере. правленных. Различные производители MPP-систем использова-
ли разные топологии. В суперкомпьютерах Intel Paragon (1992
Память Память Память год) процессоры образовывали прямоугольную двумерную
сетку. Для этого на каждом узле достаточно было иметь четыре
коммуникационных канала. В суперкомпьютерах Cray T3D
(1993 год) и Cray T3E (1995 год) использовалась топология
трехмерного тора. Соответственно, на каждом узле этого ком-
Кэш Кэш Кэш пьютера было по шесть коммуникационных каналов. Фирма
CPU CPU CPU nCUBE в начале девяностых годов использовала в своих компь-
ютерах топологию n-мерного гиперкуба.
Каждая из упомянутых топологий имела свои преимущест-
ва и недостатки. Отметим, что при обмене данными между
Коммуникационная среда процессорами, не являющимися ближайшими соседями, проис-
ходит трансляция данных через промежуточные узлы. Очевид-
Рис. 3. Архитектура MPP-системы но, что в узлах должны быть предусмотрены какие-то аппарат-
Процессоры в таких системах имеют прямой доступ только ные средства, которые освобождали бы центральный процессор
к своей локальной памяти. Доступ к памяти других узлов реали- от участия в трансляции данных. В последнее время для соеди-
зуется обычно с помощью механизма передачи сообщений. нения вычислительных узлов чаще используется иерархическая
Такая архитектура вычислительной системы устраняет одновре- система высокоскоростных коммутаторов, как это впервые было
менно как проблему конфликтов при обращении к памяти, так и реализовано в компьютерах IBM SP2 (1994 год). Такая тополо-
проблему когерентности кэш-памяти. Это дает возможность гия дает возможность прямого обмена данными между любыми
практически неограниченно наращивать число процессоров в узлами, без участия в этом промежуточных узлов.
системе, увеличивая тем самым ее производительность. Успеш- Системы с распределенной памятью идеально подходят для
но функционируют MPP-системы с сотнями и тысячами процес- параллельного выполнения независимых программ, поскольку
соров, производительность наиболее мощных из них достигает при этом каждая программа выполняется на своем узле и ника-
нескольких сотен триллионов операций в секунду. Важным ким образом не влияет на выполнение других программ. Однако
свойством MPP-систем является их высокая масштабируемость. при разработке параллельных программ приходится учитывать
В зависимости от вычислительных потребностей для достиже- более сложную, чем в SMP-системах, организацию памяти.
ния необходимой производительности требуется просто собрать Оперативная память в MPP-системах имеет трехуровневую
систему с нужным числом узлов. структуру:
Однако на практике устранение одних проблем, как это − кэш-память процессора;
обычно бывает, порождает другие. Для MPP-систем на первый − локальная оперативная память узла;
план выходит проблема эффективности коммуникационной − оперативная память других узлов.

17 18
При этом отсутствует возможность прямого доступа к дан- Computing Array), в котором на базе 25 двухпроцессорных пер-
ным, расположенным на других узлах. Такие данные должны сональных компьютеров общей стоимостью порядка 100 тыс.
быть предварительно переданы в тот узел, который в них нужда- долл. США была создана система с производительностью, экви-
ется. Это значительно усложняет программирование. Кроме валентной производительности 48-процессорного Cray T3D
того, обмены данными между узлами выполняются значительно стоимостью несколько млн. долл. США.
медленнее, чем обработка данных в локальной оперативной Конечно, о полной эквивалентности упомянутых систем го-
памяти узлов. Поэтому написание эффективных параллельных ворить не приходилось, потому что производительность систем
программ для таких компьютеров представляет собой более с распределенной памятью очень сильно зависит от производи-
сложную задачу, чем для SMP-систем. тельности коммуникационной среды. Коммуникационную среду
можно достаточно полно охарактеризовать двумя параметрами:
1.7. Кластерные системы латентностью (временем задержки при посылке сообщения) и
пропускной способностью (скоростью передачи информации).
Кластерные технологии стали логическим продолжением
Для компьютера Cray T3D эти параметры составляли 1 мкс и 480
развития идей, заложенных в архитектуре MPP. Если процес-
Мб/с соответственно. Для кластера COCOA, в котором в качест-
сорный модуль в MPP-системе представляет собой законченную
ве коммуникационной среды использовалась стандартная на тот
вычислительную систему, то следующий шаг напрашивается
момент сеть Fast Ethernet, латентность и пропускная способ-
сам собой: почему бы в качестве таких вычислительных узлов не
ность составляли 100 мкс и 10 Мб/с соответственно. При таких
использовать обычные серийно выпускаемые компьютеры.
параметрах найдется не так много задач, эффективно решаемых
Развитие коммуникационных технологий, а именно, появление
на достаточно большом числе процессоров. Это обстоятельство
высокоскоростного сетевого оборудования и специального
долгое время отчасти объясняло чрезмерную стоимость супер-
программного обеспечения, такого как интерфейс MPI, реали-
компьютеров. Для сравнения, интерфейс InfiniBand 4X SDR,
зующего механизм передачи сообщений над стандартными
используемый в кластере ИПУ РАН, обеспечивает латентность
сетевыми протоколами, сделали кластерные технологии обще-
200 нс и пропускную способность 8 Гб/с (на уровне MPI – 1 мкс
доступными. Сегодня не составляет большого труда создать
и 800 Мб/с соответственно).
небольшую кластерную систему, объединив вычислительные
Если говорить кратко, то кластер – это связанный набор
мощности нескольких компьютеров лаборатории.
полноценных компьютеров, используемый в качестве единого
Привлекательной чертой кластерных технологий является
вычислительного ресурса. Преимущества кластерной системы
то, что они позволяют для достижения необходимой производи-
перед набором независимых компьютеров очевидны. Во-первых,
тельности объединять в единые вычислительные системы ком-
разработано множество диспетчерских систем пакетной обра-
пьютеры разного типа, от персональных компьютеров до про-
ботки заданий, позволяющих послать задание на обработку
мышленных блейд-серверов.
кластеру в целом, а не какому-то отдельному компьютеру. Эти
Широкое распространение кластерные технологии получи-
диспетчерские системы автоматически распределяют задания по
ли как средство создания систем суперкомпьютерного класса из
свободным вычислительным узлам или буферизуют их при
составных частей массового производства, значительно удешев-
отсутствии таковых, что позволяет обеспечить более равномер-
ляющих стоимость готовой системы. Одним из первых совре-
ную и эффективную загрузку компьютеров. Во-вторых, появля-
менных кластеров можно считать созданный в 1998 году в Уни-
ется возможность совместного использования вычислительных
верситете штата Пенсильвания (англ. Pennsylvania State
ресурсов нескольких компьютеров для решения одной задачи.
University) суперкомпьютер COCOA (англ. The Cost Effective

19 20
Для создания современных кластеров уже не используются не менее 1/10 пиковой производительности вычислительного
однопроцессорные персональные компьютеры – сегодня их узла, измеренной в мегафлопс». Таким образом, если в качестве
место заняли многопроцессорные, многоядерные и зачастую вычислительных узлов использовать рабочие станции образца
сами по себе высокопроизводительные SMP-серверы. При этом 2006 года на базе Intel Core 2 Duo с пиковой производительно-
не накладывается никаких ограничений на состав и архитектуру стью 15 гигафлопс, то аппаратура InfiniBand будет обеспечивать
узлов. Каждый из узлов может функционировать под управлени- необходимый теоретический минимум, а все еще популярная
ем своей собственной операционной системы. Чаще всего ис- Gigabit Ethernet – уже нет. В целом можно утверждать, что
пользуются UNIX-подобные ОС, причем как коммерческие InfiniBand в настоящее время становится новым сетевым стан-
(Solaris, Tru64 Unix), так и свободные (GNU/Linux, FreeBSD). В дартом, потому что соответствует потребностям не только су-
тех случаях, когда узлы кластера неоднородны, итоговая систе- перкомпьютеров кластерного типа, но и любых других высоко-
ма называется гетерогенным кластером. производительных серверных систем.
При создании кластера можно выделить два подхода:
1. В кластер объединяются полнофункциональные компью- 2. ПРОГРАММИРОВАНИЕ
теры, которые продолжают работать как самостоятельные ДЛЯ МНОГОПРОЦЕССОРНЫХ
единицы, например, рабочие станции лаборатории. Такой ВЫЧИСЛИТЕЛЬНЫХ СИСТЕМ
подход применяется при создании небольших кластерных
систем или в целях тестирования технологий параллель- 2.1. Программирование для систем с общей памятью
ной обработки данных.
2. Системные блоки компьютеров компактно размещаются К системам с общей памятью относятся компьютеры с SMP-
в стандартных серверных стойках, а для управления сис- архитектурой, различные разновидности NUMA-систем и мно-
темой и для запуска задач выделяется один или несколько гопроцессорные векторно-конвейерные компьютеры. Характер-
полнофункциональных компьютеров. Такой подход при- ным словом для этих компьютеров является «единый»: единая
меняется при целенаправленном создании мощных вы- оперативная память, единая операционная система, единая
числительных ресурсов. подсистема ввода-вывода. Только процессоры образуют множе-
За последние годы разработаны специальные технологии ство. Единая UNIX-подобная операционная система, управляю-
соединения компьютеров в кластер. Наиболее широко в настоя- щая работой всего компьютера, функционирует в виде множест-
щее время применяется высокоскоростная коммутируемая по- ва процессов. Каждая пользовательская программа также
следовательная шина InfiniBand. Это обусловлено простотой ее запускается как отдельный процесс.
использования и относительно низкой стоимостью коммуника- Операционная система сама каким-то образом распределяет
ционного оборудования, которое обеспечивает приемлемую процессы по процессорам. В принципе, для распараллеливания
скорость обмена между узлами от 2,5 Гбит/с до 96 Гбит/с, в программ можно использовать механизм порождения процессов.
зависимости от типа установленного оборудования. Однако этот механизм не очень удобен, поскольку каждый
Разработчики пакета подпрограмм ScaLAPACK, предназна- процесс функционирует в своем адресном пространстве, и ос-
ченного для решения задач линейной алгебры на многопроцес- новное достоинство этих систем – общая память – не может
сорных системах, в которых велика доля коммуникационных быть использована простым и естественным образом.
операций, сформулировали следующим образом требование к Для распараллеливания программ используется механизм
многопроцессорной системе: «Скорость межпроцессорных порождения нитей (англ. threads) – легковесных процессов, для
обменов между двумя узлами, измеренная в Мб/с, должна быть которых не создается отдельного адресного пространства, но

21 22
которые на многопроцессорных системах также распределяются ные нити. Предполагается, что OpenMP-программа без какой-
по процессорам. В языке программирования Си возможно пря- либо модификации должна работать как на многопроцессорных
мое использование этого механизма для распараллеливания системах, так и на однопроцессорных. В последнем случае
программ посредством вызова соответствующих системных директивы OpenMP просто игнорируются.
функций, а в компиляторах с языка Fortran этот механизм ис- Следует отметить, что наличие общей памяти не препятст-
пользуется либо для автоматического распараллеливания, либо в вует использованию технологий программирования, разрабо-
режиме задания распараллеливающих директив компилятору танных для систем с распределенной памятью. Многие произво-
(такой подход поддерживают и компиляторы языка Си). дители SMP-систем предоставляют также такие технологии
Все производители симметричных многопроцессорных сис- программирования, как MPI и PVM. В этом случае в качестве
тем в той или иной мере поддерживают стандарт PThreads коммуникационной среды выступает распределенная память.
(POSIX Threads) и включают в программное обеспечение распа-
раллеливающие компиляторы для популярных языков програм- 2.2. Программирование для систем с распределенной
мирования или предоставляют набор директив компилятору для памятью
распараллеливания программ. В частности, многие поставщики
компьютеров SMP-архитектуры (Sun, HP, SGI) в своих компиля- В системах с распределенной памятью на каждом вычисли-
торах предоставляют специальные директивы для распараллели- тельном узле функционируют собственные копии операционной
вания циклов. Однако эти наборы директив, во-первых, весьма системы, под управлением которых выполняются независимые
ограничены и, во-вторых, несовместимы между собой. В резуль- программы. Это могут быть как действительно независимые
тате этого разработчикам приходится распараллеливать при- программы, так и параллельные ветви одной программы. В этом
кладные программы отдельно для каждой платформы. случае единственно возможным механизмом взаимодействия
В последние годы все более популярной, особенно на плат- между ними является механизм передачи сообщений.
формах Sun Solaris и GNU/Linux, становится система програм- Стремление добиться максимальной производительности
мирования OpenMP (англ. Open Multi-Processing), являющаяся заставляет разработчиков при реализации механизма передачи
во многом обобщением и расширением этих наборов директив. сообщений учитывать особенности архитектуры многопроцес-
Интерфейс OpenMP задумывался как стандарт для программи- сорной системы. Это способствует написанию более эффектив-
рования в модели общей памяти. В OpenMP входят специфика- ных, но ориентированных на конкретный компьютер программ.
ции набора директив компилятору, процедур и переменных Вместе с тем независимыми разработчиками программного
среды. По сути дела, он реализует идею «инкрементального обеспечения было предложено множество реализаций механиз-
распараллеливания, когда разработчик не создает новую парал- ма передачи сообщений, независимых от конкретной платфор-
лельную программу, а просто добавляет в текст последователь- мы.
ной программы OpenMP-директивы. При этом система про- В 1995 г. был принят стандарт механизма передачи сообще-
граммирования OpenMP предоставляет разработчику большие ний MPI (англ. Message Passing Interface). Он готовился с 1992
возможности по контролю над поведением параллельного при- по 1994 гг. группой Message Passing Interface Forum, в которую
ложения. вошли представители более чем сорока организаций из США и
Вся программа разбивается на последовательные и парал- Европы. Основная цель, которую ставили перед собой разработ-
лельные области. Все последовательные области выполняет чики MPI – это обеспечение полной независимости приложений,
главная нить, порождаемая при запуске программы, а при входе написанных с использованием MPI, от архитектуры многопро-
в параллельную область главная нить порождает дополнитель- цессорной системы, без какой-либо существенной потери произ-

23 24
водительности. По замыслу авторов это должно было стать Решение на компьютере любой вычислительной задачи для
мощным стимулом для разработки прикладного программного выбранного алгоритма решения предполагает выполнение неко-
обеспечения и стандартизованных библиотек подпрограмм для торого фиксированного объема арифметических операций.
многопроцессорных систем с распределенной памятью. Под- Ускорить решение задачи можно одним из трех способов:
тверждением того, что эта цель была достигнута, служит тот − использовать более производительную вычислительную
факт, что в настоящее время этот стандарт поддерживается систему с более быстрым процессором и более скорост-
всеми производителями многопроцессорных систем. Реализации ной системной шиной;
MPI успешно работают не только на классических MPP- − оптимизировать программу, например, в плане более эф-
системах, но также на SMP-системах и в сетях рабочих станций фективного использования скоростной кэш-памяти;
(в том числе неоднородных). − распределить вычислительную работу между нескольки-
Реализация MPI – это библиотека функций, обеспечиваю- ми процессорами, т.е. перейти на параллельные техноло-
щая взаимодействие параллельных процессов с помощью меха- гии.
низма передачи сообщений. Большинство реализаций MPI под- Очевидно, что без распараллеливания не обойтись при про-
держивают интерфейсы для языков Си, C++ и Fortran. граммировании алгоритмов решения тех задач, которые в прин-
Библиотека MPI включает в себя множество функций передачи ципе не могут быть решены на однопроцессорных системах. Это
сообщений типа точка-точка, развитый набор функций для может проявиться в двух случаях: либо когда для решения
выполнения коллективных операций и управления процессами задачи требуется слишком много времени, либо когда для про-
параллельного приложения. граммы недостаточно оперативной памяти на однопроцессорной
Основное отличие MPI от предшественников в том, что яв- системе.
но вводятся понятия групп процессов, с которыми можно опери- Для небольших задач зачастую оказывается, что параллель-
ровать как с конечными множествами, а также областей связи и ная версия работает медленнее, чем однопроцессорная. Замет-
коммуникаторов, описывающих эти области связи. Это предос- ный эффект от распараллеливания начинает наблюдаться при
тавляет программисту очень гибкие средства для написания решении систем уравнений с большим количеством неизвест-
эффективных параллельных программ. В настоящее время MPI ных. На кластерных системах ситуация еще хуже. Разработчики
является основной технологией программирования для много- уже упоминавшегося ранее пакета ScaLAPACK для многопро-
процессорных систем с распределенной памятью. цессорных систем с приемлемым соотношением между произ-
Несмотря на значительные успехи в развитии технологии водительностью узла и скоростью обмена дают следующую
программирования с использованием механизма передачи сооб- формулу для количества процессоров P, которое рекомендуется
щений, трудоемкость программирования с использованием этой использовать при решении задач линейной алгебры
технологии по-прежнему велика. (1) P = m × n / 106,
где m × n – размерность матрицы.
2.3. Параллельное программирование Таким образом, на один процессор должен приходиться
Разработка параллельных программ является весьма трудо- блок матрицы размером примерно 1000 × 1000. Формула (1)
емким процессом, особенно для MPP-систем, поэтому, прежде носит рекомендательный характер (особенно для процессоров
чем приступать к этой работе, важно правильно оценить как последних поколений), при этом наглядно иллюстрируя масштаб
ожидаемый эффект от распараллеливания, так и трудоемкость задач, решаемых пакетами типа ScaLAPACK.
выполнения этой работы.

25 26
Рост эффективности распараллеливания при увеличении В SPMD-модели подзадачей чаще называется обработка некото-
размера решаемой системы уравнений объясняется ростом рого блока данных.
объема вычислительной работы пропорционально n3, а количе- Таким образом, схему параллельной программы, исполь-
ства обменов между процессорами пропорционально n2. Это зующей механизм передачи сообщений, можно упрощенно
снижает относительную долю коммуникационных затрат при записать на языке Си следующим образом:
увеличении размерности системы уравнений. Однако на эффек-
if (proc_id == 1) task1 ();
тивность параллельного приложения влияют не только комму- if (proc_id == 2) task2 ();
никационные издержки. …
Параллельные технологии в MPP-системах допускают две result = reduce (result1, result2, …);
модели программирования, похожие на традиционную класси-
фикацию вычислительных систем М. Флинна: Здесь proc_id – идентификатор процессора, а функция
− SPMD (англ. Single Program Multiple Date) – на всех про- reduce формирует некий глобальный результат на основе полу-
цессорах выполняются копии одной программы, обраба- ченных на каждом процессоре локальных результатов работы
тывающие разные блоки данных; функций task1, task2 и т. д. В этом случае одна и та же копия
− MPMD (англ. Multiple Program Multiple Date) – на всех программы будет выполняться на P процессорах, но каждый
процессорах выполняются разные программы, обрабаты- процессор будет решать только свою подзадачу. Если разбиение
вающие разные данные. на подзадачи достаточно равномерное, а накладные расходы на
Второй вариант иногда называют функциональным распа- обмены не слишком велики, то можно ожидать близкого к P
раллеливанием. Такой подход, в частности, используется в коэффициента ускорения решения задачи.
системах обработки видеоинформации, когда множество кван- На практике процедура распараллеливания чаще всего при-
тов данных должны проходить несколько этапов обработки. В меняется к циклам. Тогда в качестве отдельных подзадач могут
этом случае вполне оправданной будет конвейерная организация выступать экземпляры тела цикла, выполняемые для различных
вычислений, при которой каждый этап обработки выполняется значений переменной цикла. Рассмотрим простейший пример:
на отдельном процессоре. for (i=1, i<=1000, i++) c[i] = c[i] + a[i+1];
Однако функциональное распараллеливание имеет весьма
В данном примере можно выделить 1000 независимых под-
ограниченное применение, поскольку организовать достаточно
задач вычисления элементов массива c, каждая из которых
длинный конвейер, да еще с равномерной загрузкой всех про-
теоретически может быть выполнена на отдельном процессоре.
цессоров, весьма сложно. Наиболее распространенным режимом
Предположим, что в распоряжении программиста имеется
работы на системах с распределенной памятью является загрузка
вычислительная система, состоящая из 10 процессоров, тогда в
на некотором числе процессоров копий одной и той же про-
качестве независимой подзадачи можно оформить вычисление
граммы.
100 элементов массива c. При этом до выполнения вычислений
Разработка параллельной программы подразумевает раз-
необходимо принять решение о способе размещения этих масси-
биение задачи на P подзадач, каждая из которых решается на
вов в памяти процессоров. Возможны два варианта размещения:
отдельном процессоре.
1. Все массивы целиком хранятся в каждом процессоре, то-
В параллельном программировании термин «подзадача»
гда процедура распараллеливания сводится к вычислению
имеет весьма широкий смысл. В MPMD-модели подзадачей
стартового и конечного значений переменной цикла для
называется функционально выделенный фрагмент программы.
каждого процессора. В каждом процессоре будет хра-

27 28
ниться своя копия всего массива, в которой будет моди- 4. Распределение укрупненных подзадач по процессорам с
фицирована только часть элементов. В конце вычисле- целью обеспечения равномерной загрузки процессоров
ний, возможно, потребуется сборка модифицированных (англ. mapping).
частей со всех процессоров. Подобные схемы – не более чем краткие изложения фило-
2. Все или часть массивов распределены по процессорам, софии параллельного программирования, лишь подчеркиваю-
т.е. в каждом процессоре хранится 1 / P часть массива. щие отсутствие какого-либо формализованного подхода в па-
Тогда может потребоваться алгоритм установления связи раллельном программировании для MPP-систем. Если 1-й и 2-й
индексов локального массива в некотором процессоре с пункты имеют более или менее однозначное решение, то реше-
глобальными индексами всего массива, например, если ние задач 3-го и 4-го пунктов основывается главным образом на
значение элемента массива является некоторой функцией интуиции разработчика.
индекса. Если в процессе вычислений окажется, что ка- Предположим, что требуется исследовать поведение опре-
кие-то требующиеся компоненты массива отсутствуют в делителя матрицы в зависимости от некоторого параметра. Один
данном процессоре, то потребуется их пересылка из дру- из подходов состоит в том, чтобы написать параллельную вер-
гих процессоров. сию подпрограммы вычисления определителя и вычислить его
Вопрос распределения данных по процессорам и связь этого значения для исследуемого интервала значений параметра.
распределения с эффективностью параллельной программы Однако, если размер матрицы относительно невелик, то может
является основным вопросом параллельного программирования. оказаться, что значительные усилия на разработку параллельной
Хранение копий всех массивов во всех процессорах во мно- подпрограммы вычисления определителя не дадут сколь либо
гих случаях уменьшает накладные расходы на пересылки дан- существенного выигрыша в скорости работы программы. В этом
ных, однако не дает выигрыша в плане объема решаемой задачи случае более продуктивным подходом будет использование
и создает сложности синхронизации копий массива при незави- обычной оптимизированной однопроцессорной подпрограммы,
симом изменении его элементов различными процессорами. а по процессорам разложить исследуемый диапазон изменений
Распределение массивов по процессорам позволяет решать параметра.
значительно более объемные задачи, наиболее подходящие для Теперь рассмотрим свойства многопроцессорной MPP-
распараллеливания, но тогда на первый план выходит проблема системы, необходимые для выполнения на ней параллельных
минимизации пересылок данных. программ. Минимальный набор функций невелик:
Рассмотренный выше пример вычисления элементов масси- − процессоры в системе должны иметь уникальные иден-
ва достаточно хорошо укладывается в схему методологического тификаторы (номера);
подхода к решению задачи на многопроцессорной системе, − должна существовать функция самоидентификации про-
изложенную Яном Фостером (Ian Foster) [4]. Автор выделяет цессора;
четыре этапа разработки параллельного алгоритма: − должны существовать функции обмена между двумя про-
1. Разбиение задачи на минимальные независимые подзада- цессорами: посылка сообщения одним процессором и
чи (англ. partitioning). прием сообщения другим процессором.
2. Установление связей между подзадачами (англ. Парадигма передачи сообщений подразумевает асимметрию
communication). функций передачи и приема сообщений. Инициатива инициали-
3. Объединение подзадач с целью минимизации коммуни- зации обмена принадлежит передающей стороне. Принимающий
каций (англ. agglomeration). процессор может принять только то, что ему было послано.

29 30
Различные реализации механизма передачи сообщений для
облегчения разработки параллельных программ расширяют Количественно оценить величину f путем анализа текста
минимальный набор функций. программы фактически невозможно. Такая оценка может быть
получена только путем реальных запусков программы на разном
2.4. Оценка эффективности распараллеливания числе процессоров. Из формулы (2) также следует, что P-
программ кратное ускорение работы программы может быть достигнуто
только в том случае, когда доля непараллельного кода равна
В идеале решение задачи на P процессорах должно выпол- нулю, что на практике недостижимо.
няться в P раз быстрее, чем на одном процессоре, или/и должно Закон Амдала наглядно демонстрирует, что увеличение
позволить решить задачу, содержащую в P раз больше данных. числа процессоров для увеличения производительности работы
На практике такое недостижимо, что наглядно иллюстрируется программы не всегда оправданно. В некотором смысле, закон
законом Амдала: Амдала устанавливает предельное число процессоров, на кото-
(2) S ≤ 1 / (f + (1 –f) / P), ром программа будет выполняться с приемлемой эффективно-
где S – ускорение работы программы на P процессорах; f – стью в зависимости от доли непараллельного кода. Заметим, что
доля непараллельного кода в программе. формула (2) не учитывает накладные расходы на обмены между
Формула (2) справедлива как для систем с общей памятью, процессорами, поэтому в реальной системе ускорение будет еще
так и для систем с передачей сообщений, однако термин «доля меньше.
непараллельного кода» в этих системах имеет разный смысл. Следует также заметить, что распараллеливание программы
В программах, созданных для SMP-систем, долю непарал- – это лишь одно из средств ускорения ее работы. Не меньший, а
лельного кода образуют только операторы главной нити про- иногда и больший, эффект дает оптимизация программы на
граммы. В программах, созданных для MPP-систем, доля непа- одном процессоре. Актуальность такой оптимизации сохраняет-
раллельного кода образуют операторы, выполняющиеся всеми ся из-за большого разрыва в скоростях работы кэш-памяти
процессорами. процессора и оперативной памяти системы. Многие разработчи-
Таблица 1 наглядно демонстрирует ускорение работы про- ки не уделяют этой проблеме должного внимания, тратя значи-
граммы в зависимости от доли непараллельного кода по закону тельные усилия на распараллеливание заведомо неэффективных
Амдала. программ.
Таблица 1.
P Доля непараллельного кода, % 2.5. Проблемы оптимизации программ
50 25 10 5 2 Рассмотрим проблемы оптимизации, возникающие при раз-
Ускорение работы программы работке высокоэффективных программ для современных вычис-
2 1,33 1,60 1,82 1,90 1,96 лительных систем на примере элементарной задачи перемноже-
4 1,60 2,28 3,07 3,48 3,77 ния двух квадратных матриц. Поскольку эта задача кажется
8 1,78 2,91 4,71 5,93 7,02 разработчику очень простой, она особенно наглядно демонстри-
16 1,88 3,36 6,40 9,14 12,31 рует нетривиальность проблем, возникающих при создании
32 1,94 3,66 7,80 12,55 19,75 высокоэффективных приложений.
512 1,99 3,97 9,83 19,28 45,63 Отслеживать производительность компьютера на реальном
2048 2,00 3,99 9,96 19,82 48,83 приложении, выполняющем перемножение матриц, достаточно

31 32
просто. Для перемножения двух квадратных матриц размерно- стую задачу и практически не загружает память. Расчетная
сти n × n нужно выполнить (2 × n – 1) × n × n арифметических пиковая производительность участвовавших в тесте процессоров
операций. Таким образом, для оценки производительности равна:
компьютера достаточно замерить время выполнения этих опера- − Alpha – 1 гигафлопс;
ций и разделить одну величину на другую. − Pentium – 500 мегафлопс;
Следует отметить, что фиксация одного только времени вы- − Ultra – 800 мегафлопс;
полнения программы не совсем удобна, поскольку программа − Xeon – 37 гигафлопс (9 гигафлопс на одном ядре).
может выполняться при различных исходных данных (при раз- Для решения задачи перемножения квадратных матриц рос-
личных размерностях матриц), и тогда очень сложно сопостав- товские исследователи создали простую программу на языке
лять результаты тестов. Кроме того, само по себе время выпол- Fortran, однако мы для удобства реализовали тот же самый
нения программы мало что говорит об эффективности работы алгоритм на языке Си. Наше решение было правомерным, по-
системы в целом. Намного важнее знать, с какой производи- скольку результаты тестирования не использовались для сравне-
тельностью работает вычислительная система на этой програм- ния производительности различных архитектур.
ме. Создадим текстовый файл matrix.c, содержащий следующий
Надо также признать, что тест на перемножение матриц не код на языке Си:
совсем подходит для оценки производительности системы,
поскольку использует только операции умножения и сложения, #include <stdio.h>
но результаты этого теста весьма близки к реальности и потому #include <stdlib.h>
#include <sys/time.h>
интересны для анализа.
Выполним перемножение квадратных матриц размерности
double dseconds (void);
1000 × 1000 и сравним полученную производительность с рас-
четной пиковой производительностью процессоров, которую так int main (int argc, char *argv[])
любят указывать в рекламных материалах продавцы вычисли- {
тельных узлов для суперкомпьютеров. int i, j, k;
В 2003 году В.Н. Дацюк, А.А. Букатов и А.И. Жегуло из double s;
Ростовского государственного университета опубликовали свои const n = 1000; // Размерность матрицы
результаты проведения данного теста на трех компьютерах double *a, *b, *c; // Указатели на массивы
различной архитектуры; мы провели тест на одном из вычисли- double time; // Таймер
тельных узлов суперкомпьютера ИПУ РАН. Таким образом, в
нашем распоряжении оказались результаты решения одной и // Создание матриц (выделение памяти)
той же задачи на четырех процессорах различной архитектуры: a = (double *) malloc (n * n * sizeof (double));
− DEC Alpha с тактовой частотой 667 МГц; b = (double *) malloc (n * n * sizeof (double));
− Intel Pentium III с тактовой частотой 500 МГц; c = (double *) malloc (n * n * sizeof (double));
− SUN UltraSPARC II с тактовой частотой 300 МГц;
// Инициализация матриц
− Intel Quad Core Xeon с тактовой частотой 2,33 ГГц. for (i=0; i<n; i++)
Параметры конфигурации компьютеров особого значения {
не имеют, поскольку тестовое приложение решает очень про- for (j=0; j<n; j++)

33 34
{ Откомпилируем программу в стандартном режиме, без ка-
a[i*n + j] = (double) i + 1; ких-либо флагов и опций компилятора
b[i*n + j] = (double) 1 / (j + 1);
> gcc matrix.c
}
} Результаты работы программы при стандартной компиля-
ции, приведенные в таблице 2, слегка обескураживают, посколь-
// Перемножение матриц ку реальная вычислительная мощность всех процессоров оказы-
time = dseconds (); вается в десятки и сотни раз ниже заявленной производителями.
for (i=0; i<n; i++)
{
Таблица 2.
for (j=0; j<n; j++) Решение на одном Alpha Pentium Ultra Xeon
{ процессоре (1 ядро)
s = 0.0; Время работы, с 108,41 153,21 261,42 14,39
for (k=0; k<n; k++) Производительность, 18,44 13,05 7,65 69,40
s = s + a[i*n + k] * b[k*n + j]; мегафлопс
c[i*n + j] = s;
}
Мы сознательно не включали оптимизацию при компиля-
}
ции, чтобы продемонстрировать, насколько код программы
time = dseconds () – time;
влияет на ее производительность и как компиляторы могут
// Вывод результатов
самостоятельно повышать производительность программ с
printf ("Время работы: %f с\n", time);
помощью автоматической оптимизации.
printf ("Производительность: %f мегафлопс\n", Для начала попробуем оптимизировать код программы са-
(double) (n – 1) * n * n / time / 1000000); мостоятельно, и обратим внимание на его вычислительную
return (0); часть
} s = 0.0;
for (k=0; k<n; k++)
// Текущее время в микросекундах s = s + a[i*n + k] * b[k*n + j];
double dseconds (void) c[i*n + j] = s;
{
struct timeval tv; В данном случае переменная s используется для промежу-
struct timezone tz; точного хранения результатов перемножения элементов матриц
double time; и от нее можно легко избавиться. Перепишем вычислительную
часть программы следующим образом
gettimeofday (&tv, &tz);
c[i*n + j] = 0.0;
time = (double) tv.tv_sec +
for (k=0; k<n; k++)
(double) tv.tv_usec / 1000000;
c[i*n+j] = c[i*n+j] + a[i*n+k] * b[k*n+j];
return (time);
} Снова откомпилируем программу в стандартном режиме,
без каких-либо флагов и опций компилятора.
35 36
Результаты работы «оптимизированной» программы на од- ризовать их в быстрой кэш-памяти. Объявим указатель на про-
ном процессорном ядре вычислительного узла суперкомпьютера межуточный массив *d типа double, куда мы будем предвари-
ИПУ РАН: тельно выбирать столбец матрицы b, выделим под него память
− время работы программы – 20,75 с; объемом n × sizeof (double) и перепишем вычислительную часть
− производительность ядра – 48,12 мегафлопс. программы следующим образом:
Эти цифры наглядно демонстрируют, что более элегантный for (i=0; i<n; i++)
и лаконичный код не всегда означает более производительный. {
К сожалению, для других вычислительных систем нет данных о for (j=0; j<n; j++)
величине падения производительности в результате отказа от d[j] = b[i*n + j];
промежуточной переменной, ведущего в действительности к for (j=0; j<n; j++)
более активной работе с памятью. {
Теперь откомпилируем программу в режиме полной авто- s = 0.0;
матической оптимизации. Заметим, что на производительность for (k=0; k<n; k++)
автоматически оптимизированной программы наши предыду- s = s + a[i*n + k] * d[j];
щие манипуляции с вычислительной частью влияния не окажут c[i*n + j] = s;
}
> gcc –O3 matrix.c }
Результаты работы автоматически оптимизированной про- Аналогичные действия были выполнены ростовскими ис-
граммы, приведенные в таблице 3, показывают, что автоматиче- следователями с элементами строки матрицы a (i, k), поскольку
ская оптимизация существенно ускорила решение задачи. При массивы в языке Fortran размещаются в памяти по столбцам.
этом производительность по-прежнему осталась во много раз Снова откомпилируем программу в режиме автоматической
ниже пиковой. Заметим также, что на процессорах фирмы Intel оптимизации.
прирост производительности за счет автоматической оптимиза- Результаты работы автоматически оптимизированной про-
ции оказался наибольшим. граммы с предварительной выборкой столбца (строки) матрицы,
Таблица 3. приведенные в таблице 4, демонстрируют заметный рост произ-
Решение на одном Alpha Pentium Ultra Xeon водительности на всех компьютерах кроме системы на базе
процессоре (1 ядро) процессора Pentium III, обладающего очень маленьким кэшем.
Время работы, с 58,84 134,76 90,84 7,30 Это подтверждает наше предположение о важности эффектив-
Производительность, 33,97 14,83 22,00 136,69 ного использования кэш-памяти.
мегафлопс Таблица 4.
Решение на одном Alpha Pentium Ultra Xeon
Теперь попробуем еще раз оптимизировать код программы процессоре (1 ядро)
и снова посмотрим на его вычислительную часть. Обратим Время работы, с 54,41 33,76 11,16 1,32
внимание, что при суммировании произведений во втором мно- Производительность, 36,74 59,21 179,13 754,13
жителе выполняется выборка элементов столбца матрицы b (k, j) мегафлопс
из отстоящих далеко друг от друга элементов массива (массивы
в Си размещаются в памяти по строкам). Это не позволяет буфе-

37 38
Именно на эффективное использование кэш-памяти были в коммерческих библиотек аналогичного применения (Sun
свое время нацелены поставляемые с высокопроизводительными Performance Library, Common Extended Math Library).
системами оптимизированные математические библиотеки Таблица 5.
базового набора подпрограмм линейной алгебры BLAS (англ.
Решение на одном Alpha Pentium Ultra Xeon
Basic Linear Algebra Subprograms), и это обстоятельство до сих
процессоре (1 ядро)
пор является одним из основных аргументов в конкурентной
Время работы, с 5,36 2,72 2,24 0,29
борьбе платформ для высокопроизводительных систем. В на-
стоящее время написаны и бесплатно распространяются самона- Производительность, 372,9 734,8 894,0 3372,17
страивающиеся библиотеки BLAS, которые могут устанавли- мегафлопс
ваться на любом компьютере и под любой операционной
системой. В итоге мы получили производительность, очень близкую к
В библиотеке BLAS из пакета автоматически настраивае- расчетной пиковой. Единственным исключением стал установ-
мых программ линейной алгебры ATLAS (англ. Automatically ленный на вычислительных узлах суперкомпьютера ИПУ РАН
Tuned Linear Algebra Software), установленного на суперкомпь- процессор Intel Quad Core Xeon, пиковая производительность
ютере ИПУ РАН, есть готовая процедура перемножения матриц одного ядра которого была достигнута лишь на треть. Это про-
cblas_dgemm. Чтобы использовать ее в самом начале программы изошло по трем основным причинам:
подключим заголовочный файл /opt/atlas/include/cblas.h, содер- 1. Неполноценное использование ресурсов ядра (чтобы рас-
жащий объявления необходимых функций и констант крыть свой потенциал, ядро должно работать в связке со
своей парой).
#include "/opt/atlas/include/cblas.h" 2. Недостаточная загрузка процессора (слишком маленькая
и заменим весь вычислительный блок на вызов одной един- задача).
ственной функции 3. Несовершенная настройка библиотеки BLAS из пакета
ATLAS (пакет ATLAS не самый быстрый для данного
cblas_dgemm (CblasRowMajor, процессора).
CblasNoTrans, Для проверки первого предположения запустим программу
CblasNoTrans,
на четырех ядрах, т.е. целиком на одном процессоре, расчетная
n, n, n, 1, a, n, b, n, 0, c, n);
пиковая производительность которого составляет 37 гигафлопс.
Откомпилируем программу в режиме автоматической оп- Чтобы скомпилировать программу с использованием возможно-
тимизации с подключением необходимых библиотек из пакета стей SMP-архитектуры процессора, т.е. для запуска на всех
ATLAS четырех ядрах, наберите в консоли
> gcc -O3 matrix.c -L"/opt/atlas/lib" -lcblas -latlas > gcc -O3 matrix.c -L"/opt/atlas/lib" -lm -lpthread
-lptcblas -latlas
Результаты работы программы с использованием библиоте-
ки BLAS из пакета ATLAS, приведенные в таблице 5, наглядно В результате производительность программы выросла до 17
демонстрируют практическую ценность использования пакета гигафлопс, т.е. достигла величины около половины пиковой и в
ATLAS, эффективность которого не только не уступает, но и в процентном отношении стала выше, чем при запуске на одном
некоторых случаях превосходит эффективность некоторых ядре. Справедливости ради следует отметить, что такая произво-
дительность получается не всегда, и многократные запуски

39 40
программы демонстрируют разную производительность систе- 3. СУПЕРКОМПЬЮТЕР ИПУ РАН
мы (вплоть до 6 гигафлопс). Разброс в показаниях в очередной
раз свидетельствует в пользу второго предположения об исполь- Суперкомпьютер ИПУ РАН – кластерная система, ориенти-
рованная на высокопроизводительные вычисления и предназна-
зовании в качестве теста слишком простой задачи, несоответст-
вующей вычислительной мощности процессора. ченная для решения сложных научно-технических задач. Систе-
Для проверки второго предположения увеличим размер- ма с архитектурой x64 на базе процессоров Intel Xeon построена
ность матрицы до 10000 × 10000. В результате разбросы в расче- в 2008 году на базе промышленных серверов Sun Fire X4150.
тах производительности при многократных запусках программы В состав суперкомпьютера входят:
прекратились, а производительность системы выросла до 20 − 12 вычислительных узлов (всего 96 процессорных ядер)
гигафлопс, т.е. в процентном отношении стала еще выше, чем на базе серверов Sun Fire X4150, каждый из которых ос-
при запуске на одном ядре. Это ускорение свидетельствует о нащен двумя процессорами Intel Quad Core Xeon E5345 с
росте производительности системы на больших задачах, соот- тактовой частотой 2,33 ГГц, памятью PC2 5300 667 МГц
ветствующих вычислительной мощности процессора. ECC Fully Buffered DDR2 общим объемом 4 Гб, локаль-
Наконец, для проверки третьего предположения заменим ным жестким диском, двумя интерфейсами InfiniBand
пакет ATLAS более совершенными библиотеками подпрограмм SDR и четырьмя интерфейсами Gigabit Ethernet;
линейной алгебры. На суперкомпьютере ИПУ РАН установлена − управляющий узел на базе сервера Sun Fire X4150 с дис-
математическая библиотека Intel MKL (англ. Math Kernel ковым массивом на базе жестких дисков Seagate Savvio
Library), включающая в себя собственную версию BLAS. 10K.2 SCSI;
Чтобы использовать функцию cblas_dgemm из библиотеки − высокоскоростная вычислительная сеть InfiniBand SDR
Intel MKL, заменим в коде программы соответствующую строку на базе коммутатора Mellanox InfiniScale III MT47396 (24
подключения заголовочного файла порта InfiniBand 4X со скоростью передачи до 8 Гб/c);
− управляющая сеть Fast Ethernet (для управления сервера-
#include
ми средствами Sun ILOM) на базе коммутатора D Link
"/opt/intel/mkl/10.1.0.015/include/mkl_cblas.h"
DES-1026G;
и откомпилируем программу для работы на одном процес- − транспортная сеть Gigabit Ethernet (для запуска расчетных
соре (для работы на одном ядре необходимо подключить другой задач, мониторинга работы программ и оборудования, со-
набор библиотек с помощью опций -lmkl_sequential единения с управляющим узлом и сетевой файловой сис-
-lmkl_intel_lp64 -lmkl_core -lm) темой) на базе двух коммутаторов D Link DGS 1024D;
> gcc -O3 mmatrix.c
− программное обеспечение, предоставляющее полный на-
-L"/opt/intel/mkl/10.1.0.015/lib/em64t" бор средств разработки и запуска расчетных задач:
-lmkl_intel_lp64 -lmkl_intel_thread -lmkl_core -liomp5 − операционная система OpenSUSE 11.0 (установлена
-lpthread -lm на всех узлах кластера);
− коммутирующее программное обеспечение OFED
В результате производительность системы при работе на
1.4;
одном процессоре выросла до 32,6 гигафлопс, т.е. вплотную
− среда параллельного программирования
приблизилась к расчетному пиковому значению. Это подтвер-
MVAPICH2, реализующая интерфейс передачи со-
ждает важность правильного выбора математического обеспече-
общений MPI версии 2.0;
ния для высокопроизводительных вычислений.

41 42
− компиляторы Си, C++, Fortran и другие инструмен- свободу изменения программы и публикации внесенных изме-
тальные средства разработки программ из фонда нений.
свободного программного обеспечения GNU; Дистрибутивов GNU/Linux достаточно много, однако наи-
− математические библиотеки ATLAS, Intel MKL 10.1; более распространенных, таких как openSUSE, не более десяти.
− менеджер управления очередями и вычислительны- Дистрибутивы разрабатываются и распространяются различны-
ми ресурсами SLURM. ми дистрибьюторами, в роли которых могут выступать как
Пиковая производительность суперкомпьютера ИПУ РАН крупные компании, так и сообщества добровольцев. Операцион-
составляет 0,89 терафлопс. Реально достигнутый максимум на ная система openSUSE разрабатывается и распространяется под
тестах LINPACK составляет 0,62 терафлопс. Суперкомпьютер патронажем компании Novell. Наработки проекта openSUSE
позволяет решать задачи с разделением вычислительных ресур- используются для развития коммерческой операционной систе-
сов между несколькими пользователями. мы на базе ядра Linux – SUSE Enterprise Linux (SLES), под
управлением которой, в частности, работают узлы самого быст-
3.1. Операционная система GNU/Linux рого суперкомпьютера 2004-2008 гг. IBM Blue Gene/L.
Доступ к суперкомпьютеру ИПУ РАН осуществляется по
Суперкомпьютер ИПУ РАН работает под управлением опе- протоколу SSH (TCP-порт 22). Уточните адрес суперкомпьютера
рационной системы openSUSE 11.0 из семейства GNU/Linux. в локальной сети института у вашего администратора.
История операционной системы GNU/Linux началась в 1983 Для доступа к суперкомпьютеру из среды Windows реко-
году, когда американец Ричард Столлман (Richard Stallman) мендуется пользоваться программой PuTTY. Работа с кластером
основал проект GNU (англ. GNU’s Not UNIX – «GNU – это не ИПУ РАН ведется только в текстовом (консольном) режиме,
UNIX» – традиционный для компьютерных специалистов рекур- который в среде MS Windows принято называть режимом ко-
сивный акроним). Проект GNU задумывался как свободная мандной строки.
переносимая, многозадачная и многопользовательская UNIX- Первое, что видит пользователь суперкомпьютера ИПУ
подобная операционная система. К 1991 году большинство РАН в случае удачного соединения, – это запрос учетного имени
прикладных программ, необходимых для новой операционной пользователя системы
системы, были созданы, но собственное ядро, управляющее
всеми процессами на низком уровне и взаимодействующее с login as:
оборудованием компьютера, так и не заработало. Это обстоя- В ответ на этот запрос необходимо ввести имя пользовате-
тельство привело к появлению первой версии ядра Linux финна ля, под которым вы зарегистрированы в системе, после чего
Линуса Торвальдса (Linus Torvalds). Таким образом, операцион- система запросит пароль
ная система GNU/Linux – это ядро Linux, работающее в окруже-
нии необходимых прикладных программ из проекта GNU. Using keyboard-interactive authentication.
Password:
Операционная система GNU/Linux развивается по модели
так называемого «свободного программного обеспечения» (англ. После ввода пароля выводится строка приглашения
Free Software). Такая модель предусматривает свободу исполь-
username@main:~>
зования программы любым способом, свободу изучения прин-
ципов ее работы, свободу адаптации программы для нужд поль- Появление приглашения означает, что пользователь успеш-
зователя, свободу распространения копий программы, а также но вошел в систему, и теперь консоль может принимать и вы-
полнять его команды.

43 44
В приведенном выше примере строка приглашения включа- > info passwd
ет в себя имя пользователя username, имя управляющего узла В ответ пользователь получит описание программы passwd.
суперкомпьютера main и текущий каталог ~. Во всех последую- Вся имеющаяся в системе информация по возможности предос-
щих примерах для обозначения строки приглашения мы для тавляется на русском языке, однако, существенная ее часть
краткости будем использовать только символ закрывающей существует только на английском. Поскольку информация
угловой скобки. обычно не помещается на одном экране, для передвижения по
Следует отметить, что в любой UNIX-подобной системе, в документу следует пользоваться клавишами <Page Up> и
отличие от систем семейства MS Windows, учитывается регистр <Page Dn>, а также клавишей <Пробел>. Нажатие клавиши <Q>
вводимых символов. Поэтому вводить все команды и их пара- в любой момент приводит к выходу из программы и возврату в
метры следует именно с учетом этого обстоятельства, каждый режим ввода команд. Для более подробного знакомства с воз-
раз обращая внимание на строчные и прописные буквы. можностями программ man и info наберите в консоли
После первого входа в систему желательно сменить пароль,
выданный администратором, на ваш собственный, знать кото- > man man
рый уже не будет никто кроме вас. Наберите в консоли или
> passwd > info info
В ответ программа passwd в интерактивном режиме помо- Программы man и info не дублируют, как может показаться
жет вам сменить пароль на первый взгляд, а дополняют друг друга, потому что хранят
Changing password for username. разную информацию в разных форматах. Программа man – это
Старый пароль: традиционная для любых UNIX-подобных систем справочная
Новый пароль: система, в то время как программа info входит в проект GNU и
Повторите Новый пароль: работает по своим правилам. Внешняя схожесть этих программ
Пароль изменен. – исключительная заслуга составителей документации.
Если пользователь выбрал новый пароль не очень удачно Ниже приведен краткий список команд, которые могут по-
(слишком короткий или очень простой), программа выдаст надобиться начинающему пользователю при первом знакомстве
соответствующее предупреждение, но все равно примет пароль с операционной системой:
и позволит входить с ним в систему. − whoami – сообщает ваше учетное имя в системе;
Главные команды, о которых нужно знать каждому пользо- − who или w – сообщает учетные имена пользователей, ра-
вателю GNU/Linux, – это команды man и info. Вместе эти коман- ботающих в данный момент в системе;
ды образуют большой и подробный справочник по операцион- − pwd – сообщает название текущей папки;
ной системе и ее возможностям. В качестве параметров при − ls -l или ll – выдает список файлов и папок в текущей пап-
запуске этих команд необходимо указывать название програм- ке с указанием их атрибутов;
мы, системного файла или просто ключевое слово по интере- − cd <имя папки> – осуществляет смену текущей папки;
сующей тематике, например − ps -e – выдает список всех программ (процессов), выпол-
няющихся в данный момент в системе;
> man passwd
− logout или exit – завершает текущий сеанс работы с сис-
или темой.

45 46
В суперкомпьютере ИПУ РАН используется оболочка Bash. − <Esc>+<B> – перемещение на одно слово влево;
Оболочка (англ. shell) – это программа, организующая общение − <Home> или <Ctrl>+<A> – перемещение в начало на-
операционной системы с пользователем. Именно оболочка бранной цепочки символов;
принимает и передает на запуск все команды пользователя, − <End> или <Ctrl>+<E> – перемещение в начало/конец на-
поэтому оболочку часто называют командным процессором. бранной цепочки символов;
Строку приглашения операционной системы тоже выводит − <Del> или <Ctrl>+<D> – удаление символа, на который
оболочка, она же ожидает ввода очередной команды. Каждый указывает курсор;
раз, когда пользователь входит в систему, он попадает в ее − <Backspase> – удаление символа, находящегося слева от
оболочку. курсора;
Оболочка Bash – это мощный командный процессор, обла- − <Ctrl>+<K> – удаление правой части строки, начиная с
дающий собственным языком программирования. В этой обо- символа, на который указывает курсор;
лочке имеется большой набор встроенных (внутренних) команд − <Ctrl>+<U> – удаление левой части строки, включая сим-
и операторов, список которых можно получить по команде help. вол, находящийся слева от курсор;
Чтобы получить детальную информацию по конкретной коман-
− <Enter> или <Ctrl>+<M> – запуск на выполнение набран-
де или оператору, укажите его название в качестве аргумента,
ной цепочки символов;
например
− <Ctrl>+<L> – очищение экрана и помещение набранной
> help echo цепочки символов в верхней строке экрана;
Оболочка Bash обеспечивает выполнение запросов пользо- − <Ctrl>+<T> – замена местами символа, на который ука-
вателя: находит и вызывает исполняемые файлы, организует зывает курсор, и символа, находящегося слева от курсора,
ввод/вывод, отвечает за работу с переменными окружения, с последующим перемещением курсора на один символ
выполняет некоторые преобразования (подстановки) аргументов вправо;
при запуске программ и т.п. Главное свойство оболочки, которое − <Esc>+<T> – замена местами слова, на которое указывает
делает ее мощным инструментом пользователя, – это наличие курсор, и слова, находящегося слева от курсора;
собственного языка программирования. Оболочка также исполь- − <Ctrl>+<K> – вырезание и сохранение в буфере части на-
зует все программы, доступные пользователю, как базовые бранной цепочки символов, находящейся справа от кур-
операции поддерживаемого ею языка, обеспечивает передачу им сора;
аргументов, а также передачу результатов их работы другим − <Esc>+<D> – вырезание и сохранение в буфере правой
программам и пользователю. части слова, на которое указывает курсор;
Ниже приведен список основных сочетаний клавиш, позво- − <Esc>+<Del> – вырезание и сохранение в буфере левой
ляющих эффективно работать с командной строкой Bash: части слова, на которое указывает курсор;
− <Стрелка вправо> или <Ctrl>+<F> – перемещение вправо − <Ctrl>+<W> – вырезание и сохранение в буфере части
по командной строке в пределах уже набранной цепочки набранной цепочки символов, находящейся слева от кур-
символов плюс один символ справа (место для ввода сле- сора до ближайшего пробела;
дующего символа); − <Ctrl>+<Y> – вставка из буфера строки;
− <Стрелка влево> или <Ctrl>+<B> – перемещение на один − <Esc>+<C> – замена строчного символа, на который ука-
символ влево; зывает курсор, на прописной символ и перемещение кур-
− <Esc>+<F> – перемещение на одно слово вправо; сора на ближайший пробел справа;
47 48
− <Esc>+<U> – замена строчных символов слова, на кото- выбора из списка, называемого историей ввода. Историю ввода
рое указывает курсор, прописными символами и переме- можно просмотреть с помощью команды history (и воспользо-
щение курсора на ближайший пробел справа; ваться упомянутыми ранее комбинациями клавиш
− <Esc>+<L> – замена прописных символов слова, на кото- <Shift>+<Page Up> и <Shift>+<Page Dn> для просмотра резуль-
рое указывает курсор, на строчные и перемещение курсо- тата). История ввода сохраняется в файле, полное имя которого
ра на ближайший пробел справа; хранится в переменной окружения HISTFILE, (обычно
− <Shift>+<Page Up> или <Shift>+<Page Dn> – просмотр ~/.bash_history). Для работы с историей ввода в оболочке Bash
страниц экранного вывода (количество страниц зависит используются следующие комбинации клавиш:
от размера видеопамяти); − <Стрелка вверх> или <Ctrl>+<P> – переход к предыду-
− <Ctrl>+<C> – прерывание выполнения программы; щей последовательности символов в списке;
− <Ctrl>+<D> – выход из оболочки (завершение сеанса). − <Стрелка вниз> или <Ctrl>+<N> – переход к следующей
Оболочка Bash имеет встроенную подпрограмму, предна- последовательности символов в списке;
значенную для облегчения ввода команд в командной строке. − <Page Up> – переход к самой первой последовательности
Эта подпрограмма вызывается по клавише <Tab> после того, как символов в списке;
уже введено некоторое число символов. Если эти символы − <!>, <N> – выполнение (без нажатия клавиши <Enter>) N-
являются первыми символами в названии одной из стандартных ой последовательности символов из списка, считая от на-
команд, известных оболочке, то возможны разные варианты чала;
дальнейшего развития событий: − <!>, <->, <N> – выполнение (без нажатия клавиши
− если по введенным первым символам команда определя- <Enter>) N-ой последовательности символов из списка,
ется однозначно, оболочка добавит окончание названия считая от конца;
команды в командную строку; − <!>, <строка> – выполнение первой найденной последо-
− если однозначно восстановить имя команды по введен- вательности символов из списка, начинающейся на вве-
ным первым символам невозможно, оболочка выдаст денную строку (движение по списку осуществляется от
список всех подходящих вариантов продолжения для то- конца к началу);
го, чтобы пользователь мог ввести оставшиеся символы − <Ctrl>+<O> – аналогично нажатию клавиши <Enter>, но с
самостоятельно, пользуясь выведенным списком как под- последующим отображением очередной последователь-
сказкой; ности символов из списка.
− если среди введенных символов уже есть пробел, оболоч-
ка решит, что вы ищете имя файла, который должен пе- 3.2. Инструментарий разработчика
редаваться как параметр, и выдаст в качестве подсказки
Инструментарий разработчика, созданный в рамках проекта
список файлов текущего каталога, начинающихся с сим-
GNU (англ. GNU Toolchain), является стандартным средством
волов, следующих за пробелом.
разработки программ в любой операционной системе на базе
Аналогичным образом можно получить список переменных
ядра Linux. Основу этого инструментария составляет набор
окружения, если вместо клавиши <Tab> нажать комбинацию
GNU-компиляторов (англ. GCC, GNU Compiler Collection, ранее
клавиш <Esc>+<$>.
GNU C Compiler) для различных языков программирования, в
Оболочка Bash запоминает крайнюю тысячу набранных по-
том числе Си, C++ и Fortran. В набор GNU-компиляторов входят
следовательностей символов и позволяет вызывать их путем
также компиляторы Java, Objective-C и Ada, однако эти языки

49 50
программирования не используются для высокопроизводитель- Здравствуй, мир!
ных вычислений. Компилятор gcc по умолчанию присваивает всем исполняе-
Компиляция программ осуществляется с помощью утилиты мым файлам имя a.out. С помощью флага -o можно указать имя
gcc, по традиции называемой компилятором, но фактически исполняемого файла самостоятельно. Наберите в консоли
являющейся интерфейсом целой системы компиляции, создан-
ной проектом GNU. Выбор того или иного языка программиро- > gcc hello.c -o hello
вания обусловлен скорее личными предпочтениями разработчи- В результате в текущей папке появится исполняемый файл с
ка и особенностями реализуемого алгоритма. именем hello. Чтобы запустить его наберите в консоли
Инструментарий разработчика суперкомпьютера ИПУ РАН
поддерживает языки программирования Си, C++ и Fortran, > ./hello
используемые во всех современных реализациях MPI для созда- Точка и слеш перед именем исполняемого файла означают,
ния высокопроизводительных параллельных программ. что исполняемый файл размещен в текущей папке. Без указания
Создадим и запустим простую программу на Си. По сло- пути к исполняемому файлу запускаются только программы,
жившейся традиции первая программа будет выводить в консоль размещенные в системных папках и являющиеся частью опера-
приветствие «Здравствуй, мир!». ционной системы. Для запуска всех остальных программ, в т.ч.
Файлы с кодами программ – это обычные текстовые файлы, пользовательских, необходимо указывать путь к исполняемому
создавать их можно с помощью любого текстового редактора, файлу (абсолютный или относительно текущей папки), напри-
в т.ч. с помощью традиционного для GNU/Linux консольного мер
текстового редактора vi.
Создадим в домашней папке текстовый файл hello.c, содер- > /home/nfs/username/hello
жащий следующий код на языке Си или
#include <stdio.h> > ~/hello

int main (int argc, char *argv[]) Работа системы компиляции состоит из четырех последова-
{ тельно выполняемых этапов:
printf ("Здравствуй, мир!\n"); 1. Препроцессинг (англ. preprocessing).
return (0); 2. Компиляция (англ. compilation).
} 3. Ассемблирование (англ. assembly).
4. Компоновка (англ. linking).
Для компиляции программы наберите в консоли Препроцессинг подключает к программному коду содержи-
> gcc hello.c мое заголовочных файлов, указанных в директивах #include, и
заменяет макросы, определенные директивами #define, соответ-
Если все сделано правильно, в домашней папке появится
ствующим программным кодом.
новый исполняемый файл под названием a.out. Чтобы запустить
Во время компиляции происходит превращение исходного
его наберите в консоли
кода программы на языке Си в промежуточный код на Ассемб-
> ./a.out лере. Этот промежуточный шаг очень важен для дальнейшей
В ответ вы увидите следующее работы компилятора, потому что именно во время компиляции
происходит детальный разбор исходного кода и поиск синтакси-

51 52
ческих ошибок. Возможно, поэтому, а также из-за некоторой {
исторически сложившейся терминологической путаницы, неко- z = x * y; break;
торые ошибочно полагают, что компиляция – это единственное, }
чем занимается компилятор (система компиляции). else if (o == '/')
Во время ассемблирования происходит превращение ас- {
семблерного кода программы в объектный код, очень близкий к z = x / y; break;
набору машинных команд. Результат сохраняется в объектных }
printf ("X %c Y = %f\n", o, z);
файлах.
return (0);
На этапе компоновки происходит создание исполняемого
}
файла путем связывания вызовов пользовательских и библио-
течных функций с их объектным кодом, хранящимся в различ- Программа запрашивает у пользователя два числа и ариф-
ных объектных файлах. метическое действие, после чего печатает в консоль результат
Для дальнейшей демонстрации работы инструментария раз- вычислений.
работчика GNU напишем небольшую программу-калькулятор. Флаг -E прерывает работу системы компиляции после за-
Создадим в домашней папке текстовый файл calc.c, содер- вершения препроцессинга. Наберите в консоли
жащий следующий код на языке Си > gcc -E calc.c -o calc.i
#include <stdio.h>
Название конечного файла calc.i желательно указывать, по-
тому что результаты препроцессинга по умолчанию печатаются
int main (int argc, char *argv[])
в консоль. В файл calc.i будет добавлен код заголовочного файла
{
float x, y, z;
stdio.h, указанного в директиве #include. Файл stdio.h содержит
char o; объявления встречающихся в программе функций ввода-вывода
printf, scanf и getchar. В файл calc.i также будут добавлены
printf ("X = "); некоторые теги, указывающие компилятору на способ связи с
scanf ("%f", &x); объявленными функциями. Код программы из файла calc.c без
printf ("Y = "); изменений будет добавлен в конец файла calc.i.
scanf ("%f", &y); Описания стандартных функций и заголовочных файлов для
printf ("Операция ( + – * / ): "); всех языков программирования из инструментария разработчи-
while ((o = getchar ()) != EOF) ка, точно также как и описания всех стандартных программ
if (o == '+') операционной системы, можно получить с помощью команд man
{ и info. Наберите в консоли
z = x + y; break;
} > man printf
else if (o == '-') или
{
z = x – y; break; > info stdio.h
}
else if (o == '*')

53 54
Флаг -S прерывает работу системы компиляции после за- расширениями .i и .ii содержат обработанный препроцессором
вершения компиляции программы в ассемблерный код. Набери- исходный код на языках Си и C++ соответственно и т. д. Файлы
те в консоли с неизвестными расширениями считаются объектными файлами
и сразу попадают на этап компоновки.
> gcc -S calc.c
Компилятор gcc позволяет проводить частичную обработку
Название конечного файла можно не указывать, потому что файла путем указания начального этапа с помощью флага -x и
компилятор gcc самостоятельно создаст объектный файл calc.s, конечного этапа с помощью флагов -c, -S, и -E. Однако таким
т.е. назовет его также как и файл с исходным кодом, но поставит образом нельзя изменить последовательность прохождения
стандартное расширение для файла на Ассемблере. этапов препроцессинга, компиляции, ассемблирования и компо-
Флаг -c прерывает работу системы компиляции после за- новки. В частности, одновременное использование опций
вершения ассемблирования. В результате получается объектный -x -cpp-output -E не запустит ни один из этапов.
файл, состоящий из набора машинных команд, но без связи К сожалению, реальные программы очень редко состоят из
вызываемых функций с их определениями в подключаемых одного файла. Как правило, исходных файлов всегда несколько,
библиотеках. Наберите в консоли причем в некоторых случаях программу приходится компоно-
> gcc -c calc.c вать из нескольких частей, написанных на разных языках про-
граммирования. В таких случаях принято говорить о программе
Название конечного файла можно не указывать, потому что как о проекте. Компилятор gcc используется для последователь-
компилятор gcc самостоятельно создаст объектный файл calc.o, ной обработки файлов проекта и последующей компоновки
т.е. назовет его также как и файл с исходным кодом, но поставит объектных файлов в исполняемый файл программы.
стандартное расширение для объектного файла. Добавим в программу-калькулятор операцию возведения в
Флаг -x сообщает компилятору с какого этапа следует на- степень и разобьем код программы на несколько файлов:
чать обработку файла. Например, для файла calc.i (полученного − calculator.c – главное тело программы;
ранее с помощью флага -E), наберите в консоли − calculate.c – расчетная функция;
> gcc -x cpp-output -c calc.i − calculate.h – заголовочный файл.
Создадим в домашней папке текстовый файл calculator.c,
В данном случае параметры -x cpp-output сообщает компи-
содержащий следующий код на языке Си
лятору, что файл calc.i содержит обработанный препроцессором
исходный код на языке Си (если указаны параметры -x c++ cpp #include <stdio.h>
output – на языке C++), поэтому компилятор начнет обработку #include "calculate.h"
файла сразу с этапа компиляции. Следует отметить, что расши-
рение .i сообщает компилятору ту же информацию, что и пара- int main (int argc, char *argv[])
метры -x cpp output. {
Компилятор gcc самостоятельно распознает языки програм- float x, y, z;
char o;
мирования и этапы компиляции программы, основываясь на
расширениях имен файлов, указанных пользователем. В частно-
printf ("X = ");
сти, по умолчанию считается, что файлы с расширениями .c
scanf ("%f", &x);
содержат исходный код на языке Си, файлы с расширениями .C, printf ("Y = ");
.cc, .cp или .cpp содержат исходный код на языке C++, файлы с scanf ("%f", &y);

55 56
printf ("Операция ( + – * / ^ ): "); > gcc -c calculator.c
while ((o = getchar ()) != EOF) > gcc -c calculate.c
if (o == '+' || o == '-' ||
Наконец, скомпонуем исполняемый файл проекта calc. На-
o == '*' || o == '/' || o == '^')
{
берите в консоли
z = calculate (x, y, o); break; > gcc calculator.o calculate.o -lm -o calc
}
printf ("X %c Y = %f\n", o, z); Флаг -lm подключает в проект на этапе компоновки стан-
return (0); дартную библиотеку математических функций libm.so, содер-
} жащую объектный код функции возведения в степень powf,
вызываемой пользовательской функцией calculate () и объявлен-
Создадим в домашней папке текстовый файл calculate.c, со- ной в заголовочном файле math.h. Без подключения этой биб-
держащий следующий код на языке Си лиотеки исполняемый файл не будет скомпонован и программа
#include <math.h> не заработает.
#include "calculate.h" На суперкомпьютере ИПУ РАН объектные файлы стан-
дартных библиотек компилятора располагаются в папке
float calculate (float x, float y, char o) /usr/lib64. Объектные файлы библиотеки ATLAS располагаются
{ в папке /opt/atlas/lib (соответствующие заголовочные файлы – в
float z; папке /opt/atlas/include). Объектные файлы библиотеки Intel
MKL располагаются в папке /opt/intel/mkl/10.1.0.015/lib/em64t
if (o == '+') (соответствующие заголовочные файлы – в папке
z = x + y; /opt/intel/mkl/10.1.0.015/include).
else if (o == '-') Файлы библиотек с расширением .a называются статиче-
z = x – y;
скими библиотеками. При компоновке объектный код функций
else if (o == '*')
из статических библиотек включается в код исполняемого фай-
z = x * y;
ла. Файлы библиотек с расширением .so называются динамиче-
else if (o == '/')
z = x / y;
скими библиотеками. При компоновке в исполняемом файле
else if (o == '^')
размещаются только ссылки на динамические библиотеки,
z = powf (x, y); реального включения объектного кода вызываемых функций не
return (z); происходит. Использование динамических библиотек ухудшает
} переносимость исполняемого кода, но экономит ресурсы систе-
мы, позволяя нескольким программам использовать одну и ту же
Создадим в домашней папке текстовый файл calculate.h, со- библиотеку, загруженную в память.
держащий следующий код на языке Си Стандартные имена файлов библиотек состоят из префикса
float calculate (float x, float y, char o); lib, названия библиотеки и расширения .a или .so. В параметрах
запуска компилятора gcc префикс lib заменяется префиксом -l, а
Создадим объектные файлы проекта calculator.o и
расширение файла не указывается. Таким образом, файл libm.so
calculate.o. Наберите в консоли в параметрах запуска компилятора значится как -lm.

57 58
В системе имеются статические и динамические версии всех > make
стандартных библиотек. По умолчанию компилятор gcc под- или
ключает к исполняемому файлу динамические библиотеки. Для
компоновки исполняемого файла с использованием только > make calc
статических библиотек используется параметр -static. Однако Для копирования исполняемого файла в домашний каталог
следует помнить, что некоторые библиотеки сами используют наберите в консоли
функции из других библиотек и при статической компоновке
требуют явного указания всех используемых библиотек в пара- > make install
метрах запуска компилятора, что может представлять опреде- Для удаления исполняемого файла из домашнего каталога
ленную сложность для неопытного разработчика. наберите в консоли
Стандартные функции языка Си, такие как printf, находятся
в библиотеке libc.so, которая автоматически участвует в компо- > make uninstall
новке всех программ, написанных на языке Си, и не требует Утилита make самостоятельно определяет, какие из файлов
упоминания в параметрах компилятора. проекта требуют компиляции и, в случае необходимости, произ-
Для управления большим количеством файлов и компиля- водит над ними действия, указанные в файле Makefile.
ции больших программных проектов используется утилита make Для компиляции параллельных программ, использующих
из инструментария разработчика GNU. реализацию интерфейса MPI MVAPICH2, используется утилита
Создадим в домашней папке текстовый файл Makefile, со- mpicc. Утилита mpicc не является самостоятельным компилято-
держащий следующие строки ром – это всего лишь небольшой скрипт оболочки Bash, запус-
calc: calculator.o calculate.o кающий компилятор gcc с параметрами подключения библиоте-
gcc calculator.o calculate.o -lm -o calc ки функций MPI. Таким образом, компилятор mpicc использует
calculator.o: calculator.c calculate.h тот же набор флагов и опций, что и система компиляторов gcc.
gcc -c calculator.c Чтобы скомпилировать параллельную программу program.c
calculate.o: calculate.c calculate.h наберите в консоли
gcc -c calculate.c > mpicc program.c
clean: Для запуска параллельных программ используется менед-
rm -f calc calculator.o calculate.o жер управления очередями и вычислительными ресурсами
install: SLURM. Параллельная программа может быть запущена на
cp calc ~/calc суперкомпьютере ИПУ РАН только после постановки в очередь
uninstall: с помощью утилиты srun. Наберите в консоли
rm -f ~/calc
> srun -n2 a.out
Обратите внимание на отступ слева, который должен быть
сделан только с помощью символа табуляции. Параметр -n2 указывает менеджеру управления очередями,
Файл Makefile – это список действий (макросов, правил), что программа должна быть запущена на двух вычислительных
которые утилита make может проделывать над многочисленны- узлах, в результате чего будет создано два параллельных про-
ми файлами программного проекта. цесса. Количество вычислительных узлов для запуска парал-
Для компиляции проекта наберите в консоли лельных программ на суперкомпьютере ИПУ РАН может изме-
няться в пределах от 2 до 96.

59 60
После постановки в очередь программа ждет освобождения имеют возможность компилировать и запускать программы
вычислительных узлов, которые могут быть заняты другими также на языках C++ и Fortran. Основные идеи и правила созда-
программами. При наличии необходимого количества свобод- ния программ с использованием интерфейса MPI схожи для всех
ных вычислительных узлов программа, стоящая в очереди пер- языков. Дополнительную информацию о стандарте MPI можно
вой, немедленно уходит на исполнение. получить в Интернете на сайте MPI Forum.
Для постановки программы в очередь на исполнение и не- Главное отличие параллельной программы, использующей
медленный возврат в командную строку наберите в консоли интерфейс MPI, от традиционной непараллельной программы
состоит в том, что в параллельной программе в один момент
> srun -n2 a.out &
времени могут выполняться несколько различных операций на
Для просмотра списка очередей используется утилита sinfo. разных процессорах/ядрах. Разработчик должен создать про-
По умолчанию все программы ставятся в очередь debug. граммный код для каждого ядра. Стандарт MPI предполагает
Для просмотра выполняемых программ (этапов программы) возможность разработки приложений с уникальным кодом для
в очереди используется утилита squeue. каждой из параллельных ветвей алгоритма, т.е. со своим кодом
Утилита srun по умолчанию запускает экземпляр програм- для каждого ядра. Однако в большинстве случаев для всех ядер,
мы на каждом вычислительном узле (ядре процессора). участвующих в работе приложения, используется один и тот же
программный код.
4. ОСНОВЫ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ СИ Таким образом, для создания параллельной программы дос-
С ИСПОЛЬЗОВАНИЕМ ИНТЕРФЕЙСА MPI таточно написать программный код, в который будет включена
библиотека MPI и реализованы все необходимые механизмы
Данный раздел посвящен написанию параллельных про-
взаимодействия параллельных ветвей программы, откомпилиро-
грамм с использованием программного интерфейса передачи
вать этот программный код и обеспечить запуск исполняемого
сообщений MPI (англ. Message Passing Interface). На сегодняш-
файла на нескольких ядрах.
ний день MPI является наиболее распространенной технологией
Прежде чем перейти к описанию принципов написания про-
параллельного программирования для суперкомпьютеров.
грамм введем некоторые обозначения:
Большинство реализаций этого интерфейса поддерживают
− процесс – это исполнение программы на одном процессо-
стандарты MPI 1.1 и MPI-2.0. Стандарт MPI-2.0 появился в 1997
ре, на котором установлен MPI, безотносительно к тому,
году и существенно расширил возможности стандарта 1.1, но в
течение долгого времени не был широко распространен. Тем не содержит ли эта программа внутри параллельные ветви
менее, для написания параллельных программ на суперкомпью- или операции ввода/вывода или просто последователь-
ный программный код;
тере ИПУ РАН был выбран именно стандарт MPI-2.0. Большая
часть аспектов, которые будут описаны в данном разделе, явля- − группа – это совокупность процессов, каждый из которых
ется идентичной для обоих стандартов, поэтому далее будет имеет внутри группы уникальное имя, используемое для
предполагаться, что мы имеем дело со стандартом MPI-2.0. Если взаимодействия с другими процессами группы посредст-
будет необходимо привести какие-то исключения для стандарта вом коммуникатора группы;
1.1, то это будет оговорено дополнительно. − коммуникатор группы – интерфейс синхронизации и об-
Существуют реализации MPI для языков Си, C++ и Fortran. мена данными между процессами.
Примеры и описания всех функций даны с использованием Коммуникатор выступает для прикладной группы как ком-
языка Си. Однако пользователи суперкомпьютера ИПУ РАН муникационная среда взаимодействия. Коммуникаторы бывают

61 62
внутригрупповыми (intra) и межгрупповыми (inter). Коммуника- SEEK_SET is #defined but must not be for the C++
тор определяет контекст передачи сообщений. Сообщения, binding of MPI
использующие разные коммуникаторы, не оказывают влияния Причиной этого является то, что в заголовочном файле
друг на друга и не взаимодействуют друг с другом. Каждая стандартной библиотеки ввода/вывода stdio.h и в заголовочном
группа процессов использует отдельный коммуникатор. Если в файле mpi.h для C++ объявлены одни и те же константы
группе n процессов, то процессы внутри группы имеют номера SEEK_SET, SEEK_CUR и SEEK_END, что является ошибкой
от 0 до n – 1. реализации MPI-2.0.
С точки зрения операционной системы процесс рассматри- Для решения проблемы необходимо при запуске компиля-
вается как отдельное приложение, не взаимодействующее с тора использовать флаг DMPICH_IGNORE_CXX_SEEK или
другими процессами (приложениями). С точки зрения разработ- подключать библиотеку MPI следующим образом
чика процесс – это одна из ветвей параллельной программы,
которой необходимо обеспечить коммуникацию с другими #undef SEEK_SET
ветвями (процессами) параллельного приложения. Поскольку #undef SEEK_END
зачастую все процессы одного приложения выполняют один и #undef SEEK_CUR
тот же код, то приходится реализовывать взаимодействие этого #include "mpi.h"
кода с самим собой с учетом его выполнения в разных процес- После запуска параллельная программа должна инициали-
сах. Коммуникатор группы как раз и является той абстракцией, зировать свою параллельную часть, т.е. подготовиться к работе с
которая обеспечивает взаимодействие процессов между собой. другими ветвями параллельного приложения. Для этого исполь-
При этом разработчика не должно интересовать, каким спосо- зуется функция MPI_Init.
бом передается информация между процессами: заботу о пере-
int MPI_Init (
даче сообщений между процессами берет на себя интерфейс
int *argc,
MPI.
char ***argv
);
4.1. Инициализация MPI
Параметры функции:
Любая программа на языке Си, использующая MPI, должна − argc – число аргументов в командной строке, вызвавшей
включать в себя заголовочный файл, в котором определены все программу;
функции, переменные и константы MPI. Для подключения
− argv – указатель на массив символьных строк, содержа-
библиотеки MPI необходимо внести в программу следующую
щих эти аргументы.
строку
Параметры обычно соответствуют аналогичным параметрам
#include "mpi.h" исходной программы, но не обязательно.
Директива #include подключает заголовочный файл mpi.h Функция MPI_Init обеспечивает инициализацию параллель-
библиотеки MPI. Следует отметить, что при написании про- ной части приложения. Реальная инициализация для каждого
грамм на языке C++ и использовании стандарта MPI-2.0 у поль- приложения выполняется не более одного раза, а если интерфейс
зователей могут возникнуть проблемы с подключением библио- MPI уже был инициализирован, то никакие действия не выпол-
теки MPI. На этапе компиляции программы компилятор может няются, а происходит немедленный выход из функции. Все
вывести следующее сообщение об ошибке оставшиеся функции MPI могут быть вызваны только после
вызова MPI_Init.

63 64
Для успешного выполнения функции MPI_Init необходимо, Приведенная ниже программа демонстрирует работу функ-
чтобы приложение имело информацию, необходимую для соз- ций инициализации интерфейса MPI
дания группы процессоров, которые будут участвовать в выпол-
#include <stdio.h>
нении приложения. Обычно такая информация передается в #include "mpi.h"
приложение при помощи специальных средств для запуска
программы, и разработчику не нужно заботиться об этом. int main (int argc, char *argv[])
Для завершения параллельной части приложения использу- {
ется функция MPI_Finalize. int flag;
int MPI_Finalize (void);
MPI_Initialized (&flag);
Функция MPI_Finalize обеспечивает завершение параллель- printf ("MPI_Initialized вернула %d\n", flag);
ной части приложения. Все последующие обращения к любым MPI_Init (&argc, &argv);
функциям MPI, в том числе к MPI_Init, запрещены. К моменту MPI_Initialized (&flag);
вызова MPI_Finalize некоторым процессом все действия, тре- printf ("MPI_Initialized вернула %d\n", flag);
бующие его участия в обмене сообщениями, должны быть за- MPI_Finalize ();
вершены. return (0);
Общая структура программы, использующей интерфейс }
MPI, на языке Си выглядит следующим образом Каждый из процессов этой программы печатает в консоль
int main (int argc, char *argv[]) сначала информацию о том, что функция MPI_Initialized вернула
{ значение 0, а затем 1. Обратите внимание, что сообщения каж-
MPI_Init (&argc, &argv); дый раз выводятся в хаотичном порядке. Это связано с тем, что
… без использования функций синхронизации процессы работают
MPI_Finalize (); с разной скоростью.
} Для работы над решением общей вычислительной задачи
Иногда приложению необходимо выяснить, была ли уже группе процессов необходимо выяснить, над какой частью
вызвана функция MPI_Init. Для этого используется функция задачи должен работать каждый процесс. Для этого каждый
MPI_Initialized. процесс должен уметь идентифицировать себя в группе. Работа с
группами процессов будет рассмотрена далее, поэтому пока
int MPI_Initialized ( ограничимся лишь общим представлением о группах процессов.
int *flag Каждый процесс характеризуется уникальной парой: группа
); и номер процесса в группе. Каждый из процессов может при-
Функция MPI_Initialized возвращает значение в переменную надлежать одной или нескольким группам, и в каждой из них
flag. Если MPI_Init уже была вызвана, то flag имеет ненулевое иметь свой номер. После выполнения функции MPI_Init создает-
значение, в противном случае flag равна нулю. В стандарте MPI ся базовая группа с коммуникатором MPI_COMM_WORLD,
1.1 это единственная функция, которую можно вызывать до содержащая все процессы приложения. Затем в рамках этой
MPI_Init. Вызов функции MPI_Finalize не влияет на поведение группы могут создаваться новые группы, которые будут вклю-
MPI_Initialized. чать часть процессов. Процессы могут общаться в рамках одной

65 66
группы. Для самоидентификации процесса в группе служат вызывающий функцию MPI_Abort, должен быть членом группы
функции MPI_Comm_size и MPI_Comm_rank. с указанным коммуникатором. Если указанного коммуникатора
группы не существует или вызывающий процесс не является ее
int MPI_Comm_size (
MPI_Comm comm,
членом, то прерывается вся базовая группа процессов
int *size MPI_COMM_WORLD.
); Приведенная ниже программа демонстрирует работу функ-
ций идентификации и прерывания параллельных процессов
Параметры функции:
− comm – коммуникатор группы; #include <stdio.h>
#include "mpi.h"
− size – размер группы.
Функция MPI_Comm_size определяет число процессов в int main (int argc, char *argv[])
группе. {
int MPI_Comm_rank ( int size, me;
MPI_Comm comm,
int *rank MPI_Init (&argc, &argv);
); MPI_Comm_size (MPI_COMM_WORLD, &size);
MPI_Comm_rank (MPI_COMM_WORLD, &me);
Параметры функции: printf ("Размер группы %d, мой номер %d\n",
− comm – коммуникатор группы; size, me);
− rank – номер вызывающего процесса в группе. printf ("Вызываю MPI_Abort\n");
Функция MPI_Comm_rank определяет номер вызывающего MPI_Abort (MPI_COMM_WORLD, 911);
процесса в группе. Значение, возвращаемое по адресу &rank, printf ("MPI_Abort была вызвана\n");
лежит в диапазоне от 0 до size – 1. MPI_Finalize();
Во время выполнения параллельной программы может воз- return (0);
никнуть необходимость экстренно завершить работу всей груп- }
пы процессов. Для этих целей используется функция MPI_Abort. Во время выполнения этой программы каждый из процессов
int MPI_Abort ( печатает в консоль размер своей группы и свой номер в группе.
MPI_Comm comm, Обратите внимание, что процессы выводят сообщения не по
int errorcode порядку своих номеров. Ни один из процессов не сообщает о
); завершении вызова функции MPI_Abort.
Параметры функции:
4.2. Прием/отправка сообщений с блокировкой
− comm – коммуникатор группы;
− errorcode – код ошибки, с которой завершится процесс. Помимо инициализации и идентификации все параллельные
Функция MPI_Abort прерывает процессы, ассоциированные процессы должны обмениваться информацией друг с другом.
с коммуникатором comm, и возвращает управление внешней При этом обмен информацией должен происходить не только до
среде. Прерванный процесс всегда завершает свою работу с начала вычислений, но и в процессе вычислений, и после их
ошибкой, номер которой указан в параметре errorcode. Процесс, завершения.

67 68
Обеспечение информационного обмена между процессами – далее). Значение идентификатора сообщения выбрано произ-
основная задача интерфейса MPI. Для разных видов коммуника- вольным образом. Процессу разрешается отправлять сообщения
ции MPI предлагает множество различных функций. самому себе.
Во время коммуникации типа «точка-точка» с блокировкой Блокировка гарантирует корректность повторного исполь-
соответствующие функции останавливают вызывающий процесс зования всех параметров после возврата из функции MPI_Send.
до тех пор, пока не будет завершена передача сообщения. Выбор способа реализации такой гарантии: копирование сооб-
Для отправки сообщений используется функция MPI_Send. щения в промежуточный буфер или непосредственная передача
сообщения процессу dest, остается за конкретной реализацией
int MPI_Send (
void *buf,
интерфейса MPI.
int count, Следует отметить, что возврат из функции MPI_Send не оз-
MPI_Datatype datatype, начает, что сообщение передано процессу dest или уже покинуло
int dest, вычислительный узел, на котором выполняется процесс-
int msgtag, отправитель, вызвавший функцию MPI_Send.
MPI_Comm comm Для приема сообщения используется функция MPI_Recv:
); int MPI_Recv (
Параметры функции: void *buf,
− buf – отправляемое сообщение; int count,
MPI_Datatype datatype,
− count – число элементов в отправляемом сообщении;
int source,
− datatype – тип элементов;
int msgtag,
− dest – номер процесса-получателя; MPI_Comm comm,
− msgtag – идентификатор сообщения; MPI_Status *status
− comm – коммуникатор группы. );
Функция MPI_Send осуществляет блокирующую отправку
сообщения с идентификатором msgtag, состоящего из count Параметры функции:
элементов типа datatype, процессу с номером dest в группе с − buf – принимаемое сообщение;
коммуникатором comm. Все элементы сообщения содержатся в − count – максимальное число элементов в принимаемом
буфере buf. Значение count может быть нулевым. Тип переда- сообщении;
ваемых элементов datatype должен указываться с помощью − datatype – тип элементов;
предопределенных констант из библиотеки типов. − source – номер процесса-отправителя;
Например, вызов функции MPI_Send, отправляющей три − msgtag – идентификатор сообщения;
элемента массива целого типа int нулевому процессу в группе с − comm – коммуникатор группы;
коммуникатором MPI_COMM_WORLD выглядит следующим − status – параметры принятого сообщения.
образом Функция MPI_Recv осуществляет блокирующий прием со-
MPI_Send (&buf, 3, MPI_INT, 0, 1, MPI_COMM_WORLD);
общения с идентификатором msgtag, состоящего максимум из
count элементов типа datatype, от процесса с номером dest в
В примере использован тип данных MPI_INT. Он соответст- группе с коммуникатором comm.
вует типу int в языке Си (подробнее типы данных рассмотрены

69 70
Число элементов в принимаемом сообщении не должно функции получения сообщения. Однако ни один из процессов
превышать значения count. Если число элементов в принимае- никогда не дойдет до функции получения сообщения, поскольку
мом сообщении меньше значения count, то гарантируется, что в каждый процесс заблокирован на этапе отправки, программа
буфере buf будут размещены только принимаемые элементы, т.е. остановится. В некоторых реализациях MPI подобные ситуации
функция MPI_Recv не очищает буфер. Для определения точного отслеживаются с помощью промежуточного буфера, но разра-
числа элементов в принимаемом сообщении можно воспользо- ботчик должен самостоятельно исключать взаимную блокировку
ваться функцией MPI_Probe. в коде программы для обеспечения полноценной переносимости
Блокировка гарантирует, что после возврата из функции кода.
MPI_Recv все элементы сообщения уже приняты и размещены в Функция MPI_Sendrecv объединяет функционал приема и
буфере buf. отправки сообщений.
В качестве номера процесса-отправителя можно указать
int MPI_Sendrecv (
MPI_ANY_SOURCE – признак готовности принять сообщение от void *sbuf,
любого процесса. В качестве идентификатора принимаемого int scount,
сообщения можно указать MPI_ANY_TAG – признак готовности MPI_Datatype stype,
принять сообщение с любым идентификатором. int dest,
Если процесс-отправитель успел отправить несколько со- int stag,
общений процессу-получателю, то однократный вызов функции void *rbuf,
MPI_Recv примет только одно сообщение, отправленное раньше int rcount,
других. MPI_Datatype rtype,
Например, вызов функции MPI_Recv, принимающей три int source,
элемента массива целого типа int от первого процесса в группе с MPI_DAtatype rtag,
коммуникатором MPI_COMM_WORLD выглядит следующим MPI_Comm comm,
образом MPI_Status *status
);
MPI_Recv (&buf, 3, MPI_INT, 1,
MPI_ANY_TAG, MPI_COMM_WORLD, &status); Параметры функции:
− sbuf – отправляемое сообщение;
Структура status типа MPI_Status (подробно рассматривает-
− scount – число элементов в отправляемом сообщении;
ся далее) содержит параметры принятого сообщения.
− stype – тип элементов в отправляемом сообщении;
Пользуясь функциями отправки/получения сообщений с
блокировкой, следует соблюдать осторожность. Как отмечалось − dest – номер процесса-получателя;
ранее, в зависимости от реализации MPI сообщения отправля- − stag – идентификатор отправляемого сообщения;
ются напрямую или записываются в промежуточный буфер. − rbuf – принимаемое сообщение;
Поэтому в некоторых реализациях MPI процессы могут блоки- − rcount – максимальное число элементов в принимаемом
ровать друг друга. Если двум процессам необходимо одновре- сообщении;
менно отправить друг другу некоторый объем данных, и при − rtype – тип элементов в принимаемом сообщении;
этом сообщения пересылаются напрямую, минуя буфер, то оба − source – номер процесса-отправителя;
процесса одновременно дойдут до функции отправки сообщения − rtag – идентификатор принимаемого сообщения;
и заблокируются на ней, ожидая пока получатель дойдет до − comm – коммуникатор группы;

71 72
− status – параметры принятого сообщения. int *count
Функция MPI_Sendrecv отправляет и принимает сообщения. );
С помощью этой функции процессы могут отправлять сообще- Параметры функции:
ния самим себе. Сообщение, отправленное функцией − status – параметры сообщения;
MPI_Sendrecv, может быть принято функцией MPI_Recv, и точно − datatype – тип элементов;
также функция MPI_Sendrecv может принять сообщение, от-
− count – число элементов в сообщении.
правленное функцией MPI_Send. Буферы приема и отправки
Функция MPI_Get_Count определяет по значению парамет-
сообщений обязательно должны быть различными.
ра status число уже принятых (после обращения к MPI_Recv) или
Если процесс ожидает получения сообщения, но не знает
принимаемых (после обращения к MPI_Probe или MPI_IProbe)
его параметров, можно воспользоваться функцией MPI_Probe.
элементов сообщения типа datatype.
int MPI_Probe ( Приведенная ниже программа, где каждый процесс n счита-
int source, ет сумму чисел от n × 10 + 1 до (n + 1) × 10, иллюстрирует
int msgtag, работу функций приема/отправки сообщений с блокировкой
MPI_Comm comm,
MPI_Status *status #include <stdio.h>
); #include "mpi.h"

Параметры функции: int main (int argc, char *argv[])


− source – номер процесса-отправителя или {
MPI_ANY_SOURCE; int i;
− msgtag – идентификатор принимаемого сообщения или int size, me;
MPI_ANY_TAG; int sum;
− comm – коммуникатор группы; MPI_Status status;
− status – параметры принимаемого сообщения.
MPI_Init (&argc, &argv);
Функция MPI_Probe получает параметры принимаемого со-
MPI_Comm_size (MPI_COMM_WORLD, &size);
общения с блокировкой процесса. Возврата из функции не про- MPI_Comm_rank (MPI_COMM_WORLD, &me);
исходит до тех пор, пока сообщение с подходящим идентифика- sum = 0;
тором и номером процесса-отправителя не будет доступно для for (i=me*10+1; i<=(me+1)*10; i++)
приема. Параметры принимаемого сообщения определяются sum = sum + i;
обычным образом с помощью параметра status. Функция if (me == 0)
MPI_Probe отмечает только факт получения сообщения, но {
реально его не принимает. if (size > 1)
Для определения количества уже полученных сообщений {
используется функция MPI_Get_Count. printf ("Процесс 0, сумма = %d\n", sum);
for (i=1; i<size; i++)
int MPI_Get_Count (
{
MPI_Status *status,
MPI_Recv (&sum, 1, MPI_INT, i,
MPI_Datatype datatype,
1, MPI_COMM_WORLD, &status);

73 74
printf ("Процесс %d, сумма = %d\n", i, sum); тельно полезна для разработчиков и поэтому присутствует
} практически в каждой параллельной программе.
} Для инициирования процесса асинхронной отправки сооб-
} щения используется функция MPI_Isend.
else
MPI_Send (&sum, 1, MPI_INT, 0, int MPI_Isend (
1, MPI_COMM_WORLD); void *buf,
MPI_Finalize (); int count,
return (0); MPI_Datatype datatype,
} int dest,
int msgtag,
В этой программе каждый из процессов определяет свой MPI_Comm comm,
номер в группе, вычисляет относящуюся к нему часть задачи, MPI_Request *request
отправляет результат нулевому процессу и завершает работу. );
Нулевой процесс принимает данные от других процессов и
Параметры функции:
печатает все результаты в консоль.
− buf – отправляемое сообщение;
4.3. Прием/отправка сообщений без блокировки − count – число элементов в отправляемом сообщении;
− datatype – тип элементов;
В MPI предусмотрен набор функций для осуществления − dest – номер процесса-получателя;
асинхронной передачи данных. Асинхронная передача предпо- − msgtag – идентификатор сообщения;
лагает, что возврат из функций приема/отправки происходит − comm – коммуникатор группы;
сразу же после их вызова. Наличие таких функций позволяет − request – идентификатор асинхронной передачи.
существенно разнообразить алгоритмы взаимодействия процес-
Функция MPI_Isend осуществляет отправку сообщения ана-
сов. При асинхронной передаче нет необходимости ожидать
логично функции MPI_Send, однако возврат из функции проис-
приема или отправки очередного сообщения, время ожидания
ходит сразу после инициирования процесса отправки без ожида-
может быть использовано в других целях.
ния обработки всего сообщения, находящегося в буфере buf. Это
После инициирования асинхронной отправки или приема
означает, что нельзя повторно использовать данный буфер для
сообщения программа продолжает работу. При этом с помощью
других целей без получения дополнительной информации о
специальных вспомогательных функций программа может
завершении инициированной ранее отправки. Завершение про-
контролировать процесс приема/отправки и таким образом
цесса отправки сообщения можно определить с помощью пара-
координировать свои дальнейшие действия.
метра request в функциях MPI_Wait и MPI_Test. Только после
К сожалению, асинхронные операции не всегда эффективно
успешной отправки сообщения буфер buf может быть использо-
поддерживаются аппаратурой и системным окружением, поэто-
ван повторно без опасения испортить передаваемые сообщения.
му на практике асинхронная передача не всегда повышает ско-
Сообщение, отправленное с помощью функций MPI_Send
рость вычислений, и эффект от выполнения вычислений на фоне
или MPI_Isend, может быть одинаково успешно принято с по-
пересылок сообщений может оказаться небольшим или вовсе
мощью функций MPI_Recv или MPI_Irecv.
нулевым. Однако это обстоятельство не умаляет достоинств
Для инициирования процесса асинхронного приема сооб-
асинхронной передачи, функциональность которой исключи-
щения используется функция MPI_Irecv.

75 76
int MPI_Irecv ( − request – идентификатор асинхронной передачи;
void *buf, − status – параметры переданного сообщения.
int count, Функция MPI_Wait осуществляет ожидание завершения
MPI_Datatype datatype, асинхронных функций MPI_Isend и MPI_Irecv, инициировавших
int source,
передачу с идентификатором request. В случае успешной пере-
int msgtag,
дачи, атрибуты переданного сообщения можно определить с
MPI_comm comm,
помощью параметра status.
MPI_Request *request
);
Приведенная ниже программа, где каждый процесс отправ-
ляет сообщение соседу справа и принимает сообщение от соседа
− buf – принимаемое сообщение; слева («соседи» вычисляются на основании номеров процессов),
− count – максимальное число элементов в принимаемом демонстрирует работу функций приема/отправки сообщений без
сообщении; блокировки
− datatype – тип элементов; #include <stdio.h>
− source – номер процесса-отправителя; #include <unistd.h>
− msgtag – идентификатор сообщения; #include "mpi.h"
− comm – коммуникатор группы;
− request – идентификатор асинхронной передачи. int main (int argc, char *argv[])
Функция MPI_Irecv осуществляет прием сообщения анало- {
гично функции MPI_Recv, однако возврат из функции происхо- int size, me, left, right;
дит сразу после инициирования процесса приема без ожидания int rbuffer[10], sbuffer[10];
получения сообщения в буфере buf. Завершение процесса прие- double time;
ма сообщения можно определить с помощью параметра request в MPI_Request rrequest, srequest;
MPI_Status status;
функциях MPI_Wait и MPI_Test.
При работе с асинхронными функциями очень важно от-
MPI_Init (&argc, &argv);
слеживать состояние передачи сообщения. Отправляющей
MPI_Comm_size (MPI_COMM_WORLD, &size);
стороне это необходимо для корректного использования буфера, MPI_Comm_rank (MPI_COMM_WORLD, &me);
а принимающей стороне – для своевременной обработки дан- right = (me + 1) % size;
ных. Функция MPI_Wait используется для того чтобы приоста- left = me – 1;
новить работу процесса до окончания передачи сообщения, т.е. if (left < 0)
фактически она позволяет осуществлять синхронизацию с дан- left = size – 1;
ными. time = MPI_Wtime();
int MPI_Wait ( printf ("Мой номер %d, время до паузы %.0f\n",
MPI_Request *request, me, time);
MPI_Status *status sleep (5);
); if (me == 0)
sleep(5);
Параметры функции: MPI_Irecv (rbuffer, 10, MPI_INT, left,

77 78
123, MPI_COMM_WORLD, &rrequest); int MPI_Waitall (
MPI_Isend (sbuffer, 10, MPI_INT, right, int count,
123, MPI_COMM_WORLD, &srequest); MPI_Request *requests,
time = MPI_Wtime(); MPI_Status *statuses
printf ("Мой номер %d, время после паузы %.0f\n", );
me, time);
Параметры функции:
MPI_Wait (&rrequest, &status);
time = MPI_Wtime (); − count – число идентификаторов асинхронной передачи;
printf ("Мой номер %d, время запуска %.0f\n", − requests – идентификаторы асинхронной передачи;
me, time); − statuses – параметры переданных сообщений.
MPI_Finalize (); Функция MPI_Waitall останавливает процесс до тех пор, по-
return (0); ка все указанные передачи из массива requests не будут завер-
} шены. Если во время одной или нескольких передач возникнут
В этом примере используется функция MPI_Wtime, которая ошибки, то коды ошибок будут записаны в соответствующие
возвращает астрономическое время в секундах (вещественное поля параметров передачи из массива statuses.
число), прошедшее с некоторого момента в прошлом. Гаранти- В случае успешной передачи всех сообщений, их атрибуты
руется, что этот момент не будет изменен за время существова- можно определить с помощью того же массива параметров
ния процесса. В данной программе функция MPI_Wtime исполь- statuses.
зуется для определения моментов времени, когда программа int MPI_Waitany (
проходит различные этапы. int count,
Если запустить программу на нескольких вычислительных MPI_Request *requests,
узлах и проанализировать выводимые на экран сообщения, то int *index,
можно заметить следующее: MPI_Status *status
1. До первого вывода сообщения на экран все процессы до- );
ходят в одно и то же время. Параметры функции:
2. До второго вывода сообщения на экран одновременно − count – число идентификаторов асинхронной передачи;
доходят все процессы кроме нулевого. Нулевой процесс − requests – идентификаторы асинхронной передачи;
задерживается на 5 секунд. − index – номер завершенной передачи;
3. До третьего вывода сообщения на экран одновременно
− status – параметры переданного сообщения.
доходят все процессы кроме первого и нулевого. Первый
Функция MPI_Waitany останавливает процесс до тех пор,
и нулевой процессы задерживаются на 5 секунд.
пока какая-либо передача, из указанных в массиве requests, не
На основании этих фактов можно сделать вывод о том, как с
будет завершена. Параметр index возвращает номер элемента в
помощью функции MPI_Wait происходит ожидание завершения
массиве requests, содержащего идентификатор завершенной
передачи сообщения и синхронизация процессов.
передачи. Если завершены сразу несколько передач, то случай-
Помимо функции MPI_Wait существует несколько ее моди-
ным образом выбирается одна из них.
фикаций, ожидающих завершения асинхронной передачи с
В случае успешной передачи, атрибуты переданного сооб-
различными условиями: MPI_Waitall, MPI_Waitany и
щения можно определить с помощью параметра status.
MPI_Waitsome.

79 80
int MPI_Waitsome ( − flag – признак завершения передачи;
int incount, − status – параметры переданного сообщения.
MPI_Request *requests, Функция MPI_Test осуществляет проверку завершения пе-
int *outcount, редачи с идентификатором request, инициированной асинхрон-
int *indexes,
ными функциями MPI_Isend и MPI_Irecv. Параметр flag по
MPI_Status *statuses
умолчанию равен нулю и принимает значение 1, если соответст-
);
вующая передача завершена. В случае успешной передачи,
Параметры функции: атрибуты переданного сообщения можно определить с помощью
− incount – число идентификаторов асинхронной передачи; параметра status.
− requests – идентификаторы асинхронной передачи; int MPI_Testall (
− outcount – число завершенных асинхронных передач; int count,
− indexes – массив номеров завершенных передач; MPI_Request *requests,
− statuses – параметры переданных сообщений в завершен- int *flag,
ных передачах. MPI_Status *statuses
Функция MPI_Waitsome останавливает процесс до тех пор, );
пока хотя бы одна из передач, указанных в массиве requests, не Параметры функции:
будет завершена. В отличие от функции MPI_Waitany, функция − count – число идентификаторов асинхронной передачи;
MPI_Waitsome сохраняет информацию обо всех одновременно
− requests – идентификаторы асинхронной передачи;
завершившихся передачах, таким образом, позволяя программе
− flag – признак завершения передачи;
обработать большее количество данных. Параметр outcount
содержит общее число завершенных передач, а первые outcount − statuses – параметры переданных сообщений.
элементов массива indexes содержат номера элементов массива Функция MPI_Testall проверяет завершение всех передач из
requests с идентификаторами завершенных передач. Первые массива requests. Параметр flag по умолчанию равен нулю и
outcount элементов массива statuses содержат параметры пере- принимает значение 1, если все указанные передачи завершены.
данных сообщений. В случае успешной передачи, атрибуты переданных сообщений
Аналогично функциям MPI_Wait, MPI_Waitall, MPI_Waitany можно определить с помощью массива параметров statuses. В
и MPI_Waitsome работают функции MPI_Test, MPI_Testall, противном случае массив параметров statuses не определен.
MPI_Testany и MPI_Testsome, за исключением того, что эти int MPI_Testany (
функции не останавливают процесс. int count,
MPI_Request *requests,
int MPI_Test (
int *index,
MPI_Request *request,
int *flag,
int *flag,
MPI_Status *status
MPI_Status *status
);
);
Параметры функции:
Параметры функции:
− count – число идентификаторов асинхронной передачи;
− request – идентификатор асинхронной передачи;
− requests – идентификаторы асинхронной передачи;

81 82
− index – номер завершенной передачи; на события потоковый планировщик легко реализуется с помо-
− flag – признак завершения передачи; щью циклического вызова MPI_Test. Код такого планировщика
− status – параметры переданного сообщения. упрощенно выглядит следующим образом
Функция MPI_Testany проверяет завершение передач из MPI_Test (&request, &flag, &status);
массива requests. Параметр flag по умолчанию равен нулю и while (!flag)
принимает значение 1, если хотя бы одна из указанных передач {
завершена. Параметр index возвращает номер элемента в масси- /* Выполнение полезной работы */
ве requests, содержащего идентификатор завершенной передачи. MPI_Test (&request, &flag, &status);
Если могут быть завершены сразу несколько передач, то слу- }
чайным образом выбирается одна из них.
В одних и тех же случаях функция MPI_Wait возвращает
int MPI_Testsome ( управление, а функция MPI_Test возвращает flag = true; обе
int incount, функции при этом возвращают одинаковые параметры status.
MPI_Request *requests, Таким образом, блокирующая функция MPI_Wait может быть
int *outcount, легко заменена функцией MPI_Test.
int *indexes, Для получения информации о формате принимаемого со-
MPI_Status *statuses общения без блокировки процесса, можно воспользоваться
); функцией MPI_Iprobe.
Параметры функции: int MPI_Iprobe (
− incount – число идентификаторов асинхронной передачи; int source,
− requests – идентификаторы асинхронной передачи; int msgtag,
− outcount – число завершенных асинхронных передач; MPI_Comm comm,
− indexes – массив номеров завершенных передач; int *flag,
− statuses – параметры переданных сообщений в завершен- MPI_Status *status
ных передачах. );
Функция MPI_Testsome проверяет завершение передач из Параметры функции:
массива requests. В отличие от функции MPI_Testany, функция − source – номер процесса-отправителя или
MPI_Testsome сохраняет информацию обо всех завершившихся MPI_ANY_SOURCE;
передачах. Параметр outcount содержит общее число завершен- − msgtag – идентификатор принимаемого сообщения или
ных передач, а первые outcount элементов массива indexes со- MPI_ANY_TAG;
держат номера элементов массива requests с идентификаторами − comm – коммуникатор группы;
завершенных передач. Первые outcount элементов массива − flag – признак завершения передачи;
statuses содержат параметры переданных сообщений. Если ни
− status – параметры принимаемого сообщения.
одна из указанных передач не завершена, параметр outcount
Функция MPI_Iprobe получает информацию о структуре
равен нулю.
принимаемого сообщения без блокировки процесса. Параметр
Использование функции MPI_Test и ее производных позво-
flag по умолчанию равен нулю и принимает значение 1, если
ляет разработчику организовывать дополнительную активность
сообщение с подходящим идентификатором и номером процес-
процессов без создания дополнительных потоков. Реагирующий
83 84
са-отправителя доступно для приема. Параметры принимаемого Для снижения накладных расходов при передаче сообщений
сообщения определяются обычным образом с помощью пара- между процессами несколько запросов на прием и отправку
метра status. Функция MPI_Iprobe отмечает только факт получе- сообщений предварительно объединяются вместе и затем одно-
ния сообщения, но реально его не принимает. временно выполняются. Для этого используются функции
Код планировщика с применением функции MPI_Iprobe MPI_Send_init, MPI_Recv_init, MPI_Start и их производные.
может упрощенно выглядеть следующим образом Способ приема сообщения не зависит от способа его от-
правки: любое сообщение, отправленное обычным способом или
MPI_Iprobe (MPI_ANY_SOURCE, MPI_ANY_TAG,
MPI_COMM_WORLD, &flag, &status);
с помощью объединения запросов, может быть принято как
while (!flag) обычным способом, так и с помощью объединения запросов.
{ Параллельные процессы часто обмениваются сообщениями
/* Выполнение полезной работы */ разного содержания, но одинаковой длины и с одинаковой
MPI_Iprobe (MPI_ANY_SOURCE, MPI_ANY_TAG, внутренней структурой, например, в циклах. В таких случаях
MPI_COMM_WORLD, &flag, &status); передача однократно инициализируется (подготавливается),
} после чего происходят многократные приемы/отправки сообще-
ний без затрат дополнительного времени на подготовку данных.
4.4. Объединение запросов на прием/отправку Другим случаем использования функций объединения за-
сообщений просов на прием/отправку сообщений может быть параллельный
процесс, способный самостоятельно производить вычисления,
На общее время выполнения каждого процесса (время рабо- накапливая данные для информационного обмена. Впрочем,
ты параллельной программы на каждом вычислительном узле) накапливание данных может привести к перегрузке коммуника-
оказывают влияние несколько факторов: ционной сети.
− время, затрачиваемое на выполнение вычислительных Рассмотрим подробнее функции для объединения запросов
операций; на передачу сообщений.
− время, затрачиваемое на установку соединения между
процессами (вычислительными узлами); 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 – номер процесса-получателя;

85 86
− msgtag – идентификатор сообщения; Функция MPI_Start запускает отложенную передачу с иден-
− comm – коммуникатор группы; тификатором request, сформированную вызовами функций
− request – идентификатор асинхронной передачи. MPI_Send_init и MPI_Recv_init.
Функция MPI_Send_init формирует запрос на отправку со- В момент обращения к функции MPI_Start необходимо,
общения. Все параметры точно такие же, как у функции чтобы указатель на идентификатор асинхронной передачи ука-
MPI_Isend, однако пересылка начинается только после вызова зывал на полностью сформированный запрос.
функции MPI_Start. Содержимое буфера нельзя менять до окон- int MPI_Start_all (
чания передачи. MPI_Request *requests
int MPI_Recv_init ( );
void *buf, Параметры функции:
int count, − requests – идентификаторы асинхронной передачи.
MPI_Datatype datatype,
Функция MPI_Start_all запускает все отложенные передачи
int source,
с идентификаторами из массива requests, сформированные
int msgtag,
вызовами функций MPI_Send_init и MPI_Recv_init.
MPI_Comm comm,
MPI_Request *request
В момент обращения к функции MPI_Start_all необходимо,
);
чтобы указатели на идентификаторы асинхронной передачи
указывали на полностью сформированные запросы.
Параметры функции: Для отмены отложенной передачи данных используется
− buf – принимаемое сообщение; функция MPI_Request_free.
− count – максимальное число элементов в принимаемом
int MPI_Request_free (
сообщении;
MPI_Request *request
− datatype – тип элементов; );
− source – номер процесса-отправителя;
− msgtag – идентификатор сообщения; Параметры функции:
− comm – коммуникатор группы; − request – идентификатор асинхронной передачи.
− request – идентификатор асинхронной передачи. Функция MPI_Request_free отменяет ранее сформирован-
Функция MPI_Recv_init формирует запрос на прием сооб- ный запрос на передачу сообщения и освобождает все связанные
щения. Все параметры точно такие же, как у функции MPI_Irecv, с ним элементы памяти. Если асинхронная передача с иденти-
однако пересылка начинается только после вызова функции фикатором request происходит в момент вызова функции
MPI_Start. Содержимое буфера нельзя менять до окончания MPI_Request_free, то она не будет прервана и сможет успешно
передачи. завершиться.
Функцией MPI_Request_free нужно пользоваться с осторож-
int MPI_Start ( ностью, потому что ее завершение невозможно отследить с
MPI_Request *request помощью функций MPI_Test или MPI_Wait.
); Приведенная ниже программа демонстрирует работу функ-
Параметры функции: ций отложенного взаимодействия:
− request – идентификатор асинхронной передачи.

87 88
#include <stdio.h> return (0);
#include "mpi.h" }

Данная программа использует только два параллельных


int main (int argc, char *argv[])
{
процесса. Первый процесс формирует отложенный запрос на
int i, j; отправку сообщения второму процессу, после чего четырежды
int me, size; выполняет этот запрос. Перед каждой отправкой сообщения
int buffer[3]; содержимое буфера меняется. Второй процесс принимает от-
MPI_Request request; правленные сообщения с блокировкой и выводит их на экран.
MPI_Status status; Данная программа демонстрирует алгоритм отказа от фор-
мирования запросов на отправку каждого типового сообщения.
MPI_Init (&argc, &argv); В результате экономится процессорное время.
MPI_Comm_size (MPI_COMM_WORLD, &size); Использование функции MPI_Wait гарантирует завершение
MPI_Comm_rank (MPI_COMM_WORLD, &me); передачи перед изменением содержимого буфера и повторной
if (size > 1) отправкой сообщения. Без функции MPI_Wait программа не
{ сможет выполняться и будет прервана с соответствующими
if (me == 0) ошибками.
{
MPI_Send_init (buffer, 3, MPI_INT, 1,
4.5. Барьерная синхронизация
1, MPI_COMM_WORLD, &request);
for (i=0; i<4; i++) Во всех предыдущих случаях процессы синхронизируются
{ друг с другом в момент передачи сообщений с блокировкой или
for (j=0; j<=2; j++) с помощью блокирующих функций типа MPI_Wait. Использова-
buffer[j] = j * (i + 1); ние этих функций позволяет разработчику предсказывать пове-
MPI_Start (&request); дение параллельных процессов в коммуникационной среде, т.е.
MPI_Wait (&request, &status); фактически самостоятельно организовывать синхронизацию.
} Функция MPI_Barrier предоставляет возможность синхро-
} низации процессов без передачи сообщений.
if (me == 1)
{ int MPI_Barrier (
for (i=0; i<4; i++) MPI_Comm comm
{ );
MPI_Recv (buffer, 3, MPI_INT, 0, Параметры функции:
1, MPI_COMM_WORLD, &status);
− comm – коммуникатор группы.
printf ("Сообщение %d – %d, %d, %d\n",
i + 1, buffer[0], buffer[1], buffer[2]);
Функция MPI_Barrier блокирует работу процесса до тех
}
пор, пока все процессы группы comm не вызовут эту функцию.
} Приведенная ниже программа демонстрирует работу функ-
} ции MPI_Barrier:
MPI_Finalize ();

89 90
#include <stdio.h> 1. До первого вывода сообщения на экран.
#include <unistd.h> 2. Перед вызовом функции MPI_Barrier.
#include "mpi.h" 3. После вызова функции MPI_Barrier.
Результаты первого замера времени демонстрируют син-
int main (int argc, char *argv[]) хронную работу всех процессов. Затем каждый процесс останав-
{ ливается на время, равное его порядковому номеру в группе (в
int i; секундах). Второй замер времени демонстрирует соответствую-
int me, size;
щую рассинхронизацию в работе всех процессов. Наконец,
double buffer[3];
третий замер времени демонстрирует синхронный выход всех
MPI_Status status;
процессов из функции MPI_Barrier.
MPI_Init (&argc, &argv);
MPI_Comm_size (MPI_COMM_WORLD, &size); 4.6. Группы процессов
MPI_Comm_rank (MPI_COMM_WORLD, &me); Группа – это упорядоченное множество процессов. Каждо-
buffer[0] = MPI_Wtime(); му процессу в группе сопоставлено целое число – ранг или
sleep (me); номер.
buffer[1] = MPI_Wtime();
Группа с коммуникатором MPI_GROUP_EMPTY – пустая
MPI_Barrier (MPI_COMM_WORLD);
группа, не содержащая ни одного процесса.
buffer[2] = MPI_Wtime();
Группа с коммуникатором MPI_GROUP_NULL – значение,
if (me > 0)
MPI_Send (buffer, 3, MPI_DOUBLE, 0,
используемое для ошибочной группы.
1, MPI_COMM_WORLD);
Новые группы можно создавать как на основе уже сущест-
else вующих групп, так и на основе коммуникаторов, но в операциях
{ обмена могут использоваться только коммуникаторы. Базовая
for (i=0; i<size; i++) группа, из которой создаются все остальные группы процессов,
{ связана с коммуникатором MPI_COMM_WORLD, в нее входят
if (i > 0) все процессы приложения. Операции над группами процессов
MPI_Recv (buffer, 3, MPI_DOUBLE, i, являются локальными, в них вовлекается только вызвавший
1, MPI_COMM_WORLD, &status); процедуру процесс, а выполнение не требует межпроцессорного
printf ("Процесс %d, время в точке 1 - %.0f, обмена данными. Любой процесс может производить операции
2 – %.0f, 3 – %.0f\n", над любыми группами, в том числе над такими, которые не
i, buffer[0], buffer[1], buffer[2]); содержат данный процесс. При операциях над группами может
} получиться пустая группа MPI_GROUP_EMPTY.
} Если при инициализации параллельной программы можно
MPI_Finalize (); эффективно работать только с группой MPI_COMM_WORLD, то
return (0); в дальнейшем при помощи функций-конструкторов групп мож-
}
но создавать новые группы и коммуникаторы.
В данной программе каждый из запускаемых параллельных Конструкторы групп применяются к подмножеству и рас-
процессов трижды фиксирует время: ширенному множеству существующих групп. Эти конструкторы

91 92
создают новые группы на основе существующих групп. Данные − Разность (англ. Difference) – содержит все элементы пер-
операции являются локальными и различные группы могут быть вой группы, которые не находятся во второй группе, упо-
определены на различных процессах; процесс может также рядоченные как в первой группе.
определять группу, которая не включает саму себя. Заметим, что для этих операций порядок процессов в созда-
Интерфейс MPI не имеет механизма для формирования ваемой группе определен прежде всего в соответствии с поряд-
группы с нуля, группа может формироваться только на основе ком в первой группе (если возможно) и затем, в случае необхо-
другой, предварительно определенной группы. димости, в соответствии с порядком во второй группе. Ни
Базовая группа, на основе которой определены все другие объединение, ни пересечение не коммутативны, но оба ассоциа-
группы – это группа, определяемая начальным коммуникатором тивны. Новая группа может быть пуста, то есть эквивалентна
MPI_COMM_WORLD. Доступ к этой группе, как и ко всем ос- MPI_GROUP_EMPTY.
тальным группам, осуществляется с помощью функции
int MPI_Group_union (
MPI_Comm_group.
MPI_Group group1,
Следующие конструкторы предназначены для создания MPI_Group group2,
подмножеств и расширенных множеств существующих групп. MPI_Group *newgroup
Каждый конструктор группы ведет себя так, как будто он воз- );
вращает новый объект группы.
Параметры функции:
int MPI_Comm_group (
− group1 – указатель на первую группу;
MPI_Comm comm,
− group2 – указатель на вторую группу;
MPI_Group *group
); − newgroup – указатель на создаваемую группу.
Функция MPI_Group_union создает объединение двух
Параметры функции: групп, и записывает результат в newgroup.
− comm – коммуникатор группы;
int MPI_Group_intersection (
− group – группа в коммуникаторе.
MPI_Group group1,
Функция MPI_Comm_group возвращает в group дескриптор MPI_Group group2,
группы из comm. Данная функция используется для того, чтобы MPI_Group *newgroup
получить доступ к группе, соответствующей определенному );
коммуникатору. После этого над группами можно проводить
операции. Параметры функции:
Возможные операции над множествами определяются сле- − group1 – указатель на первую группу;
дующим образом: − group2 – указатель на вторую группу;
− Объединение (англ. Union) – содержит все элементы пер- − newgroup – указатель на создаваемую группу.
вой группы и следующие за ними элементы второй груп- Функция MPI_Group_intersection образует группу, которая
пы, не входящие в первую группу. является пересечением group1 и group2, и записывает результат
− Пересечение (англ. Intersect) – содержит все элементы в newgroup.
первой группы, которые также находятся во второй груп- int MPI_Group_difference (
пе, упорядоченные как в первой группе. MPI_Group group1,

93 94
MPI_Group group2, Параметры функции:
MPI_Group *newgroup − group – указатель на группу;
); − n – количество процессов, не входящих в новую группу;
Параметры функции: − ranks – номера процессов из group, которые не должны
− group1 – указатель на первую группу; войти в создаваемую группу;
− group2 – указатель на вторую группу; − newgroup – указатель на создаваемую группу.
− newgroup – указатель на создаваемую группу. Функция MPI_Group_excl создает группу процессов
Функция MPI_Group_difference образует группу, которая newgroup, которая получается путем удаления из group процес-
является результатом исключения group2 из group1, и записыва- сов с номерами от ranks[0] до ranks[n-1]. Упорядочивание про-
ет результат в newgroup. цессов в newgroup идентично упорядочиванию в group. Каждый
из n элементов массива ranks должен быть правильным номером
int MPI_Group_incl ( в group, и все элементы должны быть различными, иначе про-
MPI_Group group, грамма будет неверна. Если n равна нулю, то newgroup идентич-
int n,
на group.
int *ranks,
MPI_Group *newgroup) int MPI_Group_range_incl (
MPI_Group group,
Параметры функции: int n,
− group – указатель на группу; int ranges[][3],
− n – количество процессов, входящих в новую группу; MPI_Group *newgroup
− ranks – номера процессов из group, которые должны вой- );
ти в создаваемую группу;
Параметры функции:
− newgroup – указатель на создаваемую группу.
− group – указатель на группу;
Функция MPI_Group_incl создает группу newgroup, которая
состоит из n процессов из group с номерами от ranks[0] до − n – число триплетов в массиве ranges;
ranks[n-1]. Процесс с номером i в newgroup есть процесс с номе- − ranges – массив триплетов, указывающий номера процес-
ром ranks[i] в group. Каждый из n элементов массива ranks сов в group, которые включены в newgroup;
должен быть правильным номером в group, и все элементы − newgroup – указатель на создаваемую группу.
должны быть различными, иначе программа будет неверна. Если Функция MPI_Group_range_incl создает группу на основе
n равна нулю, то newgroup имеет значение последовательностей процессов из существующей группы.
MPI_GROUP_EMPTY. Функция MPI_Group_incl может исполь- Результат записывается в newgroup.
зоваться, например, для переупорядочения элементов группы. Последовательности процессов вычисляются при помощи
триплетов:
int MPI_Group_excl ( 1. Первый номер.
MPI_Group group, 2. Последний номер.
int n, 3. Шаг.
int *ranks,
Если аргументы ranges состоят из триплетов вида
MPI_Group *newgroup
(first1, first2 + stride1), …, (firstn, firstn + striden),
);

95 96
то newgroup состоит из последовательности процессов с передать результирующий массив номеров и другие аргументы в
номерами MPI_Group_excl. Вызов функции MPI_Group_excl эквивалентен
first1, first1 + stride1, …, first1 + stride1 × (last1 – first1) / stride1, вызову функции MPI_Group_range_excl с каждым номером i в
… ranks, замененным триплетом (i, i, 1) в аргументе ranges.
firstn, firstn + striden, …, firstn + striden × (lastn – firstn) / striden, Рассмотрим еще некоторые функции, помимо
Каждый вычисленный номер должен быть правильным но- MPI_Group_size и MPI_Group_rank, которые позволяют рабо-
мером в group, и все вычисленные номера должны быть различ- тать с уже созданными группами.
ными, иначе программа будет неверной. Заметим, что возможен
int MPI_Group_translate_ranks (
случай, когда firsti > lasti, и stridei может быть отрицательным, MPI_Group group1,
но не может быть равным нулю. int n,
Возможности этой функции позволяют расширить массив int *ranks1,
номеров до массива включенных номеров и передать результи- MPI_Group group2,
рующий массив номеров и другие аргументы в MPI_Group_incl. int *ranks2
Вызов функции MPI_Group_incl эквивалентен вызову функции );
MPI_Group_range_incl с каждым номером i в ranks, замененным
триплетом (i, i, 1) в аргументе ranges. Параметры функции:
− group1 – указатель на первую группу;
int MPI_Group_range_excl ( − n – число элементов в массивах ranks1 и ranks2;
MPI_Group group,
− ranks1 – массив из нуля или более правильных номеров в
int n,
первой группе;
int ranges[][3],
MPI_Group *newgroup
− group2 – указатель на вторую группу;
); − ranks2 – массив соответствующих номеров во второй
группе или MPI_UNDEFINED, если такое соответствие
Параметры функции: отсутствует.
− group – указатель на группу; Функция MPI_Group_translate_ranks переводит номера про-
− n – число триплетов в массиве ranges; цессов из одной группы в другую. Эта функция важна для опре-
− ranges – массив триплетов, указывающий номера процес- деления относительной нумерации одинаковых процессов в двух
сов в group, которые исключены из newgroup; различных группах. Например, если известны номера некоторых
− newgroup – указатель на создаваемую группу. процессов в группе коммуникатора MPI_COMM_WORLD, то
Функция MPI_Group_range_excl создает группу процессов можно узнать их номера в подмножестве этой группы.
newgroup, которая получена путем удаления из group опреде-
int MPI_Group_compare (
ленных последовательностей процессов. Эти последовательно- MPI_Group group1,
сти задаются триплетами аналогично MPI_Group_range_incl. MPI_Group group2,
Каждый вычисленный номер должен быть правильным номером int *result
в group, и все вычисленные номера должны быть различными, );
иначе программа будет неверной.
Возможности функции MPI_Group_range_excl позволяют Параметры функции:
расширить массив номеров до массива исключенных номеров и − group1 – указатель на первую группу;

97 98
− group2 – указатель на вторую группу; − MPI_COMM_SELF – коммуникатор, включающий только
− result – результат сравнения. вызвавший процесс.
Функция MPI_Group_compare используется для сравнения В отличие от создания группы создание коммуникатора яв-
двух групп. Если члены группы и их порядок в обеих группах ляется коллективной операцией, требующей наличия межпро-
совершенно одинаковы, возвращается результат MPI_IDENT. цессного обмена, поэтому такие функции должны вызываться
Это происходит, например, если group1 и group2 имеют тот же всеми процессами некоторого существующего коммуникатора.
самый указатель (дескриптор группы). Если члены группы В модели клиент-сервер создается много программ, где
одинаковы, но порядок различен, то возвращается результат один процесс (обычно нулевой процесс) играет роль распоряди-
MPI_SIMILAR. В остальных случаях возвращается значение теля, а другие процессы служат вычислительными узлами. В
MPI_UNEQUAL. такой структуре для определения ролей различных процессов
коммуникатора полезны уже рассмотренные ранее функции
int MPI_Group_free (
MPI_Comm_size и MPI_Comm_rank. В дополнение к ним рас-
MPI_Group *group
);
смотрим функцию MPI_Comm_compare.
int MPI_Comm_compare (
Параметры функции:
MPI_Comm comm1,
− group1 – указатель на группу. MPI_Comm comm2,
Как и любой другой объект, созданный в памяти, группа int *result
должна быть удалена разработчиком после ее использования. );
Функция MPI_Group_free отправляет группу на удаление. Ука-
затель на group устанавливается в состояние Параметры функции:
MPI_GROUP_NULL. Любая операция, использующая в это − comm1 – коммуникатор первой группы;
время удаляемую группу, завершится нормально. − comm2 – коммуникатор второй группы;
− result – результат сравнения.
4.7. Коммуникаторы групп Функция MPI_Comm_compare используется для сравнения
коммуникаторов двух групп. Результат MPI_IDENT появляется
Коммуникатор предоставляет отдельный контекст обмена
тогда и только тогда, когда comm1 и comm2 являются коммуни-
между процессами некоторой группы. Контекст обеспечивает
каторами одной и той же группы.
возможность независимых обменов данными. Каждой группе
Результат MPI_CONGRUENT появляется, если исходные
процессов может соответствовать несколько коммуникаторов,
группы идентичны по компонентам и нумерации – эти комму-
но каждый коммуникатор в любой момент времени однозначно
никаторы отличаются только контекстом.
соответствует только одной группе.
Результат MPI_SIMILAR имеет место, если члены группы
Сразу после вызова функции MPI_Init создаются следую-
обоих коммуникаторов являются одинаковыми, но порядок их
щие коммуникаторы:
нумерации различен.
− MPI_COMM_WORLD – коммуникатор, объединяющий Во всех остальных случаях выдается результат
все процессы параллельной программы; MPI_UNEQUAL.
− MPI_COMM_NULL – значение, используемое для оши- Рассмотрим функции, которые позволяют создать коммуни-
бочного коммуникатора; катор для вновь созданной группы. Первая из таких функций
MPI_Comm_create.

99 100
int MPI_Comm_create ( новый коммуникатор с той же группой, любой скопированной
MPI_Comm comm, кэшированной информацией, но с новым контекстом.
MPI_Group group, Функция MPI_Comm_dup используется, чтобы предоставить
MPI_Comm *newcomm вызовам параллельных библиотек дублированное коммуникаци-
); онное пространство, которое имеет те же самые свойства, что и
Параметры функции: первоначальный коммуникатор.
− comm – коммуникатор группы; Обращение к MPI_Comm_dup имеет силу, даже если имеют-
− group – группа, для которой создается коммуникатор; ся ждущие парные обмены, использующие коммуникатор comm.
− newcomm – указатель на новый коммуникатор. Типичный вызов мог бы запускать MPI_Comm_dup в начале
Функция MPI_Comm_create создает новый коммуникатор параллельного обращения и MPI_Comm_free этого дублирован-
newcomm с коммуникационной группой, определенной аргумен- ного коммуникатора – в конце вызова.
том group и новым контекстом. Из comm в newcomm не переда- Отдельного внимания заслуживает функция
ется никакой кэшированной информации. Функция MPI_Comm_split, поскольку она позволяет выполнить гораздо
MPI_Comm_create возвращает MPI_COMM_NULL для процес- более сложное распределение процессов между различными
сов, не входящих в group. Запрос неверен, если group не являет- коммуникаторами.
ся подмножеством группы, связанной с comm. Заметим, что int MPI_Comm_split (
запрос должен быть выполнен всеми процессами в comm, даже MPI_Comm comm,
если они не принадлежат новой группе. int color,
Еще одна функция MPI_Comm_dup дублирует существую- int key,
щий коммуникатор. MPI_Comm *newcomm
);
int MPI_Comm_dup (
MPI_Comm comm, Параметры функции:
MPI_Comm *newcomm − comm – коммуникатор группы;
); − color – управление соданием подмножества;
Параметры функции: − key – управление назначением рангов;
− comm – коммуникатор группы; − newcomm – новый коммуникатор.
− newcomm – указатель на новый коммуникатор. Функция MPI_Comm_split делит группу, связанную с comm,
Функция MPI_Comm_dup дублирует существующий ком- на непересекающиеся подгруппы, по одной для каждого значе-
муникатор comm вместе со связанными с ним значениями клю- ния цвета color.
чей. Для каждого значения ключа соответствующая функция Каждая подгруппа содержит все процессы того же самого
обратного вызова для копирования определяет значение атрибу- цвета. То есть для создания непересекающихся коммуникаторов
та, связанного с этим ключом в новом коммуникаторе. Одно каждый процесс должен вызвать эту функцию с некоторым
частное действие, которое может сделать вызов для копирова- значением параметра color. В результате все процессы, у кото-
ния, состоит в удалении атрибута из нового коммуникатора. рых было одинаковое значение параметра color, будут объеди-
Операция MPI_Comm_dup возвращает в аргументе newcomm нены одним коммуникатором.

101 102
В пределах каждой подгруппы процессы пронумерованы в иметь отрицательного значения, чтобы не конфликтовать со
порядке, определенном значением аргумента key, со связями, значением, присвоенным MPI_UNDEFINED.
разделенными согласно их номеру в старой группе. После завершения работы с коммуникатором его можно
Для каждой подгруппы создается новый коммуникатор и удалить при помощи функции MPI_Comm_free.
возвращается в аргументе newcomm. Процесс может иметь
int MPI_Comm_free (
значение цвета MPI_UNDEFINED, тогда newcomm возвращает MPI_Comm *comm.
MPI_COMM_NULL. );
Обращение к MPI_Comm_create эквивалентно обращению к
MPI_Comm_split, где все члены group имеют color равный нулю Параметры функции:
и key равный номеру в group, а все процессы, которые не явля- − comm – удаляемый коммуникатор группы.
ются членами group, имеют color равный MPI_UNDEFINED. Коллективная функция MPI_Comm_free маркирует комму-
Функция MPI_Comm_split допускает более общее разделе- никационный объект для удаления. Удаляемый коммуникатор
ние группы на одну или несколько подгрупп с необязательным comm принимает значение MPI_COMM_NULL.
переупорядочением. Любые ждущие операции, которые используют удаляемый
Значение color должно быть неотрицательным. коммуникатор, будут завершены нормально. Объект фактически
Функция MPI_Comm_split – это чрезвычайно мощный меха- удаляется только в том случае, если не имеется никаких других
низм для разделения единственной коммуникационной группы активных ссылок на него.
процессов на k подгрупп, где k неявно выбрано пользователем Функция MPI_Comm_free используется в интра- и интер-
(количеством цветов для раскраски процессов). Полученные коммуникаторах. Удаляемые функции обратного вызова для
коммуникаторы будут не перекрывающимися. Такое деление всех кэшируемых атрибутов вызываются в произвольном поряд-
полезно для организации иерархии вычислений, например для ке.
линейной алгебры. Число ссылок на объект коммуникатора увеличивается c
Чтобы преодолеть ограничение на перекрытие создаваемых каждым вызовом MPI_Comm_dup и уменьшается c каждым
коммуникаторов, можно использовать множественные обраще- вызовом MPI_Comm_free. Объект окончательно удаляется, когда
ния к MPI_Comm_split. Этим способом можно создавать множе- число ссылок достигает нуля.
ственные перекрывающиеся коммуникационные структуры.
Заметим, что для фиксированного цвета ключи не должны 4.8. Функции коллективного взаимодействия
быть уникальными. Функция MPI_Comm_split сортирует про- Функции коллективного взаимодействия используются для
цессы в возрастающем порядке согласно этому ключу, и разде- выполнения однообразных операций, в которых должны участ-
ляет связи непротиворечивым способом. Если все ключи опре- вовать все процессы одного коммуникатора. Например, такие
делены таким же образом, то все процессы одинакового цвета функции используются для отправки сообщений одного процес-
будут иметь относительный порядок номеров такой же, как и в са всем остальным процессам.
породившей их группе. В общем случае они будут иметь раз- Особенности коллективных функций:
личные номера. − коллективные функции не используются для связи про-
Если значение ключа для всех процессов данного цвета сде- цессов типа точка-точка;
лано нулевым, то это означает, что порядок номеров процессов в
− коллективные функции выполняются в режиме с блоки-
новом коммуникаторе безразличен. Аргумент сolor не может
ровкой;

103 104
− возврат из коллективной функции после завершения пе- − с сохранением результата в адресном пространстве
редачи в каждом процессе происходит независимо от за- одного процесса с помощью функции MPI_Reduce;
вершения передачи в других процессах; − с рассылкой результата всем процессам с помощью
− количество и объем принимаемых сообщений должно функции MPI_Allreduce;
быть равно количеству и объему отправляемых сообще- − совмещенная операция Reduce/Scatter с помощью
ний; функции MPI_Reduce_scatter;
− типы элементов отправляемых и принимаемых сообще- − префиксная редукция с помощью функции
ний должны совпадать или быть совместимы; MPI_Scan.
− сообщения не имеют идентификаторов. Все коммуникационные функции, за исключением
К функциям коллективного взаимодействия относятся: MPI_Bcast, представлены в двух вариантах:
− синхронизация всех процессов одного коммуникатора с 1. Простой вариант, когда все части передаваемого сообще-
помощью функции MPI_Barrier; ния имеют одинаковую длину и занимают смежные об-
− коллективные действия, в число которых входят: ласти в адресном пространстве процессов.
− отправка информации от одного процесса всем ос- 2. «Векторный» вариант, который предоставляет более ши-
тальным членам некоторой области связи с помо- рокие возможности по организации коллективных ком-
щью функции MPI_Bcast; муникаций, снимая ограничения, присущие простому ва-
− сборка (англ. Gather) распределенного по процессам рианту, как в части длин блоков, так и в части
массива в один массив с сохранением его в адресном размещения данных в адресном пространстве процессов.
пространстве выделенного (англ. Root) процесса с Векторные варианты функций имеют постфикс «v» в
помощью функций MPI_Gather и MPI_Gatherv; конце имени функции.
− сборка распределенного массива в один массив с
рассылкой его всем процессам некоторой области 4.9. Типы данных
связи с помощью функций MPI_Allgather и Сообщение – это некая порция информации, которая пере-
MPI_Allgatherv; дается от одного процесса другому. Помимо пересылаемой
− разбиение массива и рассылка его фрагментов (англ. информации сообщение содержит служебные данные, необхо-
Scatter) всем процессам области связи с помощью димые для обеспечения передачи сообщения адресату. Служеб-
функций MPI_Scatter и MPI_Scatterv; ная информация включает идентификатор процесса отправителя
− совмещенная операция Scatter/Gather (All-to-All), сообщения и этот идентификатор называется рангом процесса.
каждый процесс делит данные из своего буфера пе- Ранг процесса это целое неотрицательное число, значение его
редачи и разбрасывает фрагменты всем остальным назначается системой от нуля и выше. Любой запущенный
процессам, одновременно собирая фрагменты, по- процесс может с помощью процедуры MPI определить свой
сланные другими процессами в свой буфер приема с ранг. Далее идет адрес, по которому размещаются данные, пере-
помощью функций MPI_Alltoall и MPI_Alltoallv. сылаемые отправителем – адрес буфера отправления сообщения.
− глобальные вычислительные операции (sum, min, max Необходимо указывать тип пересылаемых данных.
и др.) над данными, расположенными в адресных про- Особенностью MPI является то, что в сообщении пересы-
странствах различных процессов: лаются, как правило, однотипные данные. Необходимо указы-
вать количество данных – это размер буфера сообщения. При-

105 106
нимающий процесс должен зарезервировать необходимое коли- Не имеет смысла подробно останавливаться на предопреде-
чество памяти для приема. Необходимо указывать идентифика- ленных типах MPI или разбирать соответствующие аналоги в
тор процесса, который должен получить сообщение. Также Си. Далее речь пойдет о методах работы с пользовательскими
указывается адрес, по которому должны быть размещены пере- типами данных, о построении карты типа, регистрации и анну-
даваемые данные у получателя. И, наконец, указывается иден- лировании и будет приведено несколько примеров использова-
тификатор коммуникатора, в рамках которого выполняется ния пользовательских типов в несложных MPI программах.
пересылка сообщения. Также будут затронуты вопросы упаковки и распаковки данных.
Данные, содержащиеся в сообщении, в общем случае орга- Как уже говорилось, сообщение в MPI представляет собой
низованы в массив элементов, каждый из которых имеет один и массив однотипных данных, элементы которого расположены в
тот же тип. Кроме пересылки данных система передачи сообще- последовательных ячейках памяти. Такая структура не всегда
ний поддерживает пересылку информации о состоянии процес- удобна, поскольку часто приходится работать с массивами и
сов коммуникации. Это может быть, например, уведомление о пересылать, например, не целиком массив, а его фрагмент, либо
том, что прием данных, отправленных другим процессом, за- подмножество его элементов. Расположение элементов в масси-
вершен. Вообще говоря, из-за присутствия разнородных форма- вах иногда может не укладываться в такую модель организации
тов хранения данных на узлах гетерогенной системы для того, пересылки данных. Примером может служить пересылка струк-
чтобы между нитями был возможен обмен сообщениями, дан- тур данных, содержащих разнотипные элементы. В MPI-
ные могут кодироваться, могут упаковываться с помощью неко- программе для пересылки данных, расположенных не последо-
торого универсального алгоритма. вательно, или для пересылки неоднородных данных придется
В MPI принята своя унифицированная схема обозначения создать производный тип данных.
типов данных. В таблице 6 представлено соответствие между В качестве примера, иллюстрирующего подобную пробле-
типами данных MPI и типами данных Си. му, можно рассмотреть следующую ситуацию. В численных
Таблица 6. расчетах часто приходится иметь дело с матрицами. В языке Си
используется линейная модель памяти, в которой матрица хра-
Константы MPI Тип данных Си
нится в виде последовательно расположенных строк. Так, на-
MPI_CHAR signed char
пример, для параллельного умножения матриц требуются пере-
MPI_SHORT signed short int сылки столбцов матрицы, а в линейной модели Си элементы
MPI_INT signed int столбцов расположены в оперативной памяти не непрерывно, а с
MPI_LONG signed long int промежутками.
MPI_UNSIGNED_CHAR unsigned char Решать проблему пересылки разнотипных данных или дан-
MPI_UNSIGNED_SHORT unsigned short int ных, расположенных не в последовательных ячейках памяти,
MPI_UNSIGNED unsigned int можно разными средствами. Можно «уложить» элементы ис-
MPI_UNSIGNED_LONG unsigned long int ходного массива во вспомогательный массив так, чтобы данные
MPI_FLOAT float располагались непрерывно. Это неудобно и требует дополни-
MPI_DOUBLE double тельных затрат памяти и процессорного времени. Можно раз-
MPI_LONG_DOUBLE long double личные элементы данных пересылать по отдельности. Это мед-
MPI_BYTE нет соответствия ленный и неудобный способ обмена. Более эффективным
MPI_PACKED нет соответствия решением является использование производных типов данных.

107 108
Производные типы данных создаются во время выполнения Таблица 7.
программы (а не во время ее трансляции), как правило, перед их 1-й элемент пары 2-й элемент пары
использованием. Базовый тип 0 Смещение базового типа 0
Создание типа – последовательный процесс, состоящий из Базовый тип 1 Смещение базового типа 1
двух шагов: Базовый тип 2 Смещение базового типа 2
1. Конструирование типа (создание его структуры). … …
2. Регистрация типа (после регистрации типа его можно ис-
Базовый тип n-1 Смещение базового типа n-1
пользовать наряду со стандартными типами MPI).
После завершения работы с производным типом, он анну-
Наиболее общим конструктором типов в MPI является
лируется. При этом все производные от него типы остаются и
функция MPI_Type_struct – конструктор структурного типа. В
могут использоваться дальше, пока и они, в свою очередь, не
случае использования этой функции программист может ис-
будут уничтожены.
пользовать полное описание каждого элемента типа. Если пере-
Производные типы данных создаются из базовых типов с
сылаемые данные содержат подмножество элементов массива,
помощью функций-конструкторов.
такая детальная информация не нужна, поскольку у всех элемен-
Производный тип данных в MPI характеризуется последо-
тов массива один и тот же базовый тип. MPI предлагает три
вательностью базовых типов и набором целочисленных значе-
конструктора, которые можно использовать в такой ситуации:
ний смещения.
MPI_Type_сontiguous, MPI_Type_vector и MPI_Type_indexed.
Смещения отсчитываются относительно начала буфера об-
Первый из них создает производный тип, элементы которо-
мена и определяют те элементы данных, которые будут участво-
го являются непрерывно расположенными элементами массива.
вать в обмене. Не требуется, чтобы они были упорядочены (по
Второй создает тип, элементы которого расположены на одина-
возрастанию или по убыванию). Отсюда следует, что порядок
ковых расстояниях друг от друга. Третий создает тип, содержа-
элементов данных в производном типе может отличаться от
щий произвольные элементы.
исходного, кроме того, один элемент данных может появляться в
Рассмотрим далее интерфейс конструктора
новом типе многократно.
MPI_Type_vector.
Последовательность пар (базовый тип, смещение) называет-
ся картой типа. int MPI_Type_vector (
В таблице 7 представлена карта некоторого типа. В карте int count,
содержится n пар, значит, пользовательский тип содержит n int blocklen,
элементов, каждый элемент задается своим базовым типом. int stride,
Базовый тип соответствует либо стандартному предопределен- MPI_Datatype oldtype,
ному типу MPI, либо другому пользовательскому типу. MPI_Datatype *newtype
Второй компонент пары – это смещение относительно нача- );
ла буфера. Параметры функции:
Расстояние задается в количестве ячеек между началами − count – неотрицательное целое значение, задающее коли-
элементов в последовательности. Таким образом, допускается чество блоков данных в новом типе;
ситуация, когда элементы располагаются с разрывами, с некото- − blocklen – задает длину каждого блока в количестве эле-
рым расстоянием между собой, они могут и пересекаться. ментов (неотрицательное целое значение);

109 110
− stride – определяет количество элементов, расположен- Функция MPI_Type_struct является конструктором струк-
ных между началом предыдущего и началом следующего турного типа. Она позволяет создать тип, содержащий элементы
блока; различных базовых типов.
− oldtype – базовый тип. Базовые типы могут быть разными. Задавая их расположе-
Таким образом, входные параметры позволяют дать исчер- ние, можно сконструировать структурный тип.
пывающее описание того, каким образом должна быть произве- int MPI_Type_indexed (
дена выборка элементов базового типа. Выходным параметром int count,
является идентификатор нового типа newtype. Этот идентифика- int blocklens[],
тор назначается программистом. Исходные данные здесь одно- int indices[],
типные. MPI_Datatype oldtype,
MPI_Datatype *newtype
int MPI_Type_hvector (
);
int count,
int blocklen, Параметры функции:
MPI_Aint stride, − count – количество блоков, одновременно длина массивов
MPI_Datatype oldtype, indices и blocklens;
MPI_Datatype *newtype
− blocklens – количество элементов в каждом блоке;
);
− indices – смещение каждого блока, которое задается в ко-
Конструктор MPI_Type_hvector представляет собой некото- личестве ячеек базового типа (целочисленный массив);
рую модификацию предыдущего, в котором смысл параметров − oldtype – базовый тип;
сохраняется, а смещения задаются в байтах. − newtype – идентификатор производного типа.
int MPI_Type_struct ( Функция MPI_Type_indexed является конструктором индек-
int count, сированного типа. При создании индексированного типа блоки
int blocklengths[], располагаются по адресам с разным смещением и его можно
MPI_Aint indices[], считать обобщением векторного типа.
MPI_Datatype oldtypes[], Функция MPI_Type_hindexed также является конструктором
MPI_Datatype *newtype индексированного типа, однако смещения indices задаются в
); байтах.
Параметры функции: int MPI_Type_hindexed (
− count – задает количество элементов в производном типе, int count,
а также длину массивов oldtypes, indices и blocklengths; int blocklens[],
− blocklengths – количество элементов в каждом блоке; MPI_Aint indices[],
− indices – смещение каждого блока в байтах; MPI_Datatype oldtype,
MPI_Datatype *newtype
− oldtypes – тип элементов в каждом блоке;
);
− newtype – идентификатор производного типа.
Функция MPI_Type_contiguous используется для создания
типа данных с непрерывным расположением элементов.

111 112
int MPI_Type_contiguous ( int MPI_Type_create_subarray (
int count, int ndims,
MPI_Datatype oldtype, int *sizes,
MPI_Datatype *newtype int *subsizes,
); int *starts,
int order,
Параметры функции:
MPI_Datatype oldtype,
− count – счетчик повторений; MPI_Datatype *newtype
− oldtype – базовый тип; );
− newtype – идентификатор нового типа.
Функция MPI_Type_contiguous фактически создает описа- Параметры функции:
ние массива. − ndims – размерность массива;
Пример использования производного типа: − sizes – количество элементов типа oldtype в каждом изме-
рении полного массива;
MPI_Datatype a; − subsizes – количество элементов типа oldtype в каждом
float b[10];
измерении подмассива;

MPI_Type_contiguous (10, MPI_REAL, &a);
− starts – стартовые координаты подмассива в каждом из-
MPI_Type_commit (&a);
мерении;
MPI_Send (b, 1, a,…); − order – флаг, задающий переупорядочение;
MPI_Type_free (&a); − oldtype – базовый тип.
Конструктор MPI_Type_create_subarray используется, когда
Выше приведен фрагмент раздела описаний, в котором со- надо выбирать подмножества, например, при работе с много-
держится описание идентификатора нового типа MPI_Datatype и мерными решетками или при выборе гиперплоскостей.
пересылаемого массива b[10]. Работа с пользовательским типом Ограничимся рассмотренными конструкторами пользова-
выглядит следующим образом: тельских типов и обратимся к функциям их регистрации и уда-
1. Вызывается конструктор, который позволяет создать ления. С помощью вызова функции MPI_Type_commit производ-
описание нового типа. Он создает тип a – однородный ный тип datatype, сконструированный программистом,
тип, состоящий из некоторого количества последователь- регистрируется. После этого он может использоваться в опера-
но расположенных элементов типа MPI_REAL. циях обмена:
2. Конструктор обязательно должен быть зарегистрирован,
и регистрация выполняется функцией MPI_Type_commit с int MPI_Type_commit (
единственным параметром идентификатором нового ти- MPI_Datatype *datatype
па. После этого новый тип можно использовать в опера- );
циях пересылки. Аннулировать производный тип datatype можно с помощью
3. В конце вызывается деструктор MPI_Type_free, который вызова функции MPI_Type_free:
аннулирует пользовательский тип.
Тип данных, соответствующий подмассиву многомерного int MPI_Type_free (
MPI_Datatype *datatype
массива, можно создать с помощью функции
);
MPI_Type_create_subarray:

113 114
Следует напомнить, что предопределенные (базовые) типы int *integers,
данных не могут быть аннулированы. MPI_Aint *addresses,
Существует некоторое количество вспомогательных функ- MPI_Datatype *datatypes
ций, которые могут использоваться при работе с пользователь- );
скими типами. В качестве примера можно привести Параметры функции:
MPI_Type_size, позволяющую определить размер того или иного − datatype – идентификатор типа;
типа в байтах, т.е. определить объем памяти, который занимает − max_integers – количество элементов в массиве integers;
один элемент данного типа.
− max_addresses – количество элементов в массиве
int MPI_Type_size ( addresses;
MPI_Datatype datatype, − max_datatypes – количество элементов в массиве
int *size datatypes;
); − integers – содержит целочисленные аргументы, использо-
Количество элементов данных в одном объекте типа ванные при конструировании указанного типа;
datatype (его экстент) можно определить с помощью вызова − addresses – содержит аргументы address, использованные
функции MPI_Type_extent. при конструировании указанного типа;
− datatypes – содержит аргументы datatype, использованные
int MPI_Type_extent (
MPI_Datatype datatype,
при конструировании указанного типа.
MPI_Aint *extent
Приведенная ниже программа демонстрирует процесс соз-
); дания и использования структурного типа.

Смещения могут даваться относительно базового адреса, #include <stdio.h>


#include "mpi.h"
значение которого содержится в константе MPI_BOTTOM.
Адрес (англ. address) по заданному положению (англ.
struct newtype {
location) можно определить с помощью функции MPI_Address.
float a;
int MPI_Address ( float b;
void *location, int n;
MPI_Aint *address };
);
int main (int argc, char *argv[])
С помощью функции MPI_Type_get_contents можно опреде- {
лить фактические параметры, использованные при создании int myrank;
производного типа. MPI_Datatype NEW_MESSAGE_TYPE;
int MPI_Type_get_contents ( int block_lengths[3];
MPI_Datatype datatype, MPI_Aint displacements[3];
int max_integers, MPI_Aint addresses[4];
int max_addresses, MPI_Datatype typelist[3];
int max_datatypes, int blocks_number;

115 116
struct newtype indata; status.MPI_ERROR);
int tag; }
MPI_Status status; MPI_Type_free (&NEW_MESSAGE_TYPE);
MPI_Finalize ();
MPI_Init (&argc, &argv); return (0);
MPI_Comm_rank (MPI_COMM_WORLD, &myrank); }
tag = 0;
В программе задаются типы членов производного типа. За-
typelist[0] = MPI_FLOAT;
typelist[1] = MPI_FLOAT;
тем количество элементов каждого типа. Вычисляются адреса
typelist[2] = MPI_INT; членов типа indata и определяются смещения трех членов произ-
block_lengths[0] = водного типа относительно адреса первого, для которого смеще-
block_lengths[1] = ние равно нулю. Располагая этой информацией, можно опреде-
block_lengths[2] = 1; лить производный тип, что и делается с помощью функций
MPI_Address(&indata, &addresses[0]); MPI_Type_struct и MPI_Type_commit. Созданный таким образом
MPI_Address(&(indata.a), &addresses[1]); производный тип можно использовать в любых операциях обме-
MPI_Address(&(indata.b), &addresses[2]); на. Тип MPI_Aint представляет собой скалярный тип, длина
MPI_Address(&(indata.n), &addresses[3]); которого имеет размер, одинаковый с указателем.
displacements[0] = addresses[1] - addresses[0]; Функции упаковки и распаковки данных MPI_Pack и
displacements[1] = addresses[2] - addresses[0]; MPI_Unpack являются альтернативой пользовательских типов.
displacements[2] = addresses[3] - addresses[0]; Функция MPI_Pack позволяет явным образом хранить произ-
blocks_number = 3; вольные (в том числе и расположенные не в последовательных
MPI_Type_struct (blocks_number, block_lengths, ячейках) данные в непрерывной области памяти (буфере переда-
displacements, typelist, &NEW_MESSAGE_TYPE); чи). Функцию MPI_Unpack используют для копирования данных
MPI_Type_commit (&NEW_MESSAGE_TYPE);
из буфера приема в произвольные (в том числе и не располо-
if (myrank == 0)
женные непрерывно) ячейки памяти. Упаковка и распаковка
{
используется для решения таких задач как обеспечение совмес-
indata.a = 3.14159;
indata.b = 2.71828;
тимости с другими библиотеками обмена сообщениями, для
indata.n = 2009;
приема сообщений по частям, для буферизации исходящих
MPI_Send (&indata, 1, NEW_MESSAGE_TYPE, 1, сообщений в пользовательское пространство памяти, что дает
tag, MPI_COMM_WORLD); независимость от системной политики буферизации.
printf ("Процесс %i отправил: %f %f %i\n", Сообщения передаются по коммуникационной сети, связы-
myrank, indata.a, indata.b, indata.n); вающей узлы вычислительной системы. Сеть работает медлен-
} но, поэтому, чем меньше в параллельной программе обменов,
else тем меньше потери на пересылку данных. С учетом этого поле-
{ зен механизм, который позволял бы вместо отправки трех раз-
MPI_Recv (&indata, 1, NEW_MESSAGE_TYPE, 0, ных значений тремя сообщениями, отправлять их все вместе.
tag, MPI_COMM_WORLD, &status); Такие механизмы есть: это параметр count в функциях обмена,
printf ("Процесс %i принял: %f %f %i, %d\n", производные типы данных и функции MPI_Pack и MPI_Unpack.
myrank, indata.a, indata.b, indata.n,

117 118
С помощью аргумента count в функциях MPI_Send, int MPI_Unpack (
MPI_Recv, MPI_Bcast и MPI_Reduce можно отправить в одном void *inbuf,
сообщении несколько однотипных элементов данных. Для этого int insize,
элементы данных должны находиться в непрерывно располо- int *position,
женных ячейках памяти. void *outbuf,
Если элементы данных – простые переменные, они могут не int outcount,
находиться в последовательных ячейках памяти. В этом случае MPI_Datatype datatype,
MPI_Comm comm.
можно использовать производные типы данных или упаковку.
);
Функция упаковки MPI_Pack может вызываться несколько
раз перед передачей сообщения, содержащего упакованные Параметры функции:
данные, а функция распаковки MPI_Unpack в этом случае также − inbuf – начальный адрес входного буфера;
будет вызываться несколько раз после выполнения приема. − insize – размер входного буфера в байтах;
Для извлечения каждой порции данных применяется новый − position – текущее положение в байтах;
вызов. − outcount – количество данных, которые должны быть рас-
При распаковке данных текущее положение указателя в бу- пакованы;
фере сохраняется. − datatype – тип каждого выходного элемента данных;
int MPI_Pack ( − comm – коммуникатор для упаковываемого сообщения;
void *inbuf, − outbuf – стартовый адрес выходного буфера.
int incount, Существуют некоторые вспомогательные функции, которые
MPI_Datatype datatype, облегчают работу с функциями MPI_Pack и MPI_Unpack. Важ-
void *outbuf, нейшей из таких функций является MPI_Pack_size, которая
int outcount, позволяет определить объем памяти size (в байтах), необходи-
int *position, мый для распаковки сообщения.
MPI_Comm comm.
); int MPI_Pack_size (
int incount,
Параметры функции: MPI_Datatype datatype,
− inbuf – начальный адрес входного буфера; MPI_Comm comm,
− incount – количество входных данных; int *size
− datatype – тип каждого входного элемента данных; );
− outcount – размер выходного буфера в байтах; Параметры функции:
− position – текущее положение в буфере в байтах; − incount – аргумент count, использованный при упаковке;
− comm – коммуникатор для упакованного сообщения; − datatype – тип упакованных данных;
− outbuf – стартовый адрес выходного буфера. − comm – коммуникатор.
При вызове функции упаковки incount элементов указанно- Приведенная ниже программа демонстрирует работу функ-
го типа выбираются из входного буфера и упаковываются в ций широковещательной рассылки сообщений, упаковки и
выходном буфере, начиная с положения position. распаковки данных.

119 120
#include <stdio.h> }
#include "mpi.h" MPI_Finalize ();
return (0);
int main (int argc, char *argv[]) }
{
В данной программе нулевой процесс копирует в буфер
int myrank;
float a, b;
значение a, затем дописывает туда b и n. После выполнения
int n; широковещательной рассылки главным процессом остальные
char buffer[100]; используют функцию MPI_Unpack для извлечения a, b, и n из
int position; буфера. При вызове функции MPI_Bcast необходимо использо-
вать тип данных MPI_PACKED.
MPI_Init (&argc, &argv); Типы данных MPI_PACKED и MPI_BYTE не имеют анало-
MPI_Comm_rank (MPI_COMM_WORLD, &myrank); гов в языке Си. Они позволяют хранить данные в «сыром» фор-
if (myrank == 0) мате, имеющем одинаковое двоичное представление на стороне
{ отправителя сообщения и на стороне получателя. Представление
printf ("Введите a, b и n\n"); символа может различаться на разных машинах, а байт везде
scanf ("%f %f %i", &a, &b, &n); одинаков.
position = 0; Тип MPI_BYTE, например, может использоваться, если не-
MPI_Pack (&a, 1, MPI_FLOAT, &buffer, обходимо выполнить преобразование между разными представ-
100, &position, MPI_COMM_WORLD); лениями в гетерогенной вычислительной системе.
MPI_Pack (&b, 1, MPI_FLOAT, &buffer,
100, &position, MPI_COMM_WORLD);
MPI_Pack (&n, 1, MPI_INT, &buffer,
100, &position, MPI_COMM_WORLD);
MPI_Bcast (&buffer, 100, MPI_PACKED, 0,
MPI_COMM_WORLD);
}
else
{
MPI_Bcast (&buffer, 100, MPI_PACKED, 0,
MPI_COMM_WORLD);
position = 0;
MPI_Unpack (&buffer, 100, &position, &a,
1, MPI_FLOAT, MPI_COMM_WORLD);
MPI_Unpack (&buffer, 100, &position, &b,
1, MPI_FLOAT, MPI_COMM_WORLD);
MPI_Unpack (&buffer, 100, &position, &n,
1, MPI_INT, MPI_COMM_WORLD);
printf ("Процесс %i принял a=%f, b=%f, n=%i\n",
myrank, a, b, n);

121 122
ЛИТЕРАТУРА
1. Антонов А.С. Введение в параллельные вычисления: ме-
тодическое пособие. – М.: Изд-во МГУ, 2002.
2. Антонов А.С. Параллельное программирование с исполь-
зованием технологии MPI: учебное пособие. – М.: Изд-во
МГУ, 2004.
3. Букатов А.А., Дацюк В.Н., Жегуло А.И. Программирова-
ние многопроцессорных вычислительных систем. – Рос-
тов-на-Дону: Изд-во ООО «ЦВВР», 2004.
4. Дацюк В.Н., Букатов А.А., Жегуло А.И. Многопроцес-
сорные системы и параллельное программирование: ме-
тодическое пособие. – Ростов-на-Дону: Изд-во РГУ, 2003.
5. Шпаковский Г.И., Серикова Н.В., Липницкий А.С., Вер-
хотуров А.Е., Гришанович А.Н., Орлов А.В. Стандарт
MPI: A Message-Passing Interface Standart, June 12, 1995. –
Минск: Изд-во БГУ, 2004.
6. Шпаковский Г.И., Серикова Н.В., Липницкий А.С., Вер-
хотуров А.Е., Гришанович А.Н., Орлов А.В. Стандарт
MPI-2: Extension to the Message-Passing Interface, July 18,
1997. – Минск: Изд-во БГУ, 2004.
7. Amdahl G. Validity of the single-processor approach to
achieving large-scale computing capabilities. – AFIPS Conf.,
Введение в параллельные вычисления.
AFIPS Press, 1967.
Основы программирования на языке Си
8. Foster I. Designing and Building Parallel Programs. //
с использованием интерфейса MPI.
http://www.hensa.ac.uk/parallel/books/addison-wesley/dbpp –
Научное издание
1995.
В печать от 14.04.2009
Формат бумаги 60x84/16.Уч.-изд. л. 5,1
Тираж 100. Заказ 19
117997, Москва, Профсоюзная, 65
Учреждение Российской академии наук
Институт проблем управления
им. В.А. Трапезникова РАН

123

Вам также может понравиться