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

Алгоритмы решения систем линейных уравнений с блочно-ленточными матрицами. Монография

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
Алгоритмы решения СЛАУ с блочно-ленточными матрицами. Глава 4 81
Таблица 3. Результаты тестирования быстродействия алгоритма
Процессор Последовательное
в случае аффинной рекуррентной зависимости
выполнение (сек.)
Intel Xeon 0.16625 0.274417 6.0583
Выполнение 4-мя
потоками (сек.)
Ускорение
Объяснение причин ускорения
Наиболее долгими операциями считаем обращения к памяти (чтение и
запись).
В представленных примерах отображение из рекуррентно замкнутого
множества описывается несколькими числами; в частности, в распаралле­ливаемом цикле с аффинной рекуррентной зависимостью — двумя. Для вычисления суперпозиции двух таких отображений необходимые данные, аргументы операции «суперпозиция» и результат, помещаются в регистрах общего назначения. Суперпозиция нескольких таких отображений вычис­ляется в цикле, и компилятор не сохраняет в памяти суперпозиции, которые являются промежуточными данными, а временно хранит их в регистрах; в оперативную память попадает только результирующая суперпозиция. Чтения исходных отображений происходят из оперативной памяти и ав­томатически сохраняются в кэш-памяти. Впоследствии, при вычислении всех результирующих данных, эти исходные отображения уже читаются из кэш-памяти.
Таким образом, как при последовательном вычислении, так и в пред-
ставленном алгоритме исходные данные читаются по одному разу из опе­ративной памяти.
Запись в оперативную память при последовательном выполнении про-
исходит следующим образом. Перед записью в оперативную память в бу­ферной памяти накапливается кэш-линейка результирующих значений, после чего вся кэш-линейка сбрасывается в память. Далее шина, связыва­ющая память и процессор, ждет формирования следующей кэш-линейки.
Запись в оперативную память при параллельном выполнении происхо-
дит следующим образом. Перед записью в оперативную память накапли­ваются кэш-линейки во всех вычислительных ядрах. Пока накапливается кэш-линейка в одном процессорном ядре, в оперативную память сбрасыва­ется кэш-линейка другого процессорного ядра.
Таким образом, при параллельном алгоритме процесс записи в память
происходит интенсивно, а при последовательном — с большими задержка­ми в ожидании формирования кэш-линеек.
82 Б. Я. Штейнберг, О. Б. Штейнберг
4.6. Векторизация рекуррентных циклов
Рассмотрим цикл с операцией α:
for (i = 0; i < N; i++) {
 X[i]=Y[i]αZ[i]; (13)
}
Операцию α будем называть векторной, если на вычислительном железе имеется аналогичная операция над векторами (например, над векторными регистрами [67]). Векторизацию можно воспринимать частным случаем SIMD распараллеливания на архитектуре с общей памятью.
Рассмотрим следующий цикл, эквивалентный (13):
for (i = 0; i < N/4; i++) {
 VX[i]=VY[i]αVZ[i];
}
Добавление префикса V к именам массивов показывает, что новые мас­сивы рассматриваются как массивы векторов (в данном случае — длины 4). При этом они располагаются в памяти по тем же адресам, что и исходные массивы. Тогда, если операция в цикле (13) является векторной, а результат векторного выполнения цикла эквивалентен результату выполнения после­довательного, то будем называть его векторизуемым.
Определение векторизуемого цикла дано только для циклов, содержа­щих один оператор присваивания. В случае, когда тело цикла содержит больше операторов (причем, не обязательно только операторов присваива­ния), его иногда можно заменить несколькими циклами, некоторые из кото­рых окажутся векторизуемыми.
Лэмпорт показал в работе [68], что класс векторизуемых программ мо­жет быть расширен за счет применения вспомогательных преобразований.
Рекуррентные циклы обычно воспринимаются как циклы, не допускаю­щие параллельного выполнения. Но это не совсем так.
Пример 36. Рассмотрим рекуррентный цикл:
for (i = 0; i < N; i++) { X[i+P] = X[i]*Y; }
Заметим, что ни SIMD, ни MIMD выполнение не даст результат, эквивалентный последовательному выполнению данного цикла.
Алгоритмы решения СЛАУ с блочно-ленточными матрицами. Глава 4 83
С другой стороны, если вычисления ведутся на архитектуре, спо­собной одновременно выполнять умножение не для всех значений счетчика цикла (как SIMD), а для P, то результат будет эквивалентен последовательному выполнению.
Этот цикл является векторизуемым на архитектуре, содержащей операцию сложения векторов длины P. Чтобы подчеркнуть вектори­зуемость, запишем цикл следующим образом:
for (i = 0; i < N/P; i++) {
VX[i+P] = VX[i]+VVY;
}
В данном цикле префикс VV обозначает вектор, состоящий из эле­ментов, указывающих на одну и ту же ячейку памяти.
Конец примера.
Рассмотрим рекуррентный цикл более сложного вида:
X[0] = h[0];
X[1] = h[1];
...
X[m-1] = h[m-1];
for (i = m; i < N + m; i++) {
X[i] = a
X[i-1]+a2X[i-2]+...+amX[i-m]+a0; (14)
1
}
где a
≠ 0. Не умаляя общности, будем считать, что число итераций цикла
m
кратно 4 (иначе можно отщепить несколько последних итераций, оставив в основном цикле количество итераций, кратное 4).
Произведем «развертку цикла» кратности 4 [54, 59]:
X[0] = h[0];
X[1] = h[1];
...
X[m-1] = h[m-1];
for (i = m; i < N+m; i = i+4) {
X[i] = a
X[i-1]+a2X[i-2]+...+amX[i-m]+a0;
1
X[i+1] = a1X[i]+a2X[i-1]+...+amX[i-m+1]+a0;
X[i+2] = a1X[i+1]+a2X[i]+...+amX[i-m+2]+a0;
X[i+3] = a1X[i+2]+a2X[i+1]+...+amX[i-m+3]+a0;
}
84 Б. Я. Штейнберг, О. Б. Штейнберг
1 0 00
xi






