Дифференциальные уравнения в частных производных представляют собой широко применяемый математический аппарат при разработке моделей в самых разных областях науки и техники. К сожалению, явное решение этих уравнений в аналитическом виде оказывается возможным только в частных простых случаях, и, как результат, возможность анализа математических моделей, построенных на основе дифференциальных уравнений, обеспечивается при помощи приближенных численных методов решения.
Объем выполняемых при этом вычислений обычно является значительным, и использование высокопроизводительных вычислительных систем традиционно для данной области вычислительной математики.
Проблематика численного решения дифференциальных уравнений в частных производных является областью интенсивных исследований.
Рассмотрим в качестве учебного примера проблему численного
решения задачи Дирихле для уравнения Пуассона, которая
определяется как задача нахождения функции u=u(x,y),
удовлетворяющей в области определения D уравнению$$\left\{
\begin{aligned}
\frac{\partial^2u}{\partial x^2}+\frac{\partial^2 u}{\partial y^2}= f(x,y), (x,y) \in D, \\
u(x,y) = g(x,y), (x,y) \in D^0,
\end{aligned}
\right.$$
и принимающей значения g(x,y) на границе D0 области D ( f и g
являются функциями, задаваемыми при постановке задачи). Подобная
модель может применяться для описания установившегося течения
жидкости, стационарных тепловых полей, процессов теплопередачи с
внутренними источниками тепла и деформации упругих пластин.
Данный пример часто используется в качестве учебно-практической
задачи при изложении возможных способов организации эффективных
параллельных вычислений (см. [, ]).
Для простоты изложения материала в качестве области задания D
функции u(x,y) далее будет использоваться единичный квадрат
Одним из наиболее распространенных подходов к численному
решению дифференциальных уравнений является метод конечных
разностей ( , , ]). Следуя
этому подходу, область решения D можно представить в виде
дискретного (как правило, равномерного) набора ( сетки ) точек
( узлов ). Так, например, прямоугольная сетка в области D может
быть задана в виде (рис. 11.1)$$\left\{
\begin{gathered}
D_h=\{(x_i,y_j):x_i=ih, y_i=jh, 0\le i, j\le N+1, \\
h=1/(N+1)
\end{gathered}
\right.$$
где величина N задает количество внутренних узлов по каждой из
координат области D.
Обозначим оцениваемую при подобном дискретном представлении
аппроксимацию функции u(x,y) в точках (xi, yj) через uij. Тогда,
используя пятиточечный шаблон (см. рис. 11.1) для вычисления
значений производных, мы можем представить уравнение Пуассона в конечно-разностной форме$$\frac{u_{i-1,j}+u_{i+1,j}+u_{i,j-1}+u_{i,j+1}-4u_{ij}}{h^2} = f_{ij}$$
Данное уравнение может быть разрешено относительно uij:
uij=0,25(ui-1,j+ui+1,j+ui,j-1-h2fij).
Разностное уравнение, записанное в подобной форме, позволяет
определять значение uij по известным значениям функции u(x,y) в
соседних узлах используемого шаблона. Данный результат служит
основой для построения различных итерационных схем решения задачи
Дирихле, в которых в начале вычислений формируется некоторое
приближение для значений uij, а затем эти значения
последовательно уточняются в соответствии с приведенным
соотношением. Так, например, метод Гаусса – Зейделя для
проведения итераций уточнения использует правило$$u_{ij}^k=0,25(u_{i-1,j}^k + u_{i+1,j}^{k-1}+u_{i,j-1}^{k}+u_{i,j+1}^{k-1}-h^2 f_{ij})$$
по которому очередное k -е приближение значения uij вычисляется по
последнему k -му приближению значений ui-1,j и ui,j-1 и
предпоследнему (k-1) -му приближению значений ui+1,j и ui,j+1.
Выполнение итераций обычно продолжается до тех пор, пока
получаемые в результате итераций изменения значений uij не станут
меньше некоторой заданной величины ( требуемой точности
вычислений ). Сходимость описанной процедуры (получение решения с
любой желаемой точностью) является предметом всестороннего
математического анализа (см., например, [, , ]), здесь же
отметим, что последовательность решений, получаемых методом
сеток, равномерно сходится к решению h2.
(рис 11.1) Прямоугольная сетка в области D (темные точки представляют внутренние узлы сетки, нумерация узлов в строках слева направо, а в столбцах — сверху вниз)Рассмотренный алгоритм (метод Гаусса – Зейделя) на псевдокоде,
приближенном к алгоритмическому языку С++, может быть представлен
в виде:
Алгоритм 11.1. Последовательный
// Алгоритм 11.1
do {
dmax = 0; // максимальное изменение значений u
for ( i=1; i<N+1; i++ )
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
dm = fabs(temp-u[i][j]);
if ( dmax < dm ) dmax = dm;
}
} while ( dmax > eps );
(напомним, что значения uij при
индексах i,j=0,N+1 являются граничными, задаются при
постановке задачи и не изменяются в ходе вычислений).
(рис 11.2) Вид функции u(x,y) в примере для задачи ДирихлеДля примера на рис. 11.2
приведен вид функции u(x,y), полученной для задачи
Дирихле при следующих граничных условиях:$$\left\{
\begin{aligned}
\quad f(x,y)=0, \quad (x,y) \in D, \\
\quad 100-200x, \quad y=0, \\
\quad 100-200x, \quad x=0, \\
-100+200x, \quad y=1, \\
-100+200x, \quad x=1. \\
\end{aligned}
\right.$$
Общее количество итераций метода Гаусса – Зейделя составило
210 при точности решения eps=0,1 и N=100 (в качестве начального
приближения величин uij использовались значения, сгенерированные
датчиком случайных чисел из диапазона [-100, 100]).
Как следует из приведенного описания, сеточные методы характеризуются значительной вычислительной трудоемкостью
T1=kmN2,
где N есть количество узлов по каждой из координат области D, m — число операций, выполняемых методом для одного узла сетки, k
— количество итераций метода до выполнения условия остановки.
Рассмотрим возможные способы организации параллельных
вычислений для сеточных методов на многопроцессорных
вычислительных системах с общей памятью. При изложении материала
будем предполагать, что имеющиеся в составе системы процессоры
обладают равной производительностью, являются равноправными при
доступе к общей памяти и время доступа к памяти является
одинаковым (при одновременном доступе нескольких процессоров к
одному и тому же элементу памяти очередность и синхронизация
доступа обеспечиваются на аппаратном уровне). Как уже отмечалось
ранее, многопроцессорные системы подобного типа обычно именуются
симметричными
Обычный подход при организации вычислений для подобных систем – создание новых параллельных версий на основе обычных последовательных программ, в которых или автоматически компилятором, или непосредственно программистом выделяются участки независимых друг от друга вычислений. Возможности автоматического анализа программ для порождения параллельных вычислений достаточно ограничены, и второй подход является преобладающим. При этом для разработки параллельных программ могут применяться как новые алгоритмические языки, ориентированные на параллельное программирование, так и уже имеющиеся языки программирования, расширенные некоторым набором операторов для параллельных вычислений.
Оба указанных подхода приводят к необходимости значительной переработки существующего программного обеспечения, и это в значительной степени затрудняет широкое распространение параллельных вычислений. Как результат, в последнее время активно развивается еще один подход к разработке параллельных программ, когда указания программиста по организации параллельных вычислений добавляются в программу при помощи тех или иных внеязыковых средств языка программирования – например, в виде директив или комментариев, которые обрабатываются специальным препроцессором до начала компиляции программы. При этом исходный операторный текст программы остается неизменным, и по нему в случае отсутствия препроцессора компилятор построит исходный последовательный программный код. Препроцессор же, будучи примененным, заменяет директивы параллелизма на некоторый дополнительный программный код (как правило, в виде обращений к процедурам какой-либо параллельной библиотеки).
Рассмотренный выше подход является основой технологии OpenMP
(см., например, []), наиболее широко применяемой в настоящее
время для организации параллельных вычислений на
многопроцессорных системах с общей памятью. В рамках данной
технологии директивы параллелизма используются для выделения в
программе параллельных областей ( parallel regions ), в которых
последовательный исполняемый код может быть разделен на несколько
раздельных командных потоков ( threads ). Далее эти потоки могут
исполняться на разных процессорах вычислительной системы. В
результате такого подхода программа представляется в виде набора
последовательных ( однопотоковых ) и параллельных ( многопотоковых )
участков программного кода (см. рис. 11.3). Подобный принцип
организации параллелизма получил наименование "вилочного"
( fork-join ) или пульсирующего параллелизма. Более полная информация по
технологии OpenMP может быть получена в литературе (см.,
например, [, ]) или в информационных ресурсах сети Интернет.
В данной лекции возможности OpenMP будут излагаться в объеме,
необходимом для демонстрации возможных способов разработки
параллельных программ для рассматриваемого учебного примера
решения
Первый вариант параллельного алгоритма для метода сеток может
быть получен, если разрешить произвольный порядок пересчета
значений uij. Программа для данного способа вычислений может быть
представлена в следующем виде:
Алгоритм 11.2. Первый вариант параллельного
// Алгоритм 11.2
omp_lock_t dmax_lock;
omp_init_lock (dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,N,dmax) private(i,temp,d)
for ( i=1; i<N+1; i++ ) {
#pragma omp parallel for shared(u,N,dmax) private(j,temp,d)
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
omp_set_lock(dmax_lock);
if ( dmax < d ) dmax = d;
omp_unset_lock(dmax_lock);
} // конец вложенной параллельной области
} // конец внешней параллельной области
} while ( dmax > eps );
Следует отметить, что программа получена из исходного последовательного кода путем добавления директив и операторов обращения к функциям библиотеки OpenMP.
(рис 11.3) Параллельные области, создаваемые директивами OpenMPКак следует из текста программы, параллельные области в данном
примере задаются директивой parallel for, являются вложенными и
включают в свой состав операторы цикла for. Компилятор,
поддерживающий технологию OpenMP, разделяет выполнение итераций
цикла между несколькими потоками программы, количество которых
обычно совпадает с числом процессоров в вычислительной системе.
Параметры директивы shared и private определяют доступность
данных в потоках программы – переменные, описанные как shared,
являются общими для потоков, для переменных с описанием private
создаются отдельные копии для каждого потока, которые могут
использоваться в потоках независимо друг от друга.
Наличие общих данных обеспечивает возможность взаимодействия
потоков. В этом плане dmax, доступ потоков к
которой регулируется специальной служебной переменной (замком) dmax_lock и функциями omp_set_lock (разрешение или блокировка
доступа) и omp_unset_lock (снятие запрета на доступ). Подобная
организация программы гарантирует единственность доступа потоков
для изменения разделяемых данных. Участки программного кода
(блоки между обращениями к функциям omp_set_lock и omp_unset_lock ), для которых обеспечивается взаимоисключение,
обычно именуются критическими секциями.
Результаты
Оценим полученный результат. Разработанный параллельный
алгоритм является корректным, т.е. обеспечивающим решение
поставленной задачи. Использованный при разработке подход
обеспечивает достижение практически максимально возможного
параллелизма – для выполнения программы может быть задействовано
вплоть до N2 процессоров. Тем не менее результат не может быть
признан удовлетворительным: программа будет работать медленно и
вместо uij должен проверить
(и возможно, изменить) значение величины dmax. Разрешение на
использование переменной может получить только один поток – все
остальные потоки должны быть блокированы. После освобождения
общей переменной управление может получить следующий поток и т.д.
В результате необходимости синхронизации доступа многопотоковая
параллельная программа превращается фактически в последовательно
выполняемый код, причем менее эффективный, чем исходный
последовательный вариант, т.к. организация синхронизации приводит
к дополнительным вычислительным затратам – см. рис. 11.4. Следует
обратить внимание, что, несмотря на идеальное распределение
вычислительной нагрузки между процессорами, для приведенного на
рис. 11.4 соотношения параллельных и последовательных вычислений,
в каждый текущий момент времени (после момента первой
синхронизации) только не более двух процессоров одновременно
выполняют действия, связанные с решением задачи. Подобный эффект
вырождения параллелизма из-за интенсивной синхронизации
параллельных участков программы обычно именуется сериализацией
( serialization ).
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.2 | Параллельный алгоритм 11.3 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 1,97 | 0,03 | 210 | 0,03 | 2,03 |
| 200 | 273 | 0,34 | 273 | 11,22 | 0,03 | 273 | 0,14 | 2,43 |
| 300 | 305 | 0,88 | 305 | 29,09 | 0,03 | 305 | 0,36 | 2,43 |
| 400 | 318 | 3,78 | 318 | 54,20 | 0,07 | 318 | 0,64 | 5,90 |
| 500 | 343 | 6,00 | 343 | 85,84 | 0,07 | 343 | 1,06 | 5,64 |
| 600 | 336 | 8,81 | 336 | 126,38 | 0,07 | 336 | 1,50 | 5,88 |
| 700 | 344 | 12,11 | 344 | 178,30 | 0,07 | 344 | 2,42 | 5,00 |
| 800 | 343 | 16,41 | 343 | 234,70 | 0,07 | 343 | 8,08 | 2,03 |
| 900 | 358 | 20,61 | 358 | 295,03 | 0,07 | 358 | 11,03 | 1,87 |
| 1000 | 351 | 25,59 | 351 | 366,16 | 0,07 | 351 | 13,69 | 1,87 |
| 2000 | 367 | 106,75 | 367 | 1585,84 | 0,07 | 367 | 56,63 | 1,89 |
| 3000 | 370 | 243,00 | 370 | 3598,53 | 0,07 | 370 | 128,66 | 1,89 |
( k -количество итераций, t -время (сек), S -
(рис 11.4) Пример возможной схемы выполнения параллельных потоков при наличии синхронизации (взаимоисключения)Как показывают выполненные рассуждения, путь для достижения for. Кроме того,
для снижения количества возможных блокировок применим для оценки
максимальной погрешности многоуровневую схему расчета: пусть
параллельно выполняемый поток первоначально формирует локальную
оценку погрешности dm только для своих обрабатываемых данных
(одной или нескольких строк сетки), затем при завершении
вычислений поток сравнивает свою оценку dm с общей оценкой
погрешности dmax.
Новый вариант программы решения
Алгоритм 11.3. Второй вариант параллельного
// Алгоритм 11.3
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,N,dmax) private(i,temp,d,dm)
for ( i=1; i<N+1; i++ ) {
dm = 0;
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm < d ) dm = d;
}
omp_set_lock(dmax_lock);
if ( dmax < dm ) dmax = dm;
omp_unset_lock(dmax_lock);
}
} // конец параллельной области
} while ( dmax > eps );
Как результат выполненного изменения схемы вычислений,
количество обращений к общей переменной dmax уменьшается с N2 до N раз, что должно приводить к существенному снижению затрат на
синхронизацию потоков и уменьшению проявления эффекта
сериализации вычислений. Результаты экспериментов с данным
вариантом параллельного алгоритма, приведенные в табл. 11.1,
показывают существенное изменение ситуации – N процессоров).
Последний рассмотренный вариант организации параллельных
вычислений для метода сеток обеспечивает практически максимально
возможное
(рис 11.5) Возможные различные варианты взаиморасположения параллельных потоков (состязание потоков)В рассматриваемом примере при вычислении нового значения uij в
зависимости от условий выполнения могут использоваться разные (от
предыдущей или текущей итераций) оценки соседних значений по
вертикали. Тем самым, количество итераций метода до выполнения
условия остановки и, самое главное, конечное решение задачи могут
различаться при повторных запусках программы. Получаемые оценки
величин uij. будут соответствовать точному решению задачи в
пределах задаваемой точности, но, тем не менее, могут быть
различными. Применение вычислений такого типа для сеточных
алгоритмов получило наименование метода хаотической релаксации
( chaotic relaxation ).
Возможный подход для получения row_lock[N],
который позволит потокам закрывать доступ к "своим"
строкам сетки.
// поток обрабатывает i строку сетки omp_set_lock(row_lock[i]); omp_set_lock(row_lock[i+1]); omp_set_lock(row_lock[i-1]); // обработка i строки сетки omp_unset_lock(row_lock[i]); omp_unset_lock(row_lock[i+1]); omp_unset_lock(row_lock[i-1]);
Закрыв доступ к своим данным, параллельный поток уже не будет зависеть от динамики выполнения других параллельных участков программы. Результат вычислений потока однозначно определяется значениями данных в момент начала расчетов.
Данный подход позволяет продемонстрировать еще одну проблему, которая может возникать в ходе параллельных вычислений. Эта проблема состоит в том, что при организации доступа к множественным общим переменным может появляться конфликт между параллельными потоками и этот конфликт не может быть разрешен успешно. Так, в приведенном фрагменте программного кода при обработке потоками двух последовательных строк (например, строк 1 и 2) может сложиться ситуация, когда потоки блокируют сначала строки 1 и 2 и только затем переходят к блокировке оставшихся строк (см. рис. 11.6). В этом случае доступ к необходимым строкам не может быть обеспечен ни для одного потока – возникает неразрешимая ситуация, обычно именуемая тупиком. Как можно показать, необходимым условием тупика является наличие цикла в графе распределения и запросов ресурсов. В рассматриваемом примере уход от цикла может состоять в строго последовательной схеме блокировки строк потока.
// поток обрабатывает i строку сетки omp_set_lock(row_lock[i+1]); omp_set_lock(row_lock[i]); omp_set_lock(row_lock[i-1]); // <обработка i строки сетки> omp_unset_lock(row_lock[i+1]); omp_unset_lock(row_lock[i]); omp_unset_lock(row_lock[i-1]);
(следует отметить, что и эта схема блокировки строк может оказаться тупиковой, если рассматривать модифицированную задачу Дирихле, в которой горизонтальные границы являются "склеенными").
(рис 11.6) Ситуация тупика при доступе к строкам сетки (поток 1 владеет строкой 1 и запрашивает строку 2, поток 2 владеет строкой 2 и запрашивает строку 1)
Подход, рассмотренный в п. 11.2.4, уменьшает эффект состязания потоков, но не гарантирует единственности решения при повторении вычислений. Для достижения однозначности необходимо использование дополнительных вычислительных схем.
Возможный и широко применяемый в практике расчетов способ состоит в разделении места хранения результатов вычислений на предыдущей и текущей итерациях метода сеток. Схема такого подхода может быть представлена в следующем общем виде:
Алгоритм 11.4. Параллельная реализация сеточного метода Гаусса – Якоби
// Алгоритм 11.4
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,un,N,dmax) private(i,temp,d,dm)
for ( i=1; i<N+1; i++ ) {
dm = 0;
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
un[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-un[i][j])
if ( dm < d ) dm = d;
}
omp_set_lock(dmax_lock);
if ( dmax < dm ) dmax = dm;
omp_unset_lock(dmax_lock);
}
} // конец параллельной области
for ( i=1; i<N+1; i++ ) // обновление данных
for ( j=1; j<N+1; j++ )
u[i][j] = un[i][j];
} while ( dmax > eps );
Как следует из приведенного алгоритма, результаты предыдущей
итерации запоминаются в массиве u, новые вычисления значения
запоминаются в дополнительном массиве un. Как результат,
независимо от порядка выполнения вычислений для проведения
расчетов всегда используются значения величин uij от предыдущей
итерации метода. Такая схема реализации сеточных алгоритмов
обычно именуется методом Гаусса – Якоби. Этот метод гарантирует
| Размер сетки | Последовательный метод Гаусса - Якоби | Параллельный метод (11.4), разработанный по аналогии с алгоритмом 11.3 | |||
|---|---|---|---|---|---|
k |
t |
k |
t |
S | |
| 100 | 5257 | 1,39 | 5257 | 0,73 | 1,90 |
| 200 | 23067 | 23,84 | 23067 | 11,00 | 2,17 |
| 300 | 26961 | 226,23 | 26961 | 29,00 | 7,80 |
| 400 | 34377 | 562,94 | 34377 | 66,25 | 8,50 |
| 500 | 56941 | 1330,39 | 56941 | 191,95 | 6,93 |
| 600 | 114342 | 3815,36 | 114342 | 2247,95 | 1,70 |
| 700 | 64433 | 2927,88 | 64433 | 1699,19 | 1,72 |
| 800 | 87099 | 5467,64 | 87099 | 2751,73 | 1,99 |
| 900 | 286188 | 22759,36 | 286188 | 11776,09 | 1,93 |
| 1000 | 152657 | 14258,38 | 152657 | 7397,60 | 1,93 |
| 2000 | 337809 | 134140,64 | 337809 | 70312,45 | 1,91 |
| 3000 | 655210 | 247726,69 | 655210 | 129752,13 | 1,91 |
( k – количество итераций, t – время (сек), S –
(рис 11.7) Схема чередования обработки четных и нечетных строкИной возможный подход для устранения взаимозависимости параллельных потоков состоит в применении схемы чередования обработки четных и нечетных строк ( red/black row alternation scheme ), когда выполнение итерации метода сеток подразделяется на два последовательных этапа, на первом из которых обрабатываются строки только с четными номерами, а затем на втором этапе — строки с нечетными номерами (см. рис. 11.7). Данная схема может быть обобщена на применение одновременно и к строкам, и к столбцам ( шахматное разбиение ) области расчетов.
Рассмотренная схема чередования строк не требует, по сравнению
с
Рассмотрим теперь возможность построения параллельного
алгоритма, который выполнял бы только те вычислительные действия,
что и последовательный метод (может быть только в несколько ином
порядке) и, как результат, обеспечивал бы получение точно таких
же решений исходной вычислительной задачи. Как уже было отмечено
выше, в последовательном алгоритме каждое очередное k -е
приближение значения ui,j вычисляется по последнему k -му
приближению значений ui-1,j и ui,j-1 и предпоследнему (k-1) -му
приближению значений ui+1,j и ui,j+1. Таким образом, при
требовании совпадения результатов вычислений последовательных и
параллельных вычислительных схем в начале каждой итерации метода
только одно значение u11 может быть пересчитано (возможности для
распараллеливания нет). Но далее после пересчета u11 вычисления
могут выполняться уже в двух узлах сетки u12 и u21 (в этих узлах
выполняются условия последовательной схемы), затем после
пересчета узлов u12 и u21 — в узлах u13, u22 и u31 и т.д.
Обобщая сказанное, можно увидеть, что выполнение итерации метода
сеток можно разбить на последовательность шагов, на каждом из
которых к вычислениям окажутся подготовленными узлы
вспомогательной диагонали сетки с номером, определяемым номером
этапа – см. рис. 11.8. Получаемая в результате вычислительная
схема получила наименование волны или фронта вычислений, а
алгоритмы, построенные на ее основе, — методов волновой обработки
данных ( wavefront или hyperplane methods ). Следует отметить, что
в нашем случае размер волны (степень возможного параллелизма)
динамически изменяется в ходе вычислений – волна нарастает до
своего пика, а затем затухает при приближении к правому нижнему
узлу сетки.
(рис 11.8) Движение фронта волны вычисленийВозможная схема параллельного метода, основанного на эффекте волны вычислений, может быть представлена в следующей форме.
Алгоритм 11.5. Параллельный алгоритм, реализующий волновую схему вычислений
// Алгоритм 11.5
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
// нарастание волны (nx – размер волны)
for ( nx=1; nx<N+1; nx++ ) {
dm[nx] = 0;
#pragma omp parallel for shared(u,N,nx,dm) private(i,j,temp,d)
for ( i=1; i<nx+1; i++ ) {
j = nx + 1 – i;
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm[i] < d ) dm[i] = d;
} // конец параллельной области
}
// затухание волны
for ( nx=N-1; nx>0; nx-- ) {
#pragma omp parallel for shared(u,N,nx,dm) private(i,j,temp,d)
for ( i=N-nx+1; i<N+1; i++ ) {
j = 2*N - nx – I + 1;
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm[i] < d ) dm[i] = d;
} // конец параллельной области
}
#pragma omp parallel for shared(nx,dm,dmax) private(i)
for ( i=1; i<nx+1; i++ ) {
omp_set_lock(dmax_lock);
if ( dmax < dm[i] ) dmax = dm[i];
omp_unset_lock(dmax_lock);
} // конец параллельной области
} while ( dmax > eps );
При разработке алгоритма, реализующего волновую схему
вычислений, оценку погрешности решения можно осуществлять для
каждой строки в отдельности (массив значений dm ). Этот массив
является общим для всех выполняемых потоков, однако синхронизации
доступа к элементам не требуется, так как потоки используют
всегда разные элементы массива (фронт волны вычислений содержит
только по одному узлу строк сетки).
После обработки всех элементов волны в составе массива dm находится максимальная погрешность выполненной итерации вычислений. Однако именно эта последняя часть расчетов может оказаться наиболее неэффективной из-за высоких дополнительных затрат на синхронизацию. Улучшение ситуации, как и ранее, может быть достигнуто за счет увеличения размера последовательных участков и сокращения, тем самым, количества необходимых взаимодействий параллельных участков вычислений. Возможный вариант реализации такого подхода может состоять в следующем:
chunk = 200; // размер последовательного участка
#pragma omp parallel for shared(n,dm,dmax) private(i,d)
for ( i=1; i<nx+1; i+=chunk ) {
d = 0;
for ( j=i; j<i+chunk; j++ )
if ( d < dm[j] ) d = dm[j];
omp_set_lock(dmax_lock);
if ( dmax < d ) dmax = d;
omp_unset_lock(dmax_lock);
} // конец параллельной области
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.5 | Параллельный алгоритм 11.6 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,30 | 0,21 | 210 | 0,16 | 0,40 |
| 200 | 273 | 0,34 | 273 | 0,86 | 0,40 | 273 | 0,59 | 0,58 |
| 300 | 0,88 | 305 | 1,63 | 0,54 | 305 | 1,53 | 0,57 | |
| 400 | 318 | 3,78 | 318 | 2,50 | 1,51 | 318 | 2,36 | 1,60 |
| 500 | 343 | 6,00 | 343 | 3,53 | 1,70 | 343 | 4,03 | 1,49 |
| 600 | 336 | 8,81 | 336 | 5,20 | 1,69 | 336 | 5,34 | 1,65 |
| 700 | 344 | 12,11 | 344 | 8,13 | 1,49 | 344 | 10,00 | 1,21 |
| 800 | 343 | 16,41 | 343 | 12,08 | 1,36 | 343 | 12,64 | 1,30 |
| 900 | 358 | 20,61 | 358 | 14,98 | 1,38 | 358 | 15,59 | 1,32 |
| 1000 | 351 | 25,59 | 351 | 18,27 | 1,40 | 351 | 19,30 | 1,33 |
| 2000 | 367 | 106,75 | 367 | 69,08 | 1,55 | 367 | 65,72 | 1,62 |
| 3000 | 370 | 243,00 | 370 | 149,36 | 1,63 | 370 | 140,89 | 1,72 |
( k – количество итераций, t – время (сек), S –
Подобный прием укрупнения последовательных участков вычислений для снижения затрат на синхронизацию именуется фрагментированием ( chunking ). Результаты экспериментов для данного варианта параллельных вычислений приведены в табл. 11.3.
Следует обратить внимание еще на один момент при анализе
В исследуемом нами алгоритме размещение данных в памяти
осуществляется по строкам, а фронт волны вычислений располагается
по диагонали сетки, и это приводит к низкой
(рис 11.9) Блочное представление сетки области расчетовПорождаемый на основе такого подхода метод вычислений в самом
общем виде может быть описан следующим образом (блоки образуют в
области расчетов прямоугольную решетку размера NBxNB ):
Алгоритм 11.6. Блочный подход к методу волновой обработки данных
// Алгоритм 11.6
// NB количество блоков
do {
// нарастание волны (размер волны равен nx+1)
for ( nx=0; nx<NB; nx++ ) {
#pragma omp parallel for shared(nx) private(i,j)
for ( i=0; i<nx+1; i++ ) {
j = nx – i;
// <обработка блока с координатами (i,j)>
} // конец параллельной области
}
// затухание волны
for ( nx=NB-2; nx>-1; nx-- ) {
#pragma omp parallel for shared(nx) private(i,j)
for ( i=0; i<nx+1; i++ ) {
j = 2*(NB-1) - nx – i;
// <обработка блока с координатами (i,j)>
} // конец параллельной области
}
// <определение погрешности вычислений>
} while ( dmax > eps );
Вычисления в предлагаемом алгоритме происходят в соответствии с волновой схемой обработки данных: вначале вычисления выполняются только в левом верхнем блоке с координатами (0,0), далее для обработки становятся доступными блоки с координатами (0,1) и (1,0) и т.д. – см. результаты экспериментов в табл. 11.3.
Блочный подход к методу волновой обработки данных существенным образом меняет состояние дел – обработку узлов можно организовать построчно, доступ к данным осуществляется последовательно по элементам памяти, перемещенные в кэш значения используются многократно. Кроме того, поскольку обработка блоков будет выполняться на разных процессорах и блоки не пересекаются по данным, при таком подходе будут отсутствовать и накладные расходы для обеспечения однозначности (когерентности) кэшей разных процессоров.
Наилучшие показатели применения кэша будут достигаться, если в кэше будет достаточно места для размещения не менее трех строк блока (при обработке строки блока используются данные трех строк блока одновременно). Тем самым, исходя из размера кэша, можно определить рекомендуемый максимально возможный размер блока. Например, при кэше 8 Кб и 8-байтовых значениях данных этот размер составит приближенно 300 (8Кб/3)/8). Можно определить и минимально допустимый размер блока из условия совпадения размеров строк кэша и блока. Так, при размере строки кэша 256 байт и 8-байтовых значениях данных размер блока должен быть кратен 32.
Последнее замечание необходимо сделать о взаимодействии граничных узлов блоков. Учитывая граничное взаимодействие, соседние блоки целесообразно обрабатывать на одних и тех же процессорах. В противном случае можно попытаться так определить размеры блоков, чтобы объем пересылаемых между процессорами граничных данных был минимален. Например, при размере строки кэша в 256 байт, 8-байтовых значениях данных и размере блока 64х64 объем пересылаемых данных – 132 строки кэша, при размере блока 128х32 – всего 72 строки. Такая оптимизация имеет наиболее принципиальное значение при медленных операциях пересылки данных между кэшами процессоров, т.е. для систем с неоднородным доступом к памяти ( nonuniform memory access — NUMA).
Как уже отмечалось ранее, вычислительная нагрузка при волновой
обработке данных изменяется динамически в ходе вычислений. Данный
момент следует учитывать при распределении вычислительной
нагрузки между процессорами. Так, например, при фронте волны из 5
блоков и при использовании 4 процессоров обработка волны
потребует двух параллельных итераций, во время второй из которых
будет задействован только один процессор, а все остальные
процессоры будут простаивать, дожидаясь завершения вычислений.
Достигнутое
Для обеспечения равномерности ( балансировки ) загрузки процессоров можно задействовать еще один подход, широко используемый для организации параллельных вычислений. Этот подход состоит в том, что все готовые к выполнению в системе вычислительные действия организуются в виде очереди заданий. В ходе вычислений освободившийся процессор может запросить для себя работу из этой очереди; появляющиеся по мере обработки данных новые вычислительные задания пополняют очередь заданий. Такая схема балансировки вычислительной нагрузки между процессорами является простой, наглядной и эффективной. Это позволяет говорить об использовании очереди заданий как общей модели организации параллельных вычислений для систем с общей памятью.
Указанная схема балансировки может быть применена и для рассматриваемого примера. В самом деле, в ходе обработки фронта текущей волны происходит постепенное формирование блоков следующей волны вычислений. Эти блоки могут быть задействованы для обработки при нехватке достаточной вычислительной нагрузки для процессоров.
Общая схема вычислений с использованием очереди заданий может быть представлена в следующем виде:
Алгоритм 11.7. Общая схема вычислений с использованием очереди
// Алгоритм 11.7
// <инициализация служебных данных>
// <загрузка в очередь указателя на начальный блок>
// взять блок из очереди (если очередь не пуста)
while ( (pBlock=GetBlock()) != NULL ) {
// <обработка блока>
// отметка готовности соседних блоков
omp_set_lock(pBlock->pNext.Lock); // сосед справа
pBlock->pNext.Count++;
if ( pBlock->pNext.Count == 2 )
PutBlock(pBlock->pNext);
omp_unset_lock(pBlock->pNext.Lock);
omp_set_lock(pBlock->pDown.Lock); // сосед снизу
pBlock->pDown.Count++;
if ( pBlock->pDown.Count == 2 )
PutBlock(pBlock->pDown);
omp_unset_lock(pBlock->pDown.Lock);
} // завершение вычислений, т.к. очередь пуста
Для описания имеющихся в задаче блоков узлов сетки в алгоритме используется структура со следующим набором параметров:
Lock – семафор, синхронизирующий доступ к описанию блока;pNext – указатель на соседний справа блок;pDown – указатель на соседний снизу блок;Count – счетчик готовности блока к вычислениям (количество готовых границ блока).Операции для выборки из очереди и вставки в очередь указателя
на готовый к обработке блок узлов сетки обеспечивают
соответственно функции GetBlock и PutBlock.
Как следует из приведенной схемы, процессор извлекает блок для обработки из очереди, выполняет необходимые вычисления для блока и отмечает готовность своих границ для соседних справа и снизу блоков. Если при этом оказывается, что у соседних блоков являются подготовленными обе границы, процессор передает эти блоки для запоминания в очередь заданий.
Использование очереди заданий позволяет решить практически все оставшиеся вопросы организации параллельных вычислений для систем с общей памятью. Развитие рассмотренного подхода может предусматривать уточнение правил выделения заданий из очереди для согласования с состояниями процессоров (близкие блоки целесообразно обрабатывать на одних и тех же процессорах), расширение числа имеющихся очередей заданий и т.п. Дополнительная информация по этим вопросам может быть получена, например, в [, ].
Использование процессоров с распределенной памятью является другим общим способом построения многопроцессорных вычислительных систем. Актуальность их становится все более высокой в последнее время в связи с широким развитием высокопроизводительных кластерных вычислительных систем (см. лекцию 1).
Многие проблемы параллельного программирования (состязание вычислений, тупики, сериализация) являются общими для систем с общей и распределенной памятью. Основной момент, который отличает параллельные вычисления с распределенной памятью, состоит в том, что взаимодействие параллельных участков программы на разных процессорах может быть обеспечено только при помощи передачи сообщений ( message passing ).
Следует отметить, что вычислительный узел системы с распределенной памятью является, как правило, более сложным вычислительным устройством, чем процессор в многопроцессорной системе с общей памятью. Для учета этих различий в дальнейшем процессор с распределенной памятью будет именоваться вычислительным сервером (сервером может быть, в частности, многопроцессорная система с общей памятью). При проведении всех ниже рассмотренных экспериментов использовались 4 компьютера с процессорами Pentium IV, 1300 Mhz, 256 RAM, 100 Mbit Fast Ethernet.
Первая проблема, которую приходится решать при организации
параллельных вычислений на
(рис 11.10) Ленточное разделение области расчетов между процессорами (кружки представляют граничные узлы сетки)В рассматриваемом учебном примере по решению
При ленточном разбиении область расчетов делится на горизонтальные или вертикальные полосы (не уменьшая общности, далее будем рассматривать только горизонтальные полосы). Число полос определяется количеством процессоров, размер полос обычно является одинаковым, узлы горизонтальных границ (первая и последняя строки) включаются в первую и последнюю полосы соответственно. Полосы для обработки распределяются между процессорами.
Основной момент при организации вычислений с подобным разделением данных состоит в том, что на процессор, выполняющий обработку какой-либо полосы, должны быть продублированы граничные строки предшествующей и следующей полос вычислительной сетки (получаемые в результате расширенные полосы показаны на рис. 11.10 справа пунктирными рамками). Продублированные граничные строки полос используются только при проведении расчетов, пересчет же этих строк происходит в полосах своего исходного месторасположения. Тем самым, дублирование граничных строк должно осуществляться перед началом выполнения каждой очередной итерации метода сеток.
Параллельный вариант метода сеток при ленточном разделении данных состоит в обработке полос на всех имеющихся серверах одновременно в соответствии со следующей схемой работы:
Алгоритм 11.8. Параллельный алгоритм, реализующий метод сеток при ленточном разделении данных
// Алгоритм 11.8
// схема Гаусса-Зейделя, ленточное разделение данных
// действия, выполняемые на каждом процессоре
do {
// <обмен граничных строк полос с соседями>
// <обработка полосы>
// <вычисление общей погрешности вычислений dmax>}
while ( dmax > eps ); // eps — точность решения
Для конкретизации представленных в алгоритме действий введем обозначения:
ProcNum – номер процессора, на котором выполняются описываемые действия,PrevProc, NextProc – номера соседних процессоров, содержащих предшествующую и следующую полосы,NP – количество процессоров,M – количество строк в полосе (без учета продублированных граничных строк),N – количество внутренних узлов в строке сетки (т.е. всего в строке N+2 узла).При нумерации строк полосы будем считать, что строки 0 и M+1
есть продублированные из соседних полос граничные строки, а
строки собственной полосы процессора имеют номера от 1 до M.
(рис 11.11) Схема передачи граничных строк между соседними процессорамиПроцедура обмена граничных строк между соседними процессорами может быть разделена на две последовательные операции, во время первой из которых каждый процессор передает свою нижнюю граничную строку следующему процессору и принимает такую же строку от предыдущего процессора (см. рис. 11.11). Вторая часть передачи строк выполняется в обратном направлении: процессоры передают свои верхние граничные строки своим предыдущим соседям и принимают переданные строки от следующих процессоров.
Выполнение подобных
// передача нижней граничной строки следующему // процессору и прием передаваемой строки от // предыдущего процессора if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc); if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
(для записи процедур приема-передачи используется близкий к
стандарту Send ) или источник
(для операции Receive ) пересылки данных).
Для передачи данных могут быть задействованы два различных механизма. При первом из них выполнение программ, инициировавших операцию передачи, приостанавливается до полного завершения всех действий по пересылке данных (т.е. до момента получения процессором-адресатом всех передаваемых ему данных). Операции приема-передачи, реализуемые подобным образом, обычно называются синхронными или блокирующими. Иной подход – асинхронная или неблокирующая передача — может состоять в том, что операции приема-передачи только инициируют процесс пересылки и на этом завершают свое выполнение. В результате программы, не дожидаясь завершения длительных коммуникационных операций, могут продолжать свои вычислительные действия, проверяя по мере необходимости готовность передаваемых данных. Оба эти варианта операций передачи широко используются при организации параллельных вычислений и имеют свои достоинства и свои недостатки. Синхронные процедуры передачи, как правило, более просты для применения и более надежны; неблокирующие операции могут позволить совместить процессы передачи данных и вычислений, но обычно приводят к повышению сложности программирования. С учетом вышесказанного во всех последующих примерах для организации пересылки данных будут использоваться операции приема-передачи блокирующего типа.
Приведенная выше последовательность блокирующих операций
приема-передачи данных (вначале Send, затем Receive ) приводит к
строго последовательной схеме выполнения процесса пересылок
строк, т.к. все процессоры одновременно обращаются к операции Send и переходят в режим ожидания. Первым процессором, который
окажется готовым к приему пересылаемых данных, будет сервер с
номером NP-1. В результате процессор NP-2 выполнит операцию
передачи своей граничной строки и перейдет к приему строки от
процессора NP-3 и т.д. Общее количество повторений таких операций
равно NP-1. Аналогично происходит выполнение и второй части
процедуры пересылки граничных строк перед началом обработки строк
(см. рис. 11.11).
Последовательный характер рассмотренных операций пересылок данных определяется выбранным способом очередности выполнения. Изменим этот порядок очередности при помощи чередования приема и передачи для процессоров с четными и нечетными номерами.
// передача нижней граничной строки следующему
// процессору и прием передаваемой строки от
// предыдущего процессора
if ( ProcNum % 2 == 1 ) { // нечетный процессор
if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc);
if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
}
else { // процессор с четным номером
if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc);
}
Данный прием позволяет выполнить все необходимые операции
передачи всего за два последовательных шага. На первом шаге все
процессоры с нечетными номерами отправляют данные, а процессоры с
четными номерами осуществляют прием этих данных. На втором шаге
роли процессоров меняются – четные процессоры выполняют Send,
нечетные процессоры исполняют операцию приема Receive.
Рассмотренные последовательности операций приема-передачи для
взаимодействия соседних процессоров широко используются в
практике параллельных вычислений. Как результат, во многих
базовых библиотеках параллельных программ имеются процедуры для
поддержки подобных действий. Так, в стандарте Sendrecv, с использованием которой
предыдущий фрагмент программного кода может быть записан более
кратко:
// передача нижней граничной строки следующему // процессору и прием передаваемой строки от // предыдущего процессора Sendrecv(u[M][*],N+2,NextProc,u[0][*],N+2,PrevProc);
Реализация подобной объединенной функции Sendrecv обычно осуществляется таким образом, чтобы обеспечить и корректную работу на крайних процессорах, когда не нужно выполнять одну из операций передачи или приема, и организацию чередования процедур передачи на процессорах для ухода от тупиковых ситуаций, и возможности параллельного выполнения всех необходимых пересылок данных.
Для завершения круга вопросов, связанных с параллельной
реализацией метода сеток на log2NP параллельных итераций для
получения конечного значения ( NP – количество процессоров).
С учетом возможности применения
Reduce(dm,dmax,op,proc) – процедура сборки на процессоре proc
итогового результата dmax среди локальных на каждом процессоре
значений dm с применением операции op ;Broadcast(dmax,proc) – процедура рассылки с процессора proc
значения dmax всем имеющимся процессорам системы.С учетом перечисленных процедур общая схема вычислений на каждом процессоре может быть представлена в следующем виде:
// Алгоритм 11.8 – уточненный вариант
// схема Гаусса-Зейделя, ленточное разделение данных
// действия, выполняемые на каждом процессоре
do {
// обмен граничных строк полос с соседями
Sendrecv(u[M][*],N+2,NextProc,u[0][*],N+2,PrevProc);
Sendrecv(u[1][*],N+2,PrevProc,u[M+1][*],N+2,NextProc);
// <обработка полосы с оценкой погрешности dm>
// вычисление общей погрешности вычислений dmax
Reduce(dm,dmax,MAX,0);
Broadcast(dmax,0);
} while ( dmax > eps ); // eps — точность решения
(в приведенном алгоритме переменная dm представляет собой
локальную погрешность вычислений на отдельном процессоре, а
параметр MAX задает операцию поиска максимального значения для
операции сборки). Следует отметить, что в составе Allreduce, которая совмещает действия редукции и
рассылки данных. Результаты экспериментов для данного варианта
параллельных вычислений для метода Гаусса – Зейделя приведены в
рис. 11.4.
Представленные в пп. 11.3.1 – 11.3.3 алгоритмы определяют
общую схему параллельных вычислений для метода сеток в
многопроцессорных
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.8 | Параллельный алгоритм с волновой схемой расчета (см. п. 11.3.4) | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,54 | 0,11 | 210 | 1,27 | 0,05 |
| 200 | 273 | 0,35 | 273 | 0,86 | 0,41 | 273 | 1,37 | 0,26 |
| 300 | 305 | 0,92 | 305 | 0,92 | 1,00 | 305 | 1,83 | 0,50 |
| 400 | 318 | 1,69 | 318 | 1,27 | 1,33 | 318 | 2,53 | 0,67 |
| 500 | 343 | 2,88 | 343 | 1,72 | 1,68 | 343 | 3,26 | 0,88 |
| 600 | 336 | 4,04 | 336 | 2,16 | 1,87 | 336 | 3,66 | 1,10 |
| 700 | 344 | 5,68 | 344 | 2,52 | 2,25 | 344 | 4,64 | 1,22 |
| 800 | 343 | 7,37 | 343 | 3,32 | 2,22 | 343 | 5,65 | 1,30 |
| 900 | 358 | 9,94 | 358 | 4,12 | 2,41 | 358 | 7,53 | 1,32 |
| 1000 | 351 | 11,87 | 351 | 4,43 | 2,68 | 351 | 8,10 | 1,46 |
| 2000 | 367 | 50,19 | 367 | 15,13 | 3,32 | 367 | 27,00 | 1,86 |
| 3000 | 364 | 113,17 | 364 | 37,96 | 2,98 | 364 | 55,76 | 2,03 |
( k – количество итераций, t – время (сек), S –
В завершение рассмотрим возможность организации параллельных
вычислений, при которых обеспечивалось бы нахождение таких же
решений
(рис 11.12) Организация волны вычислений при ленточной схеме разделения данныхИнтересный момент при организации подобной схемы параллельных вычислений может состоять в попытке совмещения операций пересылки граничных строк и действий по обработке блоков данных.
Ленточная схема разделения данных может быть естественным
образом обобщена на блочный способ представления сетки области
расчетов (см. рис. 11.9). При этом столь радикальное изменение
способа разбиения сетки практически не потребует каких-либо
существенных корректировок рассмотренной схемы параллельных
вычислений. Основной новый момент при блочном представлении
данных состоит в увеличении количества граничных строк на каждом
процессоре (для блока их количество становится равным 4), что
приводит, соответственно, к большему числу (N+2)
значения. Для блочного же способа происходит 8 операций пересылки
и объем каждого сообщения равен $$(N/ \sqrt{NP}+2)$$ ( N – количество
внутренних узлов сетки, NP – число процессоров, размер всех
блоков предполагается одинаковым). Тем самым, блочная схема
представления области расчетов становится оправданной при большом
количестве узлов сетки, когда увеличение количества
коммуникационных операций приводит к снижению затрат на пересылку
данных в силу сокращения размеров передаваемых сообщений.
Результаты экспериментов при блочной схеме разделения данных
приведены в табл. 11.5.
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм с блочной схемой рассчета (см. п. 11.3.5) | Параллельный алгоритм 11.9 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,71 | 0,08 | 210 | 0,60 | 0,10 |
| 200 | 273 | 0,35 | 273 | 0,74 | 0,47 | 273 | 1,06 | 0,33 |
| 300 | 305 | 0,92 | 305 | 1,04 | 0,88 | 305 | 2,01 | 0,46 |
| 400 | 318 | 1,69 | 318 | 1,44 | 1,18 | 318 | 2,63 | 0,64 |
| 500 | 343 | 2,88 | 343 | 1,91 | 1,51 | 343 | 3,60 | 0,80 |
| 600 | 336 | 4,04 | 336 | 2,39 | 1,69 | 336 | 4,63 | 0,87 |
| 700 | 344 | 5,68 | 344 | 2,96 | 1,92 | 344 | 5,81 | 0,98 |
| 800 | 343 | 7,37 | 343 | 3,58 | 2,06 | 343 | 7,65 | 0,96 |
| 900 | 358 | 9,94 | 358 | 4,50 | 2,21 | 358 | 9,57 | 1,04 |
| 1000 | 351 | 11,87 | 351 | 4,90 | 2,42 | 351 | 11,16 | 1,06 |
| 2000 | 367 | 50,19 | 367 | 16,07 | 3,12 | 367 | 39,49 | 1,27 |
| 3000 | 364 | 113,17 | 364 | 39,25 | 2,88 | 364 | 85,72 | 1,32 |
( k – количество итераций, t – время (сек), S –
При блочном представлении сетки может быть реализован также и волновой метод выполнения расчетов (см. рис. 11.13). Пусть процессоры образуют прямоугольную решетку размером $$NB\times NB (NB=\sqrt{NP})$$ и процессоры пронумерованы от 0 слева направо по строкам решетки.
Общая схема параллельных вычислений в этом случае имеет вид:
Алгоритм 11.9. Блочная схема разделения данных
// Алгоритм 11.9
// схема Гаусса-Зейделя, блочное разделение данных
// действия, выполняемые на каждом процессоре
do {
// получение граничных узлов
if ( ProcNum / NB != 0 ) { // строка не нулевая
// получение данных от верхнего процессора
Receive(u[0][*],M+2,TopProc); // верхняя строка
Receive(dmax,1,TopProc); // погрешность
}
if ( ProcNum % NB != 0 ) { // столбец не нулевой
// получение данных от левого процессора
Receive(u[*][0],M+2,LeftProc); // левый столбец
Receive(dm,1,LeftProc); // погрешность
if ( dm > dmax ) dmax = dm;
}
// <обработка блока с оценкой погрешности dmax>
// пересылка граничных узлов
if ( ProcNum / NB != NB-1 ) { // строка решетки не последняя
// пересылка данных нижнему процессору
Send(u[M+1][*],M+2,DownProc); // нижняя строка
Send(dmax,1,DownProc); // погрешность
}
if ( ProcNum % NB != NB-1 ) { // столбец решетки не последний
// пересылка данных правому процессору
Send(u[*][M+1],M+2,RightProc); // правый столбец
Send(dmax,1, RightProc); // погрешность
}
// синхронизация и рассылка погрешности dmax
Broadcast(dmax,NP-1);
} while ( dmax > eps ); // eps — точность решения
При реализации алгоритма необходимо обеспечить, чтобы в начальный момент времени все процессоры (кроме процессора с нулевым номером) оказались в состоянии передачи своих граничных узлов (верхней строки и левого столбца). Вычисления должен начинать процессор с левым верхним блоком, после завершения обработки которого обновленные значения правого столбца и нижней строки блока нужно переправить правому и нижнему процессорам решетки соответственно. Данные действия обеспечат снятие блокировки процессоров второй диагонали процессорной решетки (ситуация слева на рис. 11.13) и т.д.
Анализ NB подобных шагов в
обработке будет находиться одновременно NB итераций и все
процессоры окажутся задействованными. Подобная схема организации
расчетов позволяет рассматривать имеющуюся процессорную решетку
как вычислительный конвейер поэтапного выполнения итераций метода
сеток. Остановка конвейера может осуществляться, как и ранее, по
максимальной погрешности вычислений (проверку условия остановки
следует начинать только при достижении полной загрузки конвейера
после запуска NB итераций расчетов). Необходимо отметить также,
что получаемое после выполнения условия остановки решение задачи
Дирихле будет содержать значения узлов сетки от разных итераций
метода и не будет, тем самым, совпадать с решением, получаемым
при помощи исходного последовательного алгоритма.
(рис 11.13) Организация волны вычислений при блочной схеме разделения данных
Время выполнения коммуникационных операций значительно превышает длительность вычислительных команд. Оценка трудоемкости операций приема-передачи может быть осуществлена с использованием двух основных характеристик сети передачи: латентности ( latency ), определяющей время подготовки данных к передаче по сети, и пропускной способности сети ( bandwidth ), задающей объем передаваемых по сети за 1 секунду данных, – более полное изложение вопроса содержится в лекции 3.
Пропускная способность наиболее распространенной на данный момент сети Fast Ethernet – 100 Mбит/с, для более современной сети Gigabit Ethernet – 1000 Мбит/с. В то же время скорость передачи данных в системах с общей памятью обычно составляет сотни и тысячи миллионов байт в секунду. Тем самым, использование систем с распределенной памятью приводит к снижению скорости передачи данных не менее чем в 100 раз.
Еще хуже дело обстоит с латентностью. Для сети Fast Ethernet
эта характеристика имеет значение порядка 150 мкс, для сети
Gigabit Ethernet – около 100 мкс. Для современных компьютеров с
тактовой частотой свыше 2 ГГц различие в производительности
достигает не менее чем 10000 – 100000 раз. При указанных
характеристиках вычислительной системы для достижения 90% N=7500 узлов по
вертикали и горизонтали (объем вычислений в блоке составляет 5N2
операций с плавающей запятой).
Как результат, можно заключить, что
(рис 11.14) Операции передачи данных при выполнении метода сеток с распределенной памятьюMPI_Bcast );MPI_Scatter );MPI_Sendrecv );MPI_Allreduce );MPI_Gather ).В лекции рассматриваются вопросы организации параллельных
вычислений для решения задач, в которых при математическом
моделировании используются дифференциальные уравнения в частных
производных. Для численного решения подобных задач обычно
применяется метод конечных разностей ( метод сеток ), обладающий
высокой вычислительной трудоемкостью. В лекции последовательно
разбираются возможные способы распараллеливания сеточных методов
на многопроцессорных вычислительных системах с общей и
распределенной памятью. При этом большое внимание уделяется
проблемам, возникающим при организации параллельных вычислений,
анализу причин появления таких проблем и нахождению путей их
преодоления. Для наглядной демонстрации излагаемого материала в
качестве учебного примера рассматривается проблема численного
решения
В пункте 11.1 приводится краткое описание сеточных методов на
примере решения
В пункте 11.2 даются возможные способы организации параллельных вычислений при численном решении дифференциальных уравнений в частных производных для вычислительных систем с общей памятью. В основе излагаемого подхода – технология OpenMP, широко применяемая в настоящее время для разработки параллельных программ. В рамках этой технологии параллельный программный код формируется программистом посредством добавления специальных директив или комментариев в существующие последовательные программы. Как результат, программный код является единым для последовательных и параллельных программ, что делает более простым развитие и сопровождение программного обеспечения.
Следует отметить, что принятая в лекции последовательность
представления учебного материала может быть рассмотрена как
наглядная демонстрация поэтапной методики разработки программного
обеспечения. Такой подход позволяет достаточно быстро получать
начальные варианты параллельных программ, которые далее могут
совершенствоваться для достижения максимально возможной
В пункте 11.3 вопросы организации параллельных вычислений при
численном решении дифференциальных уравнений в частных
производных рассматриваются применительно к вычислительным
системам с распределенной памятью. Прежде всего отмечается, что
многие проблемы параллельного программирования (состязание
вычислений, тупики, сериализация) являются общими для систем с
общей и распределенной памятью. Основное отличие параллельных
вычислений с распределенной памятью состоит в том, что
взаимодействие параллельных участков программы на разных
процессорах может быть обеспечено только при помощи передачи
сообщений ( message passing ). При этом
Изложение учебного материала данного раздела лекции начинается
с обсуждения общих принципов распределения данных между
процессорами, которые применительно к рассматриваемой учебной
Дополнительная информация по численным методам решения дифференциальных уравнений в частных производных может быть получена в [, ]. Рассмотрение вопросов организации при численном решении дифференциальных уравнений в частных производных проводится в [, , , ].
При рассмотрении вопросов организации памяти компьютеров могут оказаться полезными работы [, ].
Технология
Более подробное рассмотрение вопросов, связанных с использованием очередей заданий при организации параллельных вычислений, проводится в [, ].
Дифференциальные уравнения в частных производных представляют собой широко применяемый математический аппарат при разработке моделей в самых разных областях науки и техники. К сожалению, явное решение этих уравнений в аналитическом виде оказывается возможным только в частных простых случаях, и, как результат, возможность анализа математических моделей, построенных на основе дифференциальных уравнений, обеспечивается при помощи приближенных численных методов решения.
Объем выполняемых при этом вычислений обычно является значительным, и использование высокопроизводительных вычислительных систем традиционно для данной области вычислительной математики.
Проблематика численного решения дифференциальных уравнений в частных производных является областью интенсивных исследований.
Рассмотрим в качестве учебного примера проблему численного
решения задачи Дирихле для уравнения Пуассона, которая
определяется как задача нахождения функции u=u(x,y),
удовлетворяющей в области определения D уравнению$$\left\{
\begin{aligned}
\frac{\partial^2u}{\partial x^2}+\frac{\partial^2 u}{\partial y^2}= f(x,y), (x,y) \in D, \\
u(x,y) = g(x,y), (x,y) \in D^0,
\end{aligned}
\right.$$
и принимающей значения g(x,y) на границе D0 области D ( f и g
являются функциями, задаваемыми при постановке задачи). Подобная
модель может применяться для описания установившегося течения
жидкости, стационарных тепловых полей, процессов теплопередачи с
внутренними источниками тепла и деформации упругих пластин.
Данный пример часто используется в качестве учебно-практической
задачи при изложении возможных способов организации эффективных
параллельных вычислений (см. [, ]).
Для простоты изложения материала в качестве области задания D
функции u(x,y) далее будет использоваться единичный квадрат
Одним из наиболее распространенных подходов к численному
решению дифференциальных уравнений является метод конечных
разностей ( , , ]). Следуя
этому подходу, область решения D можно представить в виде
дискретного (как правило, равномерного) набора ( сетки ) точек
( узлов ). Так, например, прямоугольная сетка в области D может
быть задана в виде (рис. 11.1)$$\left\{
\begin{gathered}
D_h=\{(x_i,y_j):x_i=ih, y_i=jh, 0\le i, j\le N+1, \\
h=1/(N+1)
\end{gathered}
\right.$$
где величина N задает количество внутренних узлов по каждой из
координат области D.
Обозначим оцениваемую при подобном дискретном представлении
аппроксимацию функции u(x,y) в точках (xi, yj) через uij. Тогда,
используя пятиточечный шаблон (см. рис. 11.1) для вычисления
значений производных, мы можем представить уравнение Пуассона в конечно-разностной форме$$\frac{u_{i-1,j}+u_{i+1,j}+u_{i,j-1}+u_{i,j+1}-4u_{ij}}{h^2} = f_{ij}$$
Данное уравнение может быть разрешено относительно uij:
uij=0,25(ui-1,j+ui+1,j+ui,j-1-h2fij).
Разностное уравнение, записанное в подобной форме, позволяет
определять значение uij по известным значениям функции u(x,y) в
соседних узлах используемого шаблона. Данный результат служит
основой для построения различных итерационных схем решения задачи
Дирихле, в которых в начале вычислений формируется некоторое
приближение для значений uij, а затем эти значения
последовательно уточняются в соответствии с приведенным
соотношением. Так, например, метод Гаусса – Зейделя для
проведения итераций уточнения использует правило$$u_{ij}^k=0,25(u_{i-1,j}^k + u_{i+1,j}^{k-1}+u_{i,j-1}^{k}+u_{i,j+1}^{k-1}-h^2 f_{ij})$$
по которому очередное k -е приближение значения uij вычисляется по
последнему k -му приближению значений ui-1,j и ui,j-1 и
предпоследнему (k-1) -му приближению значений ui+1,j и ui,j+1.
Выполнение итераций обычно продолжается до тех пор, пока
получаемые в результате итераций изменения значений uij не станут
меньше некоторой заданной величины ( требуемой точности
вычислений ). Сходимость описанной процедуры (получение решения с
любой желаемой точностью) является предметом всестороннего
математического анализа (см., например, [, , ]), здесь же
отметим, что последовательность решений, получаемых методом
сеток, равномерно сходится к решению h2.
(рис 11.1) Прямоугольная сетка в области D (темные точки представляют внутренние узлы сетки, нумерация узлов в строках слева направо, а в столбцах — сверху вниз)Рассмотренный алгоритм (метод Гаусса – Зейделя) на псевдокоде,
приближенном к алгоритмическому языку С++, может быть представлен
в виде:
Алгоритм 11.1. Последовательный
// Алгоритм 11.1
do {
dmax = 0; // максимальное изменение значений u
for ( i=1; i<N+1; i++ )
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
dm = fabs(temp-u[i][j]);
if ( dmax < dm ) dmax = dm;
}
} while ( dmax > eps );
(напомним, что значения uij при
индексах i,j=0,N+1 являются граничными, задаются при
постановке задачи и не изменяются в ходе вычислений).
(рис 11.2) Вид функции u(x,y) в примере для задачи ДирихлеДля примера на рис. 11.2
приведен вид функции u(x,y), полученной для задачи
Дирихле при следующих граничных условиях:$$\left\{
\begin{aligned}
\quad f(x,y)=0, \quad (x,y) \in D, \\
\quad 100-200x, \quad y=0, \\
\quad 100-200x, \quad x=0, \\
-100+200x, \quad y=1, \\
-100+200x, \quad x=1. \\
\end{aligned}
\right.$$
Общее количество итераций метода Гаусса – Зейделя составило
210 при точности решения eps=0,1 и N=100 (в качестве начального
приближения величин uij использовались значения, сгенерированные
датчиком случайных чисел из диапазона [-100, 100]).
Как следует из приведенного описания, сеточные методы характеризуются значительной вычислительной трудоемкостью
T1=kmN2,
где N есть количество узлов по каждой из координат области D, m — число операций, выполняемых методом для одного узла сетки, k
— количество итераций метода до выполнения условия остановки.
Рассмотрим возможные способы организации параллельных
вычислений для сеточных методов на многопроцессорных
вычислительных системах с общей памятью. При изложении материала
будем предполагать, что имеющиеся в составе системы процессоры
обладают равной производительностью, являются равноправными при
доступе к общей памяти и время доступа к памяти является
одинаковым (при одновременном доступе нескольких процессоров к
одному и тому же элементу памяти очередность и синхронизация
доступа обеспечиваются на аппаратном уровне). Как уже отмечалось
ранее, многопроцессорные системы подобного типа обычно именуются
симметричными
Обычный подход при организации вычислений для подобных систем – создание новых параллельных версий на основе обычных последовательных программ, в которых или автоматически компилятором, или непосредственно программистом выделяются участки независимых друг от друга вычислений. Возможности автоматического анализа программ для порождения параллельных вычислений достаточно ограничены, и второй подход является преобладающим. При этом для разработки параллельных программ могут применяться как новые алгоритмические языки, ориентированные на параллельное программирование, так и уже имеющиеся языки программирования, расширенные некоторым набором операторов для параллельных вычислений.
Оба указанных подхода приводят к необходимости значительной переработки существующего программного обеспечения, и это в значительной степени затрудняет широкое распространение параллельных вычислений. Как результат, в последнее время активно развивается еще один подход к разработке параллельных программ, когда указания программиста по организации параллельных вычислений добавляются в программу при помощи тех или иных внеязыковых средств языка программирования – например, в виде директив или комментариев, которые обрабатываются специальным препроцессором до начала компиляции программы. При этом исходный операторный текст программы остается неизменным, и по нему в случае отсутствия препроцессора компилятор построит исходный последовательный программный код. Препроцессор же, будучи примененным, заменяет директивы параллелизма на некоторый дополнительный программный код (как правило, в виде обращений к процедурам какой-либо параллельной библиотеки).
Рассмотренный выше подход является основой технологии OpenMP
(см., например, []), наиболее широко применяемой в настоящее
время для организации параллельных вычислений на
многопроцессорных системах с общей памятью. В рамках данной
технологии директивы параллелизма используются для выделения в
программе параллельных областей ( parallel regions ), в которых
последовательный исполняемый код может быть разделен на несколько
раздельных командных потоков ( threads ). Далее эти потоки могут
исполняться на разных процессорах вычислительной системы. В
результате такого подхода программа представляется в виде набора
последовательных ( однопотоковых ) и параллельных ( многопотоковых )
участков программного кода (см. рис. 11.3). Подобный принцип
организации параллелизма получил наименование "вилочного"
( fork-join ) или пульсирующего параллелизма. Более полная информация по
технологии OpenMP может быть получена в литературе (см.,
например, [, ]) или в информационных ресурсах сети Интернет.
В данной лекции возможности OpenMP будут излагаться в объеме,
необходимом для демонстрации возможных способов разработки
параллельных программ для рассматриваемого учебного примера
решения
Первый вариант параллельного алгоритма для метода сеток может
быть получен, если разрешить произвольный порядок пересчета
значений uij. Программа для данного способа вычислений может быть
представлена в следующем виде:
Алгоритм 11.2. Первый вариант параллельного
// Алгоритм 11.2
omp_lock_t dmax_lock;
omp_init_lock (dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,N,dmax) private(i,temp,d)
for ( i=1; i<N+1; i++ ) {
#pragma omp parallel for shared(u,N,dmax) private(j,temp,d)
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
omp_set_lock(dmax_lock);
if ( dmax < d ) dmax = d;
omp_unset_lock(dmax_lock);
} // конец вложенной параллельной области
} // конец внешней параллельной области
} while ( dmax > eps );
Следует отметить, что программа получена из исходного последовательного кода путем добавления директив и операторов обращения к функциям библиотеки OpenMP.
(рис 11.3) Параллельные области, создаваемые директивами OpenMPКак следует из текста программы, параллельные области в данном
примере задаются директивой parallel for, являются вложенными и
включают в свой состав операторы цикла for. Компилятор,
поддерживающий технологию OpenMP, разделяет выполнение итераций
цикла между несколькими потоками программы, количество которых
обычно совпадает с числом процессоров в вычислительной системе.
Параметры директивы shared и private определяют доступность
данных в потоках программы – переменные, описанные как shared,
являются общими для потоков, для переменных с описанием private
создаются отдельные копии для каждого потока, которые могут
использоваться в потоках независимо друг от друга.
Наличие общих данных обеспечивает возможность взаимодействия
потоков. В этом плане dmax, доступ потоков к
которой регулируется специальной служебной переменной (замком) dmax_lock и функциями omp_set_lock (разрешение или блокировка
доступа) и omp_unset_lock (снятие запрета на доступ). Подобная
организация программы гарантирует единственность доступа потоков
для изменения разделяемых данных. Участки программного кода
(блоки между обращениями к функциям omp_set_lock и omp_unset_lock ), для которых обеспечивается взаимоисключение,
обычно именуются критическими секциями.
Результаты
Оценим полученный результат. Разработанный параллельный
алгоритм является корректным, т.е. обеспечивающим решение
поставленной задачи. Использованный при разработке подход
обеспечивает достижение практически максимально возможного
параллелизма – для выполнения программы может быть задействовано
вплоть до N2 процессоров. Тем не менее результат не может быть
признан удовлетворительным: программа будет работать медленно и
вместо uij должен проверить
(и возможно, изменить) значение величины dmax. Разрешение на
использование переменной может получить только один поток – все
остальные потоки должны быть блокированы. После освобождения
общей переменной управление может получить следующий поток и т.д.
В результате необходимости синхронизации доступа многопотоковая
параллельная программа превращается фактически в последовательно
выполняемый код, причем менее эффективный, чем исходный
последовательный вариант, т.к. организация синхронизации приводит
к дополнительным вычислительным затратам – см. рис. 11.4. Следует
обратить внимание, что, несмотря на идеальное распределение
вычислительной нагрузки между процессорами, для приведенного на
рис. 11.4 соотношения параллельных и последовательных вычислений,
в каждый текущий момент времени (после момента первой
синхронизации) только не более двух процессоров одновременно
выполняют действия, связанные с решением задачи. Подобный эффект
вырождения параллелизма из-за интенсивной синхронизации
параллельных участков программы обычно именуется сериализацией
( serialization ).
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.2 | Параллельный алгоритм 11.3 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 1,97 | 0,03 | 210 | 0,03 | 2,03 |
| 200 | 273 | 0,34 | 273 | 11,22 | 0,03 | 273 | 0,14 | 2,43 |
| 300 | 305 | 0,88 | 305 | 29,09 | 0,03 | 305 | 0,36 | 2,43 |
| 400 | 318 | 3,78 | 318 | 54,20 | 0,07 | 318 | 0,64 | 5,90 |
| 500 | 343 | 6,00 | 343 | 85,84 | 0,07 | 343 | 1,06 | 5,64 |
| 600 | 336 | 8,81 | 336 | 126,38 | 0,07 | 336 | 1,50 | 5,88 |
| 700 | 344 | 12,11 | 344 | 178,30 | 0,07 | 344 | 2,42 | 5,00 |
| 800 | 343 | 16,41 | 343 | 234,70 | 0,07 | 343 | 8,08 | 2,03 |
| 900 | 358 | 20,61 | 358 | 295,03 | 0,07 | 358 | 11,03 | 1,87 |
| 1000 | 351 | 25,59 | 351 | 366,16 | 0,07 | 351 | 13,69 | 1,87 |
| 2000 | 367 | 106,75 | 367 | 1585,84 | 0,07 | 367 | 56,63 | 1,89 |
| 3000 | 370 | 243,00 | 370 | 3598,53 | 0,07 | 370 | 128,66 | 1,89 |
( k -количество итераций, t -время (сек), S -
(рис 11.4) Пример возможной схемы выполнения параллельных потоков при наличии синхронизации (взаимоисключения)Как показывают выполненные рассуждения, путь для достижения for. Кроме того,
для снижения количества возможных блокировок применим для оценки
максимальной погрешности многоуровневую схему расчета: пусть
параллельно выполняемый поток первоначально формирует локальную
оценку погрешности dm только для своих обрабатываемых данных
(одной или нескольких строк сетки), затем при завершении
вычислений поток сравнивает свою оценку dm с общей оценкой
погрешности dmax.
Новый вариант программы решения
Алгоритм 11.3. Второй вариант параллельного
// Алгоритм 11.3
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,N,dmax) private(i,temp,d,dm)
for ( i=1; i<N+1; i++ ) {
dm = 0;
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm < d ) dm = d;
}
omp_set_lock(dmax_lock);
if ( dmax < dm ) dmax = dm;
omp_unset_lock(dmax_lock);
}
} // конец параллельной области
} while ( dmax > eps );
Как результат выполненного изменения схемы вычислений,
количество обращений к общей переменной dmax уменьшается с N2 до N раз, что должно приводить к существенному снижению затрат на
синхронизацию потоков и уменьшению проявления эффекта
сериализации вычислений. Результаты экспериментов с данным
вариантом параллельного алгоритма, приведенные в табл. 11.1,
показывают существенное изменение ситуации – N процессоров).
Последний рассмотренный вариант организации параллельных
вычислений для метода сеток обеспечивает практически максимально
возможное
(рис 11.5) Возможные различные варианты взаиморасположения параллельных потоков (состязание потоков)В рассматриваемом примере при вычислении нового значения uij в
зависимости от условий выполнения могут использоваться разные (от
предыдущей или текущей итераций) оценки соседних значений по
вертикали. Тем самым, количество итераций метода до выполнения
условия остановки и, самое главное, конечное решение задачи могут
различаться при повторных запусках программы. Получаемые оценки
величин uij. будут соответствовать точному решению задачи в
пределах задаваемой точности, но, тем не менее, могут быть
различными. Применение вычислений такого типа для сеточных
алгоритмов получило наименование метода хаотической релаксации
( chaotic relaxation ).
Возможный подход для получения row_lock[N],
который позволит потокам закрывать доступ к "своим"
строкам сетки.
// поток обрабатывает i строку сетки omp_set_lock(row_lock[i]); omp_set_lock(row_lock[i+1]); omp_set_lock(row_lock[i-1]); // обработка i строки сетки omp_unset_lock(row_lock[i]); omp_unset_lock(row_lock[i+1]); omp_unset_lock(row_lock[i-1]);
Закрыв доступ к своим данным, параллельный поток уже не будет зависеть от динамики выполнения других параллельных участков программы. Результат вычислений потока однозначно определяется значениями данных в момент начала расчетов.
Данный подход позволяет продемонстрировать еще одну проблему, которая может возникать в ходе параллельных вычислений. Эта проблема состоит в том, что при организации доступа к множественным общим переменным может появляться конфликт между параллельными потоками и этот конфликт не может быть разрешен успешно. Так, в приведенном фрагменте программного кода при обработке потоками двух последовательных строк (например, строк 1 и 2) может сложиться ситуация, когда потоки блокируют сначала строки 1 и 2 и только затем переходят к блокировке оставшихся строк (см. рис. 11.6). В этом случае доступ к необходимым строкам не может быть обеспечен ни для одного потока – возникает неразрешимая ситуация, обычно именуемая тупиком. Как можно показать, необходимым условием тупика является наличие цикла в графе распределения и запросов ресурсов. В рассматриваемом примере уход от цикла может состоять в строго последовательной схеме блокировки строк потока.
// поток обрабатывает i строку сетки omp_set_lock(row_lock[i+1]); omp_set_lock(row_lock[i]); omp_set_lock(row_lock[i-1]); // <обработка i строки сетки> omp_unset_lock(row_lock[i+1]); omp_unset_lock(row_lock[i]); omp_unset_lock(row_lock[i-1]);
(следует отметить, что и эта схема блокировки строк может оказаться тупиковой, если рассматривать модифицированную задачу Дирихле, в которой горизонтальные границы являются "склеенными").
(рис 11.6) Ситуация тупика при доступе к строкам сетки (поток 1 владеет строкой 1 и запрашивает строку 2, поток 2 владеет строкой 2 и запрашивает строку 1)
Подход, рассмотренный в п. 11.2.4, уменьшает эффект состязания потоков, но не гарантирует единственности решения при повторении вычислений. Для достижения однозначности необходимо использование дополнительных вычислительных схем.
Возможный и широко применяемый в практике расчетов способ состоит в разделении места хранения результатов вычислений на предыдущей и текущей итерациях метода сеток. Схема такого подхода может быть представлена в следующем общем виде:
Алгоритм 11.4. Параллельная реализация сеточного метода Гаусса – Якоби
// Алгоритм 11.4
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
#pragma omp parallel for shared(u,un,N,dmax) private(i,temp,d,dm)
for ( i=1; i<N+1; i++ ) {
dm = 0;
for ( j=1; j<N+1; j++ ) {
temp = u[i][j];
un[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-un[i][j])
if ( dm < d ) dm = d;
}
omp_set_lock(dmax_lock);
if ( dmax < dm ) dmax = dm;
omp_unset_lock(dmax_lock);
}
} // конец параллельной области
for ( i=1; i<N+1; i++ ) // обновление данных
for ( j=1; j<N+1; j++ )
u[i][j] = un[i][j];
} while ( dmax > eps );
Как следует из приведенного алгоритма, результаты предыдущей
итерации запоминаются в массиве u, новые вычисления значения
запоминаются в дополнительном массиве un. Как результат,
независимо от порядка выполнения вычислений для проведения
расчетов всегда используются значения величин uij от предыдущей
итерации метода. Такая схема реализации сеточных алгоритмов
обычно именуется методом Гаусса – Якоби. Этот метод гарантирует
| Размер сетки | Последовательный метод Гаусса - Якоби | Параллельный метод (11.4), разработанный по аналогии с алгоритмом 11.3 | |||
|---|---|---|---|---|---|
k |
t |
k |
t |
S | |
| 100 | 5257 | 1,39 | 5257 | 0,73 | 1,90 |
| 200 | 23067 | 23,84 | 23067 | 11,00 | 2,17 |
| 300 | 26961 | 226,23 | 26961 | 29,00 | 7,80 |
| 400 | 34377 | 562,94 | 34377 | 66,25 | 8,50 |
| 500 | 56941 | 1330,39 | 56941 | 191,95 | 6,93 |
| 600 | 114342 | 3815,36 | 114342 | 2247,95 | 1,70 |
| 700 | 64433 | 2927,88 | 64433 | 1699,19 | 1,72 |
| 800 | 87099 | 5467,64 | 87099 | 2751,73 | 1,99 |
| 900 | 286188 | 22759,36 | 286188 | 11776,09 | 1,93 |
| 1000 | 152657 | 14258,38 | 152657 | 7397,60 | 1,93 |
| 2000 | 337809 | 134140,64 | 337809 | 70312,45 | 1,91 |
| 3000 | 655210 | 247726,69 | 655210 | 129752,13 | 1,91 |
( k – количество итераций, t – время (сек), S –
(рис 11.7) Схема чередования обработки четных и нечетных строкИной возможный подход для устранения взаимозависимости параллельных потоков состоит в применении схемы чередования обработки четных и нечетных строк ( red/black row alternation scheme ), когда выполнение итерации метода сеток подразделяется на два последовательных этапа, на первом из которых обрабатываются строки только с четными номерами, а затем на втором этапе — строки с нечетными номерами (см. рис. 11.7). Данная схема может быть обобщена на применение одновременно и к строкам, и к столбцам ( шахматное разбиение ) области расчетов.
Рассмотренная схема чередования строк не требует, по сравнению
с
Рассмотрим теперь возможность построения параллельного
алгоритма, который выполнял бы только те вычислительные действия,
что и последовательный метод (может быть только в несколько ином
порядке) и, как результат, обеспечивал бы получение точно таких
же решений исходной вычислительной задачи. Как уже было отмечено
выше, в последовательном алгоритме каждое очередное k -е
приближение значения ui,j вычисляется по последнему k -му
приближению значений ui-1,j и ui,j-1 и предпоследнему (k-1) -му
приближению значений ui+1,j и ui,j+1. Таким образом, при
требовании совпадения результатов вычислений последовательных и
параллельных вычислительных схем в начале каждой итерации метода
только одно значение u11 может быть пересчитано (возможности для
распараллеливания нет). Но далее после пересчета u11 вычисления
могут выполняться уже в двух узлах сетки u12 и u21 (в этих узлах
выполняются условия последовательной схемы), затем после
пересчета узлов u12 и u21 — в узлах u13, u22 и u31 и т.д.
Обобщая сказанное, можно увидеть, что выполнение итерации метода
сеток можно разбить на последовательность шагов, на каждом из
которых к вычислениям окажутся подготовленными узлы
вспомогательной диагонали сетки с номером, определяемым номером
этапа – см. рис. 11.8. Получаемая в результате вычислительная
схема получила наименование волны или фронта вычислений, а
алгоритмы, построенные на ее основе, — методов волновой обработки
данных ( wavefront или hyperplane methods ). Следует отметить, что
в нашем случае размер волны (степень возможного параллелизма)
динамически изменяется в ходе вычислений – волна нарастает до
своего пика, а затем затухает при приближении к правому нижнему
узлу сетки.
(рис 11.8) Движение фронта волны вычисленийВозможная схема параллельного метода, основанного на эффекте волны вычислений, может быть представлена в следующей форме.
Алгоритм 11.5. Параллельный алгоритм, реализующий волновую схему вычислений
// Алгоритм 11.5
omp_lock_t dmax_lock;
omp_init_lock(dmax_lock);
do {
dmax = 0; // максимальное изменение значений u
// нарастание волны (nx – размер волны)
for ( nx=1; nx<N+1; nx++ ) {
dm[nx] = 0;
#pragma omp parallel for shared(u,N,nx,dm) private(i,j,temp,d)
for ( i=1; i<nx+1; i++ ) {
j = nx + 1 – i;
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm[i] < d ) dm[i] = d;
} // конец параллельной области
}
// затухание волны
for ( nx=N-1; nx>0; nx-- ) {
#pragma omp parallel for shared(u,N,nx,dm) private(i,j,temp,d)
for ( i=N-nx+1; i<N+1; i++ ) {
j = 2*N - nx – I + 1;
temp = u[i][j];
u[i][j] = 0.25*(u[i-1][j]+u[i+1][j]+
u[i][j-1]+u[i][j+1]–h*h*f[i][j]);
d = fabs(temp-u[i][j])
if ( dm[i] < d ) dm[i] = d;
} // конец параллельной области
}
#pragma omp parallel for shared(nx,dm,dmax) private(i)
for ( i=1; i<nx+1; i++ ) {
omp_set_lock(dmax_lock);
if ( dmax < dm[i] ) dmax = dm[i];
omp_unset_lock(dmax_lock);
} // конец параллельной области
} while ( dmax > eps );
При разработке алгоритма, реализующего волновую схему
вычислений, оценку погрешности решения можно осуществлять для
каждой строки в отдельности (массив значений dm ). Этот массив
является общим для всех выполняемых потоков, однако синхронизации
доступа к элементам не требуется, так как потоки используют
всегда разные элементы массива (фронт волны вычислений содержит
только по одному узлу строк сетки).
После обработки всех элементов волны в составе массива dm находится максимальная погрешность выполненной итерации вычислений. Однако именно эта последняя часть расчетов может оказаться наиболее неэффективной из-за высоких дополнительных затрат на синхронизацию. Улучшение ситуации, как и ранее, может быть достигнуто за счет увеличения размера последовательных участков и сокращения, тем самым, количества необходимых взаимодействий параллельных участков вычислений. Возможный вариант реализации такого подхода может состоять в следующем:
chunk = 200; // размер последовательного участка
#pragma omp parallel for shared(n,dm,dmax) private(i,d)
for ( i=1; i<nx+1; i+=chunk ) {
d = 0;
for ( j=i; j<i+chunk; j++ )
if ( d < dm[j] ) d = dm[j];
omp_set_lock(dmax_lock);
if ( dmax < d ) dmax = d;
omp_unset_lock(dmax_lock);
} // конец параллельной области
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.5 | Параллельный алгоритм 11.6 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,30 | 0,21 | 210 | 0,16 | 0,40 |
| 200 | 273 | 0,34 | 273 | 0,86 | 0,40 | 273 | 0,59 | 0,58 |
| 300 | 0,88 | 305 | 1,63 | 0,54 | 305 | 1,53 | 0,57 | |
| 400 | 318 | 3,78 | 318 | 2,50 | 1,51 | 318 | 2,36 | 1,60 |
| 500 | 343 | 6,00 | 343 | 3,53 | 1,70 | 343 | 4,03 | 1,49 |
| 600 | 336 | 8,81 | 336 | 5,20 | 1,69 | 336 | 5,34 | 1,65 |
| 700 | 344 | 12,11 | 344 | 8,13 | 1,49 | 344 | 10,00 | 1,21 |
| 800 | 343 | 16,41 | 343 | 12,08 | 1,36 | 343 | 12,64 | 1,30 |
| 900 | 358 | 20,61 | 358 | 14,98 | 1,38 | 358 | 15,59 | 1,32 |
| 1000 | 351 | 25,59 | 351 | 18,27 | 1,40 | 351 | 19,30 | 1,33 |
| 2000 | 367 | 106,75 | 367 | 69,08 | 1,55 | 367 | 65,72 | 1,62 |
| 3000 | 370 | 243,00 | 370 | 149,36 | 1,63 | 370 | 140,89 | 1,72 |
( k – количество итераций, t – время (сек), S –
Подобный прием укрупнения последовательных участков вычислений для снижения затрат на синхронизацию именуется фрагментированием ( chunking ). Результаты экспериментов для данного варианта параллельных вычислений приведены в табл. 11.3.
Следует обратить внимание еще на один момент при анализе
В исследуемом нами алгоритме размещение данных в памяти
осуществляется по строкам, а фронт волны вычислений располагается
по диагонали сетки, и это приводит к низкой
(рис 11.9) Блочное представление сетки области расчетовПорождаемый на основе такого подхода метод вычислений в самом
общем виде может быть описан следующим образом (блоки образуют в
области расчетов прямоугольную решетку размера NBxNB ):
Алгоритм 11.6. Блочный подход к методу волновой обработки данных
// Алгоритм 11.6
// NB количество блоков
do {
// нарастание волны (размер волны равен nx+1)
for ( nx=0; nx<NB; nx++ ) {
#pragma omp parallel for shared(nx) private(i,j)
for ( i=0; i<nx+1; i++ ) {
j = nx – i;
// <обработка блока с координатами (i,j)>
} // конец параллельной области
}
// затухание волны
for ( nx=NB-2; nx>-1; nx-- ) {
#pragma omp parallel for shared(nx) private(i,j)
for ( i=0; i<nx+1; i++ ) {
j = 2*(NB-1) - nx – i;
// <обработка блока с координатами (i,j)>
} // конец параллельной области
}
// <определение погрешности вычислений>
} while ( dmax > eps );
Вычисления в предлагаемом алгоритме происходят в соответствии с волновой схемой обработки данных: вначале вычисления выполняются только в левом верхнем блоке с координатами (0,0), далее для обработки становятся доступными блоки с координатами (0,1) и (1,0) и т.д. – см. результаты экспериментов в табл. 11.3.
Блочный подход к методу волновой обработки данных существенным образом меняет состояние дел – обработку узлов можно организовать построчно, доступ к данным осуществляется последовательно по элементам памяти, перемещенные в кэш значения используются многократно. Кроме того, поскольку обработка блоков будет выполняться на разных процессорах и блоки не пересекаются по данным, при таком подходе будут отсутствовать и накладные расходы для обеспечения однозначности (когерентности) кэшей разных процессоров.
Наилучшие показатели применения кэша будут достигаться, если в кэше будет достаточно места для размещения не менее трех строк блока (при обработке строки блока используются данные трех строк блока одновременно). Тем самым, исходя из размера кэша, можно определить рекомендуемый максимально возможный размер блока. Например, при кэше 8 Кб и 8-байтовых значениях данных этот размер составит приближенно 300 (8Кб/3)/8). Можно определить и минимально допустимый размер блока из условия совпадения размеров строк кэша и блока. Так, при размере строки кэша 256 байт и 8-байтовых значениях данных размер блока должен быть кратен 32.
Последнее замечание необходимо сделать о взаимодействии граничных узлов блоков. Учитывая граничное взаимодействие, соседние блоки целесообразно обрабатывать на одних и тех же процессорах. В противном случае можно попытаться так определить размеры блоков, чтобы объем пересылаемых между процессорами граничных данных был минимален. Например, при размере строки кэша в 256 байт, 8-байтовых значениях данных и размере блока 64х64 объем пересылаемых данных – 132 строки кэша, при размере блока 128х32 – всего 72 строки. Такая оптимизация имеет наиболее принципиальное значение при медленных операциях пересылки данных между кэшами процессоров, т.е. для систем с неоднородным доступом к памяти ( nonuniform memory access — NUMA).
Как уже отмечалось ранее, вычислительная нагрузка при волновой
обработке данных изменяется динамически в ходе вычислений. Данный
момент следует учитывать при распределении вычислительной
нагрузки между процессорами. Так, например, при фронте волны из 5
блоков и при использовании 4 процессоров обработка волны
потребует двух параллельных итераций, во время второй из которых
будет задействован только один процессор, а все остальные
процессоры будут простаивать, дожидаясь завершения вычислений.
Достигнутое
Для обеспечения равномерности ( балансировки ) загрузки процессоров можно задействовать еще один подход, широко используемый для организации параллельных вычислений. Этот подход состоит в том, что все готовые к выполнению в системе вычислительные действия организуются в виде очереди заданий. В ходе вычислений освободившийся процессор может запросить для себя работу из этой очереди; появляющиеся по мере обработки данных новые вычислительные задания пополняют очередь заданий. Такая схема балансировки вычислительной нагрузки между процессорами является простой, наглядной и эффективной. Это позволяет говорить об использовании очереди заданий как общей модели организации параллельных вычислений для систем с общей памятью.
Указанная схема балансировки может быть применена и для рассматриваемого примера. В самом деле, в ходе обработки фронта текущей волны происходит постепенное формирование блоков следующей волны вычислений. Эти блоки могут быть задействованы для обработки при нехватке достаточной вычислительной нагрузки для процессоров.
Общая схема вычислений с использованием очереди заданий может быть представлена в следующем виде:
Алгоритм 11.7. Общая схема вычислений с использованием очереди
// Алгоритм 11.7
// <инициализация служебных данных>
// <загрузка в очередь указателя на начальный блок>
// взять блок из очереди (если очередь не пуста)
while ( (pBlock=GetBlock()) != NULL ) {
// <обработка блока>
// отметка готовности соседних блоков
omp_set_lock(pBlock->pNext.Lock); // сосед справа
pBlock->pNext.Count++;
if ( pBlock->pNext.Count == 2 )
PutBlock(pBlock->pNext);
omp_unset_lock(pBlock->pNext.Lock);
omp_set_lock(pBlock->pDown.Lock); // сосед снизу
pBlock->pDown.Count++;
if ( pBlock->pDown.Count == 2 )
PutBlock(pBlock->pDown);
omp_unset_lock(pBlock->pDown.Lock);
} // завершение вычислений, т.к. очередь пуста
Для описания имеющихся в задаче блоков узлов сетки в алгоритме используется структура со следующим набором параметров:
Lock – семафор, синхронизирующий доступ к описанию блока;pNext – указатель на соседний справа блок;pDown – указатель на соседний снизу блок;Count – счетчик готовности блока к вычислениям (количество готовых границ блока).Операции для выборки из очереди и вставки в очередь указателя
на готовый к обработке блок узлов сетки обеспечивают
соответственно функции GetBlock и PutBlock.
Как следует из приведенной схемы, процессор извлекает блок для обработки из очереди, выполняет необходимые вычисления для блока и отмечает готовность своих границ для соседних справа и снизу блоков. Если при этом оказывается, что у соседних блоков являются подготовленными обе границы, процессор передает эти блоки для запоминания в очередь заданий.
Использование очереди заданий позволяет решить практически все оставшиеся вопросы организации параллельных вычислений для систем с общей памятью. Развитие рассмотренного подхода может предусматривать уточнение правил выделения заданий из очереди для согласования с состояниями процессоров (близкие блоки целесообразно обрабатывать на одних и тех же процессорах), расширение числа имеющихся очередей заданий и т.п. Дополнительная информация по этим вопросам может быть получена, например, в [, ].
Использование процессоров с распределенной памятью является другим общим способом построения многопроцессорных вычислительных систем. Актуальность их становится все более высокой в последнее время в связи с широким развитием высокопроизводительных кластерных вычислительных систем (см. лекцию 1).
Многие проблемы параллельного программирования (состязание вычислений, тупики, сериализация) являются общими для систем с общей и распределенной памятью. Основной момент, который отличает параллельные вычисления с распределенной памятью, состоит в том, что взаимодействие параллельных участков программы на разных процессорах может быть обеспечено только при помощи передачи сообщений ( message passing ).
Следует отметить, что вычислительный узел системы с распределенной памятью является, как правило, более сложным вычислительным устройством, чем процессор в многопроцессорной системе с общей памятью. Для учета этих различий в дальнейшем процессор с распределенной памятью будет именоваться вычислительным сервером (сервером может быть, в частности, многопроцессорная система с общей памятью). При проведении всех ниже рассмотренных экспериментов использовались 4 компьютера с процессорами Pentium IV, 1300 Mhz, 256 RAM, 100 Mbit Fast Ethernet.
Первая проблема, которую приходится решать при организации
параллельных вычислений на
(рис 11.10) Ленточное разделение области расчетов между процессорами (кружки представляют граничные узлы сетки)В рассматриваемом учебном примере по решению
При ленточном разбиении область расчетов делится на горизонтальные или вертикальные полосы (не уменьшая общности, далее будем рассматривать только горизонтальные полосы). Число полос определяется количеством процессоров, размер полос обычно является одинаковым, узлы горизонтальных границ (первая и последняя строки) включаются в первую и последнюю полосы соответственно. Полосы для обработки распределяются между процессорами.
Основной момент при организации вычислений с подобным разделением данных состоит в том, что на процессор, выполняющий обработку какой-либо полосы, должны быть продублированы граничные строки предшествующей и следующей полос вычислительной сетки (получаемые в результате расширенные полосы показаны на рис. 11.10 справа пунктирными рамками). Продублированные граничные строки полос используются только при проведении расчетов, пересчет же этих строк происходит в полосах своего исходного месторасположения. Тем самым, дублирование граничных строк должно осуществляться перед началом выполнения каждой очередной итерации метода сеток.
Параллельный вариант метода сеток при ленточном разделении данных состоит в обработке полос на всех имеющихся серверах одновременно в соответствии со следующей схемой работы:
Алгоритм 11.8. Параллельный алгоритм, реализующий метод сеток при ленточном разделении данных
// Алгоритм 11.8
// схема Гаусса-Зейделя, ленточное разделение данных
// действия, выполняемые на каждом процессоре
do {
// <обмен граничных строк полос с соседями>
// <обработка полосы>
// <вычисление общей погрешности вычислений dmax>}
while ( dmax > eps ); // eps — точность решения
Для конкретизации представленных в алгоритме действий введем обозначения:
ProcNum – номер процессора, на котором выполняются описываемые действия,PrevProc, NextProc – номера соседних процессоров, содержащих предшествующую и следующую полосы,NP – количество процессоров,M – количество строк в полосе (без учета продублированных граничных строк),N – количество внутренних узлов в строке сетки (т.е. всего в строке N+2 узла).При нумерации строк полосы будем считать, что строки 0 и M+1
есть продублированные из соседних полос граничные строки, а
строки собственной полосы процессора имеют номера от 1 до M.
(рис 11.11) Схема передачи граничных строк между соседними процессорамиПроцедура обмена граничных строк между соседними процессорами может быть разделена на две последовательные операции, во время первой из которых каждый процессор передает свою нижнюю граничную строку следующему процессору и принимает такую же строку от предыдущего процессора (см. рис. 11.11). Вторая часть передачи строк выполняется в обратном направлении: процессоры передают свои верхние граничные строки своим предыдущим соседям и принимают переданные строки от следующих процессоров.
Выполнение подобных
// передача нижней граничной строки следующему // процессору и прием передаваемой строки от // предыдущего процессора if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc); if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
(для записи процедур приема-передачи используется близкий к
стандарту Send ) или источник
(для операции Receive ) пересылки данных).
Для передачи данных могут быть задействованы два различных механизма. При первом из них выполнение программ, инициировавших операцию передачи, приостанавливается до полного завершения всех действий по пересылке данных (т.е. до момента получения процессором-адресатом всех передаваемых ему данных). Операции приема-передачи, реализуемые подобным образом, обычно называются синхронными или блокирующими. Иной подход – асинхронная или неблокирующая передача — может состоять в том, что операции приема-передачи только инициируют процесс пересылки и на этом завершают свое выполнение. В результате программы, не дожидаясь завершения длительных коммуникационных операций, могут продолжать свои вычислительные действия, проверяя по мере необходимости готовность передаваемых данных. Оба эти варианта операций передачи широко используются при организации параллельных вычислений и имеют свои достоинства и свои недостатки. Синхронные процедуры передачи, как правило, более просты для применения и более надежны; неблокирующие операции могут позволить совместить процессы передачи данных и вычислений, но обычно приводят к повышению сложности программирования. С учетом вышесказанного во всех последующих примерах для организации пересылки данных будут использоваться операции приема-передачи блокирующего типа.
Приведенная выше последовательность блокирующих операций
приема-передачи данных (вначале Send, затем Receive ) приводит к
строго последовательной схеме выполнения процесса пересылок
строк, т.к. все процессоры одновременно обращаются к операции Send и переходят в режим ожидания. Первым процессором, который
окажется готовым к приему пересылаемых данных, будет сервер с
номером NP-1. В результате процессор NP-2 выполнит операцию
передачи своей граничной строки и перейдет к приему строки от
процессора NP-3 и т.д. Общее количество повторений таких операций
равно NP-1. Аналогично происходит выполнение и второй части
процедуры пересылки граничных строк перед началом обработки строк
(см. рис. 11.11).
Последовательный характер рассмотренных операций пересылок данных определяется выбранным способом очередности выполнения. Изменим этот порядок очередности при помощи чередования приема и передачи для процессоров с четными и нечетными номерами.
// передача нижней граничной строки следующему
// процессору и прием передаваемой строки от
// предыдущего процессора
if ( ProcNum % 2 == 1 ) { // нечетный процессор
if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc);
if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
}
else { // процессор с четным номером
if ( ProcNum != 0 ) Receive(u[0][*],N+2,PrevProc);
if ( ProcNum != NP-1 ) Send(u[M][*],N+2,NextProc);
}
Данный прием позволяет выполнить все необходимые операции
передачи всего за два последовательных шага. На первом шаге все
процессоры с нечетными номерами отправляют данные, а процессоры с
четными номерами осуществляют прием этих данных. На втором шаге
роли процессоров меняются – четные процессоры выполняют Send,
нечетные процессоры исполняют операцию приема Receive.
Рассмотренные последовательности операций приема-передачи для
взаимодействия соседних процессоров широко используются в
практике параллельных вычислений. Как результат, во многих
базовых библиотеках параллельных программ имеются процедуры для
поддержки подобных действий. Так, в стандарте Sendrecv, с использованием которой
предыдущий фрагмент программного кода может быть записан более
кратко:
// передача нижней граничной строки следующему // процессору и прием передаваемой строки от // предыдущего процессора Sendrecv(u[M][*],N+2,NextProc,u[0][*],N+2,PrevProc);
Реализация подобной объединенной функции Sendrecv обычно осуществляется таким образом, чтобы обеспечить и корректную работу на крайних процессорах, когда не нужно выполнять одну из операций передачи или приема, и организацию чередования процедур передачи на процессорах для ухода от тупиковых ситуаций, и возможности параллельного выполнения всех необходимых пересылок данных.
Для завершения круга вопросов, связанных с параллельной
реализацией метода сеток на log2NP параллельных итераций для
получения конечного значения ( NP – количество процессоров).
С учетом возможности применения
Reduce(dm,dmax,op,proc) – процедура сборки на процессоре proc
итогового результата dmax среди локальных на каждом процессоре
значений dm с применением операции op ;Broadcast(dmax,proc) – процедура рассылки с процессора proc
значения dmax всем имеющимся процессорам системы.С учетом перечисленных процедур общая схема вычислений на каждом процессоре может быть представлена в следующем виде:
// Алгоритм 11.8 – уточненный вариант
// схема Гаусса-Зейделя, ленточное разделение данных
// действия, выполняемые на каждом процессоре
do {
// обмен граничных строк полос с соседями
Sendrecv(u[M][*],N+2,NextProc,u[0][*],N+2,PrevProc);
Sendrecv(u[1][*],N+2,PrevProc,u[M+1][*],N+2,NextProc);
// <обработка полосы с оценкой погрешности dm>
// вычисление общей погрешности вычислений dmax
Reduce(dm,dmax,MAX,0);
Broadcast(dmax,0);
} while ( dmax > eps ); // eps — точность решения
(в приведенном алгоритме переменная dm представляет собой
локальную погрешность вычислений на отдельном процессоре, а
параметр MAX задает операцию поиска максимального значения для
операции сборки). Следует отметить, что в составе Allreduce, которая совмещает действия редукции и
рассылки данных. Результаты экспериментов для данного варианта
параллельных вычислений для метода Гаусса – Зейделя приведены в
рис. 11.4.
Представленные в пп. 11.3.1 – 11.3.3 алгоритмы определяют
общую схему параллельных вычислений для метода сеток в
многопроцессорных
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм 11.8 | Параллельный алгоритм с волновой схемой расчета (см. п. 11.3.4) | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,54 | 0,11 | 210 | 1,27 | 0,05 |
| 200 | 273 | 0,35 | 273 | 0,86 | 0,41 | 273 | 1,37 | 0,26 |
| 300 | 305 | 0,92 | 305 | 0,92 | 1,00 | 305 | 1,83 | 0,50 |
| 400 | 318 | 1,69 | 318 | 1,27 | 1,33 | 318 | 2,53 | 0,67 |
| 500 | 343 | 2,88 | 343 | 1,72 | 1,68 | 343 | 3,26 | 0,88 |
| 600 | 336 | 4,04 | 336 | 2,16 | 1,87 | 336 | 3,66 | 1,10 |
| 700 | 344 | 5,68 | 344 | 2,52 | 2,25 | 344 | 4,64 | 1,22 |
| 800 | 343 | 7,37 | 343 | 3,32 | 2,22 | 343 | 5,65 | 1,30 |
| 900 | 358 | 9,94 | 358 | 4,12 | 2,41 | 358 | 7,53 | 1,32 |
| 1000 | 351 | 11,87 | 351 | 4,43 | 2,68 | 351 | 8,10 | 1,46 |
| 2000 | 367 | 50,19 | 367 | 15,13 | 3,32 | 367 | 27,00 | 1,86 |
| 3000 | 364 | 113,17 | 364 | 37,96 | 2,98 | 364 | 55,76 | 2,03 |
( k – количество итераций, t – время (сек), S –
В завершение рассмотрим возможность организации параллельных
вычислений, при которых обеспечивалось бы нахождение таких же
решений
(рис 11.12) Организация волны вычислений при ленточной схеме разделения данныхИнтересный момент при организации подобной схемы параллельных вычислений может состоять в попытке совмещения операций пересылки граничных строк и действий по обработке блоков данных.
Ленточная схема разделения данных может быть естественным
образом обобщена на блочный способ представления сетки области
расчетов (см. рис. 11.9). При этом столь радикальное изменение
способа разбиения сетки практически не потребует каких-либо
существенных корректировок рассмотренной схемы параллельных
вычислений. Основной новый момент при блочном представлении
данных состоит в увеличении количества граничных строк на каждом
процессоре (для блока их количество становится равным 4), что
приводит, соответственно, к большему числу (N+2)
значения. Для блочного же способа происходит 8 операций пересылки
и объем каждого сообщения равен $$(N/ \sqrt{NP}+2)$$ ( N – количество
внутренних узлов сетки, NP – число процессоров, размер всех
блоков предполагается одинаковым). Тем самым, блочная схема
представления области расчетов становится оправданной при большом
количестве узлов сетки, когда увеличение количества
коммуникационных операций приводит к снижению затрат на пересылку
данных в силу сокращения размеров передаваемых сообщений.
Результаты экспериментов при блочной схеме разделения данных
приведены в табл. 11.5.
| Размер сетки | Последовательный метод Гаусса - Зейделя (алгоритм 11.1) | Параллельный алгоритм с блочной схемой рассчета (см. п. 11.3.5) | Параллельный алгоритм 11.9 | |||||
|---|---|---|---|---|---|---|---|---|
k |
t |
k |
t |
S |
k |
t |
S | |
| 100 | 210 | 0,06 | 210 | 0,71 | 0,08 | 210 | 0,60 | 0,10 |
| 200 | 273 | 0,35 | 273 | 0,74 | 0,47 | 273 | 1,06 | 0,33 |
| 300 | 305 | 0,92 | 305 | 1,04 | 0,88 | 305 | 2,01 | 0,46 |
| 400 | 318 | 1,69 | 318 | 1,44 | 1,18 | 318 | 2,63 | 0,64 |
| 500 | 343 | 2,88 | 343 | 1,91 | 1,51 | 343 | 3,60 | 0,80 |
| 600 | 336 | 4,04 | 336 | 2,39 | 1,69 | 336 | 4,63 | 0,87 |
| 700 | 344 | 5,68 | 344 | 2,96 | 1,92 | 344 | 5,81 | 0,98 |
| 800 | 343 | 7,37 | 343 | 3,58 | 2,06 | 343 | 7,65 | 0,96 |
| 900 | 358 | 9,94 | 358 | 4,50 | 2,21 | 358 | 9,57 | 1,04 |
| 1000 | 351 | 11,87 | 351 | 4,90 | 2,42 | 351 | 11,16 | 1,06 |
| 2000 | 367 | 50,19 | 367 | 16,07 | 3,12 | 367 | 39,49 | 1,27 |
| 3000 | 364 | 113,17 | 364 | 39,25 | 2,88 | 364 | 85,72 | 1,32 |
( k – количество итераций, t – время (сек), S –
При блочном представлении сетки может быть реализован также и волновой метод выполнения расчетов (см. рис. 11.13). Пусть процессоры образуют прямоугольную решетку размером $$NB\times NB (NB=\sqrt{NP})$$ и процессоры пронумерованы от 0 слева направо по строкам решетки.
Общая схема параллельных вычислений в этом случае имеет вид:
Алгоритм 11.9. Блочная схема разделения данных
// Алгоритм 11.9
// схема Гаусса-Зейделя, блочное разделение данных
// действия, выполняемые на каждом процессоре
do {
// получение граничных узлов
if ( ProcNum / NB != 0 ) { // строка не нулевая
// получение данных от верхнего процессора
Receive(u[0][*],M+2,TopProc); // верхняя строка
Receive(dmax,1,TopProc); // погрешность
}
if ( ProcNum % NB != 0 ) { // столбец не нулевой
// получение данных от левого процессора
Receive(u[*][0],M+2,LeftProc); // левый столбец
Receive(dm,1,LeftProc); // погрешность
if ( dm > dmax ) dmax = dm;
}
// <обработка блока с оценкой погрешности dmax>
// пересылка граничных узлов
if ( ProcNum / NB != NB-1 ) { // строка решетки не последняя
// пересылка данных нижнему процессору
Send(u[M+1][*],M+2,DownProc); // нижняя строка
Send(dmax,1,DownProc); // погрешность
}
if ( ProcNum % NB != NB-1 ) { // столбец решетки не последний
// пересылка данных правому процессору
Send(u[*][M+1],M+2,RightProc); // правый столбец
Send(dmax,1, RightProc); // погрешность
}
// синхронизация и рассылка погрешности dmax
Broadcast(dmax,NP-1);
} while ( dmax > eps ); // eps — точность решения
При реализации алгоритма необходимо обеспечить, чтобы в начальный момент времени все процессоры (кроме процессора с нулевым номером) оказались в состоянии передачи своих граничных узлов (верхней строки и левого столбца). Вычисления должен начинать процессор с левым верхним блоком, после завершения обработки которого обновленные значения правого столбца и нижней строки блока нужно переправить правому и нижнему процессорам решетки соответственно. Данные действия обеспечат снятие блокировки процессоров второй диагонали процессорной решетки (ситуация слева на рис. 11.13) и т.д.
Анализ NB подобных шагов в
обработке будет находиться одновременно NB итераций и все
процессоры окажутся задействованными. Подобная схема организации
расчетов позволяет рассматривать имеющуюся процессорную решетку
как вычислительный конвейер поэтапного выполнения итераций метода
сеток. Остановка конвейера может осуществляться, как и ранее, по
максимальной погрешности вычислений (проверку условия остановки
следует начинать только при достижении полной загрузки конвейера
после запуска NB итераций расчетов). Необходимо отметить также,
что получаемое после выполнения условия остановки решение задачи
Дирихле будет содержать значения узлов сетки от разных итераций
метода и не будет, тем самым, совпадать с решением, получаемым
при помощи исходного последовательного алгоритма.
(рис 11.13) Организация волны вычислений при блочной схеме разделения данных
Время выполнения коммуникационных операций значительно превышает длительность вычислительных команд. Оценка трудоемкости операций приема-передачи может быть осуществлена с использованием двух основных характеристик сети передачи: латентности ( latency ), определяющей время подготовки данных к передаче по сети, и пропускной способности сети ( bandwidth ), задающей объем передаваемых по сети за 1 секунду данных, – более полное изложение вопроса содержится в лекции 3.
Пропускная способность наиболее распространенной на данный момент сети Fast Ethernet – 100 Mбит/с, для более современной сети Gigabit Ethernet – 1000 Мбит/с. В то же время скорость передачи данных в системах с общей памятью обычно составляет сотни и тысячи миллионов байт в секунду. Тем самым, использование систем с распределенной памятью приводит к снижению скорости передачи данных не менее чем в 100 раз.
Еще хуже дело обстоит с латентностью. Для сети Fast Ethernet
эта характеристика имеет значение порядка 150 мкс, для сети
Gigabit Ethernet – около 100 мкс. Для современных компьютеров с
тактовой частотой свыше 2 ГГц различие в производительности
достигает не менее чем 10000 – 100000 раз. При указанных
характеристиках вычислительной системы для достижения 90% N=7500 узлов по
вертикали и горизонтали (объем вычислений в блоке составляет 5N2
операций с плавающей запятой).
Как результат, можно заключить, что
(рис 11.14) Операции передачи данных при выполнении метода сеток с распределенной памятьюMPI_Bcast );MPI_Scatter );MPI_Sendrecv );MPI_Allreduce );MPI_Gather ).В лекции рассматриваются вопросы организации параллельных
вычислений для решения задач, в которых при математическом
моделировании используются дифференциальные уравнения в частных
производных. Для численного решения подобных задач обычно
применяется метод конечных разностей ( метод сеток ), обладающий
высокой вычислительной трудоемкостью. В лекции последовательно
разбираются возможные способы распараллеливания сеточных методов
на многопроцессорных вычислительных системах с общей и
распределенной памятью. При этом большое внимание уделяется
проблемам, возникающим при организации параллельных вычислений,
анализу причин появления таких проблем и нахождению путей их
преодоления. Для наглядной демонстрации излагаемого материала в
качестве учебного примера рассматривается проблема численного
решения
В пункте 11.1 приводится краткое описание сеточных методов на
примере решения
В пункте 11.2 даются возможные способы организации параллельных вычислений при численном решении дифференциальных уравнений в частных производных для вычислительных систем с общей памятью. В основе излагаемого подхода – технология OpenMP, широко применяемая в настоящее время для разработки параллельных программ. В рамках этой технологии параллельный программный код формируется программистом посредством добавления специальных директив или комментариев в существующие последовательные программы. Как результат, программный код является единым для последовательных и параллельных программ, что делает более простым развитие и сопровождение программного обеспечения.
Следует отметить, что принятая в лекции последовательность
представления учебного материала может быть рассмотрена как
наглядная демонстрация поэтапной методики разработки программного
обеспечения. Такой подход позволяет достаточно быстро получать
начальные варианты параллельных программ, которые далее могут
совершенствоваться для достижения максимально возможной
В пункте 11.3 вопросы организации параллельных вычислений при
численном решении дифференциальных уравнений в частных
производных рассматриваются применительно к вычислительным
системам с распределенной памятью. Прежде всего отмечается, что
многие проблемы параллельного программирования (состязание
вычислений, тупики, сериализация) являются общими для систем с
общей и распределенной памятью. Основное отличие параллельных
вычислений с распределенной памятью состоит в том, что
взаимодействие параллельных участков программы на разных
процессорах может быть обеспечено только при помощи передачи
сообщений ( message passing ). При этом
Изложение учебного материала данного раздела лекции начинается
с обсуждения общих принципов распределения данных между
процессорами, которые применительно к рассматриваемой учебной
Дополнительная информация по численным методам решения дифференциальных уравнений в частных производных может быть получена в [, ]. Рассмотрение вопросов организации при численном решении дифференциальных уравнений в частных производных проводится в [, , , ].
При рассмотрении вопросов организации памяти компьютеров могут оказаться полезными работы [, ].
Технология
Более подробное рассмотрение вопросов, связанных с использованием очередей заданий при организации параллельных вычислений, проводится в [, ].
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.