Параллельное программирование среди других парадигм программирования

№1(31)2011

Н. В. Шилов, Л. В. Городняя, А. Г. Марчук

Параллельное программирование среди других парадигм программирования1

В данном материале авторы представляют свою точку зрения на определение парадигмы параллельного программирования с учетом мнений, высказанных в ходе дискуссионнго обсуждения участниками конференции «Научный сервис в сети Интернет: суперкомпьютерные центры и задачи» (22-25 сентября 2010 г., Абрау-Дюрсо).

Введение

Одним из «культовых» трудов по философии и методологии науки является диссертация Томаса Куна «Структура научных революций», которая была защищена около 40 лет назад [5]. По Т. Куну, парадигма — это метод, подход к формулировке задач (проблем) и путей их решения. Само слово «парадигма» греческого происхождения и означает «пример», «образец», а в общефилософском смысле — категория, состоящая из сущностей с общими характеристиками. Первым понятие «парадигма программирования» ввел в употребление Роберт Флойд в 1978 г. в лекции по случаю присуждения премии им. Тьюринга [7]. В этой лекции он ссылается только на известную диссертацию Т. Куна и трактует «парадигмы программирования» именно как разные способы постановки и решения программистских задач, но подчеркивает, что концептуально такие «разные способы» обычно фиксируются на уровне языков программирования2. Многообразие

1 Работа выполнена при поддержке гранта РФФИ 08-01-00899-а.

2 Следует иметь в виду, что конкретный язык программирования может допускать несколько разных «стилей программирования» [6]. Можно использовать функциональный стиль программирования, например, при программировании на объектно-ориентированном языке Java. По нашему мнению, «стиль» в таком случае — это возможность естественным образом моделировать одну парадигму средствами другой.

компьютерных языков является распределенной базой научного знания о парадигмах, опыте и истории применения информационных технологий. Для усвоения студентами этого знания необходимо иметь разумную систему классификации компьютерных языков и парадигм с современной технической поддержкой. Подход к созданию такой классификации, основанный на разработке интернет-портала знаний о компьютерных языках, описан в работе [1].

По мнению авторов статьи, Р. Флойд в своей лекции явно обозначил задачу выделения и классификации парадигм программирования как чрезвычайно важную философско-методологическую и научно-практическую проблему. Однако эта проблема возникла отнюдь не одновременно с появлением программирования в начале 1950-х годов, а стала актуальной только к середине 1970-х годов, когда наряду с языками последовательного императивного программирования (низкого и высокого уровней) были разработаны языки параллельного и декларативного программирования (прежде всего функционального). Актуальность проблемы возросла в начале 1980-х годов, когда появилось несколько новых «программирований» — в том числе логическое.

Актуальность вопроса

Для авторов данной статьи очевидно, что перечисленные выше «программирования» — это (в соответствии с Т. Куном

№1(31)2011

и Р. Флойдом) исторически сложившиеся парадигмы программирования, т.е. разные способы постановки и решения задач. Однако необходимо более четко определить эти способы, чтобы можно было их сравнивать и совместно использовать для решения программистских задач.

Поэтому на примере факториала мы конспективно3 познакомимся с тремя «классическими» парадигмами (императивной, функциональной и логической), с основными понятиями параллельного программирования, в качестве примера рассмотрим головоломку о параллельном жаренье бифштексов, обсудим варианты параллельного вычисления факториала, предложим определение парадигмы «параллельного программирования». Для представления алгоритмов будем использовать соответствующий «псевдокод», т.е. формат, ориентированный на человека, а не код на каком-либо конкретном языке программирования.

Поводом для написания этой работы послужили статьи [3] и[4],в которых описана функциональная парадигма программирования с примерами функциональных программ на языке Haskell. Однако в данных статьях не был охвачен ряд принципиальных вопросов: что такое «парадигмы программирования», «функциональная парадигма» в частности, зачем нужны разные парадигмы программирования, какие из них стали классическими, а какие появились сравнительно недавно и наиболее актуальны на сегодняшний день?

Эти вопросы очень важны для развития программирования, они требуют понимания среди широкого круга людей, вовлеченных в программирование, — от школьников, интересующихся этой областью, до программистов-профессионалов и руководителей IT-проектов. Именно актуальности обсуждения перечисленных вопросов был посвящен доклад [8] одного из соавторов настоящей статьи на IV Международной научно-прак-