∗=



 

[ 1]
[]
xi












 
     
[ 1]
[]
xi







∗∗





 
Рассмотрим операторы присваивания тела цикла как СЛАУ от неизвест­ных x[i], x[i + 1], x[i + 2], x[i + 3]. Перенесем их в получившихся равенствах в левую часть и получим следующее матричное уравнение:
[]
1 00
a
1
aa
−−
21
aaa
−−−
321
10
[ 1]
xi
+
[ 2]
xi
+
1
[ 3]
xi
+
aa a a a a a
12 3 2 1 0
aa a a a a
23 2 1 0
=
aa a a a
34 1 0
aa a a
45 0
...
m m mm
−−−
... 0
mm m
−−
... 0 0
mm
... 0 0 0
m
[ 2]
xi
...
xi m
1
.
Так как в левой части матрица имеет нижнетреугольную форму, то у нее существует обратная. Умножим обе части получившегося равенства слева на эту матрицу и получим:
1
[]
xi
 
[ 1]
xi
+
 
+
[ 2]
xi

[ 3]
xi
+

aa a a a a a
12 3 2 1 0
aa a a a a
23 2 1 0
aa a a a
34 1 0
aa a a
45 0
...
... 0
... 0 0
... 0 0 0
1 0 00
1 00
a
1
=
−−
aa
21
aaa
−−−
321
m m mm
−−−
mm m
−−
mm
m
10
1
[ 2]
xi
...
xi m
1
.
Результат произведения двух матриц правой части равенства обозначим через матрицу C.
X[0] = h[0]; X[1] = h[1]; ... X[m-1] = h[m-1];
Алгоритмы решения СЛАУ с блочно-ленточными матрицами. Глава 4 85
d
  
 
for (i = m; i < N + m; i = i + 4) {
 X[i]=с
 X[i+1]=с
 X[i+2]=с
 X[i+3]=с
X[i-1]+с
1,1
X[i-1]+с
2,1
X[i-1]+с
3,1
X[i-1]+с
4,1
X[i-2]+...+с
1,2
X[i-2]+...+с
2,2
X[i-2]+...+с
3,2
X[i-2]+...+с
4,2
X[i-m]+d1;
1,m
X[i-m]+d2;
2,m
X[i-m]+d3;
3,m
X[i-m]+d4;
4,m
}
где d
= a0, d2 = a0– a1, d3 = a0– a1– a2, d4 = a0– a1– a2– a3.
1
Теперь цикл можно векторизовать следующим образом:
X[0] = h[0];
X[1] = h[1];
...
X[m-1] = h[m-1];
for (i = m; i < N + m; i = i + 4) {
VX[i] = vc
VVX[i-1]+ vc
i,1
VVX[i-2]+...+ vc
i,2
VVX[i-m]+vd;
i,m
}
1
d
где
vd =
2
.
d
3
d
4
4.7. Совместное применение распараллеливания и векторизации циклов с линейной
рекуррентной зависимостью
В данном разделе предложен алгоритм автоматического распараллели­вания рекуррентных циклов с линейной рекуррентной зависимостью. За­тем предлагается способ векторизации полученного кода. Кроме того, рас­сматриваемый метод может быть скомбинирован с методом, изложенным в [69].
Далее будет рассматриваться задача распараллеливания цикла (14).
Распараллеливание выполняется на вычислительную модель, состоя­щую из 2k вычислительных устройств (k N). Описываемый алгоритм мож­но использовать в случае архитектуры с общей памятью, распределенной памятью, а также и на специализированных параллельных вычислительных системах. От типа памяти, как и от других элементов архитектуры, зависит эффективность, но не зависит корректность и устойчивость алгоритма.
86 Б. Я. Штейнберг, О. Б. Штейнберг
Не умаляя общности, будем считать, что N кратно m. Если это не так, то применим преобразование «расщепление цикла», заменяя фрагмент про­граммы (14) следующим фрагментом:
X[0] = h[0]; X[1] = h[1]; ... X[m-1] = h[m-1]; for (i = m; i < N+m — (N % m); i++) { X[i] = a
X[i-1]+a2X[i-2]+...+amX[i-m]+a0;
1
} for (i = N+m — (N % m); i < N+m; i++) { X[i] = a1X[i-1]+a2X[i-2]+...+amX[i-m]+a0; }
Количество итераций в первом цикле равно N – (N % m), а во втором, соответственно, N % m. После этого к первому циклу можно применить рассматриваемый алгоритм. Второй цикл можно не распараллеливать, по­скольку он содержит мало итераций.
Алгоритм параллельного вычисления цикла с линейной
рекуррентной зависимостью
Применяя к циклу (14) преобразование «раскрутка цикла», получим:
X[0] = h[1]; X[1] = h[2]; ... X[m-1] = h[m-1]; X[m] = a X[m+1] = amX[1]+a ... X[i+N-1] = amX[N-1]+a
В результате получим код, вычисление которого равносильно решению системы линейных алгебраических уравнений (СЛАУ). Данной СЛАУ со­ответствует следующая расширенная матрица:
X[0]+a
m
X[1]+...+a1X[m-1]+a0;
m-1
X[2]+...+a1X[m]+a0;
m-1
X[N]+...+a1X[N+m-2]+a0;
m-1
Алгоритмы решения СЛАУ с блочно-ленточными матрицами. Глава 4 87
          
         

