Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

u_course

.pdf
Скачиваний:
39
Добавлен:
04.06.2015
Размер:
1.87 Mб
Скачать

Средства разработки параллельных программм

161

Дело в том, что не все циклы пригодны для распараллеливания. Цикл должен быть

независимым по данным, то есть данные, обрабатываемые одной итерацией цикла, не должны изменяться другой его итерацией;

независимым по выводу, то есть никакие две итерации цикла не должны изменять одну и ту же переменную;

независимым по вводу/выводу, то есть никакие две итерации цикла не обращаются к одному файлу;

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

Приведем примеры циклов, неудовлетворяющих этим условиям.

for (int i = 1; i < size; ++i)

x[i] = (x[i-1] + x[i+1])/2; //зависимость по данным

for (int i = 1; i < size; ++i)

{x[i] = (y[i-1] + z[i+1])/2; x[i+1] = (z[i-1] + y[i+1])/2;} //зависимость по выводу

for (int i = 1; i < size; ++i)

if (x[i-1] <c) x[i] = 0; //зависимость по управлению

Компилятор OpenMP не анализирует правильность кода, следовательно, не может обнаружить возникновение подобной ситуации, и многопоточный код будет генерировать неверный результат. Выделение проблемных участков алгоритма и разработка кода с защищенными потоками – обязанность программистов.

Существует ряд ограничений на алгоритмы при применении техноло-

гии OpenMP.

Например, синхронизация потоков-производителей и потоковпотребителей, а также однотипных потоков в задаче о кольцевом буфере (см. главы 2 – 4) практически невозможна при использовании OpenMP.

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

В OpenMP нет объектов, подобных семафорам и мьютексам. Не возможно какое-либо планирование выполнения потоков средствами OpenMP.

И, наконец, последнее соображение, которое следует учитывать в отношении OpenMP. Этот интерфейс в фоновом режиме выполняет большое количество работы по разбиению на потоки. Фактически OpenMP предоставляет очень мало информации о том, чем он занят, и программист не всегда

Средства разработки параллельных программм

162

знает все, что происходит на самом деле в связи с синхронизацией потоков, распределением нагрузки и т.д.

Самым узким местом всех многопоточных технологий является отладка программ. Существует ряд инструментов корпорации Intel, таких, как Intel Thread Checker, которые обеспечивают возможность анализа работы многопоточного приложения, использующего OpenMP. Данный отладчик встраивает свои функции в код программы и при ее запуске анализирует состояние потоков, преключателей и доступ к памяти. Отладчик способен выявить широкий спектр ошибок, таких как:

неверный доступ к памяти;

дедлоки;

состояния гонок;

ошибки синхронизации.

Единственный минус такого способа отладки – это замедление исполнения программы в несколько сотен раз.

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

OpenMP – простая, но мощная технология распараллеливания приложений. Она позволяет реализовать параллельное выполнение, как циклов, так и функциональных блоков кода. OpenMP легко интегрируется в существующие приложения и включается/выключается одним параметром компилятора. OpenMP позволяет более полно использовать вычислительную мощь многоядерных процессоров.

За счет идеи «инкрементального распараллеливания» OpenMP идеально подходит для разработчиков, желающих быстро распараллелить свои вычислительные программы с большими циклами. Разработчик не создает новую параллельную программу, а просто добавляет в текст последовательной программы OpenMP-директивы.

КОНТРОЛЬНЫЕВОПРОСЫ

1.Для каких целей была разработана система программирования

OpenMP?

2.Общая концепция написания программ с использованием OpenMP.

5.Каким образом и для чего используется директивы OpenMP?

6.Опишите формат директивы OpenMP #pragma omp parallel.

7.Опишите формат директивы #pragma omp sections для описания параллельных секций.

8.Опишите формат директивы OpenMP #pragma omp for.

Средства разработки параллельных программм

163

9.Какие директивы синхронизации используются в OpenMP?

10.Какие переменные среды OpenMP и функции для работы с ними вы

знаете?

11.Алгоритмы планирования и их использование при разработке программ с применением OpenMP.

12.Какие методы используются для оптимизации программ, написанных с использованием OpenMP?

13.Какие ограничения системы программирования OpenMP вы можете перечислить?