3 Более подробно данный материал представлен в статьях [9] и [10], атакже в монографии [6].

а«

тической конференции «Современные ин- ? формационные технологии и ИТ-образова- Ц ние» в 2009 г. Также в журнале «Потенци- ^ ал» была опубликована статья [9], в которой прописаны классические парадигмы програм- § мирования (императивная, функциональная ^ и логическая). В статье [10] продемонстри- со ровано, как могут взаимодействовать раз- ^ ные парадигмы программирования при ре- § шении трудной олимпиадной задачи по про- Ц граммированию.

Настоящая статья продолжает эту серию публикаций и посвящена новой, активно развивающейся парадигме программирования — параллельному программированию. Поводом к ее написанию послужили неформальная дискуссия «Параллельное программирование среди других парадигм программирования», организованная авторами данной статьи 23 сентября 2010 г. на конференции «Научный сервис в сети Интернет: суперкомпьютерные центры и задачи», и материалы круглого стола этой конференции. Во время работы круглого стола (24 сентября) А. В. Климов и В. В. Корнеев вынесли вопрос о параллельной парадигме программирования на общее обсуждение, а другие участники — проблему популяризации параллельного программирования.

С точки зрения А. В. Климова, «парадигма программирования» — расплывчатое понятие, поэтому целесообразно говорить о «моделях вычислений» и «стилях программирования». Модели вычислений — это хорошо формализованные математические объекты, например, РАМ-машина (императивное программирование), А-термы (функциональное программирование), хорновы дизъюнкты (логическое программирование), исчисления взаимодействующих процессов А. Хоара и Р. Милнера (параллельное программирование). Стиль программирования— способ мышления, наиболее характерный при разработке алгоритмов в той или иной модели вычислений. Соотвествен-но язык программирования может поддерживать несколько разных стилей программирования и быть открытым для них.

№1(31)2011

Позиция В. В. Корнеева сводилась к тому, что на данный момент существуют только три парадигмы программирования, поддерживаемые аппаратно: последовательное и параллельное программирование; распределенные вычисления. Последовательное программирование — это реализация алгоритмов на вариантах классической вычислительной машины с одним одноядерным процессором. Язык программирования при этом может быть разным (т.е. императивным, функциональным, логическим, «параллельным»), но исполнение программного кода все ровно будет единичным последовательным императивным процессом. Параллельное программирование — эффективная реализация мультитредовых алгоритмов на многоядерном процессоре. В этом случае язык программирования должен явно использовать команды порождения и слияния потоков. Наиболее перспективным вариантом является использование «микротредов» — коротких по времени исполнения потоков, ими планировщик хорошо загружает все ядра процессора. Распределенные =§ вычисления — это вычисления на компью-§ терных кластерах, в которых все компью-|| теры работают по одной и той же (последо-1 вательной или параллельной) программе, <5 но каждый со своими данными, которыми изредка обмениваются между собой.

| Вычисление факториала как пример | многообразия подходов к построению алгоритма

ч

§ «Императивный» факториал

и

| Слово «императивный» образовано

§ от латинского «командный». Императивная

Л парадигма программирования известна благодаря Дж. Бэкусу как «фон-Неймановский

о стиль программирования» [2]. Императивная

Ц; постановка задачи: факториал натурально-

§ го числа N > 0 — произведение всех нату-

| ральных чисел от 1 до N. Такая постановка

<| уже сама по себе неявно дает команду «вы-

¿2 числить произведение». Императивная про-

грамма состоит из команд (операторов, изменяющих содержание ячеек памяти). Для вычисления факториала может быть задана следующая последовательность команд.

Листинг 1

1 var k, n, f: integer;

2 input (n);

3 f: = 1;

4 for k: = 1 to n do

5 f: = f*k;

6 output (f);

7 end.