a
    

1 21
32
...
, .
aa a a
a aa
AB
    

1 0 ... 0 0 0 ... 0 0 0
0 1 ... 0 0 0 ... 0 0 0
... ... ... ... ... ... ... ... ... ...
0 0 ... 1 0 0 ... 0 0 0
aa a
−−
mm
0 ... 1 ... 0 0 0
−−
... 1 0 ... 0 0 0
11
a aa
m
21
... ... ... ... ... ... ... ... ... ...
0 0 ... ... 1 0 0
0 0 ... 0 ... 1 0 0 0 ... 0 0
aa a
−− −
mm m
−−
12
aa a
−−
mm
11
a
m
... 1
aa
−−
21
[0]
h
[1]
h
...
[ 1]
hm a
0
a
0
...
a
0
a
0
a
0
Эту матрицу в свою очередь можно представить в виде блочной [70]
двухдиагональной матрицы:
h
0 0 0 ... 0 0 0
I
BA
0 0 ... 0 0 0 0 0 ... 0 0 0
0 0 ... 0 0 0
BA
BA
... ... ... ... ... ... ... ...
0 0 0 0 ... 0 0 0 0 0 0 ... 0 0 0 0 0 ... 0
1 0 ... 0 0 [0]
   
0 1 ... 0 0 [1]