ГЛАВА6. РАЗРАБОТКАПАРАЛЛЕЛЬНЫХ ПРИЛОЖЕНИЙДЛЯВЫЧИСЛИТЕЛЬНЫХСИСТЕМ СРАСПРЕДЕЛЕННОЙПАМЯТЬЮ

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

ПРИМИТИВЫПЕРЕДАЧИСООБЩЕНИЙ

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

примитивы передачи сообщений. Передачу сообщений можно рассматривать как обобщение семафоров для перенаправления данных и обеспечения синхронизации.

При передаче сообщений процессы разделяют не переменные, а каналы. Канал – абстракция сети связи, обеспечивающая физический путь между процессами. Реализация канала принципиально разная, если он соединяет различные процессы на одной машине (реализация с помощью разделяемых переменных) и если общение между процессами осуществляется с помощью связующей сети. В последнем случае каналы – единственные объекты, которые разделяют процессы.

В рамках распределенного программирования варьируются следующие характеристики взаимодействия процессов:

направленность потока информации по каналу (одно- / двунаправленный);

тип взаимодействия (синхронное / асинхронное).

В результате рассматриваются четыре механизма взаимодействия распределенных процессов:

1.асинхронная передача сообщений: однонаправленный канал, асинхронное взаимодействие;

2.синхронная передача сообщений: однонаправленный канал, синхронное взаимодействие;

3.удаленный вызов процедур (Remote procedure call, RPC): двунаправленный канал, асинхронное взаимодействие;

4.рандеву: двунаправленный канал, синхронное взаимодействие.

Средства разработки параллельных программм

165

Эти механизмы взаимозаменяемы, но парадигмы «взаимодействующие равные» и «производитель-потребитель» удобнее реализовать с использованием передачи сообщений, а «клиент-сервер» и «управляющий-рабочий» – с помощью RPC и рандеву.

Асинхронная передача сообщений

Канал и при асинхронной и при синхронной передаче сообщений представляется очередью FIFO, реализующей однонаправленную передачу. Доступ к каналу производится с использованием двух примитивов – send и

receive.

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

Таким образом, при асинхронной передаче сообщений канал является очередью сообщений, которые уже отправлены, но еще не получены. Рассмотрим следующую нотацию для объявления канала:

chan ch(type_1, id_1, ..., type_n, id_n);

Имя канала ch; type_i, id_i – типы и имена полей данных в передаваемых по каналу сообщениях. Типы указывать необходимо, имена полей – не обязательно. Допустимо использовать массивы каналов, например строкой

chan result [n] (int);

объявлен массив каналов, нумерованных от 0 до n-1.

Процесс отправляет сообщение каналу ch, выполняя операцию:

send ch(expr_1, ..., expr_n);

Необходимо соответствие типов expr_i и типов в декларации канала. При выполнении операции send вычисляются выражения, затем полученные выражения присоединяются к концу очереди, связанной с каналом ch. Теоретически эта очередь не ограничена, поэтому выполнение процесса после этого продолжается, таким образом, в объявленной нотации операция send() является не блокирующей операцией.

Процесс получает сообщение из канала ch, выполняя операцию:

receive ch(var_1, ..., var_n);

Переменные var_i должны иметь тот же тип, что и соответствующие поля в декларации канала. При выполнении операции receive() принимающий процесс приостанавливается до тех пор, пока в очереди канала не появится хотя бы одно сообщение. Затем из очереди удаляется первое сообщение, а значения его полей присваиваются переменным var_i. Таким образом, операция receive() всегда является блокирующей операцией. При этом процесс, вы-

Средства разработки параллельных программм

166

полняющий операцию receive(), не обязан применять активное ожидание, для опроса канала.

Предполагается, что доступ к содержимому канала является неделимым, а сообщения передаются без ошибок. Таким образом, каждое передан-

ное в канал сообщение будет, в конце концов, принято, причем без искажений.

Каналы используются процессами совместно, поэтому объявляются как глобальные переменные.

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

Иногда получающий процесс можно занять полезной работой, пока он дожидается появления в канале сообщения. Над каналом разумно определить функцию

empty(ch);