Императивное вычисление — это преобразование состояний памяти в соответствии с командами программы. Запуск императивной программы осуществляется в порядке размещения ее команд в памяти и их последовательного исполнения, начиная с первой (команды с номером 1). К примеру, вычисление факториала числа начинается с исполнения команды с номером 1, которая выделила три ячейки памяти под переменные к, подготовила передачу управления команде со следующим по порядку номером 2. Во второй момент времени выполняется команда с номером 2; в результате введенное значение (например, 3) заносится в ячейку, зарезервированную для п, и таким образом подготовлена передача управления команде со следующим по порядку номером 3. В последний момент работы программы исполняется команда с номером 7, которая освобождает всю память.

«Функциональный» факториал

В функциональном программировании постановка задачи — это система функциональных уравнений, которым удовлетворяет нужная нам функция; функциональная программа — упорядоченная система функциональных равенств, позволяющая «направленно» вычислить значения данной функции в необходимых «точках» (т.е. при нужных значениях аргументов, на фактических параметрах, передаваемых по значению); вычисление — это цепочка равенств, где каждое следующее получается из предыдуще-

№1(31)2011

го в результате однократного применения функции к аргументу (или аргументам). В частности, функциональная постановка задачи про факториал выглядит следующим образом: факториал — функция F, которая каждому натуральному числу сопоставляет натуральное число и удовлетворяет тождеству

F(x) = если х—1 то 1 иначе x*F(x-l) и поэтому является решением функционального уравнения

F(x) = if x=1 then 1 else x*F(x-l).

Функциональный псевдокод состоит из одного определения функции F, но включает два случая (или «кпаузы»): F : 1 = 1; x = x*F(x -1).

Такая программа «направляет» вычисления значений функции на фактических параметрах в соответствии с порядком кпауз: сначала нужно отождествить с фактическими параметрами аргументы первой кпаузы, и если отождествление получилось, перейти к вычислению значения правой части этой кпаузы. В противном случае — перейти ко второй кпаузе и применить ту же стратегию и так до последней кпаузы. Приведенная программа для факториала означает, что если константа 1 может быть отождествлена с конкретным значением аргумента функции F (т. е. аргумент равен 1), то значение функции F на этом аргументе равно 1; в противном случае следует перейти ко второй клаузе. Так как аргумент данной клаузы — формальный параметр х, то он может быть отождествлен с любым значением фактического параметра, и, следовательно, функция F на этом аргументе равна значению аргумента, умноженному на значение функции F на декременте аргумента.

В общем случае, когда определение функции состоит из трех или более кпауз, или аргумент имеет более сложный вид, чем константа или переменная, оно означает выбор в порядке перечисления первой клаузы, у которой выражение, стоящее слева от знака «=», может быть отождествлено со значения-

1«

ми фактических параметров. Никаких ячеек ?

памяти для хранения значений в функцио- Ц

нальном программировании нет, но есть па- ^

мять (или «база клауз»), где хранится сама се

функциональная программа. Вызов функцио- §

нальной программы состоит в применении ^

одной из функций программы к конкретному со

значению аргумента. Далее приведем при- ^

мер вычисления факториала числа 3 в соот- §

ветствии с представленной программой. Ц

Листинг 2 2

F (3) = (так как 1 неотождествима с 3,

но х отождествим сЗ) =3*F (3-1) = 3

* F (2) =