I h= a =
=
где
... ... ... ... ... ... ...

0 0 ... 1 0 [ 2]

0 0 ... 0 1 [ 1

A
BA
, , ,
(0)
a
(0)
a
(0)
,
a
...
(0)
a
(0)
a
BA
(0)
a
h
h
hm a
hm
(0)
0
a
0
0
a
0
.
1 0 ... 0 0
 

... ... ... ... ...
= =

aa
−−

aa a
−−

1 ... 0 0
a
1
23
mm
−−
12 1
mm
−−
... 1 0 ... 1
mm
0 ...
m
... ... ... ... ...
0 0 ... 0 0 ... 0
aa
mm
1
a
m
Матрица A является нижнетреугольной с главной диагональю, состоя-
щей из единиц, а значит — обратимой. В блочной матрице умножим слева все строки, кроме первой, на A–1. Получим:
88 Б. Я. Штейнберг, О. Б. Штейнберг
         

−−
1,1 1,2 1, 1
1 1 1 (1)
m,1 m,2 m,
1 1 ... 1+− − + + + − +
() () ()
mm
x[i m ]= b x[i m] b x[i m + ] b x[i ] a ;
BI
         

( )
2
− =+=+
h
(1)
a
(1)
a
(1)
a
,
...
(1)
a
(1)
a
(1)
(1)
a
(1) 1 (1) 1 (0)
где
=
, .B A B a =A a
I
0 0 0 ... 0 0 0
(1)
BI
0 0 ... 0 0 0
0 0 ... 0 0 0
0 0 ... 0 0 0
(1)
BI
(1)
BI
... ... ... ... ... ... ... ...
0 0 0 0 ... 0 0 0 0 0 0 ... 0 0 0 0 0 ... 0
I
(1)
BI
BI
Таким образом, задача решения полученной СЛАУ эквивалентна задаче вычисления цикла (14). Кроме того, эта СЛАУ может быть решена следую­щим фрагментом кода:
X[0] = h[0]; X[1] = h[1]; ... X[m-1] = h[m-1]; for (i = m; i < N + m; i = i + 4) {
1 1 1 (1)
x[i]= b x[i m] b x[i m + ] b x[i ] a ;
() () ()
1 ... 1− + ++ −+
m
...
}
Теперь прибавим ко всем строкам, кроме первой, предыдущую строку, умноженную слева на B
(1)
. Получим:
I
0 0 0 ... 0 0 0
BI
0 0 ... 0 0 0
0 0 0 ... 0 0 0
I
(2)
0 0 ... 0 0 0
(2)
BI
... ... .. ... ... ... ... ...
0 0 0 0 ... 0 0 0 0 0 0 ... 0 0
0 0 0 0 ... 0
2 1 (1) (1) (2 ) (1) (1) (1)
( ) ()
где
B = B h a Bha a Ba
, , .
2
I
(2)
h
h
2
(2)
a
(2)
a
,
...
(2)
a
I
(2)
a
(2)
a
Алгоритмы решения СЛАУ с блочно-ленточными матрицами. Глава 4 89
22 22
1,1 1,2 1,m 1
2 * 2* 1 ... 1 + + + −− +
() () () ()
x[i]= b x[i m] b x[i m + ] b x[i m ] a ;
22
22 22
1,1 1,2 1,m 1
2 * 2* 1 ... 1 + + + −− +
() () () ()
x[i]= b x[i m] b x[i m + ] b x[i m ] a ;
22
22
Решение полученной СЛАУ равносильно вычислению цикла (14). Кро-
ме того, эта СЛАУ может быть решена следующим фрагментом кода:
X[0] = h[0]; ... X[m – 1] = h[m – 1]; X[m] = h
[0];
2
... X[2*m – 1] = h2[m – 1]; for (i = 2*m; i < N+2*m; i = i+2*m) {
...
22
() ()
x[i m ]= b x[i m] b x[i m + ]
b x[i m ] a ;
1 2 * 2* 1 ...+− + +
() ()
m,m m
m,1 m,2
1+ −− +
}
for (i = 2*m + m; i < N+2*m; i = i+2*m) {
...
() ()
x[i m ]= b x[i m] b x[i m + ]
b x[i m ] a ;
1 2 * 2* 1 ...+− + +
() ()
m,m m
m,1 m,2
1+ −− +
}
Первый цикл этой программы работает с нечетными строчками блочной
матрицы, а второй — с четными. Эта программа может быть параллельно выполнена на двух ВУ (каждое ВУ вычисляет по одному циклу).
Таким же образом есть возможность получить фрагмент кода, эквива-
лентный фрагменту (14), который может быть распараллелен на P = 2
K
ВУ:
X[0] = h[0]; ... X[m – 1] = h[m – 1]; X[m] = h
[0];
2
... X[2*m – 1] = h2[m – 1]; ... X[(P – 1)*m] = hP[0]; ... X[P*m – 1] = hP[m – 1];
90 Б. Я. Штейнберг, О. Б. Штейнберг
P
* * 1 ...−+ − +
( 1) * 1+ −− −+
P
P
( 1) * 1+ −− −+
1,1 1,2
−+ − +
1,m 1
P
1 * * 1 ...+− + +
m,m m
P
−+ − +
for (i = P*m; i < N+P*m; i = i+P*m) {
x[i]= b x[i P m] b x[i P m + ]
(P) ( )
1,1 1,2
(P) (P)
b x[i P m ] a ;
1,m 1
...
( ) (P)
x[i m ]= b x[i P m] b x[i P m + ]
b x[i P m ] a ;
1 * * 1 ...+− + +
( ) (P)
m,m m
m,1 m,2
}
for (i = P*m + m; i < N+P*m; i = i+P*m) {
x[i]= b x[i P m] b x[i P m + ]
(P) ( )
(P) (P)
b x[i P m ] a ;
* * 1 ...
( 1) * 1+ −− −+
P
...
x[i m ]= b x[i P m] b x[i P m + ]
P
( ) (P)
b x[i P m ] a ;
( ) (P)
m,1 m,2
( 1) * 1+ −− −+
}
Итак, получен алгоритм, состоящий в замене фрагмента программы (14) фрагментом программы, циклы которого могут вычисляться параллельно. При этом если коэффициенты a
, a1, …, am не зависят от счетчика цикла,
0
то преобразование является устойчивым, что показано в работе [30]. Под устойчивостью понимается следующее: если значения элементов a0, a1, …,
am изменить на малую величину, то значения элементов вычисляемого мас­сива X[m], X [m + 1], …, X[N + m – 1] тоже изменятся на малую величину.
Добавление векторизации циклов
к рассмотренному алгоритму
Выше показано, как можно распараллелить цикл (14) на P вычислитель­ных устройств. Но каждое из этих устройств может быть способно выпол­нять векторные операции. В этом случае полученный параллельный код можно векторизовать следующим образом. Векторизовать можно каждый цикл по отдельности, а значит, алгоритм позволяет проводить векториза­цию, использующую векторы разной длины для каждого вычислительного устройства. Рассмотрим, как это сделать, на примере первого цикла, полу­ченного в результате применения алгоритма.
for (i = P*m; i < N+P*m; i = i+P*m) {
x[i]= b x[i P m] b x[i P m + ]
(P) ( )
1,1 1,2
(P) (P)
b x[i P m ] a ;
1,m 1
* * 1 ...
( 1) * 1+ −− −+
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]