которая возвращает значение true, если канал пуст, и false в противном случае. Однако есть существенное отличие операции empty(ch) от подобной операции с очередью для условной переменной монитора. Процесс может выполнить проверку очереди, выяснить, что очередь не пуста, но к моменту продолжения работы окажется, что другой процесс уже опустошил очередь, и наоборот, значение true не гарантирует, что в момент выполнения процессом следующего действия очередь будет по-прежнему пуста.

Синхронная передача сообщений

При синхронной передаче сообщений отправка сообщений приводит к блокировке процесса-отправителя до тех пор, пока сообщение не будут получено. Определим для синхронной передачи сообщений примитив

synch_send(expr_1, …, expr_n);

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

Синхронная передача обладает двумя недостатками.

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

Средства разработки параллельных программм

167

Во-вторых, синхронная организация обмена подвержена взаимным блокировкам. Следующий пример зависнет, поскольку программа заблокируется на операторах synch_send():

channel in1(int), in2(int);

process P1 {

int val_1=1, val_2; synch_send in2(val_1); receive in1(val_2);

}

process P2 {

int val_1, val_2=2; synch_send in1(val_2); receive in2(val_1);

}

Однако при замене операторов synch_send() примитивами асинхронной передачи send(), программы будут работать успешно

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

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

Пример: поиск глобального экстремума

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

Можно предложить, по крайней мере, три принципиально различных решения задачи.

1. Централизованное решение. Это решение с управляющим процессом, который можно назвать плохим руководителем. Предполагается, что всю работу по поиску экстремумов берет на себя управляющий процесс, а рабочие процессы, сгенерировав свои магические числа, отправляют их управляющему процессу и ждут получения результатов. На рис. 6.1 изображена схема взаимодействия процессов. Отметим, что для ее реализации на n процессах потребуется 2(n-1) обмен. Ясно, что основным недостатком этого решения является неравномерность распределения нагрузки по процессам. Алгоритм имеет смысл применять в случае, если поиск экстремума является не основной задачей, и, тем более, если результат необходим только управляющему процессу (то есть количество обменов можно сократить до n-1).

chan values(int);

chan results[n](int smallest, int largest);

Средства разработки параллельных программм

process P[0] { # управляющий процесс

int v; # считается, что v инициализирована

int new, smallest = v, largest = v; # начальное состояние

#собрать числа, запомнить минимальное и максимальное for [ i = 1 to n – 1 ] {

receive values(new);

if (new < smallest) smallest = new; if (new > largest) largest = new;

}

#разослать результаты остальным процессам

for [ i = 1 to n – 1 ]

send results[ i ](smallest, largest);

}

process P [ i = 1 to n – 1 ] {

int v; # считается, что v инициализирована int smallest, largest;

send values(v);

receive results[ i ](smallest, largest);

}

168

Рис. 6.1.

Централизованное

решение

2. Симметричное решение. Это решение исключает управляющий процесс, за счет дублирования работы по поиску экстремумов всеми процесами. Каждый процесс отправляет свое магическое число всем процессам и, получая от них информацию, самостоятельно определяет экстремумы. Заметим, что если сразу производить обработку получаемой информации, то хранить все получаемые данные не обязательно. С точки зрения производительности, такое решение оптимальным назвать сложно, более того, для его реализации на n процессах потребуется n(n-1) обмен. На рис. 6.2 изображена схема взаимодействия процессов.

chan values[n](int);

process P [ i = 0 to n – 1 ] # все процессы выполняют один код

{

int v; # считается, что v инициализирована

int new, smallest = v, largest = v; # начальное состояние

#разослать мое решение всем остальным процессам for [ j = 0 to n – 1 st j !=i ] send values[ i ](v);

#собрать значения, запомнить минимум и максимум for [ j = 1 to n – 1 ] {

receive values[ i ](new);

if (new < smallest) smallest = new; if (new > largest) largest = new;

}

}

Рис. 6.2.

Симметричное

решение

3. Кольцевое решение. При реализации этого подхода информация проходит через все процессы по кольцу. Каждый процесс сначала дожидается числа от левого соседа, сравнивает его со своим значением и отправляет правому соседу экстремумы, которые в данном случае будут являться локальными. Глобальные максимум и минимум определяться, только когда ин-

Средства разработки параллельных программм

169

формация пройдет все кольцо. Далее следует

 

глобальные экстремумы распространить по

 

кольцу для того, чтобы этим знанием облада-

 