= (так как 1 неотождествима с 2, но X

отождествим с2) =3* (2*F (2-1) = 3

* (2 * F (1)) = (так как 1 отождествимо

cl) =3* (2*1) =3*2 = 6.

«Логический» факториал

В логическом программировании постановка задачи — аксиоматизация отношения между аргументами и результатами; функциональная программа — это упорядочение допустимых аксиом и правил вывода, позволяющая «направленно» строить доказательства; вычисление — это поиск доказательства при нужных значениях аргументов. Например, логическое определение факториала может состоять из двух следующих аксиом, описывающих свойства отношения Р(т, л) = = «число т является факториалом числа л»:

Р( 1,1);

Р (min, л-1) ^ Р (т, л).

Первая из этих аксиом означает, что «число 1 является факториалом числа 1», а вторая — «если частное от деления нацело т на л — факториал числа (л -1), то само число т — факториал числа л». Логический псевдокод состоит из одного факта и одного правила:

Р( 1,1);

Р (т, л) :: Р (т/п, л-1).

Как и в функциональном программировании, никаких ячеек памяти для хранения значений в логическом программировании нет, но есть память (или «база знаний»), где хранится сама логическая программа. Вызов ло-

№1(31)2011

гической программы — один из предикатов этой программы, в котором подставлены фактические значения некоторых аргументов. Их предстоит определить так, чтобы при подстановке получилось утверждение, доказуемое в аксиоматике, соответствующее данной логической программе. Унификация двух выражений в логическом программировании — подбор значений переменных в этих выражениях, при котором оба выражения синтаксически совпадут. Вычисление по логической программе — поиск «кратчайшего» доказательства с использованием унификации.

Например, вычисление факториала числа 3 выглядит следующим образом:

• для доказательства Р(т, 3) необходимо доказать, что Р((т/3), 3-1), так как аргументы Р{т, 3) невозможно унифицировать с аргументами факта Р( 1, 1);

• доказательство Р((т/3), 3-1) совпадает с доказательством Р((т/3), 2), поскольку (3-1) есть 2;

• для доказательства Р((т/3), 2) нужно доказать, что Р{{{т/3)/2), 2-1), так как аргументы Р((т, 3), 2) невозможно унифицировать с аргументами факта Р( 1, 1);

• доказательство Р{{{т/3)/2), 2-1) совпадает с доказательством Р{{{т/3)/2), 1), так как(2-1) есть 1;

• поскольку аргументы Р{{{т/3)/2), 1) возможно унифицировать с аргументами факта Р(1, 1) посредством подстановки числа 6 в качестве значения переменной4 т, то таким образом доказано Р{6, 3), т. е. «число 6 является факториалом числа 3».

О параллельном жаренье бифштексов

В параллельном программировании постановка задачи и ее решение предполагают, что несколько вычислительных потоков или/и процессов могут работать одновременно («параллельно») с целью получить ускорение за счет использования имеющихся вычис-

4 Следует отметить, что число 6 — не единственная возможная подстановка, так как деление происходит нацело. Унификатором могли бы быть числа 7и8.

лительных ресурсов. Здесь «ускорение» — это «во сколько раз быстрее», а не «скорость прирастания скорости» в физике.

Далее обратимся к известной головоломке о бифштексах. На сковородке помещаются два бифштекса. Чтобы приготовить бифштекс, его нужно поджарить с двух сторон, а поджаривание каждого бифштекса с одной стороны занимает 5 мин. Сколько потребуется времени для того, чтобы приготовить три бифштекса?

Первое решение: по очереди готовить все три бифштекса; тогда на приготовление каждого потребуется 10 мин (так как его следует обжарить с двух сторон), а трех бифштексов — 30 мин.

Второе решение: сначала готовят одновременно (т.е. параллельно) первую пару бифштексов (на сковородке помещаются два бифштекса), а потом отдельно — третий бифштекс; такой вариант займет 20 мин.

Третье решение: предварительно дадим бифштексам имена «А», «Б» и «С», а их сторонам присвоим индексы «1» и «2»; будем считать, что эта операция не заняла много времени, но в результате мы можем говорить, например, о стороне «Б2», т.е. о второй стороне второго бифштекса. Далее алгоритм представлен в виде листинга 3.

Листинг 3

1: поджарим одновременно «А1» и «Б1»;

2: поджарим одновременно «Б2» и «С1»;

3: поджарим одновременно «С2» и «А2».

С помощью этого алгоритма можно приготовить три бифштекса за 15 мин., следовательно, достигнуто ускорение по сравнению с первым решением 2 (=30/15), а по сравнению со вторым решением — примерно 1,33 (= 20/15).

Потоки на двухъядерной сковородке

Поток или тред5 — это последовательность команд, которая «исполняется» вычислительным устройством «одновременно» с

5 ТЛгеа^—отангл.«нить».

№1(31)2011

несколькими другими потоками над общей памятью; слова «исполняется» и «одновременно» взяты в кавычки, так как у вычислительного устройства может не хватать средств для одновременного исполнения всех потоков, поскольку потоки приняты к исполнению, а вычислительное устройство поочередно по частям выполняет все действия, предусмотренные в принятых к исполнению потоках6. Частный случай многопоточного программирования — это императивное программирование, когда единственное вычислительное устройство занято одним потоком.

Как правило, многопотоковый алгоритм реализуется на «многоядерном процессоре» — вычислительном устройстве, состоящим из нескольких «ядер», т.е. обычных процессоров, работающих над общей памятью. Следует иметь в виду, что одно ядро нужно планировщику — алгоритму, который отвечает за загрузку других ядер (т. е. какому ядру, какой поток и когда следует выполнять).

В терминах многопоточного параллельного программирования головоломку о бифштексах можно интерпретировать так: сковородка — это двухъядерный процессор; бифштексы — три потока «А», «Б» и «С», каждый из них состоит из двух последовательных команд «поджарить со стороны 1» и «поджарить со стороны 2»; планировщик — человек, который составляет алгоритм приготовления бифштексов. Результат работы «умного» планировщика — алгоритм приготовления бифштексов на «двухъядер-ной» сковородке за 15 мин., а если с планировщиком не повезло, то приготовление занимает 20 или даже 30 мин.

Потоки могут разделяться и «сливаться», обычно для этого используют конструкцию fork-join7 и разделитель «II». Описанный

6 В таком случае говорят о «псевдопараллелизме», так как нет реального одновременного исполнения разных действий, или об «интерливинговом параллелизме» (калька с английского термина «interleave», означающего прокладывание между листами книги (например, гербария)).

7 Fork — от англ. «развилка»; join — от англ. «соединение».

трехпоточный алгоритм приготовления трех бифштексов представлен далее.

Листинг 4

begin fork

поджарить А1; поджарить А2 II

поджарить Б1; поджарить Б2 II

поджарить С1; поджарить С2

join end

Процессная интерпретация задачи

Процесс — программный шаблон (или матрица), с которого можно сделать сколько угодно программ-копий — экземпляров процесса8. Каждый процесс имеет имя и две числовые характеристики: число экземпляров этого процесса ¡ni, они создаются при запуске параллельной программы; максимальное число max, ограничивающее сверху число экземпляров процесса, которые могут существовать одновременно. Если число ¡ni не указано явно, то оно считается равным 1; если число max не указано явно, то считается, что одновременно могут существовать сколько угодно экземпляров этого процесса. Каждый экземпляр получает свой уникальный адрес или пин9 и собственную память (которая недоступна другим экземплярам ни того же, ни других процессов). Экземпляры процессов «общаются» между собой посредством обмена сообщениями: посылка сообщения происходит по пину (т.е. экземпляр-отправитель сообщения должен знать пин экземпляра-получателя) при помощи команды send10, а получение сообщений не требует пина (т. е. экземпляр-получатель сообщения не знает пина конкретного экземпляра-отправителя) и осуществляется при помощи команды receive11. Каждый сеанс связи состоит в том, чтобы экземпляр-

8 Можно образно представить, что процесс — это чертеж робота, а экземпляры процесса — роботы, изготовленные по данному чертежу.

9 Pin — process identification number — от англ. «идентификационный номер процесса».

10 Send—отангл. «послать» или «отправить».

11 Receive — отангл.«получить».

№1(31)2011

отправитель отправил сообщение, а экземпляр-получатель получил его, только одновременно, как бы во время рукопожатия или рандеву12. Если отправитель или получатель еще не готовы отправить или получить сообщение (не пришли на рандеву), то партнер ждет; но сразу после успешного рандеву оба экземпляра продолжают работу каждый по своей программе. Экземпляры процессов могут порождать новые экземпляры по именам процессов при помощи команды create13. Предполагается, что каждый экземпляр любого процесса имеет несколько специальных переменных для хранения пинов, среди которых обязательно есть переменные parent и offspring14 для пина экземпляра процесса, его породившего, и пина последнего порожденного им самим экземпляра.

Обычно процессный параллелизм реализуется в многопроцессорной вычислительной среде или кластере15 — совокупности процессоров или компьютеров, соединенных каналами для передачи сообщений, каждый из которых имеет свою собственную (локальную) память. Причем один из этих процессоров осуществляет функции диспетчера по выделению пинов для процессов и реализует алгоритм планировщика загрузки процессоров (когда, кому и какой процесс выполнять), а еще несколько процессоров — маршрутизаторы сообщений (какое сообщение и кому передать).

Вернемся к головоломке про бифштексы и дадим ей процессную интерпретацию. Теперь сковородка — это уже не двухъядер-ный процессор, а вычислительная среда, состоящая из двух отдельных процессоров; три бифштекса — три экземпляра одного процесса «бифштекс», который состоит из двух последовательных команд «поджарить со стороны 1» и «поджарить со сторо-

12 Rendez-vous — отфранц. «свидание».

13 Create — отангл. «создать».

14 Parent— от англ. «родитель» или «предок»; offspring — отангл. «отпрыск» или «потомок».

15 Cluster—отангл. «скопление». 126 j-

ны 2»; планировщик, как и прежде, — человек, составляющий алгоритм приготовления бифштексов. Псевдокод процесса «бифштекс» представлен далее.

Листинг 5

process бифштекс: ini=3, max=3;

поджарить сторону 1; поджарить сторону 2

end.

Как и в многопотоковом случае, «умный» планировщик справится с приготовлением трех бифштексов в «двухпроцессорной» сковородке за 15 мин.

Параллельное вычисление факториала

Факториал в несколько потоков

Рассмотрим простой многопоточный алгоритм (см. листинг 6) для вычисления факториала числа N как произведения двух чисел: одно из них — произведение всех четных чисел из диапазона [1 .../V], а другое — произведение всех нечетных чисел из этого же диапазона; произведения всех четных и нечетных чисел вычисляются и хранятся в разных переменных (е р и о_р соответственно); кроме того, используется функция odd16 проверки числа на нечетность.

Листинг 6

begin

var m, о_р, е_р, n, f, i, j: integer;

input (m); n: = m/2;

fork;

e_p: = 1; for i: =1 to n do e_p: = e_p*

(2*i) od;

II

o_p: = 1; for j: =1 to (n-1) do o_p: =

o_p* (2*j + 1) od;

if odd (m) then o_p: = o_p*m;

join;

f: = e_p*o_p; output

end.

В этом алгоритме команда begin инициирует первый поток исполнения, команда fork задает разделение данного пото-

16 Odd— отангл. «нечетный».

№1(31)2011

ка на два (второй и третий), а команда join снова сливает два потока в один (четвертый), который заканчивается командой end.

Описанный алгоритм при реализации на трехъядерном процессоре способен дать ускорение примерно в 2 раза по сравнению с рассмотренной ранее императивной программой вычисления факториала.

Три ядра могут быть использованы следующим образом. Первое ядро постоянно занято самим планировщиком. При запуске алгоритма планировщик выделяет второе ядро для первого потока (от begin до fork), затем — для второго потока (от fork до разделителя «II»), третье — для третьего потока (от разделителя «II» —до join), а в конце — второе ядро — для четвертого (от join до end). Если считать, что один оператор занимает одну единицу времени, которую условно можно назвать «тик» (соответствующую частоте ядра процессора), то первый потокзанимаетЗ тика, четвертый поток — 2 тика, а второй и третий потоки при вычислении факториала числа N > 0 займут (2 + 4-(N/2)) тиков каждый17 по отдельности и вместе (за счет одновременной работы). Нужно учесть 4 тика на накладные расходы планировщика (который работает каждый раз, когда начинается или заканчивается хотя бы один поток). В сумме получается, что многопотоковый алгоритм на трехъядерном процессоре вычислит факториал числа N > 0 за 3 + 2 +(2 + 4 -(N/2)) + + 4 = 11 +2-N тиков; императивный алгоритм — факториал числа N > 0 на одном ядре за 3 + 4-N + 2 = 5 + 4-N тиков. Таким образом, многопотоковый алгоритм дает ускорение (5 + + 4-/V)/(11 + 2-N)~2 раза.

Факториал как процесс

Теперь рассмотрим процессный алгоритм вычисления факториала. В момент запуска данный алгоритм создает экземпляр процес-

17 Следует иметь в виду, что перед каждой итерацией цикла for вычислитель выполняет 3 действия: goto к началу цикла; увеличение счетчика; сравнение с граничным значением.

са factorial, получающий от пользователя ^ значение N (факториал которого нужно вы- Ц числить), затем, если это значение «нетри- ^ виально» (т.е. >2), создает два экземпляра процесса product и одному «поручает» вы- § числить произведение всех целых чисел ин- ^ тервала [1.. .N/2], а другому — произведение со всех целых чисел интервала [{N/2 + 1)...N], ^ потом дожидается от порожденных экзем- § пляров результатов, вычисляет их произве- § дение и сообщает пользователю значение факториала числа N. Формализовать этот алгоритм можно в виде псевдокода, представленного далее.

Листинг 7

process factorial: ini=1, max=1;

begin var n, m, l, r, f: integer;

input (n);

if n>2 then

begin

m: = (n+l)/2;

{ {create (product) ; send (2) to

offspring; send (m) to offspring},

{create (product); send (m+1) to

offspring; send (n) to offspring}};

{receive (l), receive (r)}; f: = l*r

end

else f: = n;

output

end.

process product: ini=0, max=2;

begin var a, b, i, p: integer;

receive (a); receive (b); p: = 1;

for i: = a to b do p: = p*i od;

send (p) to parent

end.

В алгоритме использованы скобки « {...}» как сокращение для «begin... end» и новый разделитель «,» — обозначает последовательное исполнение разделенных им конструкций, но в любом порядке. Например, конСТрукциЯ {reseive (l), reseive (r)}BO время исполнения эквивалентна одной из двух следующих последовательных конструкций:

•reseive (l); reseive (r), •reseive (r); reseive (l),

но не двухпотоковой параллельной конст-py^HH{reseive (1) llreseive (г)}.

Описанный алгоритм можно реализовать в вычислительной среде, состоящей из четы-

-ч ПРИКЛАДНАЯ ИНФОРМАТИКА

№1(31)2011 ' -

рех однотипных процессоров, соединенных общей шиной18, один из которых предназначен для планировщика, второй — для единственного экземпляра процесса factorial, а третий и четвертый — для двух экземпляров процесса product. В таком случае наш процессный алгоритм также способен дать ускорение примерно в 2 раза по сравнению с императивной программой при ее реализации только на одном из этих процессоров.

К вопросу определения понятия

Вернемся к вопросу, что такое парадигма «параллельного программирования» и какие языки программирования ее поддерживают. Поскольку парадигма — это способ постановки и решения задач, то следует определить, что называют «параллельной постановкой» и «параллельным решением» задач программирования. Параллельная постановка — это описание желаемого результата, имеющихся аппаратных возможностей и ограничений, а также базовый «непараллельный» алгоритм, позволяющий получить тот же желаемый результат. Параллельное решение — пара алгоритмов: собственно параллельный алгоритм и алгоритм-планировщик, которые используют имеющуюся аппаратуру, не нарушают никаких ограничений, вычисляют желаемый результат, но достигают (как можно большего) ускорения по сравнению с базовым алгоритмом. Описание желаемого результата в простейшем случае — это описание наблюдаемого поведения в терминах последовательности входных сообщений (получаемых алгоритмом) и выходных сообщений (посылаемых алгоритмом). Аппаратные ограничения — число и тип вычислительных устройств с индивидуальной памятью, а также способ их сообщения между собой посредством каналов связи.

18 Общая шина — универсальный канал связи, к которому имеют доступ все процессоры и каждый может послать сообщение непосредственно без дополнительной маршрутизации.

В случае факториала возможное описание желаемого поведения — требование, согласно которому программа после получения натурального числа N > 0 вычислит и пошлет в ответ значение факториала числа N (равное произведению всех натуральных чисел от 1 до N), но при этом уменьшит время вычислений за счет использования всех имеющихся в распоряжении вычислительных устройств и средств общения между ними. В качестве базового «непараллельного» алгоритма авторы статьи использовали императивный алгоритм вычисления факториала.

Парадигмы программирования обычно фиксируются на уровне языков программирования. Однако в параллельном программировании трудно привести примеры «классических» или «универсальных» языков. На сегодняшний день очень популярны и востребованы параллельные расширения FORTRAN и С библиотеками MPI (Message Passing Interface) и OpenMP (Open MultiProcessing). MPI — это библиотека поддержки процессного параллелизма, a OpenMP — потокового (см. [12]).

Заключение

Вопросы развития параллельных вычислений сегодня являются предметом активного обсуждения. Авторы хотели бы акцентировать внимание на некоторых из них, вынесенных, в том числе, на повестку дня дискуссии участников конференции «Научный сервис в сети Интернет: суперкомпьютерные центры и задачи», но за недостатком времени не получивших должного освещения. По мнению авторов статьи, нижеперечисленные аспекты чрезвычайно важны в контексте существующих технологий обучения и реализации парадигм параллельного программирования:

1. Многообразие «параллельных про-граммирований»: процессное и потоковое (треды), синхронное и асинхронное, мелкозернистый и крупноблочный параллелизм и др.

128 у

№1(31)2011

2. Каким образом могут взаимодействовать и помогать друг другу разные парадигмы программирования? Как императивный алгоритм перевести в функциональный? Как по логической спецификации задачи синтезировать параллельный алгоритм?

3. Как связаны параллельное программирование, распределенные алгоритмы, мультиагентные системы и другие парадигмы, где одновременность и распределенность действий в самой природе вещей?

4. Как знакомить детей с парадигмами параллельного программирования, кроме головоломки о жарении бифштексов? Кто может предложить что-нибудь еще?

5. Как знакомить с парадигмами параллельного программирования студентов разных специальностей: на формальных моделях параллельных вычислений, на уровне архитектурно-независимого «универсального языка параллельного программирования», расширяя базовые языки последовательного программирования библиотеками поддержки параллелизма или сразу обучать параллельному программированию на конкретной платформе?

6. Могут ли помочь олимпиады и конкурсы по параллельному программированию его популяризации? Каким должен быть регламент подобных мероприятий в сравнении с ACM ICPC?

7. С какой формальной моделью параллельного программирования должен быть знаком каждый молодой программист, системотехник и т. д.: сети Петри, алгебры процессов Бергсты и Кпопа, алгебра и исчисление CSP Хоара, исчисление CCS и л-исчисление Милнера, исчисление окружений (ambient) Кардели и др.?

8. Что такое «теория параллельного программирования»: формальные модели параллелизма + вычислительные методы + ...? Формальная семантика, спецификация и верификация параллельных алгоритмов и программ — это диссертационный Клондайк для теоретиков или магистральный путь развития надежного и безопасного программирования?

а«

Авторы будут рады, если эти вопросы вы- ? зовут отклик у заинтересованных читателей. Ц

Описок литературы "Ч

§

1. Андреева Т. А., Ануреев И. С., Бодин Е. В., Го- § родняя Л. В., Марчук А. Г., Мурзин Ф. А., Ши- ^ лов Н. В. Образовательное значение класси- со фикации компьютерных языков // Прикладная ^

информатика. 2009. №6 (24). 1

§

2. Бэкус Дж. Как преодолеть фон-Неймановский Э стиль программирования? Функциональный стиль

и соответствующая алгебра программ. В кн.: Лекции лауреатов премии Тьюринга. М.: Мир, 1993.

3. Душкин Р. В. Функциональный подход в программировании // Потенциал. 2009. №8.

4. Душкин Р. В. Задача о ранце // Потенциал. 2009. №9.

5. Кун Т. Структура научных революций. М.: АСТ, 2003.

6. Непейвода Н. Н. Стили и методы программирования. М.: Интернет-университет информационных технологий, 2005. [Электронный ресурс]. http://www.intuit.ru/department/se/progstyles/.

7. Флойд Р. О парадигмах программирования. В кн.: Лекции лауреатов премии Тьюринга. М.: Мир, 1993.

8. Шилов Н. В. Заметки о преподавании парадигм программирования // IV Международная научно-практическая конференция «Современные информационные технологии и ИТ-образование», М„ 2009.

9. Шилов Н. В. Заметки о парадигмах программирования // Потенциал. 2010. №4.

10. Шилов Н. В. Заметки о трех парадигмах программирования // Компьютерные инструменты в образовании. 2010. №2.

11. Шилов Н. В., Городняя Л. В., Марчук А. Г. К определению парадигмы параллельного программирования // Труды Международной суперкомпьютерной конференции «Научный сервис в сети Интернет: суперкомпьютерные центры и задачи» (электронное издание). Новороссийск— Москва, 2010.

12. Чернышов А. Введение в технологии параллельного программирования. [Электронный ресурс], http://software.intel.com/ru-ru/articles/ wr¡t¡ng-parallel-programs-a-mult¡-language-tutor¡al-¡п1:гос1ис1:юп/.