ли все процессы.

 

Кольцевое решение в некотором смысле

 

объединяет два предыдущих. С одной сторо-

 

ны все процессы занимаются вычислением

Рис. 6.3.

экстремума, с другой стороны количество об-

Кольцевое решение

менов минимизировано и, как и для первого

 

алгоритма, равно 2(n-1), где n – количество процессов. Не смотря на то, что все процессы задействованы в вычислениях, решение не является симметричным, поскольку должен быть выделен процесс-зачинщик, который запускает конвейер. На рис. 6.3 изображена схема взаимодействия процессов в данном случае. Сплошными стрелками обозначены передачи локальных экстремумов, пунктирными – распространение глобальных экстремумов по кольцу.

chan values[n](int smallest, int largest);

process P[0] { # процесс – инициализатор вычислений int v; # считается, что v инициализирована

int smallest = v, largest = v; # начальное состояние

#послать v следующему процессу, P[1] send values[1](smallest, largest);

#получить глобальные минимум и максимум от P[n – 1] и отправить их P[1] receive values[n-1](smallest, largest);

send values[1](smallest, largest);

}

process P [ i = 1 to n – 1 ] {

int v; # считается, что v инициализирована int smallest, largest;

#получить текущие минимум и максимум и обновить их, сравнивая с v receive values[ i ](smallest, largest);

if (v < smallest) smallest = v; if (v > largest) largest = v;

#отправить результат следующему процессу, затем ожидать получения

#глобальных результатов

if (i != n-2) send values[ (i + 1) mod n ] (smallest, largest); if (i != n-1) receive values[ i ] (smallest, largest);

}

Этот простой пример показывает, что обмены можно организовывать различными способами, оптимизация заключается, как правило, в поиске компромисса между количеством обменов и требуемой каждому потоку для реализации алгоритма памятью.

Задачи на поиск глобального экстремума, общей суммы и т.д. часто встречаются как составные части алгоритмов и носят название операций приведения. Языковые реализации, как правило, предоставляют высокоуров-

Средства разработки параллельных программм

170

невый механизм операций приведения. Более того, многие суперкомпьютеры имеют аппаратную реализацию таких алгоритмов.

Удаленные операции

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

Удаленные операции сочетают в себе основные черты мониторов и синхронной передачи сообщений. Операции используют двунаправленный канал от процесса вызывающего к процессу, обслуживающему вызов.

Предполагается, что процессы могут «экспортировать» операции. В случае RPC вызывающий процесс примитивом call инициализирует запуск нового процесса, с требованием исполнить его экспортируемую процедуру. При использовании механизма рандеву вызывающий процесс инициализиру-

ет встречу с существующим процессом примитивом ввода in, который ожи-

дает вызова, обрабатывает его и возвращает результат1.

Рандеву и RPC обычно используются для программировния распределенных приложений. Проблемы создания таких приложений выходят за рамки нашего изложения, и далее не обсуждается.

ТЕХНОЛОГИЯРАСПАРАЛЛЕЛИВАНИЯДЛЯМАССИВНО-ПАРАЛЛЕЛЬНЫХАРХИ- ТЕКТУРИОСНОВЫMPI

Наиболее распространенной технологиенй программирования параллельных компьютеров с распределенной памятью в настоящее время является MPI (Message Passing Interface). Как уже обсуждалось, основным способом взаимодействия параллельных процессов в таких системах является передача сообщений, что и положено в основу MPI. Современные реализации, чаще всего, соответствуют стандарту MPI версии 1.1, разработанному в 1993 -1995 годах группой MPI Форум, в состав которой входили представители академических и промышленных кругов. Хотя в 1998 г. и появился стандарт MPI- 2.0, значительно расширивший возможности первой версии, до сих пор этот вариант MPI не получил широкого распространения. Поэтому в этой главе обсуждается в основном реализация MPI, соответствующая версии 1.12.

Архитектурная парадигма MPI

Представим себе, что перед тремя рабочими нужно поставить задачу выкопать и обустроить некоторым образом какую-то одну яму. Причем рабо-

1Сравните работу сервера баз данных и использование DHTML с обработкой экранных форм.

2Полные тексты стандартов расположены в Интернете по адресу http://www-unix.mcs.anl.gov/mpi.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]