https://algowiki-project.org/w/ru/api.php?action=feedcontributions&user=VadimVV&feedformat=atomАлговики - Вклад участника [ru]2024-03-29T05:05:38ZВклад участникаMediaWiki 1.34.0https://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21050Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T10:53:47Z<p>VadimVV: /* Количественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 3 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 2 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
На рис. 4 показан отдельно фрагмент 6, выделенный на рис. 3. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 3. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (представлен на рис. 5, выделен на рис. 3 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 5) по строению очень похожа на первую часть; вторая (часть 2 на рис. 5) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&diff=21049Алгоритм Беллмана-Форда2016-12-16T10:52:58Z<p>VadimVV: /* Структура обращений в память и качественная оценка локальности */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Беллмана-Форда'''<ref>Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.</ref><ref>Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.</ref><ref>Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.</ref> предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность <math>O(mn)</math> против <math>O(m + n\ln n)</math> у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.<br />
<br />
=== Математическое описание алгоритма ===<br />
Пусть задан граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math> и выделенной вершиной-источником <math>u</math>. Обозначим через <math>d(v)</math> кратчайшее расстояние от источника <math>u</math> до вершины <math>v</math>.<br />
<br />
Алгоритм Беллмана-Форда ищет функцию <math>d(v)</math> как единственное решение уравнения<br />
:<math><br />
d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,<br />
</math><br />
с начальным условием <math>d(u) = 0</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основной операцией алгоритма является релаксация ребра: если <math>e = (w, v) \in E</math> и <math>d(v) > d(w) + f(e)</math>, то производится присваивание <math>d(v) \leftarrow d(w) + f(e)</math>.<br />
<br />
=== Макроструктура алгоритма ===<br />
Алгоритм последовательно уточняет значения функции <math>d(v)</math>.<br />
* В самом начале производится присваивание <math>d(u) = 0</math>, <math>d(v) = \infty</math>, <math>\forall v \ne u</math>.<br />
* Далее происходит <math>n-1</math> итерация, в ходе каждой из которых производится релаксация всех рёбер графа.<br />
<br />
Структуру можно описать следующим образом:<br />
<br />
1. Инициализация: всем вершинам присваивается предполагаемое расстояние <math>t(v)=\infty</math>, кроме вершины-источника, для которой <math>t(u)=0</math> .<br />
<br />
2. Релаксация множества рёбер <math>E</math><br />
<br />
а) Для каждого ребра <math>e=(v,z) \in E</math> вычисляется новое предполагаемое расстояние <math>t^' (z)=t(v)+ w(e)</math>.<br />
<br />
б) Если <math>t^' (z)< t(z)</math>, то происходит присваивание <math>t(z) := t' (z)</math> (релаксация ребра <math>e</math> ).<br />
<br />
3. Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.<br />
<br />
Если на <math>n</math>-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро <math>e=(v,z)</math>, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): <math>t(v)+w(e)<d(z)</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Последовательный алгоритм реализуется следующим псевдокодом:<br />
<br />
'''Входные данные''':<br />
граф с вершинами ''V'', рёбрами ''E'' с весами ''f''(''e'');<br />
вершина-источник ''u''.<br />
'''Выходные данные''': расстояния ''d''(''v'') до каждой вершины ''v'' ∈ ''V'' от вершины ''u''.<br />
<br />
'''for each''' ''v'' ∈ ''V'' '''do''' ''d''(''v'') := ∞<br />
''d''(''u'') = 0<br />
<br />
'''for''' ''i'' '''from''' 1 '''to''' |''V''| - 1:<br />
'''for each''' ''e'' = (''w'', ''v'') ∈ ''E'':<br />
'''if''' ''d''(''v'') > ''d''(''w'') + ''f''(''e''):<br />
''d''(''v'') := ''d''(''w'') + ''f''(''e'')<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Алгоритм выполняет <math>n-1</math> итерацию, на каждой из которых происходит релаксация <math>m</math> рёбер. Таким образом, общий объём работы составляет <math>O(mn)</math> операций.<br />
<br />
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.<br />
<br />
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.<br />
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).<br />
<br />
=== Информационный граф ===<br />
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).<br />
<br />
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.<br />
<br />
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]<br />
<br />
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется <math>O(n)</math> шагов при использовании <math>O(m)</math> процессоров.<br />
<br />
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math> с весами <math>f_j</math>), вершина-источник <math>u</math>.<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''' (возможные варианты):<br />
# для каждой вершины <math>v</math> исходного графа – последнее ребро <math>e^*_v = (w, v)</math>, лежащее на кратчайшем пути от вершины <math>u</math> к <math>v</math>, или соответствующая вершина <math>w</math>;<br />
# для каждой вершины <math>v</math> исходного графа – суммарный вес <math>f^*(v)</math> кратчайшего пути от от вершины <math>u</math> к <math>v</math>.<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма===<br />
<br />
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро <math>e = (v, w)</math> лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния <math>d(v)</math> удовлетворяют условию<br />
:<math><br />
d(v) + f(e) < d(w),<br />
</math><br />
где <math>f(e)</math> – вес ребра <math>e</math>. Условие может быть проверено для всех рёбер графа за время <math>O(m)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.<br />
<br />
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практические идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 3 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.<br />
<br />
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. <br />
<br />
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]<br />
<br />
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.<br />
<br />
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 1]]<br />
<br />
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. <br />
<br />
<br />
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.<br />
<br />
[[file:APSP scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция <code>[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]</code>).<br />
* Python: [https://networkx.github.io NetworkX] (функция <code>[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]</code>).<br />
* Java: [http://jgrapht.org JGraphT] (класс <code>[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]</code>).<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BC%D0%BE%D0%BD%D0%BE%D1%82%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21048Классическая монотонная прогонка, повторный вариант2016-12-16T10:49:47Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>5n-4</math><br />
| pf_width = <math>1</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
[[file:ProgonkaRep.png|thumb|right|200px|Рисунок 1. Граф алгоритма повторной прогонки при n=7 без отображения входных и выходных данных. '''/''' - деление, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Повторная прогонка''' - один из вариантов метода исключения неизвестных, применяемого к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где как минимум один раз уже была решена прогонкой другая СЛАУ с той же матрицей<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
Здесь рассматривается тот вариант прогонки, когда обрабатывается вся СЛАУ сверху вниз и обратно - так называемая '''правая прогонка'''. Суть метода состоит в исключении из уравнений неизвестных, сначала - сверху вниз - под диагональю, а затем - снизу вверх - над диагональю. Вариант, когда СЛАУ "проходится" наоборот, снизу вверх и обратно вниз - '''левая прогонка''' - принципиально ничем не отличается от рассматриваемого, поэтому нет смысла включать его отдельное описание.<br />
<br />
[[file:ProgonkaRepMul.png|thumb|left|200px|Рисунок 2. Граф алгоритма повторной прогонки с заранее выполненным предвычислением обратных чисел при n=7 без отображения входных и выходных данных. '''m''' - умножение на предвычисленное обратное число, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В приведённых выше обозначениях в повторной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}</math>, <br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i})</math>, <math>\quad i = 1, 2, \cdots , N</math>.<br />
<br />
(при этом отношения <math>1/c_{0}</math> и <math>1/(c_{i}-a_{i}\alpha_{i})</math> уже предвычислены при решении первой системы первой прогонкой) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{N} = \beta_{N+1}</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = N-1, N-2, \cdots , 1, 0</math>.<br />
<br />
В литературе<ref name="SETKI" /> указывается, что данные формулы эквивалентны использованию предвычисленного при полной прогонке одного из вариантов <math>LU</math>-разложения матрицы системы для последующего решения двухдиагональных систем посредством прямой и обратной подстановок.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно считать состоящим из двух частей - прямого и обратного хода прогонки. Как в прямом ходе, так и в обратном, вычислительное ядро составляют последовательности операций умножения (в прямом ходе их две за шаг; в принципе, можно воспользоваться ассоциативностью и вычислять сразу оба произведения, но "промежуточный" результат нужен для обратного хода) и сложения/вычитания. <br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - треугольного разложения матрицы и прямого хода решения двухдиагональной СЛАУ, которые выполняются "одновременно", т.е. параллельно друг другу. При этом решение двухдиагональной СЛАУ использует результаты треугольного разложения.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход прогонки:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0}) </math><br />
<br />
2. Последовательно для всех <math>i</math> от <math>1</math> до <math>N-1</math> выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>.<br />
<br />
3. Инициализируется обратный ход прогонки:<br />
<br />
<math>y_{N} = (f_{N}+a_{N}\beta_{N})(1/(c_{N}-a_{N}\alpha_{N}))</math><br />
<br />
4. Последовательно для всех <math>i</math> с убыванием от <math>N-1</math> до <math>0</math> выполняются формулы обратного хода:<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в последовательном варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности, повторная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
Информационный граф повторной прогонки в случае первой прогонки без предвычислений обратных чисел на рис.1. Информационный граф повторной прогонки в случае первой прогонки с предвычислениями обратных чисел - на рис.2. Как следует из анализа графа, он является полностью последовательным. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названиям "прямой и обратный ход".<br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>3n - 2</math> ярусов умножений и <math>2n - 2</math> сложений/вычитаний (в ярусах - по <math>1</math> операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам со сложностью <math>O(n)</math>. При классификации по ширине ЯПФ её сложность будет равна <math>1</math> (чисто последовательный алгоритм).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Предварительно обработанная полной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, равно <math>1</math>. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных также является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно прогонка используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от способа хранения матрицы СЛАУ (в виде одного массива с <math>3</math> строками или в виде <math>3</math> разных массивов) и способа хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно) возможны различные реализации алгоритма.<br />
<br />
Приведем пример подпрограммы на языке Fortran, реализующей прогонку, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые в процессе первой прогонки коэффициенты --- на месте элементов исходной матрицы. <br />
<br />
<source lang="fortran"><br />
subroutine progmr (a,x,N)<br />
real a(3,0:N), x(0:N)<br />
x(0)=x(0)*a(2,0) ! beta 1<br />
do 10 i=1,N-1<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
10 continue<br />
<br />
x(N)=(x(N)-a(1,N)*x(N-1))*a(2,N) ! y N<br />
do 20 i=N-1,0,-1<br />
x(i)=a(3,i)*x(i+1)+x(i) ! y i<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, которые необходимы для операций, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом углу" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, чтобы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
При этом, однако, повторная прогонка относится к таким последовательным алгоритмам, в которых локальность вычислений настолько велика, что является даже излишней<ref>Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ'2016): труды международной научной конференции (г. Архангельск, 28 марта - 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref>. Из-за того, что данные, необходимые для выполнения основных операций прогонки, вычисляются в непосредственно предшествующим им операциях, возможность использования суперскалярности вычислительных ядер процессоров практически сводится на нет, что ухудшает эффективность выполнения прогонки даже на современных однопроцессорных и одноядерных системах. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 3. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:monosweep_repeated_1.png|thumb|center|700px|Рисунок 4. Классическая монотонная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 4 представлен общий профиль обращений в память для реализации повторного варианта классической монотонной прогонки. Явно видно два этапа в работе программы – на первом этапе выполняется перебор 4 массивов в прямой последовательности, а на втором этапе 2 массива перебираются в обратной последовательности. Профиль состоит из фрагментов, каждый из которых очень похож на обычный последовательный перебор, который обладает высокой пространственной и низкой временной локальностями. Однако необходимо более детальное рассмотрение профиля, чтобы понять, как именно этот перебор устроен.<br />
<br />
На рис. 5 представлен фрагмент 1 (выделен на рис. 4 зеленым). В данном фрагменте происходит смена двух этапов, а также входят обращения ко всем массивам. Из рис. 5 видно, что перебор 4 массивов в нижней части рисунка состоит из единичных обращений; в данном случае фрагменты представляют собой обычный последовательный перебор элементов массива, возможно с некоторым небольшим шагом по памяти. Однако самая верхняя часть фрагмента отличается от остальных, более того, можно увидеть, что характер обращений на разных этапах отличается. Рассмотрим данную часть более детально.<br />
<br />
[[file:monosweep_repeated_2.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 1]]<br />
<br />
На рис. 6 представлена часть фрагмента 1, выделенная зеленым на рис. 5. Можно увидеть, что, действительно, профили на разных этапах отличаются. На первом этапе выполняются два параллельно последовательных перебора, при этом сдвиг по памяти между ними очень невелик. На втором этапе параллельно выполняется уже три последовательных перебора, причем сдвиг по памяти также очень мал. При таких условиях пространственная локальность высока, поскольку данные перебираются почти последовательно. Временная локальность для данной части относительно невысока, поскольку повторных обращений к данным немного, хотя они расположены очень близко друг к другу.<br />
<br />
Отметим, однако, что, во-первых, переборы других массивов реже используют данные повторно, и, во-вторых, данные также повторно используются на разных этапах далеко друг от друга, что приводит к достаточно серьезному снижению временной локальности при рассмотрении общего профиля.<br />
<br />
[[file:monosweep_repeated_3.png|thumb|center|500px|Рисунок 6. Фрагмент 1, верхняя часть]]<br />
<br />
В целом можно сказать, что общий профиль обладает высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 7 приведены значения daps для реализаций некоторых распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно невысока и находится на уровне таких алгоритмов, как вычисление нормы вектора и решение уравнения Пуассона. По всей видимости, это связано с достаточно низкой временной локальностью, как было сказано выше.<br />
<br />
Однако отметим, что в целом данный профиль обладает более высокой производительностью работы с памятью по сравнению с большинством алгоритмов, которые представляют собой совокупность различных последовательных переборов, таких как вычисление нормы вектора, реализация схемы Горнера и т.д.<br />
<br />
[[file:monosweep_repeated_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Как видно из анализа графа алгоритма, его (без существенных модификаций) практически невозможно распараллелить. Поэтому есть два способа использования прогонок для параллельных вычислительных систем: либо разбивать задачу, где используются прогонки, так, чтобы их было достаточно много, например, так, чтобы на каждую из прогонок приходился 1 процессор (1 ядро), либо использовать вместо прогонки её параллельные варианты (циклическую редукцию, последовательно-параллельные варианты и т.п.).<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой прогонки, как полностью непараллельного алгоритма, говорить не имеет смысла. Однако необходимо отметить, что анализ масштабируемости параллельных вариантов прогонки должен проводиться относительно однопроцессорной реализации описанного классического варианта прогонки, а не относительно однопроцессорных расчетов для её параллельных вариантов.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная монотонная прогонка - метод для архитектуры классического, фон-неймановского типа, устаревший даже для эффективной загрузки одноядерных систем, поддерживающих суперскалярность, где проигрывает повторной встречной прогонке. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует использовать какой-либо её параллельный заменитель, например, наиболее распространённую [[Повторный метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный вариант]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм прогонки является настолько простым, что, несмотря на его "дежурное" присутствие в стандартных пакетах программ решения задач линейной алгебры, большинство использующих его исследователей-прикладников часто пишут соответствующий фрагмент программы самостоятельно. Однако надо отметить, что, как правило, метод прогонки в пакетах реализуют не в его "чистом виде", а в виде пары "разложение на две двухдиагональные матрицы" и "решение СЛАУ с предварительно факторизованной трёхдиагональной матрицей". Так обстоит дело, например, в пакете SCALAPACK и его предшественниках.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[En:Two-sided elimination method, pointwise version]]<br />
[[Категория:Алгоритмы с низким уровнем параллелизма]]<br />
[[Категория:Законченные статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B,_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21047Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант2016-12-16T10:48:45Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = QR-разложение методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{4 n^3}{3}</math><br />
| pf_height = <math>n^2</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 1)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для разложения матриц в виде <math>A=QR</math> (<math>Q</math> - унитарная, <math>R</math> — правая треугольная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием минимального одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QR</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце. Таким образом, после <math>n-1</math> шагов преобразований получается матрица <math>R</math> из <math>QR</math>-разложения.<br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma =1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф шага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего или из входных данных), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего, красных и жёлтых кружков, а на последнем шаге и голубого - выходные для алгоритма.]]<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. При этом, однако, строгая последовательность выполнения этих трёх подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>2n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На Рисунке 1 приведён шаг графа алгоритма метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом. Операции привязаны к обрабатываемым элементам матрицы. Для получения полного графа графы шагов нужно положить друг на друга последовательными слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>\frac{n^2}{2}</math> умножений и <math>\frac{n^2}{2}</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''. Однако все эти широко распространённые методы пока не дали возможности снизить критический путь метода Хаусхолдера до ''линейного'' (как, скажем, у метода Гивенса).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': правая треугольная матрица <math>R</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>n^2+n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) QR-разложения квадратной вещественной матрицы на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SX(K)=2.<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
В этом варианте R расположена в верхнем правом треугольнике массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов, а их первые элементы - в элементах массива SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qr_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации вещественного варианта метода Хаусхолдера (отражений) QR-разложения квадратной матрицы. Из данного рисунка можно сделать несколько выводов. Во-первых, видно, что в среднем к каждому элементу выполняется примерно 90 обращений (отношение общего числа обращений к числу задействованных данных), что является достаточно хорошим показателем в плане повторного использования данных. Далее, явно видна итерационная структура профиля, причем на каждой следующей итерации обращения к небольшой части первых элементов перестают выполняться. Это говорит о том, что ближе к концу профиля задействовано меньше данных, что хорошо сказывается на пространственной локальности. Однако в то же время видно, что на каждой следующей итерации выполняется меньше обращений к одним и тем же локальным областям, то есть временная локальность, по всей вероятности, становится ниже. Рассмотрим подробнее первую итерацию, что более детально оценить их строение.<br />
<br />
На рис. 3 представлена одна итерация общего профиля (выделенный зеленый фрагмент на рис. 2). Ее можно условно разбить на 5 фрагментов, каждую из которых рассмотреть отдельно. Фрагменты 2 и 5 устроены просто – элементы перебираются с большим шагом по памяти, при достижении конца данных перебор повторяется, только с небольшим сдвигом. Про такие фрагменты можно сказать, что они обладают низкой пространственной локальностью, поскольку шаг по памяти достаточно большой, и низкой временной локальностью, так как данные повторно не используются. Однако отметим, что данные фрагменты состоят из небольшого числа обращений, что означает, что они не так сильно будут снижать общую локальность профиля.<br />
<br />
[[file:householder_qr_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагмент 1 и 3 устроены практически идентично (основное отличие – два основных этапа переставлены местами), поэтому рассмотрим только один из них. На рис. 4 представлен фрагмент 3. Здесь хорошо видно, как устроены эти этапы. На первом этапе выполняется перебор элементов в обратном порядке с шагом 1, при этом к каждому элементу подряд обращаются несколько раз. Такой фрагмент обладает очень высокой пространственной локальностью и достаточно высокой временной локальностью. <br />
<br />
[[file:householder_qr_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 3]]<br />
<br />
На втором этапе также выполняется последовательный перебор, но уже с небольшим шагом, и к каждому элементу обращаются только по 1 разу на каждой итерации. Однако, поскольку в каждой небольшой итерации на втором этапе всего около 30 обращений, повторное обращение к каждому элементу происходит недалеко от предыдущего, что говорит о достаточно высокой временной локальности. Пространственная локальность также высока, поскольку шаг по памяти невелик.<br />
<br />
Далее рассмотрим фрагмент 4. Чтобы лучше понять локальную структуру, детально изучим небольшую его часть (рис. 5). Из данного рисунка хорошо видно, что здесь профиль устроен очень просто и представляет собой обычный последовательный перебор с шагом 1 (с небольшими и нечастыми сдвигами, которые не влияют на локальность этого фрагмента). Такой фрагмент обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:householder_qr_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмента 4]]<br />
<br />
В целом можно сказать, что одна итерация обладает высокой пространственной и средней временной локальностью, хотя она несколько снижается из-за наличия фрагментов 2, 4 и 5. При условии, что итерации подобны, выводы относительно локальности одной итерации можно перенести и на общий профиль.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном примере высока. Значение daps для него находится на одном уровне с тестом Linpack и лишь немногим ниже, чем, например, самые эффективные варианты перемножения плотных матриц. Это означает, что негативное влияние фрагментов 2 и 5 оказывается достаточно слабым, вероятно (как и предполагалось ранее) из-за небольшого их размера.<br />
<br />
<br />
[[file:householder_qr_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%B0%D1%8F_%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%87%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21046Повторная встречная прогонка, точечный вариант2016-12-16T10:47:52Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Встречная повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>2.5n-1</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Встречная повторная прогонка''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где уже однажды СЛАУ с такой же матрицей была решена методом встречной прогонки<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
'''Встречная повторная прогонка''', как и [[Классическая монотонная прогонка, повторный вариант|повторная монотонная]], заключается в исключении из уравнений неизвестных, однако, в отличие от монотонной, в ней исключение ведут одновременно с обоих "краёв" СЛАУ (верхнего и нижнего). В принципе, её можно считать простейшим вариантом [[Метод редукции|метода редукции]] (при m=1 и встречных направлениях монотонных прогонок).<br />
<br />
=== Математическое описание алгоритма ===<br />
<math>m</math> здесь - номер уравнения, на котором "встречаются" две ветви прямого хода - "сверху" и "снизу". <br />
<br />
В приведенных обозначениях во встречной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
"сверху":<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}, </math><br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i}), \quad i = 1, 2, \cdots , m-1.<br />
<br />
</math><br />
<br />
и "снизу":<br />
<br />
<math>\eta_{N} = f_{N}/c_{N}</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})/(c_{i}-b_{i}\xi_{i+1})</math>, <math>\quad i = N-1, N-2, \cdots , m.</math><br />
<br />
(все отношения <math>1/c_{0}</math>, <math>1/c_{N}</math>, <math>1/(c_{i}-a_{i}\alpha_{i})</math>, <math>1/(c_{i}-b_{i}\xi_{i+1})</math> при этом вычислены при выполнении первой встречной прогонки) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}, \quad i = m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}, \quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
<br />
В приводимых обычно<ref name="SETKI" /> формулах встречной прогонки нет формулы для компоненты <math>y_{m-1}</math>, которая вычисляется позже в обратном ходе. Однако это удлиняет критический путь графа как в случае с чётным числом переменных, откладывая вычисление <math>y_{m-1}</math> на момент, когда уже будет вычислена <math>y_{m}</math>, хотя обе компоненты могут быть вычислены одновременно почти независимо друг от друга, так и в случае с нечётным числом переменных, когда для вычисления на "месте встречи" нужно подождать дополнительно одно вычисление коэффициентов либо "сверху" от него, либо "снизу".<br />
<br />
Поэтому в более поздних источниках<ref name="IK">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г. ,208 с.</ref> приводятся формулы, которые более оптимальны для нечётного количества неизвестных. В них старт обратного хода заменяется на формулу (в наших обозначениях)<br />
<br />
<math>y_{m} = (f_{m}+b_{m}\eta_{m+1}+a_{m}\beta_{m})/(c_{m}-a_{m}\alpha_{m}-b_{m}\xi_{m+1})</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно, как и в случае [[Классическая монотонная прогонка, повторный вариант|повторной классической монотонной прогонки]], представить состоящим из двух частей - прямого и обратного хода; однако их ширина вдвое больше, чем в монотонном случае. В прямом ходе ядро составляют две независимые последовательности операций двух умножений и сложения/вычитания. В обратном ходе в ядре остаются только две независимые последовательности операций умножения и сложения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - прямой ход правой и левой повторных прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ. Обратный ход также может быть разложен на две макроединицы - обратный ход правой и левой прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
[[file:VstrProgonkaInv.png|thumb|left|600px|Рисунок 1. Детальный граф алгоритма встречной прогонки с однократным вычислением обратных чисел при n=6 без отображения входных и выходных данных. '''inv''' - вычисление обратного числа, '''mult''' - операция перемножения чисел. Без обращений - ветви, повторяющиеся при замене правой части СЛАУ в '''повторной встречной прогонке'''.]]<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0})</math>, <br />
<br />
<math>\eta_{1} = f_{N}(1/c_{N})</math>.<br />
<br />
2. Последовательно выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>, <math>\quad i = 1, 2, \cdots , m-1</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})(1/(c_{i}-b_{i}\xi_{i+1}))</math>, <math>\quad i = N-1, N-2, \cdots , m</math>.<br />
<br />
<br />
3. Инициализируется обратный ход:<br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>, <br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>.<br />
<br />
4. Последовательно выполняются формулы обратного хода:<br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = m-1, m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}</math>, <math>\quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения встречной прогонки в трёхдиагональной СЛАУ из n уравнений с n неизвестными в последовательном (наиболее быстром) варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности встречная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
Информационный граф встречной прогонки представлен на рис.1. Как видно, он параллелен со степенью не более 2. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названию "обратный ход". <br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Обе ветви прямого хода можно выполнять одновременно, если <math>N=2m-1</math>, т.е. <math>n=2m</math>. В этом случае встречная прогонка требует последовательного выполнения следующих ярусов:<br />
<br />
* по <math>3m-2</math> ярусов умножений и <math>2m-1</math> ярусов сложений/вычитаний (по две операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ встречная прогонка относится к алгоритмам с ''линейной'' сложностью. При классификации по ширине ЯПФ сложность этого алгоритма равна <math>2</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': преобразованная первой встречной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''константой'' (2). <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных – тоже ''константа''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно повторная встречная прогонка, как и повторная монотонная, используется для решения СЛАУ с диагональным преобладанием. Тогда гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от нужд вычислений, возможны как разные способы хранения матрицы СЛАУ (в виде одного массива с 3 строками или в виде 3 разных массивов), так и разные способы хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно).<br />
<br />
Приведем пример подпрограммы, реализующей встречную прогонку СЛАУ с нечётным числом уравнений, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы.<br />
<br />
<source lang="fortran"><br />
subroutine vprogmr (a,x,N) ! N=2m-1<br />
real a(3,N), x(N)<br />
<br />
m=(N+1)/2<br />
<br />
x(1)=x(1)*a(2,1) ! beta 2<br />
x(N)=x(N)*a(2,N) ! eta N<br />
<br />
do 10 i=2,m-1<br />
<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
x(N+1-i)=(x(N+1-i)-a(3,N+1-i)*x(N+2-i))* a(2,N+1-i) ! eta N-i<br />
<br />
10 continue<br />
<br />
x(m) =(x(m)-a(1,m)*x(m-1)-a(3,m)*x(m+1))*a(2,m) ! y m<br />
<br />
do 20 i=m+1,N<br />
x(i)=a(1,i)*x(i-1)+x(i) ! y i up<br />
x(N+1-i)=a(3,N+1-i)*x(N+2-i)+x(N+1-i) ! y i down<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, что нужны операциям, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом" и "нижнем правом" "углах" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, что бы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:countersweep_repeated_1.png|thumb|center|700px|Рисунок 2. Встречная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации повторного варианта встречной прогонки. Данный профиль состоит из набора параллельно выполняемых последовательных переборов, как возрастающих, так и убывающих. В общем случае такой профиль характеризуется высокой пространственной локальностью, поскольку соседние обращения выполняются к близким по памяти данным, а также низкой временной локальностью, так как данные практически не используются повторно. Это подтверждает тот факт, что общее число обращений в память менее чем в два раза превышает число задействованных данных – достаточно плохой показатель производительности работы с памятью.<br />
<br />
Из рисунка можно предположить, что переборы, скорее всего, устроены достаточно регулярно и не меняют своего поведения в течение программы. Однако нужно детально разобрать, как устроены эти переборы; для этого рассмотрим некоторые наиболее интересные фрагменты профиля более детально. <br />
<br />
На рис. 3 приведен фрагмент 1 (выделен на рис. 2 зеленым). Видна регулярность обращений к памяти, которая нарушается только один раз, примерно по центру рисунка, где происходит смена этапов работы программы. На первом этапе параллельно выполняются два возрастающих и два убывающих последовательных перебора с небольшим шагом по памяти; на втором этапе число переборов возрастает в 1.5 раза, однако характер обращений остается примерно тем же. Подобный фрагмент, в отличие от обычного последовательного перебора, обладает более высокой временной локальностью, поскольку данные используются повторно по несколько раз, при этом большая часть повторных обращений расположена близко друг к другу.<br />
<br />
[[file:countersweep_repeated_2.png|thumb|center|500px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим фрагмент 2 (рис. 4). Здесь профиль устроен очень просто – параллельно выполняются возрастающий и убывающий переборы данных с небольшим шагом по памяти. Подобное поведение можно увидеть также во всех переборах, расположенных ниже фрагмента 2 на рис. 2.<br />
<br />
[[file:countersweep_repeated_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]<br />
<br />
В целом можно сказать, что данный профиль действительно состоит из небольшого числа возрастающих и убывающих последовательных переборов с небольшим шагом по памяти. Такое строение характеризуется высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что реализация данного алгоритма показывает достаточно неплохую производительность работы с памятью. Результаты daps примерно совпадают с результатами для реализации метод Холецкого и обгоняют большинство других алгоритмов, построенных на основе переборов элементов массива, таких как вычисление скалярного произведения, реализация повторного варианта монотонной прогонки и т.д.<br />
<br />
[[file:countersweep_repeated_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Встречная повторная прогонка задумана изначально для случая, когда нужно найти только какую-то близкую к "середине" компоненту вектора решения, а остальные не нужны (решение т.н. "частичной задачи"). При появлении параллельных компьютерных устройств оказалось, что у встречной прогонки есть небольшой ресурс параллелизма и она убыстряет счёт, если её верхнюю и нижнюю ветви "раскидать" на 2 процессора. Однако для получения массового параллелизма встречная прогонка непригодна из-за низкой ширины своей [[Глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] (равной 2).<br />
<br />
С появлением суперскалярных процессоров оказалось, что для получения выигрыша (около 20%) перед монотонной повторной прогонкой встречную даже необязательно "раскидывать" на 2 процессора или даже на 2 ядра. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 2. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой повторной встречной прогонки, как почти непараллельного алгоритма, говорить нельзя в принципе, за исключением разве что двухпроцессорных систем. Понятие масштабируемости неприменимо, поскольку описываемый алгоритм не предполагает параллельной реализации.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная встречная прогонка - метод для архитектуры классического, фон-неймановского типа. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует взять какой-либо её параллельный заменитель, например, наиболее распространённую [[Метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный метод]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм повторной встречной прогонки настолько прост, что, в тех случаях, когда он по каким-либо причинам понадобился, большинство использующих его исследователей-прикладников просто пишут соответствующий фрагмент программы самостоятельно. Поэтому встречную прогонку в пакеты программ обычно не включают.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Законченные статьи]]<br />
[[Категория:Алгоритмы с небольшим уровнем параллелизма]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4_%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D1%80%D0%B5%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D0%B8&diff=21045Полный метод циклической редукции2016-12-16T10:46:58Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = Циклическая редукция для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>17n + o(n)</math><br />
| pf_height = <math>7 log_2 n</math><br />
| pf_width = <math>3n/2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Циклическая редукция''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где <br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ2}}<br />
<br />
'''Циклическая редукция''', как и все варианты прогонки, заключается <ref name="IK3d">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г., 208 с.</ref><ref name="FAVT-2016">Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ’2016): труды международной научной конференции (г. Архангельск, 28 марта – 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref> в исключении из уравнений неизвестных, однако, в отличие от них, в ней исключение ведут одновременно по всей СЛАУ. В принципе, её можно считать вариантом [[Метод редукции|метода редукции]], выполняемого максимально возможное для данной СЛАУ число раз.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Лучше всего схема циклической редукции<ref name="IK3d" /> разработана для случая <math>n = 2^{m}-1</math>. Эта схема состоит из прямого и обратного ходов. <br />
Прямой ход состоит из последовательного уменьшения в СЛАУ количества уравнений почти в 2 раза (за счёт подстановки из уравнений с нечётными номерами заменяются уравнения с чётными), пока не останется одно уравнение, обратный - в получении всё большего количества компонент решения исходной СЛАУ. Оба хода - как прямой, так и обратный - разбиты на шаги. Здесь мы приведём тот вариант алгоритма, в котором операции экономятся за счёт предварительной нормировки уравнений, используемых для исключения неизвестных. <br />
<br />
==== Прямой ход редукции ====<br />
<br />
В начале считается, что все <br />
<math>c^{(0)}_{i} = c_{i}, b^{(0)}_{i} = b_{i}, a^{(0)}_{i} = a_{i}, f^{(0)}_{i} = f_{i}, x^{(0)}_{i} = x_{i}</math><br />
<br />
На k-м шаге теперь выполняем процедуру редукции системы уравнений размерности n.<br />
<br />
Для каждого из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math> <br />
<br />
с нечётными <math>i</math> с помощью деления уравнения на <math>b^{(k)}_{i}</math> выполняется его замена на уравнение <br />
<br />
<math> (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} + x^{(k)}_{i} + (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} = f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
[[file:CycRedMicro0.png|thumb|right|150px|Рисунок 1. Микрограф "узла" подготовительного шага прямого хода алгоритма циклической редукции ]]<br />
<br />
Уравнение <br />
<br />
<math>b^{(k)}_{1} x^{(k)}_{1} + a^{(k)}_{1} x^{(k)}_{2} = f^{(k)}_{1}</math><br />
<br />
аналогично меняется на уравнение <br />
<br />
<math>x^{(k)}_{1} + (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2} = f^{(k)}_{1}/b^{(k)}_{1}</math>:<br />
<br />
а уравнение <br />
<br />
<math>c^{(k)}_{n} x^{(k)}_{n-1} + b^{(k)}_{n} x^{(k)}_{n} = f^{(k)}_{n}</math> <br />
<br />
меняется на уравнение <br />
<br />
<math>(c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1} + x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n}</math>:<br />
<br />
[[file:CycRedMicroDirect.png|thumb|right|400px|Рисунок 2a. Микрограф "узла" с нечётным номером прямого хода алгоритма циклической редукции]]<br />
<br />
Для каждого же из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math><br />
<br />
с чётными <math>i</math> (кроме <math>2</math> и <math>n-2</math>) выполняется, с учётом <math>x^{(k+1)}_{i/2} = x^{(k)}_{i}</math> его замена на уравнение <br />
<br />
<math>c^{(k+1)}_{i/2} x^{(k+1)}_{(i-2)/2} + b^{(k+1)}_{i/2} x^{(k+1)}_{i/2} + a^{(k+1)}_{i/2} x^{(k+1)}_{(i+2)/2} = f^{(k+1)}_{i/2}</math><br />
<br />
[[file:CycRedMicroDirect2.png|thumb|right|400px|Рисунок 2b. Микрограф "узла" с чётным номером прямого хода алгоритма циклической редукции ]]<br />
<br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{i/2} = - c^{(k)}_{i}(c^{(k)}_{i-1}/b^{(k)}_{i-1})</math>, <br />
<br />
<math>a^{(k+1)}_{i/2} = - a^{(k)}_{i}(a^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>b^{(k+1)}_{i/2} = b^{(k)}_{i} - c^{(k)}_{i}(a^{(k)}_{i-1}/b^{(k)}_{i-1}) - a^{(k)}_{i}(c^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>f^{(k+1)}_{i/2} = f^{(k)}_{i} - c^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1} - a^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1}</math>.<br />
<br />
Для 2го уравнения выполняется его замена на уравнение<br />
<br />
<math>b^{(k+1)}_{1} x^{(k+1)}_{1} + a^{(k+1)}_{1} x^{(k+1)}_{(2} = f^{(k+1)}_{1}</math><br />
<br />
при этом<br />
<br />
<math>a^{(k+1)}_{1} = - a^{(k)}_{2}(a^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>b^{(k+1)}_{1} = b^{(k)}_{2} - c^{(k)}_{2}(a^{(k)}_{1}/b^{(k)}_{1}) - a^{(k)}_{2}(c^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>f^{(k+1)}_{1} = f^{(k)}_{2} - c^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1} - a^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1}</math><br />
<br />
<br />
<math>n-1</math>-е уравнение заменяется на <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-3)/2} + b^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-1)/2} = f^{(k+1)}_{(n-1)/2}</math><br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} = - c^{(k)}_{n-1}(c^{(k)}_{n-2}/b^{(k)}_{n-2})</math>,<br />
<br />
<math>b^{(k+1)}_{(n-1)/2} = b^{(k)}_{n-1} - c^{(k)}_{n-1}(a^{(k)}_{n-2}/b^{(k)}_{n-2}) - a^{(k)}_{n-1}(c^{(k)}_{n}/b^{(k)}_{n})</math>,<br />
<br />
<math>f^{(k+1)}_{(n-1)/2} = f^{(k)}_{n-1} - c^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2} - a^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2}</math>.<br />
<br />
По окончании всех этих манипуляций размерность k+1-й СЛАУ оказывается равной <math>(n-1)/2</math>.<br />
<br />
Шаги повторяются до тех пор, пока после <math>m-1</math> шагов редукции размерность СЛАУ не становится равной 1 и остаётся одно уравнение<br />
<br />
<math>b^{(m-1)}_{1} x^{(m-1)}_{1} = f^{(m-1)}_{1}</math><br />
<br />
==== Обратный ход редукции ====<br />
<br />
[[file:CycRedMicroRev.png|thumb|right|150px|Рисунок 3. Микрограф "узла" обратного хода алгоритма циклической редукции ]]<br />
<br />
Из последнего уравнения, полученного прямым ходом, вычисляется <br />
<br />
<math>x^{(m-1)}_{1} = f^{(m-1)}_{1}/b^{(m-1)}_{1}</math><br />
<br />
Теперь, последовательно уменьшая верхние индексы неизвестных, используется нечётные уравнения каждого шага для вычисления неизвестных с соотвествующими нечётными номерами. Чётные неизвестные получаются из тождеств <math>x^{(k)}_{i} = x^{(k+1)}_{i/2}</math>, а для нечётных <math>i</math> <br />
<br />
<math>x^{(k)}_{i} = f^{(k)}_{i}/b^{(k)}_{i} - (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} - (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} </math><br />
<br />
c "левого края" системы будет <br />
<br />
<math>x^{(k)}_{1} = f^{(k)}_{1}/b^{(k)}_{1} - (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2}</math><br />
<br />
а с "правого"<br />
<br />
<math>x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n} - (c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1}</math><br />
<br />
После вычисления всех <math>x^{(0)}_{i}</math> значения искомых неизвестных <math>x_{i} = x^{(k)}_{i}</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Видно, что, поскольку вычисляемые на каждом шаге прямого хода редукции при преобразовании нечётных уравнений отношения коэффициентов <br />
<br />
<math> c^{(k)}_{i}/b^{(k)}_{i} , a^{(k)}_{i}/b^{(k)}_{i} , f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
почти все используются для преобразований двух чётных уравнений, то при выделении "микровычислений", из которых следует составить шаги редукции и которые составляют его ядро, лучше отнести вычисления этих отношений к предыдущему шагу редукции. Таким образом, на "подготовительном шаге" микроядро будет для каждого нечётного <math>i</math> состоять только из трёх делений (кроме <math>2</math> и <math>n</math> - там будет по 2 деления), а затем на каждом последующем шаге редукции для каждого нечётного <math>i</math> - из двух умножений и двух вычислений выражений типа <math>a-bc-de</math>, с последующими тремя делениями (на "краях" часть этих операций отсутствует или урезана, но общую картину это не очень меняет). Для чётных <math>i</math> деления в микроядре будут отсутствовать.<br />
<br />
Что касается шагов обратного хода, то там для каждого <math>i</math> рано или поздно выполняется одна операция типа <math>a-bc-de</math> (на "краях" - типа <math>a-bc</math>).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[file:CycRedDirect.png|thumb|right|400px|Рисунок 4. Граф алгоритма прямого хода циклической редукции при n=15. Светло-зелёным обозначены микроядра "подготовительного шага" с тремя (или меньше) делениями, синим - микроядра нечётных узов, сине-зелёным - ядра чётных узлов (без делений).]]<br />
<br />
[[file:CycRedRev.png|thumb|right|400px|Рисунок 5. Граф алгоритма обратного хода циклической редукции при n=15. В вершинах - операции вида a-bc-de, в вершинах с 1 входящей дугой - вида a-bc. Показаны только дуги, передающие значения найденных неизвестных.]]<br />
<br />
Если выразить макроструктуру алгоритма циклической редукции в терминах её "микроядер", то прямой ход редукции несколько схож на схемы сдваивания, но отличается от неё не только количеством входящих в макровершины дуг, из-за чего её схема - скорее "страивание", но и зацеплением соседних ветвей. Поэтому, в отличие от схем сдваивания, для которых характерно полностью независимое исполнение ветвей до определённого момента, в схеме редукции это зацепление ветвей не даёт им выполняться полностью независимо. <br />
<br />
Обратный ход циклической редукции - уже практически полное "обратное" сдваивание, и на этом этапе разделение на независимые ветви может быть проделано после старта, если размножить необходимые данные.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Метод циклической редукции изначально спроектирован для параллельного исполнения, поскольку является по отношению к, например, классической прогонке, алгоритмом с избыточными вычислениями. Поэтому смысла в его последовательной реализации обычно не видят и они не встречаются в библиотеках программ. Тем не менее, из-за того, что современная архитектура даже одиночных узлов и персональных компьютеров не вполне последовательна, то этот смысл, как вполне может оказаться<ref name="FAVT-2016" />, уже появился, поскольку, кроме простого количества операций, производительность вычислений на компьютере определяется и другими параметрами алгоритма, в частности, локальностью вычислений.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Поскольку алгоритм циклической редукции обычно не предназначен для последовательного исполнения, то его "последовательную сложность" скорее следует считать только оценкой общего количества операций. Если взять только главный член, линейный по размеру, и опустить меньшие по порядку (логарифмические и константы), то получается, что в циклической редукции ''3n'' делений, ''8n'' умножений и ''6n'' вычитаний/сложений. Таким образом, при классификации по последовательной сложности, алгоритм циклической редукции относится к алгоритмам ''с линейной сложностью''.<br />
<br />
Сравнение сложности с алгоритмом [[Прогонка, точечный вариант|прогонки]] показывает также, что циклическая редукция - алгоритм с избыточными вычислениями: избыточность по сравнению с прогонкой более чем в 2 раза.<br />
<br />
=== Информационный граф ===<br />
<br />
У прямого хода циклической редукции макрограф представлен на Рис. 4, при этом графы разные макровершин представлены на Рис. 1, 2a, 2b. Как видно, после первого шага схему можно условно назвать "схемой страивания", поскольку связанные с каждым уравнением, остающимся в редуцированной СЛАУ, коэффициенты вычисляются по формулам из коэффициентов '''трёх''' соседних уравнений в нередуцированной СЛАУ. <br />
<br />
Обратный ход имеет макрограф, представленный на Рис. 5, при этом графы макровершин представлены на Рис. 3 (крайние макровершины, имеющие только одну входящую дугу на макрографе, замещают недостающие данные нулями, соответственно уменьшая вычисления). Этот макрограф, в принципе, имеет макроструктуру, обратную к схеме сдваивания, поэтому её можно условно назвать "схемой раздваивания", с одной поправкой: все макровершины, кроме крайних, имеют по две входящих дуги, которые немного изменяют обычное "раздваивание".<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для выполнения циклической редукции в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>log_2 n</math> ярусов делений, из них самый широкий ярус - первый, в нём <math>3n/2</math> делений. <br />
* <math>2 log_2 n</math> ярусов умножений и <math>4 log_2 n</math>сложений/вычитаний.<br />
<br />
Ширина ярусов экспоненциально, с показателем 2, убывает с их номерами, а потом, на обратном ходе, экспоненциально растёт, также с показателем 2. Эта неоднородность приводит к тому, что при практическом полном распараллеливании большую часть времени большинство устройств будет простаивать. <br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам с ''логарифмической сложностью''. При классификации по ширине ЯПФ её сложность будет ''линейной''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является отношением размера задачи к его логарифму. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно циклическая редукция используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
В случае, когда требуется решение нескольких СЛАУ с одной и той же матрицей, большую часть прямого хода вычислений (см. рисунки с графом алгоритма) можно не повторять.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Из-за избыточности вычислений на компьютерах без параллельных устройств обычно используют более простую монотонную или встречную [[Прогонка, точечный вариант|прогонку]], а циклическую редукцию в последовательном варианте обычно просто не реализуют. Этот выбор, однако, не так очевиден на современных "однопроцессорных" суперскалярных компьютерах, поскольку в циклической редукции нет такой избыточной локальности, как в [[Прогонка, точечный вариант|прогонке]]. Однако, эта сторона циклической редукции ещё не исследована и ждёт тех, кто проведёт соответствующую работу.<br />
<br />
Приведем пример подпрограммы, реализующей циклическую прогонку СЛАУ с числом уравнений "степень двойки минус 1", где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы. Следует обратить внимание на то, что данная версия циклической редукции непригодна для повторного решения СЛАУ с той же матрицей и новой правой частью, поскольку при повторении нужно несколько большее количество промежуточных данных. <br />
<br />
<source lang="fortran"><br />
subroutine cprogm (a,x,N,m) ! N=2^m-1<br />
real a(3,N), x(N)<br />
<br />
k=1<br />
k2=2<br />
kk=1 <br />
<br />
a(2,1)=1./a(2,1)<br />
a(2,N)=1./a(2,N)<br />
a(3,1)=a(3,1)*a(2,1)<br />
a(1,N)=a(1,N)*a(2,N)<br />
x(1) = x(1)*a(2,1)<br />
x(N) = x(N)*a(2,N)<br />
do i=3,N-2,2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
do j=1,m-2<br />
<br />
k=k2 ! k=2^j<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
a(3,k) = -a(3,k)*a(3,k+kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(1,N+1-k)*a(3,N+1-k-kk)<br />
a(1,N+1-k)= - a(1,N+1-k)*a(1,N+1-k-kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k-kk)*a(1,N+1-k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k) - x(k+kk)*a(3,k+kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(3,N+1-k)*a(1,N+1-k+kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k+kk)*a(3,N+1-k+kk) <br />
<br />
k2=k2*2 ! k2=2^(j+1)<br />
<br />
do i = k2, N+1-k2, k<br />
a(2,i) = a(2,i) - a(1,i)*a(3,i-kk)<br />
x(i) = x(i)- x(i-kk)*a(1,i-kk)<br />
a(3,i) = -a(3,i)*a(3,i+kk)<br />
a(1,i)= - a(1,i)*a(1,i-kk) <br />
a(2,i) = a(2,i) - a(3,i)*a(1,i+kk)<br />
x(i) = x(i)- x(i+kk)*a(3,i+kk)<br />
end do <br />
<br />
a(2,k)=1./a(2,k)<br />
a(2,N+1-k)=1./a(2,N+1-k)<br />
a(3,k)=a(3,k)*a(2,k)<br />
a(1,N+1-k)=a(1,N+1-k)*a(2,N+1-k)<br />
x(k) = x(k)*a(2,k)<br />
x(N+1-k) = x(N+1-k)*a(2,N+1-k) <br />
<br />
do i = k2+k, N+1-k2-k, k2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
<br />
kk=k ! budet kk=2^(j-1)<br />
<br />
end do <br />
<br />
c m-1 shag & start of reverse<br />
<br />
k=k2 ! k=2^(m-1) i kk=2^(m-2)<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k)- x(k+kk)*a(3,k+kk)<br />
a(2,k) = 1./a(2,k)<br />
x(k) = x(k)*a(2,k)<br />
<br />
c start reverse <br />
<br />
x(kk) = x(kk) - a(3,kk)*x(k)<br />
<br />
x(k+kk) = x(k+kk) - a(1,k+kk)*x(k)<br />
<br />
do j=m-2, 1, -1<br />
<br />
k2 = k ! k2 = 2^(j+1)<br />
k = kk ! k = 2^j<br />
kk = kk/2 ! kk = 2^(j-1)<br />
<br />
x (kk) = x (kk) - a(3,kk)*x(kk+kk)<br />
x (N+1-kk) = x (N+1-kk) - a(1,N+1-kk)*x(N+1-k) <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(1,i)*x(i-kk) <br />
end do <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(3,i)*x(i+kk) <br />
end do <br />
end do<br />
<br />
return<br />
end<br />
</source><br />
<br />
Здесь необходимо отметить, что, несмотря на последовательность этой реализации, она вполне может быть исполнена и параллельно, нужно только как-то скомандовать имеющемуся в наличии компилятору, что циклы по i параллельны (например, спецкомментариями OpenMP).<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
По графу видно, что, хотя и налицо "местная" локальность по вычислениям, но от яруса к ярусу расстояние между запрашиваемыми данными растёт от первых и последних ярусов алгоритма к его середине, являющимся не только узким местом алгоритма, но и имеющим наибольшую длину передачи данных между устройствами. Поэтому, хотя в циклической редукции нет той избыточной локальности, которая притормаживает работу прогонки, это преимущество, как показали замеры<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref> (см. Рисунок 6), не способно компенсировать недостатки общей локальности графа циклической редукции.<br />
<br />
[[File:I3-cyc.png|thumb|center|1000px|Рисунок 6. Отношение времени исполнения циклической редукции и монотонных прогонок на ПК. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение циклической редукции, к времени, затраченному на выполнение монотонной повторной прогонки. Система Core i3 + Windows 8, сборка кода компилятором GNU Fortran. На других системах получена качественно примерно такая же картина, с различием по выходу за границы кэша. Между синей и красной линиями - примерная область взаимной компенсации неарифметических факторов. При малых размерах сказывается неиспользование прогонками суперскалярности процессора.]]<br />
<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:cyclic_reduction_1.png|thumb|center|700px|Рисунок 7. Полный метод циклической редукции. Общий профиль обращений в память]]<br />
<br />
На рис. 7 представлен профиль обращений в память для реализации полного метода циклической редукции. Данный профиль достаточно интересно. Как нам известно из описания самого алгоритма, он состоит из двух частей – прямого и обратного хода. На общем профиле синяя вертикальная черта отделяет эти два этапа. Рассмотрим их в отдельности.<br />
<br />
На первом этапе явно видны отдельные итерации (первые 3 итерации разделены желтыми линиями), каждая из которых состоит из 4 параллельно выполняемых переборов данных. Отметим, что на каждой итерации в рамках каждого перебора задействованы данные из одного и того же диапазона. Число обращений в память в каждой итерации различается, и это позволяет предположить, что характер переборов также может отличаться. <br />
<br />
Рассмотрим фрагменты двух итераций более детально. На рис. 8 и 9 соответственно представлены фрагменты 1 и 2, обозначенные на рис. 7 зеленым. Данные фрагменты содержат по 1000 обращений. Можно увидеть, что в целом все переборы в двух фрагментах выполняются с регулярным и достаточно небольшим шагом по памяти, однако интенсивность обращений в каждом случае разная. Причем при смене итерации меняется и интенсивность обращений к одному и тому диапазону данных. <br />
<br />
[[file:cyclic_reduction_2.png|thumb|center|500px|Рисунок 8. Профиль обращений, фрагмент 1]]<br />
<br />
[[file:cyclic_reduction_3.png|thumb|center|500px|Рисунок 9. Профиль обращений, фрагмент 2]]<br />
<br />
Рассмотрим еще более детально небольшие части данных фрагментов, чтобы дальше проанализировать структуру представленных переборов. На рис. 10 представлены небольшие локальные области, выделенные зеленым на рис. 8 и 9. Можно увидеть, что локальная структура обращений в целом отличается. В частности, в области, выделенной на рис. 9, шаг по памяти больше, а также выполняется меньше повторных обращений, что говорит о более низкой пространственной локальности. <br />
<br />
[[file:cyclic_reduction_4.png|thumb|center|700px|Рисунок 10. Локальная структура фрагментов 2 и 3]]<br />
<br />
В целом можно сказать, что структура итераций похожа, однако локальность несколько отличается в силу указанных выше причин. Если же говорить в среднем, такое строение итераций характеризуется от средней до высокой (в зависимости от локальной структуры) пространственной локальностью и низкой временной локальностью, поскольку данные перебираются подряд (обычно с небольшим шагом по памяти), но при этом повторно почти не используются. Отметим, что размер итераций достаточно велик, так что повторное обращение к данным на следующей итерации не приводит к улучшению локальности.<br />
<br />
Отдельно рассмотрим самый конец первого этапа (фрагмент 3 на рис. 7), который показан на рис. 11. Здесь локальность обращений в память самая низкая, поскольку шаг по памяти в переборах становится наибольшим. В центре рисунка можно заметить область особенно низкой локальности (наиболее разрозненные обращения). Хотя данный фрагмент небольшого размера, он может серьезно снижать общую производительность взаимодействия с памятью.<br />
<br />
[[file:cyclic_reduction_5.png|thumb|center|500px|Рисунок 11. Профиль обращений, фрагмент 3]]<br />
<br />
Второй этап также имеет регулярную структуру. Рассмотрим на рис. 12 небольшую область более детально (фрагмент 4 на рис. 7). Из данного рисунка видно, что здесь также выполняются последовательные переборы, локальная структура которых может отличаться. Как и в рассмотренном на первом этапе случае, это приводит к различиям по пространственной и временной локальности. Стоит отметить, что угол наклона переборов на втором этапе в целом больше, чем на первом этапе (см. рис 7), что говорит о большем шаге по памяти, и, как следствие, более низкой пространственной локальности, однако это различие может нивелироваться различиями в локальной структуре.<br />
<br />
[[file:cyclic_reduction_6.png|thumb|center|500px|Рисунок 12. Профиль обращений, фрагмент 4]]<br />
<br />
В целом можно сказать, что общий профиль обладает низкой временной локальностью. Пространственная локальность, скорее всего, не очень высокая, в частности, из-за неэффективной области в конце первого этапа. Однако делать предположения относительно пространственной локальности в данном случае достаточно сложно ввиду большого разнообразия локальных структур переборов.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 13 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае низка и лишь чуть выше значения daps для такой неэффективной с точки зрения работы с памятью программы как тест RandomAccess (rand). В целом это соотносится с выводами, которые были сделаны нами ранее<br />
<br />
<br />
[[file:cyclic_reduction_daps.png|thumb|center|700px|Рисунок 13. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Исходя из таких свойств алгоритма циклической редукции, как плохая локальность и наличие избыточности, логично было бы использовать его не в "чистом виде", а, проведя несколько этапов редукции, решать оставшуюся СЛАУ более простым методом вроде прогонки.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
При оценке масштабируемости этого алгоритма, как и всех алгоритмов с избыточными вычислениями, следует учитывать, что сравнение по быстродействию и эффективности нужно проводить не с однопроцессорным вариантом исполнения самого алгоритма, а с алгоритмом [[Прогонка, точечный вариант|прогонки]]. <br />
<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации циклической редукции согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [2 : 256] с шагом степени двойки;<br />
* размер матрицы [64 : 33554432] с шагом степени двойки.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 3.89e-09%;<br />
* максимальная эффективность реализации 0.00163%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации циклической редукции в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:Cyclic reduction performance.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:Cyclic reduction eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[https://github.com/yflim/Code-sample--parallel-tridiagonal-solver Исследованная параллельная реализация на языке C]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В силу того, что граф алгоритма несколько схож по структуре на сдваивание, он лучше всего отображался бы на архитектуры типа гиперкуб, однако в последнее десятилетие стали ясны физические и технологические сложности для проектирования таких систем. Вместе с тем, его разработанность отдельными группами исследователей (в блочных вариантах, в том числе) вполне позволяет применять циклическую редукцию и на обычных массово параллельных компьютерах. Особенно рекомендуется не доводить циклическую редукцию до предела, а на отдельных узлах использовать такие старые методы, как монотонная и встречная прогонки.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Обычно циклическую редукцию как в блочном, так и в точечном варианте реализуют матфизики, решающие задачи с задачами, содержащими, например, дискретизацию оператора Лапласа. Её, несмотря на популярность, редко включают в пакеты программ. В основном это связано с тем, что простота схемы циклической редукции остаётся только для определённых размерностей задач, а универсальные решатели на её основе никто не делает. Для других размерностей могут быть придуманы разные, в том числе и более быстрые варианты циклической редукции<ref>А.В.Фролов "О коэффициенте при логарифме в критическом пути графа циклической редукции" // Суперкомпьютерные дни в России: Труды международной конференции (26-27 сентября 2016 г., г. Москва). – М.: Изд-во МГУ, 2016. с. 307-313.</ref>, но у них очень сложная схема, и каждый раз нужно выполнять анализ на устойчивость.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
{{algorithm}}<br />
[[Категория:Алгоритмы с избыточными вычислениями]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4_%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D1%80%D0%B5%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D0%B8&diff=21044Полный метод циклической редукции2016-12-16T10:46:28Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = Циклическая редукция для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>17n + o(n)</math><br />
| pf_height = <math>7 log_2 n</math><br />
| pf_width = <math>3n/2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Циклическая редукция''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где <br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ2}}<br />
<br />
'''Циклическая редукция''', как и все варианты прогонки, заключается <ref name="IK3d">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г., 208 с.</ref><ref name="FAVT-2016">Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ’2016): труды международной научной конференции (г. Архангельск, 28 марта – 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref> в исключении из уравнений неизвестных, однако, в отличие от них, в ней исключение ведут одновременно по всей СЛАУ. В принципе, её можно считать вариантом [[Метод редукции|метода редукции]], выполняемого максимально возможное для данной СЛАУ число раз.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Лучше всего схема циклической редукции<ref name="IK3d" /> разработана для случая <math>n = 2^{m}-1</math>. Эта схема состоит из прямого и обратного ходов. <br />
Прямой ход состоит из последовательного уменьшения в СЛАУ количества уравнений почти в 2 раза (за счёт подстановки из уравнений с нечётными номерами заменяются уравнения с чётными), пока не останется одно уравнение, обратный - в получении всё большего количества компонент решения исходной СЛАУ. Оба хода - как прямой, так и обратный - разбиты на шаги. Здесь мы приведём тот вариант алгоритма, в котором операции экономятся за счёт предварительной нормировки уравнений, используемых для исключения неизвестных. <br />
<br />
==== Прямой ход редукции ====<br />
<br />
В начале считается, что все <br />
<math>c^{(0)}_{i} = c_{i}, b^{(0)}_{i} = b_{i}, a^{(0)}_{i} = a_{i}, f^{(0)}_{i} = f_{i}, x^{(0)}_{i} = x_{i}</math><br />
<br />
На k-м шаге теперь выполняем процедуру редукции системы уравнений размерности n.<br />
<br />
Для каждого из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math> <br />
<br />
с нечётными <math>i</math> с помощью деления уравнения на <math>b^{(k)}_{i}</math> выполняется его замена на уравнение <br />
<br />
<math> (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} + x^{(k)}_{i} + (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} = f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
[[file:CycRedMicro0.png|thumb|right|150px|Рисунок 1. Микрограф "узла" подготовительного шага прямого хода алгоритма циклической редукции ]]<br />
<br />
Уравнение <br />
<br />
<math>b^{(k)}_{1} x^{(k)}_{1} + a^{(k)}_{1} x^{(k)}_{2} = f^{(k)}_{1}</math><br />
<br />
аналогично меняется на уравнение <br />
<br />
<math>x^{(k)}_{1} + (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2} = f^{(k)}_{1}/b^{(k)}_{1}</math>:<br />
<br />
а уравнение <br />
<br />
<math>c^{(k)}_{n} x^{(k)}_{n-1} + b^{(k)}_{n} x^{(k)}_{n} = f^{(k)}_{n}</math> <br />
<br />
меняется на уравнение <br />
<br />
<math>(c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1} + x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n}</math>:<br />
<br />
[[file:CycRedMicroDirect.png|thumb|right|400px|Рисунок 2a. Микрограф "узла" с нечётным номером прямого хода алгоритма циклической редукции]]<br />
<br />
Для каждого же из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math><br />
<br />
с чётными <math>i</math> (кроме <math>2</math> и <math>n-2</math>) выполняется, с учётом <math>x^{(k+1)}_{i/2} = x^{(k)}_{i}</math> его замена на уравнение <br />
<br />
<math>c^{(k+1)}_{i/2} x^{(k+1)}_{(i-2)/2} + b^{(k+1)}_{i/2} x^{(k+1)}_{i/2} + a^{(k+1)}_{i/2} x^{(k+1)}_{(i+2)/2} = f^{(k+1)}_{i/2}</math><br />
<br />
[[file:CycRedMicroDirect2.png|thumb|right|400px|Рисунок 2b. Микрограф "узла" с чётным номером прямого хода алгоритма циклической редукции ]]<br />
<br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{i/2} = - c^{(k)}_{i}(c^{(k)}_{i-1}/b^{(k)}_{i-1})</math>, <br />
<br />
<math>a^{(k+1)}_{i/2} = - a^{(k)}_{i}(a^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>b^{(k+1)}_{i/2} = b^{(k)}_{i} - c^{(k)}_{i}(a^{(k)}_{i-1}/b^{(k)}_{i-1}) - a^{(k)}_{i}(c^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>f^{(k+1)}_{i/2} = f^{(k)}_{i} - c^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1} - a^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1}</math>.<br />
<br />
Для 2го уравнения выполняется его замена на уравнение<br />
<br />
<math>b^{(k+1)}_{1} x^{(k+1)}_{1} + a^{(k+1)}_{1} x^{(k+1)}_{(2} = f^{(k+1)}_{1}</math><br />
<br />
при этом<br />
<br />
<math>a^{(k+1)}_{1} = - a^{(k)}_{2}(a^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>b^{(k+1)}_{1} = b^{(k)}_{2} - c^{(k)}_{2}(a^{(k)}_{1}/b^{(k)}_{1}) - a^{(k)}_{2}(c^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>f^{(k+1)}_{1} = f^{(k)}_{2} - c^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1} - a^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1}</math><br />
<br />
<br />
<math>n-1</math>-е уравнение заменяется на <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-3)/2} + b^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-1)/2} = f^{(k+1)}_{(n-1)/2}</math><br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} = - c^{(k)}_{n-1}(c^{(k)}_{n-2}/b^{(k)}_{n-2})</math>,<br />
<br />
<math>b^{(k+1)}_{(n-1)/2} = b^{(k)}_{n-1} - c^{(k)}_{n-1}(a^{(k)}_{n-2}/b^{(k)}_{n-2}) - a^{(k)}_{n-1}(c^{(k)}_{n}/b^{(k)}_{n})</math>,<br />
<br />
<math>f^{(k+1)}_{(n-1)/2} = f^{(k)}_{n-1} - c^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2} - a^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2}</math>.<br />
<br />
По окончании всех этих манипуляций размерность k+1-й СЛАУ оказывается равной <math>(n-1)/2</math>.<br />
<br />
Шаги повторяются до тех пор, пока после <math>m-1</math> шагов редукции размерность СЛАУ не становится равной 1 и остаётся одно уравнение<br />
<br />
<math>b^{(m-1)}_{1} x^{(m-1)}_{1} = f^{(m-1)}_{1}</math><br />
<br />
==== Обратный ход редукции ====<br />
<br />
[[file:CycRedMicroRev.png|thumb|right|150px|Рисунок 3. Микрограф "узла" обратного хода алгоритма циклической редукции ]]<br />
<br />
Из последнего уравнения, полученного прямым ходом, вычисляется <br />
<br />
<math>x^{(m-1)}_{1} = f^{(m-1)}_{1}/b^{(m-1)}_{1}</math><br />
<br />
Теперь, последовательно уменьшая верхние индексы неизвестных, используется нечётные уравнения каждого шага для вычисления неизвестных с соотвествующими нечётными номерами. Чётные неизвестные получаются из тождеств <math>x^{(k)}_{i} = x^{(k+1)}_{i/2}</math>, а для нечётных <math>i</math> <br />
<br />
<math>x^{(k)}_{i} = f^{(k)}_{i}/b^{(k)}_{i} - (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} - (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} </math><br />
<br />
c "левого края" системы будет <br />
<br />
<math>x^{(k)}_{1} = f^{(k)}_{1}/b^{(k)}_{1} - (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2}</math><br />
<br />
а с "правого"<br />
<br />
<math>x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n} - (c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1}</math><br />
<br />
После вычисления всех <math>x^{(0)}_{i}</math> значения искомых неизвестных <math>x_{i} = x^{(k)}_{i}</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Видно, что, поскольку вычисляемые на каждом шаге прямого хода редукции при преобразовании нечётных уравнений отношения коэффициентов <br />
<br />
<math> c^{(k)}_{i}/b^{(k)}_{i} , a^{(k)}_{i}/b^{(k)}_{i} , f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
почти все используются для преобразований двух чётных уравнений, то при выделении "микровычислений", из которых следует составить шаги редукции и которые составляют его ядро, лучше отнести вычисления этих отношений к предыдущему шагу редукции. Таким образом, на "подготовительном шаге" микроядро будет для каждого нечётного <math>i</math> состоять только из трёх делений (кроме <math>2</math> и <math>n</math> - там будет по 2 деления), а затем на каждом последующем шаге редукции для каждого нечётного <math>i</math> - из двух умножений и двух вычислений выражений типа <math>a-bc-de</math>, с последующими тремя делениями (на "краях" часть этих операций отсутствует или урезана, но общую картину это не очень меняет). Для чётных <math>i</math> деления в микроядре будут отсутствовать.<br />
<br />
Что касается шагов обратного хода, то там для каждого <math>i</math> рано или поздно выполняется одна операция типа <math>a-bc-de</math> (на "краях" - типа <math>a-bc</math>).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[file:CycRedDirect.png|thumb|right|400px|Рисунок 4. Граф алгоритма прямого хода циклической редукции при n=15. Светло-зелёным обозначены микроядра "подготовительного шага" с тремя (или меньше) делениями, синим - микроядра нечётных узов, сине-зелёным - ядра чётных узлов (без делений).]]<br />
<br />
[[file:CycRedRev.png|thumb|right|400px|Рисунок 5. Граф алгоритма обратного хода циклической редукции при n=15. В вершинах - операции вида a-bc-de, в вершинах с 1 входящей дугой - вида a-bc. Показаны только дуги, передающие значения найденных неизвестных.]]<br />
<br />
Если выразить макроструктуру алгоритма циклической редукции в терминах её "микроядер", то прямой ход редукции несколько схож на схемы сдваивания, но отличается от неё не только количеством входящих в макровершины дуг, из-за чего её схема - скорее "страивание", но и зацеплением соседних ветвей. Поэтому, в отличие от схем сдваивания, для которых характерно полностью независимое исполнение ветвей до определённого момента, в схеме редукции это зацепление ветвей не даёт им выполняться полностью независимо. <br />
<br />
Обратный ход циклической редукции - уже практически полное "обратное" сдваивание, и на этом этапе разделение на независимые ветви может быть проделано после старта, если размножить необходимые данные.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Метод циклической редукции изначально спроектирован для параллельного исполнения, поскольку является по отношению к, например, классической прогонке, алгоритмом с избыточными вычислениями. Поэтому смысла в его последовательной реализации обычно не видят и они не встречаются в библиотеках программ. Тем не менее, из-за того, что современная архитектура даже одиночных узлов и персональных компьютеров не вполне последовательна, то этот смысл, как вполне может оказаться<ref name="FAVT-2016" />, уже появился, поскольку, кроме простого количества операций, производительность вычислений на компьютере определяется и другими параметрами алгоритма, в частности, локальностью вычислений.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Поскольку алгоритм циклической редукции обычно не предназначен для последовательного исполнения, то его "последовательную сложность" скорее следует считать только оценкой общего количества операций. Если взять только главный член, линейный по размеру, и опустить меньшие по порядку (логарифмические и константы), то получается, что в циклической редукции ''3n'' делений, ''8n'' умножений и ''6n'' вычитаний/сложений. Таким образом, при классификации по последовательной сложности, алгоритм циклической редукции относится к алгоритмам ''с линейной сложностью''.<br />
<br />
Сравнение сложности с алгоритмом [[Прогонка, точечный вариант|прогонки]] показывает также, что циклическая редукция - алгоритм с избыточными вычислениями: избыточность по сравнению с прогонкой более чем в 2 раза.<br />
<br />
=== Информационный граф ===<br />
<br />
У прямого хода циклической редукции макрограф представлен на Рис. 4, при этом графы разные макровершин представлены на Рис. 1, 2a, 2b. Как видно, после первого шага схему можно условно назвать "схемой страивания", поскольку связанные с каждым уравнением, остающимся в редуцированной СЛАУ, коэффициенты вычисляются по формулам из коэффициентов '''трёх''' соседних уравнений в нередуцированной СЛАУ. <br />
<br />
Обратный ход имеет макрограф, представленный на Рис. 5, при этом графы макровершин представлены на Рис. 3 (крайние макровершины, имеющие только одну входящую дугу на макрографе, замещают недостающие данные нулями, соответственно уменьшая вычисления). Этот макрограф, в принципе, имеет макроструктуру, обратную к схеме сдваивания, поэтому её можно условно назвать "схемой раздваивания", с одной поправкой: все макровершины, кроме крайних, имеют по две входящих дуги, которые немного изменяют обычное "раздваивание".<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для выполнения циклической редукции в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>log_2 n</math> ярусов делений, из них самый широкий ярус - первый, в нём <math>3n/2</math> делений. <br />
* <math>2 log_2 n</math> ярусов умножений и <math>4 log_2 n</math>сложений/вычитаний.<br />
<br />
Ширина ярусов экспоненциально, с показателем 2, убывает с их номерами, а потом, на обратном ходе, экспоненциально растёт, также с показателем 2. Эта неоднородность приводит к тому, что при практическом полном распараллеливании большую часть времени большинство устройств будет простаивать. <br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам с ''логарифмической сложностью''. При классификации по ширине ЯПФ её сложность будет ''линейной''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является отношением размера задачи к его логарифму. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно циклическая редукция используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
В случае, когда требуется решение нескольких СЛАУ с одной и той же матрицей, большую часть прямого хода вычислений (см. рисунки с графом алгоритма) можно не повторять.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Из-за избыточности вычислений на компьютерах без параллельных устройств обычно используют более простую монотонную или встречную [[Прогонка, точечный вариант|прогонку]], а циклическую редукцию в последовательном варианте обычно просто не реализуют. Этот выбор, однако, не так очевиден на современных "однопроцессорных" суперскалярных компьютерах, поскольку в циклической редукции нет такой избыточной локальности, как в [[Прогонка, точечный вариант|прогонке]]. Однако, эта сторона циклической редукции ещё не исследована и ждёт тех, кто проведёт соответствующую работу.<br />
<br />
Приведем пример подпрограммы, реализующей циклическую прогонку СЛАУ с числом уравнений "степень двойки минус 1", где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы. Следует обратить внимание на то, что данная версия циклической редукции непригодна для повторного решения СЛАУ с той же матрицей и новой правой частью, поскольку при повторении нужно несколько большее количество промежуточных данных. <br />
<br />
<source lang="fortran"><br />
subroutine cprogm (a,x,N,m) ! N=2^m-1<br />
real a(3,N), x(N)<br />
<br />
k=1<br />
k2=2<br />
kk=1 <br />
<br />
a(2,1)=1./a(2,1)<br />
a(2,N)=1./a(2,N)<br />
a(3,1)=a(3,1)*a(2,1)<br />
a(1,N)=a(1,N)*a(2,N)<br />
x(1) = x(1)*a(2,1)<br />
x(N) = x(N)*a(2,N)<br />
do i=3,N-2,2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
do j=1,m-2<br />
<br />
k=k2 ! k=2^j<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
a(3,k) = -a(3,k)*a(3,k+kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(1,N+1-k)*a(3,N+1-k-kk)<br />
a(1,N+1-k)= - a(1,N+1-k)*a(1,N+1-k-kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k-kk)*a(1,N+1-k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k) - x(k+kk)*a(3,k+kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(3,N+1-k)*a(1,N+1-k+kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k+kk)*a(3,N+1-k+kk) <br />
<br />
k2=k2*2 ! k2=2^(j+1)<br />
<br />
do i = k2, N+1-k2, k<br />
a(2,i) = a(2,i) - a(1,i)*a(3,i-kk)<br />
x(i) = x(i)- x(i-kk)*a(1,i-kk)<br />
a(3,i) = -a(3,i)*a(3,i+kk)<br />
a(1,i)= - a(1,i)*a(1,i-kk) <br />
a(2,i) = a(2,i) - a(3,i)*a(1,i+kk)<br />
x(i) = x(i)- x(i+kk)*a(3,i+kk)<br />
end do <br />
<br />
a(2,k)=1./a(2,k)<br />
a(2,N+1-k)=1./a(2,N+1-k)<br />
a(3,k)=a(3,k)*a(2,k)<br />
a(1,N+1-k)=a(1,N+1-k)*a(2,N+1-k)<br />
x(k) = x(k)*a(2,k)<br />
x(N+1-k) = x(N+1-k)*a(2,N+1-k) <br />
<br />
do i = k2+k, N+1-k2-k, k2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
<br />
kk=k ! budet kk=2^(j-1)<br />
<br />
end do <br />
<br />
c m-1 shag & start of reverse<br />
<br />
k=k2 ! k=2^(m-1) i kk=2^(m-2)<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k)- x(k+kk)*a(3,k+kk)<br />
a(2,k) = 1./a(2,k)<br />
x(k) = x(k)*a(2,k)<br />
<br />
c start reverse <br />
<br />
x(kk) = x(kk) - a(3,kk)*x(k)<br />
<br />
x(k+kk) = x(k+kk) - a(1,k+kk)*x(k)<br />
<br />
do j=m-2, 1, -1<br />
<br />
k2 = k ! k2 = 2^(j+1)<br />
k = kk ! k = 2^j<br />
kk = kk/2 ! kk = 2^(j-1)<br />
<br />
x (kk) = x (kk) - a(3,kk)*x(kk+kk)<br />
x (N+1-kk) = x (N+1-kk) - a(1,N+1-kk)*x(N+1-k) <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(1,i)*x(i-kk) <br />
end do <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(3,i)*x(i+kk) <br />
end do <br />
end do<br />
<br />
return<br />
end<br />
</source><br />
<br />
Здесь необходимо отметить, что, несмотря на последовательность этой реализации, она вполне может быть исполнена и параллельно, нужно только как-то скомандовать имеющемуся в наличии компилятору, что циклы по i параллельны (например, спецкомментариями OpenMP).<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
По графу видно, что, хотя и налицо "местная" локальность по вычислениям, но от яруса к ярусу расстояние между запрашиваемыми данными растёт от первых и последних ярусов алгоритма к его середине, являющимся не только узким местом алгоритма, но и имеющим наибольшую длину передачи данных между устройствами. Поэтому, хотя в циклической редукции нет той избыточной локальности, которая притормаживает работу прогонки, это преимущество, как показали замеры<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref> (см. Рисунок 6), не способно компенсировать недостатки общей локальности графа циклической редукции.<br />
<br />
[[File:I3-cyc.png|thumb|center|1000px|Рисунок 6. Отношение времени исполнения циклической редукции и монотонных прогонок на ПК. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение циклической редукции, к времени, затраченному на выполнение монотонной повторной прогонки. Система Core i3 + Windows 8, сборка кода компилятором GNU Fortran. На других системах получена качественно примерно такая же картина, с различием по выходу за границы кэша. Между синей и красной линиями - примерная область взаимной компенсации неарифметических факторов. При малых размерах сказывается неиспользование прогонками суперскалярности процессора.]]<br />
<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:cyclic_reduction_1.png|thumb|center|700px|Рисунок 7. Полный метод циклической редукции. Общий профиль обращений в память]]<br />
<br />
На рис. 7 представлен профиль обращений в память для реализации полного метода циклической редукции. Данный профиль достаточно интересно. Как нам известно из описания самого алгоритма, он состоит из двух частей – прямого и обратного хода. На общем профиле синяя вертикальная черта отделяет эти два этапа. Рассмотрим их в отдельности.<br />
<br />
На первом этапе явно видны отдельные итерации (первые 3 итерации разделены желтыми линиями), каждая из которых состоит из 4 параллельно выполняемых переборов данных. Отметим, что на каждой итерации в рамках каждого перебора задействованы данные из одного и того же диапазона. Число обращений в память в каждой итерации различается, и это позволяет предположить, что характер переборов также может отличаться. <br />
<br />
Рассмотрим фрагменты двух итераций более детально. На рис. 8 и 9 соответственно представлены фрагменты 1 и 2, обозначенные на рис. 7 зеленым. Данные фрагменты содержат по 1000 обращений. Можно увидеть, что в целом все переборы в двух фрагментах выполняются с регулярным и достаточно небольшим шагом по памяти, однако интенсивность обращений в каждом случае разная. Причем при смене итерации меняется и интенсивность обращений к одному и тому диапазону данных. <br />
<br />
[[file:cyclic_reduction_2.png|thumb|center|500px|Рисунок 8. Профиль обращений, фрагмент 1]]<br />
<br />
[[file:cyclic_reduction_3.png|thumb|center|500px|Рисунок 9. Профиль обращений, фрагмент 2]]<br />
<br />
Рассмотрим еще более детально небольшие части данных фрагментов, чтобы дальше проанализировать структуру представленных переборов. На рис. 10 представлены небольшие локальные области, выделенные зеленым на рис. 2 и 3. Можно увидеть, что локальная структура обращений в целом отличается. В частности, в области, выделенной на рис. 9, шаг по памяти больше, а также выполняется меньше повторных обращений, что говорит о более низкой пространственной локальности. <br />
<br />
[[file:cyclic_reduction_4.png|thumb|center|700px|Рисунок 10. Локальная структура фрагментов 2 и 3]]<br />
<br />
В целом можно сказать, что структура итераций похожа, однако локальность несколько отличается в силу указанных выше причин. Если же говорить в среднем, такое строение итераций характеризуется от средней до высокой (в зависимости от локальной структуры) пространственной локальностью и низкой временной локальностью, поскольку данные перебираются подряд (обычно с небольшим шагом по памяти), но при этом повторно почти не используются. Отметим, что размер итераций достаточно велик, так что повторное обращение к данным на следующей итерации не приводит к улучшению локальности.<br />
<br />
Отдельно рассмотрим самый конец первого этапа (фрагмент 3 на рис. 7), который показан на рис. 11. Здесь локальность обращений в память самая низкая, поскольку шаг по памяти в переборах становится наибольшим. В центре рисунка можно заметить область особенно низкой локальности (наиболее разрозненные обращения). Хотя данный фрагмент небольшого размера, он может серьезно снижать общую производительность взаимодействия с памятью.<br />
<br />
[[file:cyclic_reduction_5.png|thumb|center|500px|Рисунок 11. Профиль обращений, фрагмент 3]]<br />
<br />
Второй этап также имеет регулярную структуру. Рассмотрим на рис. 12 небольшую область более детально (фрагмент 4 на рис. 7). Из данного рисунка видно, что здесь также выполняются последовательные переборы, локальная структура которых может отличаться. Как и в рассмотренном на первом этапе случае, это приводит к различиям по пространственной и временной локальности. Стоит отметить, что угол наклона переборов на втором этапе в целом больше, чем на первом этапе (см. рис 7), что говорит о большем шаге по памяти, и, как следствие, более низкой пространственной локальности, однако это различие может нивелироваться различиями в локальной структуре.<br />
<br />
[[file:cyclic_reduction_6.png|thumb|center|500px|Рисунок 12. Профиль обращений, фрагмент 4]]<br />
<br />
В целом можно сказать, что общий профиль обладает низкой временной локальностью. Пространственная локальность, скорее всего, не очень высокая, в частности, из-за неэффективной области в конце первого этапа. Однако делать предположения относительно пространственной локальности в данном случае достаточно сложно ввиду большого разнообразия локальных структур переборов.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 13 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае низка и лишь чуть выше значения daps для такой неэффективной с точки зрения работы с памятью программы как тест RandomAccess (rand). В целом это соотносится с выводами, которые были сделаны нами ранее<br />
<br />
<br />
[[file:cyclic_reduction_daps.png|thumb|center|700px|Рисунок 13. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Исходя из таких свойств алгоритма циклической редукции, как плохая локальность и наличие избыточности, логично было бы использовать его не в "чистом виде", а, проведя несколько этапов редукции, решать оставшуюся СЛАУ более простым методом вроде прогонки.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
При оценке масштабируемости этого алгоритма, как и всех алгоритмов с избыточными вычислениями, следует учитывать, что сравнение по быстродействию и эффективности нужно проводить не с однопроцессорным вариантом исполнения самого алгоритма, а с алгоритмом [[Прогонка, точечный вариант|прогонки]]. <br />
<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации циклической редукции согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [2 : 256] с шагом степени двойки;<br />
* размер матрицы [64 : 33554432] с шагом степени двойки.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 3.89e-09%;<br />
* максимальная эффективность реализации 0.00163%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации циклической редукции в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:Cyclic reduction performance.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:Cyclic reduction eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[https://github.com/yflim/Code-sample--parallel-tridiagonal-solver Исследованная параллельная реализация на языке C]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В силу того, что граф алгоритма несколько схож по структуре на сдваивание, он лучше всего отображался бы на архитектуры типа гиперкуб, однако в последнее десятилетие стали ясны физические и технологические сложности для проектирования таких систем. Вместе с тем, его разработанность отдельными группами исследователей (в блочных вариантах, в том числе) вполне позволяет применять циклическую редукцию и на обычных массово параллельных компьютерах. Особенно рекомендуется не доводить циклическую редукцию до предела, а на отдельных узлах использовать такие старые методы, как монотонная и встречная прогонки.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Обычно циклическую редукцию как в блочном, так и в точечном варианте реализуют матфизики, решающие задачи с задачами, содержащими, например, дискретизацию оператора Лапласа. Её, несмотря на популярность, редко включают в пакеты программ. В основном это связано с тем, что простота схемы циклической редукции остаётся только для определённых размерностей задач, а универсальные решатели на её основе никто не делает. Для других размерностей могут быть придуманы разные, в том числе и более быстрые варианты циклической редукции<ref>А.В.Фролов "О коэффициенте при логарифме в критическом пути графа циклической редукции" // Суперкомпьютерные дни в России: Труды международной конференции (26-27 сентября 2016 г., г. Москва). – М.: Изд-во МГУ, 2016. с. 307-313.</ref>, но у них очень сложная схема, и каждый раз нужно выполнять анализ на устойчивость.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
{{algorithm}}<br />
[[Категория:Алгоритмы с избыточными вычислениями]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&diff=21043Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду2016-12-16T10:44:33Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение симметричной вещественной матрицы к трёхдиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>O(n^3)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2/2</math><br />
| input_data = <math>(n^2+n)/2</math><br />
| output_data = <math>(n^2+3n)/2</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к трёхдиагональному виду, или, что то же самое, для разложения <math>A=QTQ^T</math> (<math>Q</math> - ортогональная, <math>T</math> — симметричная трёхдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QTQ^T</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений) с последующим умножением на те же матрицы справа. <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-м столбце. После умножения на эту же матрицу отражения справа автоматически убираются и ненулевые наддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-й строке, а полученная модификация снова приобретает симметричный вид. <br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Потом по аналогичным формулам модифицируются строки ниже ведущей. Благодаря ассоциативности этих операций после вычисления вектора <math>v</math> можно сразу выписать формулы модификации всех элементов справа и снизу от ведущих столбца и строки. Оказывается, что если для каждого столбца матрицы <math>x^{(j)}</math> с номером <math>j</math> известно <math>\beta_{j}=\frac{(x^{(j)},v)}{\gamma}</math>, то для модифицируемого элемента матрицы <math>y</math> в позиции <math>(i,j)</math> выполняется модификация <math>y' = y - \beta_{i} v_{i} - \beta_{j} v_{j} - \Delta v_{i} v_{j}</math>, где <math>\Delta = \frac{(\beta,v)}{\gamma}</math>. При этом все эти модификации на каждом конкретном шаге алгоритма для разных пар <math>(i,j)</math> можно выполнять независимо друг от друга, в том числе и параллельно.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
[[File:HausLeftSym1.png|thumb|right|800px|Рисунок 1. Граф первой половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-4</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Зелёные кружки - операция вида <math>a+b^2</math>, салатовые - операция вида <math>a+bc</math>. Синий кружок - вычисление параметров матрицы отражения, светло-красные - вычисление коэффициентов <math>\beta_i</math>для следующего полушага, жёлтые - вычисление вектора <math>v</math>.]]<br />
[[File:HousLeftSym2.png|thumb|right|600px|Рисунок 2. Граф второй половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-3</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Синий, жёлтые и светло-красные кружки выполняются на первом полушаге (см. Рисунок 1). Голубые кружки - операции <math>a+bc</math>, зелёное - умножение, чёрные - операции <math>a+bc+de+fce</math>.]]<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также проводимые над нижним правым квадратом матрицы операции вида <math>y'=y-ab-cd-fbd</math>, с учётом симметрии матрицы.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также массовые покомпонентные операции <math>y'=y-ab-cd-fbd</math>. При этом, однако, строгая последовательность выполнения первых двух подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них коэффициентов модификации. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) одновременное умножение слева матрицы отражения <math>U_{i}</math> и справа матрицы отражения <math>U_{i}^T</math>на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также массовых модификаций элементов вида <math>y'=y-\alpha v - \beta w - \Delta vw</math>. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>O(n^3)</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На рисунках 1 и 2 приведён граф алгоритма шага метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в симметричном приведении матрицы порядка <math>n</math> к трёхдиагональной методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>2(n-i)</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. <br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная симметричная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>(n^2+n)/2</math>.<br />
<br />
'''Выходные данные''': трёхдиагональная матрица <math>D</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>(n^2+3n)/2</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной симметричной вещественной матрицы к трёхдиагональному виду на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-2<br />
<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
SX(I)=SX(I)+A(J,I)*A(J,I)<br />
END DO <br />
DO K = I+1, N<br />
DO J = N-1, K, -1<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
DO J = K-1,I+1,-1<br />
SX(K)=SX(K)+A(J,I)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I+1,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I+1,I)*BETA+SIGN(1.,A(I+1,I)) <br />
A(I+1,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N<br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2<br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I+1,I)=ALPHA<br />
G=1.! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N <br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2 <br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1.<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
Здесь симметричная трёхдиагональная матрица хранится в диагонали и нижней кодиагонали массива A, вектора v - в поддиагональной части массива, за исключением первых их элементов, для которых выделен массив SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qtq_1.png|thumb|center|700px|Рисунок 3. Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Общий профиль обращений в память]]<br />
<br />
На рис. 3 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Данный профиль обладает явной итерационной структурой, при этом видно, что итерации очень похожи. Основное их отличие заключается в наборе используемых данных – на каждой следующей итерации несколько первых элементов отбрасываются их рассмотрения, то есть чем позже итерация, тем меньше данных в ней задействовано. Также можно отметить, что число обращений в каждой последующей итерации немного уменьшается. Для того чтобы проанализировать общий профиль, в таком случае чаще всего достаточно изучить одну итерацию. Рассмотрим самую первую из них более детально.<br />
<br />
На рис. 4 показан набор обращений в рамках первой итерации (выделен на рис. 3 зеленым). Его можно разбить на несколько фрагментов и рассмотреть их отдельно. Строение фрагментов 2-5 можно оценить по общему графику для итерации. Фрагмент 2 представляет собой набор последовательных переборов в обратном порядке с небольшим шагом, причем число элементов в переборе постепенно уменьшается. Такое строение характеризуется достаточно высокой пространственной локальностью, поскольку часто происходит обращений к близко расположенным данным, однако низкой временной, поскольку данные повторно не используются. То же самое верно и для фрагмента 4, основное отличие которого заключается в том, что переборы выполняются в прямом порядке. Отметим, что некоторое искривление данного фрагмента связано не со строением самого фрагмента, а с разным числом параллельно выполняющихся обращений (см., например, правую область фрагмента 6), чего не наблюдается при выполнении фрагмента 2.<br />
<br />
[[file:householder_qtq_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагменты 3 и 5 состоят из небольшого числа обращений к данным, расположенным далеко друг от друга. Такие фрагменты характеризуются низкой пространственной и временной локальностью.<br />
<br />
Оставшиеся фрагменты требуют более детального рассмотрения. Перейдем к изучению фрагмента 1, который целиком представлен на рис. 5. Здесь мы можем видеть несколько этапов, каждый из которых представляет собой последовательный перебор элементов с шагом по памяти 1, причем в некоторых случаях данные много раз используются повторно (особенно это заметно в правой нижней области рисунка, где выполняется множество обращений к одному и тому же элементу). При условии, что в данном фрагменте задействовано всего около 40 элементов, можно говорить, что данный набор обращений обладает высокой пространственной и временной локальностью. <br />
<br />
[[file:householder_qtq_3.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, фрагмент 1]]<br />
<br />
Далее рассмотрим детально фрагмент 6 (рис. 6). Здесь можно увидеть, что повторные обращения к данным выполняются не подряд, а через некоторые промежутки, однако число задействованных элементов также очень невелико, так что и в этом случае локальность (и пространственная, и временная) будет высокой.<br />
<br />
[[file:householder_qtq_4.png|thumb|center|500px|Рисунок 6. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
В целом можно сказать, что рассматриваемая итерация обладает достаточно высокой пространственной и временной локальностью. Поскольку остальные итерации устроены подобным образом, данные вывод можно перенести и на общий профиль обращений.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 7 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно высока – значение daps лишь немногим уступает тесту Linpack, который обладает высокой эффективностью работы с памятью, и немного превосходит, например, реализацию метода Якоби или метода Гаусса.<br />
<br />
[[file:householder_qtq_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&diff=21042Алгоритм Беллмана-Форда2016-12-16T10:42:55Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Беллмана-Форда'''<ref>Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.</ref><ref>Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.</ref><ref>Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.</ref> предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность <math>O(mn)</math> против <math>O(m + n\ln n)</math> у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.<br />
<br />
=== Математическое описание алгоритма ===<br />
Пусть задан граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math> и выделенной вершиной-источником <math>u</math>. Обозначим через <math>d(v)</math> кратчайшее расстояние от источника <math>u</math> до вершины <math>v</math>.<br />
<br />
Алгоритм Беллмана-Форда ищет функцию <math>d(v)</math> как единственное решение уравнения<br />
:<math><br />
d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,<br />
</math><br />
с начальным условием <math>d(u) = 0</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основной операцией алгоритма является релаксация ребра: если <math>e = (w, v) \in E</math> и <math>d(v) > d(w) + f(e)</math>, то производится присваивание <math>d(v) \leftarrow d(w) + f(e)</math>.<br />
<br />
=== Макроструктура алгоритма ===<br />
Алгоритм последовательно уточняет значения функции <math>d(v)</math>.<br />
* В самом начале производится присваивание <math>d(u) = 0</math>, <math>d(v) = \infty</math>, <math>\forall v \ne u</math>.<br />
* Далее происходит <math>n-1</math> итерация, в ходе каждой из которых производится релаксация всех рёбер графа.<br />
<br />
Структуру можно описать следующим образом:<br />
<br />
1. Инициализация: всем вершинам присваивается предполагаемое расстояние <math>t(v)=\infty</math>, кроме вершины-источника, для которой <math>t(u)=0</math> .<br />
<br />
2. Релаксация множества рёбер <math>E</math><br />
<br />
а) Для каждого ребра <math>e=(v,z) \in E</math> вычисляется новое предполагаемое расстояние <math>t^' (z)=t(v)+ w(e)</math>.<br />
<br />
б) Если <math>t^' (z)< t(z)</math>, то происходит присваивание <math>t(z) := t' (z)</math> (релаксация ребра <math>e</math> ).<br />
<br />
3. Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.<br />
<br />
Если на <math>n</math>-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро <math>e=(v,z)</math>, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): <math>t(v)+w(e)<d(z)</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Последовательный алгоритм реализуется следующим псевдокодом:<br />
<br />
'''Входные данные''':<br />
граф с вершинами ''V'', рёбрами ''E'' с весами ''f''(''e'');<br />
вершина-источник ''u''.<br />
'''Выходные данные''': расстояния ''d''(''v'') до каждой вершины ''v'' ∈ ''V'' от вершины ''u''.<br />
<br />
'''for each''' ''v'' ∈ ''V'' '''do''' ''d''(''v'') := ∞<br />
''d''(''u'') = 0<br />
<br />
'''for''' ''i'' '''from''' 1 '''to''' |''V''| - 1:<br />
'''for each''' ''e'' = (''w'', ''v'') ∈ ''E'':<br />
'''if''' ''d''(''v'') > ''d''(''w'') + ''f''(''e''):<br />
''d''(''v'') := ''d''(''w'') + ''f''(''e'')<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Алгоритм выполняет <math>n-1</math> итерацию, на каждой из которых происходит релаксация <math>m</math> рёбер. Таким образом, общий объём работы составляет <math>O(mn)</math> операций.<br />
<br />
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.<br />
<br />
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.<br />
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).<br />
<br />
=== Информационный граф ===<br />
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).<br />
<br />
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.<br />
<br />
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]<br />
<br />
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется <math>O(n)</math> шагов при использовании <math>O(m)</math> процессоров.<br />
<br />
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math> с весами <math>f_j</math>), вершина-источник <math>u</math>.<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''' (возможные варианты):<br />
# для каждой вершины <math>v</math> исходного графа – последнее ребро <math>e^*_v = (w, v)</math>, лежащее на кратчайшем пути от вершины <math>u</math> к <math>v</math>, или соответствующая вершина <math>w</math>;<br />
# для каждой вершины <math>v</math> исходного графа – суммарный вес <math>f^*(v)</math> кратчайшего пути от от вершины <math>u</math> к <math>v</math>.<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма===<br />
<br />
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро <math>e = (v, w)</math> лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния <math>d(v)</math> удовлетворяют условию<br />
:<math><br />
d(v) + f(e) < d(w),<br />
</math><br />
где <math>f(e)</math> – вес ребра <math>e</math>. Условие может быть проверено для всех рёбер графа за время <math>O(m)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.<br />
<br />
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 3 представлен фрагмент 1 (выделен на рис. 2), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практические идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 2 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.<br />
<br />
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 2 (см. рис. 4). Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. <br />
<br />
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]<br />
<br />
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 4 зеленым. Однако в данном случае дальнейшее приближение (рис. 5) не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.<br />
<br />
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 1]]<br />
<br />
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. <br />
<br />
<br />
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.<br />
<br />
[[file:APSP scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция <code>[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]</code>).<br />
* Python: [https://networkx.github.io NetworkX] (функция <code>[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]</code>).<br />
* Java: [http://jgrapht.org JGraphT] (класс <code>[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]</code>).<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&diff=21041Алгоритм Борувки2016-12-16T10:39:57Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Борувки'''<ref>Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.</ref><ref>Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.</ref> предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть задан связный неориентированный граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math>. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).<br />
<br />
Алгоритм Борувки основан на следующих двух свойствах задачи:<br />
* '''Минимальное ребро фрагмента'''. Пусть <math>F</math> – фрагмент минимального остовного дерева и <math>e_F</math> – ребро наименьшего веса, исходящее из <math>F</math> (т.е. ровно один его конец является вершиной из <math>F</math>). Если ребро <math>e_F</math> единственно, то оно принадлежит минимальному остовному дереву.<br />
* '''Схлопывание фрагментов'''. Пусть <math>F</math> – фрагмент минимального остовного дерева графа <math>G</math>, а граф <math>G'</math> получен из <math>G</math> склеиванием вершин, принадлежащих <math>F</math>. Тогда объединение <math>F</math> и минимального остовного дерева графа <math>G'</math> даёт минимальное остовное дерево исходного графа <math>G</math>.<br />
<br />
В начале работы алгоритма каждая вершина графа <math>G</math> является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основными операциями являются:<br />
# Поиск минимального по весу исходящего ребра в каждом фрагменте.<br />
# Объединение фрагментов.<br />
<br />
=== Макроструктура алгоритма ===<br />
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. <br />
<br />
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.<br />
<br />
Пусть задан связный граф <math>G=(V,E)</math> с вершинами <math>V = ( v_{1}, v_{2}, ..., v_{n} )</math> и рёбрами <math>E = ( e_{1}, e_{2}, ..., e_{m} )</math>. Каждому ребру <math>e \in E</math> приписан вес <math>w(e)</math>. <br />
<br />
Требуется построить дерево <math>T^* \subseteq E</math>, связывающее все вершины, и имеющее наименьший возможный вес среди всех таких деревьев:<br />
<br />
<math> w(T^* )= \min_T( w(T)) </math><br />
<br />
где вес множества рёбер есть сумма их весов:<br />
<br />
<math>w(T)=\sum_{e \in T} (w(T))</math><br />
<br />
Если граф <math>G</math> не является связным, то дерева, связывающего все вершины, не существует. <br />
<br />
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности <math>G</math>. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).<br />
<br />
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====<br />
<br />
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:<br />
<br />
1. <math>FIND(v) = w</math> – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда <math>FIND(u) = FIND(v)</math>.<br />
<br />
2. <math>MERGE(u, v)</math> – объединяет два фрагмента, которым принадлежат вершины <math>u</math> и <math>v.</math> (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.<br />
<br />
==== Последовательная версия====<br />
<br />
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя <math>parent(v)</math>.<br />
<br />
1. Изначально <math>parent(v) := v</math> для всех вершин.<br />
<br />
2. <math>FIND(v)</math> выполняется следующим образом: полагаем <math>u := v</math>, и далее следуем по указателям <math>u := parent(u)</math> до тех пор, пока не станет <math>u = parent(u)</math>. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: <math>parent(u_i) := u</math>, либо производить схлопывание по пути: <math>parent(u) := parent(parent(u)))</math>.<br />
<br />
3. <math>MERGE(u, v)</math> выполняется следующим образом: вначале находим корневые вершины <math>u := FIND(u), v := FIND(v)</math>. Если <math>u = v</math>, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из <math>parent(u) := v</math> или <math>parent(v) := u</math>. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.<br />
<br />
1. В самом начале каждая вершина является отдельным фрагментом.<br />
<br />
2. На каждом шаге:<br />
<br />
* Для каждого фрагмента определяется минимальное по весу исходящее ребро.<br />
<br />
* Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.<br />
<br />
3. Алгоритм останавливается, когда остаётся только один фрагмент, либо когда ни у одного из фрагментов нет исходящих рёбер.<br />
<br />
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).<br />
<br />
Объединение фрагментов также может быть реализовано параллельно, с использованием описанной выше параллельной версии структуры Union-Find.<br />
<br />
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:<br />
<br />
1. В начале итерации счётчик активных фрагментов обнуляется.<br />
<br />
2. На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.<br />
<br />
3. На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция <math>MERGE(u, v)</math> вернула значение истина.<br />
<br />
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.<br />
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Последовательная сложность алгоритма Борувки для графа с <math>n</math> вершинами и <math>m</math> рёбрами составляет <math>O(m \ln(n))</math> операций.<br />
<br />
=== Информационный граф ===<br />
<br />
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний), и параллелизм в алгоритме обработка графа, не помещающегося в память.<br />
<br />
'''Нижний уровень параллелизма''': поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно, с использованием описанной структуры Union-Find. <br />
<br />
'''Верхний уровень параллелизма''': построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. <br />
<br />
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]<br />
<br />
Рассмотрим информационные графы и подробное описание каждого из них. Так же можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.<br />
<br />
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующим операциям параллельного поиска минимальных исходящих ребер, а так же уровнях {6, 7, 8}, соответствующим операциям параллельного объединения деревьев. Так же, различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла, производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе аналогичная следующая итерация. <br />
<br />
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального основного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные compute mst. Затем, после параллельных вычислений mst, происходит вычисление итого основного дерево, после чего после чего полученный результат сохраняется (save results).<br />
<br />
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Алгоритму Борувки обладает большим потенциалом параллелизма, так как его основная операция (выбор минимального исходящего ребра во фрагменте) может исполнятся независимо для каждого фрагмента.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j},<br />
v^{(2)}_{j})</math> с весами <math>f_j</math>).<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''': список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма ===<br />
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.<br />
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более <math>\log_2 n</math>. Отсюда следует и оценка сложности алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
На этапе поиска минимального ребра происходят следующие обращения к памяти:<br />
# Чтение информации о рёбрах. Может производится последовательно.<br />
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива <math>parent(u)</math> с вероятным промахой по кэшу.<br />
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.<br />
<br />
На этапе схлопывания фрагментов требуется атомарно обновить массив <math>parent(u)</math> для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:boruvka_1.png|thumb|center|700px|Рисунок 3. Алгоритм Борувки. Общий профиль обращений в память]]<br />
<br />
На рис. 3 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.<br />
<br />
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 3 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.<br />
<br />
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 3), которая показана на рис. 4. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 4 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.<br />
<br />
[[file:boruvka_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 1]]<br />
<br />
Далее перейдем к изучению фрагмента 2 (рис. 5). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.<br />
<br />
[[file:boruvka_3.png|thumb|center|700px|Рисунок 5. Профиль обращений, фрагмент 2]]<br />
<br />
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 6 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.<br />
<br />
[[file:boruvka_4.png|thumb|center|700px|Рисунок 6. Профиль обращений, фрагмент 3]]<br />
<br />
Отдельное рассмотрение фрагмента 4 (рис. 7) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:boruvka_5.png|thumb|center|500px|Рисунок 7. Профиль обращений, фрагмент 4]]<br />
<br />
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 8 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.<br />
<br />
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 8. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм Борувки, состоит из двух частей: <br />
<br />
1. части, отвечающей за общую координацию вычислений<br />
<br />
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.<br />
<br />
<br />
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации <math>MERGE</math> результаты операций <math>FIND(u)</math> и <math>FIND(v)</math> могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.<br />
<br />
1. Каждой вершине v соответствует запись <math>A[v] = { parent, rank }</math>. Изначально <math>A[v] := { v, 0 }</math>.<br />
<br />
2. Вспомогательная операция <math>UPDATE(v, rank_v, u, rank_u)</math>:<br />
<br />
old := A[v]<br />
<br />
if old.parent != v or old.rank != rank_v then return false<br />
<br />
new := { u, rank_u }<br />
<br />
return CAS(A[v], old, new)<br />
<br />
3. Операция <math>FIND(v)</math>:<br />
<br />
while v != A[v].parent do<br />
u := A[v].parent<br />
CAS(A[v].parent, u, A[u].parent)<br />
v := A[u].parent<br />
return v<br />
<br />
4. Операция UNION(u, v):<br />
<br />
while true do<br />
(u, v) := (FIND(u), FIND(v))<br />
if u = v then return false<br />
(rank_u, rank_v) := (A[u].rank, A[v].rank)<br />
if (A[u].rank, u) > (A[v].rank, v) then<br />
swap((u, rank_u), (v, rank_v))<br />
if UPDATE(u, rank_u, v, rank_u) then<br />
if rank_u = rank_v then<br />
UPDATE(v, rank_v, v, rank_v + 1)<br />
return true<br />
<br />
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются<br />
# пропускная способность памяти при чтении данных графа<br />
# соперничество потоков при выполнении атомарных операций с памятью<br />
# барьерная синхронизация после каждого подшага алгоритма.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера<br />
<br />
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]<br />
<br />
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]</code> сочетают алгоритм Борувки и [[алгоритм Крускала]].<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0_dqds&diff=21040Итерация алгоритма dqds2016-12-16T10:38:09Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div><br />
{{algorithm<br />
| name = Алгоритм dqds нахождения<br /> сингулярных чисел двухдиагональной матрицы<br />
| serial_complexity = <math>5n-4</math> <br />
| pf_height = <math>4n-3</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>2n</math><br />
| output_data = <math>2n</math><br />
}}<br />
<br />
<br />
Основные авторы описания: [[Участник:Chernyavskiy|А.Ю.Чернявский]]<br />
<br />
== Свойства и структура алгоритмов ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Итерация алгоритма dqds''' является одним шагом алгоритма dqds нахождения сингулярных чисел двухдиагональной матрицы.<br />
<br />
Сам алгоритм '''dqds''' (''differential quotient-difference algorithm with shifts'')<ref name="vla">Деммель Д. Вычислительная линейная алгебра. – М : Мир, 2001.</ref><ref name="hola">Hogben L. (ed.). Handbook of linear algebra. – CRC Press, 2006.</ref> строит последовательность двухдиагональных матриц, сходящуюся к диагональной матрице, содержащей квадраты искомых сингулярных чисел. Его особенностью является высокая точность решения задачи. <br />
Вычислительным ядром алгоритма является именно внутренняя итерация, вне итераций происходит подбор сдвига <math>\delta</math> (параметер итерации, см. [[#Математическое описание алгоритма|математическое описание алгоритма]]), отслеживание сходимости, а также применение различных оптимизационных "хитростей". Отметим, что внеитерационная часть алгоритма не существенна с точки зрения структуры вычислений, т.к. основные затраты ложатся на вычисления внутри итерации. Подробности и варианты внеитерационной части, а также анализ сходимости можно найти в соответствующей литературе<br />
<ref>Fernando K. V., Parlett B. N. Accurate singular values and differential qd algorithms //Numerische Mathematik. – 1994. – Т. 67. – №. 2. – С. 191-229.</ref><br />
<ref>Parlett B. N., Marques O. A. An implementation of the dqds algorithm (positive case) //Linear Algebra and its Applications. – 2000. – Т. 309. – №. 1. – С. 217-259.</ref> <br />
<ref>Aishima K. et al. On convergence of the DQDS algorithm for singular value computation //SIAM Journal on Matrix Analysis and Applications. – 2008. – Т. 30. – №. 2. – С. 522-537.</ref>.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== Вспомогательные сведения ====<br />
Для понимания математических основ dqds-итерации полезно рассмотреть кратко её вывод, частично отражающий и историю возникновения алгоритма (подробности можно найти в <ref name="vla"/>).<br />
За основу dqds-алгоритма удобно взять так называемую LR-итерацию, предшествующую хорошо-известной QR-итерации. LR-алгоритм, начиная с входной матрицы <math>T_0>0,</math> строит сходящуюся последовательность подобных <math>T_0</math> матриц <math>T_i>0,</math> итерационно используя следующие три шага:<br />
#Выбрать сдвиг <math>\tau_i</math> меньший младшего собственного значения <math>T_i.</math><br />
#Вычислить разложение Холецкого <math>T_i-\tau^2_iI=B_i^TB_i,</math> где <math>B_i</math> - верхняя треугольная матрица с положительной диагональю.<br />
#<math>T_{i+1}=B_iB_i^T+\tau_i^2I.</math><br />
<br />
Отметим, что два шага LR-итерации с нулевым сдвигом эквивалентны одному шагу QR-итерации. Итерационная процедура приводит матрицу к диагональному виду, тем самым вычисляя собственные значения исходной матрицы. LR-алгоритм достаточно легко может быть переформулирован с заметными упрощениями для задачи поиска сингулярных значений двухдиагональных матриц. А именно, будем вычислять последовательность двухдиагональных матриц <math>B_i</math> без непосредственного вычисления <math>T_i</math>(которые в данном случае будут трехдиагональными). Пусть матрица <math>B_i</math> имеет диагональные элементы <math>a_1 \ldots a_n</math> и наддиагональные элементы <math>b_1 \ldots b_{n-1}</math>, а матрица <math>B_{i+1}</math> - диагональные элементы <math>\widehat{a}_1 \ldots \widehat{a}_n</math> и наддиагональные элементы <math>\widehat{b}_1 \ldots \widehat{b}_{n-1}.</math> Тогда шаг LR-итерации в терминах матриц <math>B_i</math> можно привести к простому циклу, пробегающему значения <math>j</math> от <math>1</math> до <math>n-1:</math><br />
<br />
:<math><br />
\widehat{a}^2_j = a^2_j+b^2_j-\widehat{b}^2_{j-1}-\delta<br />
</math><br />
:<math><br />
\widehat{b}^2_j = b^2_j\dot (a^2_{j+1}/a^2_j)<br />
</math><br />
и вычислению <math>\widehat{a}^2_n = a^2_n-\widehat{b}^2_{n-1}-\delta.</math> Очевидно, что работу с извлечением квадратов выгодно вести лишь после окончания работы алгоритма, поэтому можно ввести замену <math>q_j=a^2_j,\; e_j=b^2,</math> что в итоге приводит к так называемому алгоритму qds.<br />
Формулы алгоритма следующие:<br />
<br />
<br />
:<math><br />
\widehat{q}_j = q_j + e_j - \widehat{e}_{j-1} - \delta, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{e}_j = e_j \cdot q_{j+1} / \widehat{q}_j, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{q}_n = q_n - \widehat{e}_{n-1} - \delta. <br />
</math><br />
<br />
<br />
Здесь <math>q_j, \; j \in [1,n]</math> и <math>e_j, \; j \in [1,n-1]</math> - квадраты элемнтов главной и верхней побочной диагонали соответственно. Крышка означает выходные переменные, а <br />
<br />
<math>\delta</math> - сдвиг (параметр алгоритма).<br />
Такая математическая запись наиболее компактна и соответствует так называемой qds-итерации.<br />
<br />
==== Математическое описание итерации алгоритма dqds====<br />
Представим теперь математическую запись, приближенную к dqds-итерации (с математической точки зрения qds и dqds-итерации эквивалентны) с введенными вспомогательными переменными <br />
<br />
<math>t_j</math> и <math>d_j.</math> Итерация алгоритма dqds преобразует входную двухдиагональную матрицу <math>B</math> в выходную <math>\widehat{B}.</math> <br />
<br />
Входные и выходные данные: <math>q_{j}, \; j\in [1,n], \; e_{k}, \; k\in [1,n-1] </math> - квадраты элементов главной и побочной диагонали входной матрицы <math>B</math>, <math> \widehat{q}_j , \; \widehat{e}_k </math> - то же для вычисляемой матрицы <math>\widehat{B}.</math>. <br />
<br />
Формулы метода выглядят следующим образом:<br />
<br />
<br />
:<math><br />
d_1 = q_1 - \delta, \; q_n = d_n <br />
</math><br />
:для<math><br />
\quad j\in [1,n-1]: <br />
</math><br />
:<math><br />
\widehat{q}_j = d_j + e_j<br />
</math><br />
:<math><br />
t_j = q_{j+1}/\widehat{q}_j<br />
</math><br />
:<math><br />
\widehat{e}_j=e_j \cdot t_j <br />
</math><br />
:<math><br />
d_{j+1} = d \cdot t - \delta<br />
</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром алгоритма является последовательный расчёт квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы. Учитывая использование вспомогательных переменных расчёт каждой новой пары содержит по одной операции сложения, вычитания и деления, а также две операции умножения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Алгоритм состоит из отдельного вычисления начального значения вспомогательной переменной <math>d,</math> последующим (n-1)-кратным выполнением повторяющейся последовательности из 5 операций (+,/,*,*,-) для вычисления квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы и завершающего вычислением крайнего значения <math>\widehat{q}_n</math>.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Отметим, что выходные данные сразу могут быть записаны на место входных (это учтено в схеме), также для хранения вспомогательных переменных <math>t_j</math> и <math>d_j</math> достаточно двух перезаписываемых переменных. Таким образом элементы главной (<math>q_j</math>) и побочной (<math>e_k</math>) диагонали входной матрицы последовательно перезаписываются соответствующими элементами выходной матрицы.<br />
<br />
Последовательность исполнения метода следующая:<br />
<br />
1. Вычисляется начальное значение вспомогательной переменной <math>d = q_1-\delta.</math><br />
<br />
2. Производится цикл по j от 1 до n-1, состоящий из:<br />
<br />
:2.1 Вычисляется значение <math>q_j = d + e_j;</math><br />
:2.2 Вычисляется значение вспомогательной переменной <math>t = q_{j+1}/q_j;</math><br />
:2.3 Вычисляется значение <math>e_j = e_j \cdot t;</math><br />
:2.4 Вычисляется значение вспомогательной переменной <math>d = d \cdot t - \delta.</math><br />
:<br />
<br />
3. Вычисляется <math>q_n = d.</math><br />
<br />
<br />
Легко заметить, что можно представить вычисления в другой форме, например, в виде qds-итерации (см. [[Итерация алгоритма dqds#Математическое описание алгоритма|Математическое описание dqds-итерации]]), однако, именно dqds реализация вычисления позволяет достичь высокой точности<ref name="vla"></ref>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения одной итерации dqds необходимо выполнить:<br />
<br />
* <math>n-1</math> делений,<br />
* <math>2n-2</math> умножений,<br />
* <math>2n-1</math> сложений/вычитаний.<br />
<br />
Таким образом одна dqds-итерация имеет ''линейную сложность''.<br />
<br />
=== Информационный граф ===<br />
[[file:dqds.png|thumb|center|600px|Рисунок 1. Граф алгоритма для n=4 без отображения входных и выходных данных.]]<br />
<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Как видно из информационного графа алгоритма, на каждом шаге основного цикла возможно лишь параллельное выполнение операции умножения (2.2) и умножения+сложения (2.4). Это позволяет сократить число ярусов на одной итерации цикла c 5 до 4, а общее число ярусов алгоритма с 5n-4 до 4n-3. Ярусы с операциями умножения состоят из двух операций, остальные же из одной.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Квадраты элементов основной и верхней побочной диагонали двухдиагональной матрицы (вектора <math>q</math> длины n и <math>e</math> длины n-1), а также параметр сдвига <math>\delta</math>.<br />
<br />
'''Объём входных данных''': <math>2n</math>. <br />
<br />
'''Выходные данные''': Квадраты элементов основной и верхней побочной диагонали выходной двухдиагональной матрицы.<br />
<br />
'''Объём выходных данных''': <math>2n-1</math>. <br />
<br />
=== Свойства алгоритма===<br />
<br />
Соотношение последовательной и параллельной сложности при наличии возможности параллельного выполнения операций умножения составляет <math>\frac{5n-4}{4n-3}</math>, т.е. алгоритм плохо распараллеливается. <br />
<br />
Вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных - константа.<br />
<br />
Описываемый алгоритм является полностью детерминированным. <br />
<br />
<br />
== Программная реализация алгоритмов ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Алгоритм на языке Matlab может быть записать так:<br />
<source lang="matlab"><br />
<br />
d = q(1)-delta;<br />
for j = 1:n-1<br />
q(j)=d+e(j);<br />
t=q(j+1)/q(j);<br />
e(j) = e(j)*t;<br />
d = d*t-delta;<br />
end<br />
q(n) = d;<br />
<br />
</source><br />
<br />
Как говорилось в [[#Схема реализации последовательного алгоритма|cхеме реализации последовательного алгоритма]], вычисляемые данные записываются сразу на место входных.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Легко видеть, что локальность данных высока. Её легко повысить, размещая рядом соответствующие элементы массивов e и q, однако, это не оказывает существенного влияния на производительность в силу константного количества операций относительно объема обрабатываемых данных.<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:dqds_1.png|thumb|center|700px|Рисунок 2. Итерация алгоритма dqds. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации итерации алгоритма dqds. Данный профиль внешне устроен очень просто и состоит из двух параллельно выполняемых последовательных переборов. Отметим, что общее число обращений в память всего в 3 раза больше числа задействованных данных, что говорит о том, что данные повторно используют редко. Зачастую это сигнализирует о достаточно невысокой локальности.<br />
<br />
Рассмотрим более детально строение одного из переборов (второй устроен практически идентично). На рис. 3 показан выделенный на рис. 2 небольшой фрагмент 1. Видно, что на каждом шаге в данном переборе выполняется три обращения в память; подобное строение говорит о высокой пространственной локальности, однако низкой временной, поскольку данные повторно используются только по 3 раза. <br />
<br />
Поскольку данная структура обращений и составляет общий профиль, то же самое можно говорить и о локальности всего профиля.<br />
<br />
[[file:dqds_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
<br />
===== Количественная оценка локальности =====<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 4 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Результат получен достаточно неожиданный – производительность работы с памятью очень невелика. Оценка daps для данного профиля сравнима с реализациями самых неэффективных алгоритмов в части работы с памятью – тестов RandomAccess и неэффективных вариантов обычного перемножения матриц. Отчасти это может объясняться низкой временной локальностью. Однако в данном случае причина может также заключаться в том, что в данной реализации объем вычислений на одно обращение в память достаточно велик, что может приводить к недостаточной загруженности подсистемы памяти. В таком случае, несмотря на в целом неплохую эффективность работы с памятью, производительность будет достаточно низка.<br />
<br />
[[file:dqds_daps.png|thumb|center|700px|Рисунок 4. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Итерация dqds практически полностью последовательна. Единственная возможность - одновременное выполнение операции умножения (2.3) и операции (2.4) умножения и сложения, что дает небольшой выигрыш в производительности.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
Алгоритм не является масштабируемым, максимального эффекта ускорения можно добиться на двух независимых процессорах.<br />
<br />
Проведём исследование масштабируемости вширь реализации согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров 1;<br />
* размер матрицы [1000 : 260000] с шагом 500.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 2.559e-06%;<br />
* максимальная эффективность реализации 2.492e-08%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации DQDS в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:DQDS Perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:DQDS eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[http://git.algowiki-project.org/Teplov/Scalability/tree/master/DQDS/dqds.c Исследованная параллельная реализация на языке C]<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Эффективное выполнение алгоритма возможно только на вычислительных устройствах с одним или двумя ядрами. <br />
<br />
=== Существующие реализации алгоритма ===<br />
Сам алгоритм dqds реализован в функции xBDSQR пакета LAPACK и используется при её вызове без расчёта сингулярных векторов.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21039Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T10:37:31Z<p>VadimVV: /* Структура обращений в память и качественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 3 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 2 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
На рис. 4 показан отдельно фрагмент 6, выделенный на рис. 3. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 3. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (представлен на рис. 5, выделен на рис. 3 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 5) по строению очень похожа на первую часть; вторая (часть 2 на рис. 5) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21038Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T10:36:28Z<p>VadimVV: /* Структура обращений в память и качественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 3 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 2 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
На рис. 4 показан отдельно фрагмент 6, выделенный на рис. 3. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 3. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (представлен на рис. 5, выделен на рис. 3 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 4) по строению очень похожа на первую часть; вторая (область 2 на рис. 3) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21037Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T10:35:47Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 2 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 3 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 2 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
На рис. 4 показан отдельно фрагмент 6, выделенный на рис. 3. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 2. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (представлен на рис. 5, выделен на рис. 3 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 4) по строению очень похожа на первую часть; вторая (область 2 на рис. 3) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&diff=21036Алгоритм Беллмана-Форда2016-12-16T10:34:29Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Беллмана-Форда'''<ref>Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.</ref><ref>Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.</ref><ref>Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.</ref> предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность <math>O(mn)</math> против <math>O(m + n\ln n)</math> у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.<br />
<br />
=== Математическое описание алгоритма ===<br />
Пусть задан граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math> и выделенной вершиной-источником <math>u</math>. Обозначим через <math>d(v)</math> кратчайшее расстояние от источника <math>u</math> до вершины <math>v</math>.<br />
<br />
Алгоритм Беллмана-Форда ищет функцию <math>d(v)</math> как единственное решение уравнения<br />
:<math><br />
d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,<br />
</math><br />
с начальным условием <math>d(u) = 0</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основной операцией алгоритма является релаксация ребра: если <math>e = (w, v) \in E</math> и <math>d(v) > d(w) + f(e)</math>, то производится присваивание <math>d(v) \leftarrow d(w) + f(e)</math>.<br />
<br />
=== Макроструктура алгоритма ===<br />
Алгоритм последовательно уточняет значения функции <math>d(v)</math>.<br />
* В самом начале производится присваивание <math>d(u) = 0</math>, <math>d(v) = \infty</math>, <math>\forall v \ne u</math>.<br />
* Далее происходит <math>n-1</math> итерация, в ходе каждой из которых производится релаксация всех рёбер графа.<br />
<br />
Структуру можно описать следующим образом:<br />
<br />
1. Инициализация: всем вершинам присваивается предполагаемое расстояние <math>t(v)=\infty</math>, кроме вершины-источника, для которой <math>t(u)=0</math> .<br />
<br />
2. Релаксация множества рёбер <math>E</math><br />
<br />
а) Для каждого ребра <math>e=(v,z) \in E</math> вычисляется новое предполагаемое расстояние <math>t^' (z)=t(v)+ w(e)</math>.<br />
<br />
б) Если <math>t^' (z)< t(z)</math>, то происходит присваивание <math>t(z) := t' (z)</math> (релаксация ребра <math>e</math> ).<br />
<br />
3. Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.<br />
<br />
Если на <math>n</math>-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро <math>e=(v,z)</math>, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): <math>t(v)+w(e)<d(z)</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Последовательный алгоритм реализуется следующим псевдокодом:<br />
<br />
'''Входные данные''':<br />
граф с вершинами ''V'', рёбрами ''E'' с весами ''f''(''e'');<br />
вершина-источник ''u''.<br />
'''Выходные данные''': расстояния ''d''(''v'') до каждой вершины ''v'' ∈ ''V'' от вершины ''u''.<br />
<br />
'''for each''' ''v'' ∈ ''V'' '''do''' ''d''(''v'') := ∞<br />
''d''(''u'') = 0<br />
<br />
'''for''' ''i'' '''from''' 1 '''to''' |''V''| - 1:<br />
'''for each''' ''e'' = (''w'', ''v'') ∈ ''E'':<br />
'''if''' ''d''(''v'') > ''d''(''w'') + ''f''(''e''):<br />
''d''(''v'') := ''d''(''w'') + ''f''(''e'')<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Алгоритм выполняет <math>n-1</math> итерацию, на каждой из которых происходит релаксация <math>m</math> рёбер. Таким образом, общий объём работы составляет <math>O(mn)</math> операций.<br />
<br />
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.<br />
<br />
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.<br />
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).<br />
<br />
=== Информационный граф ===<br />
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).<br />
<br />
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.<br />
<br />
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]<br />
<br />
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется <math>O(n)</math> шагов при использовании <math>O(m)</math> процессоров.<br />
<br />
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math> с весами <math>f_j</math>), вершина-источник <math>u</math>.<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''' (возможные варианты):<br />
# для каждой вершины <math>v</math> исходного графа – последнее ребро <math>e^*_v = (w, v)</math>, лежащее на кратчайшем пути от вершины <math>u</math> к <math>v</math>, или соответствующая вершина <math>w</math>;<br />
# для каждой вершины <math>v</math> исходного графа – суммарный вес <math>f^*(v)</math> кратчайшего пути от от вершины <math>u</math> к <math>v</math>.<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма===<br />
<br />
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро <math>e = (v, w)</math> лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния <math>d(v)</math> удовлетворяют условию<br />
:<math><br />
d(v) + f(e) < d(w),<br />
</math><br />
где <math>f(e)</math> – вес ребра <math>e</math>. Условие может быть проверено для всех рёбер графа за время <math>O(m)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 2. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.<br />
<br />
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 2 представлен фрагмент 1 (выделен на рис. 1), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практические идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 2 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.<br />
<br />
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 1. Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. <br />
<br />
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 2]]<br />
<br />
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 3 зеленым. Однако в данном случае дальнейшее приближение не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.<br />
<br />
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 5. Небольшая часть фрагмента 1]]<br />
<br />
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. <br />
<br />
<br />
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.<br />
<br />
[[file:APSP scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция <code>[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]</code>).<br />
* Python: [https://networkx.github.io NetworkX] (функция <code>[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]</code>).<br />
* Java: [http://jgrapht.org JGraphT] (класс <code>[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]</code>).<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&diff=21035Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду2016-12-16T10:34:03Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение симметричной вещественной матрицы к трёхдиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>O(n^3)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2/2</math><br />
| input_data = <math>(n^2+n)/2</math><br />
| output_data = <math>(n^2+3n)/2</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к трёхдиагональному виду, или, что то же самое, для разложения <math>A=QTQ^T</math> (<math>Q</math> - ортогональная, <math>T</math> — симметричная трёхдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QTQ^T</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений) с последующим умножением на те же матрицы справа. <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-м столбце. После умножения на эту же матрицу отражения справа автоматически убираются и ненулевые наддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-й строке, а полученная модификация снова приобретает симметричный вид. <br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Потом по аналогичным формулам модифицируются строки ниже ведущей. Благодаря ассоциативности этих операций после вычисления вектора <math>v</math> можно сразу выписать формулы модификации всех элементов справа и снизу от ведущих столбца и строки. Оказывается, что если для каждого столбца матрицы <math>x^{(j)}</math> с номером <math>j</math> известно <math>\beta_{j}=\frac{(x^{(j)},v)}{\gamma}</math>, то для модифицируемого элемента матрицы <math>y</math> в позиции <math>(i,j)</math> выполняется модификация <math>y' = y - \beta_{i} v_{i} - \beta_{j} v_{j} - \Delta v_{i} v_{j}</math>, где <math>\Delta = \frac{(\beta,v)}{\gamma}</math>. При этом все эти модификации на каждом конкретном шаге алгоритма для разных пар <math>(i,j)</math> можно выполнять независимо друг от друга, в том числе и параллельно.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
[[File:HausLeftSym1.png|thumb|right|800px|Рисунок 1. Граф первой половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-4</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Зелёные кружки - операция вида <math>a+b^2</math>, салатовые - операция вида <math>a+bc</math>. Синий кружок - вычисление параметров матрицы отражения, светло-красные - вычисление коэффициентов <math>\beta_i</math>для следующего полушага, жёлтые - вычисление вектора <math>v</math>.]]<br />
[[File:HousLeftSym2.png|thumb|right|600px|Рисунок 2. Граф второй половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-3</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Синий, жёлтые и светло-красные кружки выполняются на первом полушаге (см. Рисунок 1). Голубые кружки - операции <math>a+bc</math>, зелёное - умножение, чёрные - операции <math>a+bc+de+fce</math>.]]<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также проводимые над нижним правым квадратом матрицы операции вида <math>y'=y-ab-cd-fbd</math>, с учётом симметрии матрицы.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также массовые покомпонентные операции <math>y'=y-ab-cd-fbd</math>. При этом, однако, строгая последовательность выполнения первых двух подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них коэффициентов модификации. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) одновременное умножение слева матрицы отражения <math>U_{i}</math> и справа матрицы отражения <math>U_{i}^T</math>на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также массовых модификаций элементов вида <math>y'=y-\alpha v - \beta w - \Delta vw</math>. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>O(n^3)</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На рисунках 1 и 2 приведён граф алгоритма шага метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в симметричном приведении матрицы порядка <math>n</math> к трёхдиагональной методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>2(n-i)</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. <br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная симметричная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>(n^2+n)/2</math>.<br />
<br />
'''Выходные данные''': трёхдиагональная матрица <math>D</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>(n^2+3n)/2</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной симметричной вещественной матрицы к трёхдиагональному виду на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-2<br />
<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
SX(I)=SX(I)+A(J,I)*A(J,I)<br />
END DO <br />
DO K = I+1, N<br />
DO J = N-1, K, -1<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
DO J = K-1,I+1,-1<br />
SX(K)=SX(K)+A(J,I)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I+1,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I+1,I)*BETA+SIGN(1.,A(I+1,I)) <br />
A(I+1,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N<br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2<br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I+1,I)=ALPHA<br />
G=1.! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N <br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2 <br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1.<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
Здесь симметричная трёхдиагональная матрица хранится в диагонали и нижней кодиагонали массива A, вектора v - в поддиагональной части массива, за исключением первых их элементов, для которых выделен массив SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qtq_1.png|thumb|center|700px|Рисунок 3. Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Данный профиль обладает явной итерационной структурой, при этом видно, что итерации очень похожи. Основное их отличие заключается в наборе используемых данных – на каждой следующей итерации несколько первых элементов отбрасываются их рассмотрения, то есть чем позже итерация, тем меньше данных в ней задействовано. Также можно отметить, что число обращений в каждой последующей итерации немного уменьшается. Для того чтобы проанализировать общий профиль, в таком случае чаще всего достаточно изучить одну итерацию. Рассмотрим самую первую из них более детально.<br />
<br />
На рис. 2 показан набор обращений в рамках первой итерации (выделен на рис. 1 зеленым). Его можно разбить на несколько фрагментов и рассмотреть их отдельно. Строение фрагментов 2-5 можно оценить по общему графику для итерации. Фрагмент 2 представляет собой набор последовательных переборов в обратном порядке с небольшим шагом, причем число элементов в переборе постепенно уменьшается. Такое строение характеризуется достаточно высокой пространственной локальностью, поскольку часто происходит обращений к близко расположенным данным, однако низкой временной, поскольку данные повторно не используются. То же самое верно и для фрагмента 4, основное отличие которого заключается в том, что переборы выполняются в прямом порядке. Отметим, что некоторое искривление данного фрагмента связано не со строением самого фрагмента, а с разным числом параллельно выполняющихся обращений (см., например, правую область фрагмента 6), чего не наблюдается при выполнении фрагмента 2.<br />
<br />
[[file:householder_qtq_2.png|thumb|center|700px|Рисунок 4. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагменты 3 и 5 состоят из небольшого числа обращений к данным, расположенным далеко друг от друга. Такие фрагменты характеризуются низкой пространственной и временной локальностью.<br />
<br />
Оставшиеся фрагменты требуют более детального рассмотрения. Перейдем к изучению фрагмента 1, который целиком представлен на рис. 3. Здесь мы можем видеть несколько этапов, каждый из которых представляет собой последовательный перебор элементов с шагом по памяти 1, причем в некоторых случаях данные много раз используются повторно (особенно это заметно в правой нижней области рисунка, где выполняется множество обращений к одному и тому же элементу). При условии, что в данном фрагменте задействовано всего около 40 элементов, можно говорить, что данный набор обращений обладает высокой пространственной и временной локальностью. <br />
<br />
[[file:householder_qtq_3.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, фрагмент 1]]<br />
<br />
Далее рассмотрим детально фрагмент 6 (рис. 4). Здесь можно увидеть, что повторные обращения к данным выполняются не подряд, а через некоторые промежутки, однако число задействованных элементов также очень невелико, так что и в этом случае локальность (и пространственная, и временная) будет высокой.<br />
<br />
[[file:householder_qtq_4.png|thumb|center|500px|Рисунок 6. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
В целом можно сказать, что рассматриваемая итерация обладает достаточно высокой пространственной и временной локальностью. Поскольку остальные итерации устроены подобным образом, данные вывод можно перенести и на общий профиль обращений.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно высока – значение daps лишь немногим уступает тесту Linpack, который обладает высокой эффективностью работы с памятью, и немного превосходит, например, реализацию метода Якоби или метода Гаусса.<br />
<br />
[[file:householder_qtq_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4_%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D1%80%D0%B5%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D0%B8&diff=21034Полный метод циклической редукции2016-12-16T10:33:27Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = Циклическая редукция для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>17n + o(n)</math><br />
| pf_height = <math>7 log_2 n</math><br />
| pf_width = <math>3n/2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Циклическая редукция''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где <br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ2}}<br />
<br />
'''Циклическая редукция''', как и все варианты прогонки, заключается <ref name="IK3d">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г., 208 с.</ref><ref name="FAVT-2016">Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ’2016): труды международной научной конференции (г. Архангельск, 28 марта – 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref> в исключении из уравнений неизвестных, однако, в отличие от них, в ней исключение ведут одновременно по всей СЛАУ. В принципе, её можно считать вариантом [[Метод редукции|метода редукции]], выполняемого максимально возможное для данной СЛАУ число раз.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Лучше всего схема циклической редукции<ref name="IK3d" /> разработана для случая <math>n = 2^{m}-1</math>. Эта схема состоит из прямого и обратного ходов. <br />
Прямой ход состоит из последовательного уменьшения в СЛАУ количества уравнений почти в 2 раза (за счёт подстановки из уравнений с нечётными номерами заменяются уравнения с чётными), пока не останется одно уравнение, обратный - в получении всё большего количества компонент решения исходной СЛАУ. Оба хода - как прямой, так и обратный - разбиты на шаги. Здесь мы приведём тот вариант алгоритма, в котором операции экономятся за счёт предварительной нормировки уравнений, используемых для исключения неизвестных. <br />
<br />
==== Прямой ход редукции ====<br />
<br />
В начале считается, что все <br />
<math>c^{(0)}_{i} = c_{i}, b^{(0)}_{i} = b_{i}, a^{(0)}_{i} = a_{i}, f^{(0)}_{i} = f_{i}, x^{(0)}_{i} = x_{i}</math><br />
<br />
На k-м шаге теперь выполняем процедуру редукции системы уравнений размерности n.<br />
<br />
Для каждого из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math> <br />
<br />
с нечётными <math>i</math> с помощью деления уравнения на <math>b^{(k)}_{i}</math> выполняется его замена на уравнение <br />
<br />
<math> (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} + x^{(k)}_{i} + (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} = f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
[[file:CycRedMicro0.png|thumb|right|150px|Рисунок 1. Микрограф "узла" подготовительного шага прямого хода алгоритма циклической редукции ]]<br />
<br />
Уравнение <br />
<br />
<math>b^{(k)}_{1} x^{(k)}_{1} + a^{(k)}_{1} x^{(k)}_{2} = f^{(k)}_{1}</math><br />
<br />
аналогично меняется на уравнение <br />
<br />
<math>x^{(k)}_{1} + (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2} = f^{(k)}_{1}/b^{(k)}_{1}</math>:<br />
<br />
а уравнение <br />
<br />
<math>c^{(k)}_{n} x^{(k)}_{n-1} + b^{(k)}_{n} x^{(k)}_{n} = f^{(k)}_{n}</math> <br />
<br />
меняется на уравнение <br />
<br />
<math>(c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1} + x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n}</math>:<br />
<br />
[[file:CycRedMicroDirect.png|thumb|right|400px|Рисунок 2a. Микрограф "узла" с нечётным номером прямого хода алгоритма циклической редукции]]<br />
<br />
Для каждого же из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math><br />
<br />
с чётными <math>i</math> (кроме <math>2</math> и <math>n-2</math>) выполняется, с учётом <math>x^{(k+1)}_{i/2} = x^{(k)}_{i}</math> его замена на уравнение <br />
<br />
<math>c^{(k+1)}_{i/2} x^{(k+1)}_{(i-2)/2} + b^{(k+1)}_{i/2} x^{(k+1)}_{i/2} + a^{(k+1)}_{i/2} x^{(k+1)}_{(i+2)/2} = f^{(k+1)}_{i/2}</math><br />
<br />
[[file:CycRedMicroDirect2.png|thumb|right|400px|Рисунок 2b. Микрограф "узла" с чётным номером прямого хода алгоритма циклической редукции ]]<br />
<br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{i/2} = - c^{(k)}_{i}(c^{(k)}_{i-1}/b^{(k)}_{i-1})</math>, <br />
<br />
<math>a^{(k+1)}_{i/2} = - a^{(k)}_{i}(a^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>b^{(k+1)}_{i/2} = b^{(k)}_{i} - c^{(k)}_{i}(a^{(k)}_{i-1}/b^{(k)}_{i-1}) - a^{(k)}_{i}(c^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>f^{(k+1)}_{i/2} = f^{(k)}_{i} - c^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1} - a^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1}</math>.<br />
<br />
Для 2го уравнения выполняется его замена на уравнение<br />
<br />
<math>b^{(k+1)}_{1} x^{(k+1)}_{1} + a^{(k+1)}_{1} x^{(k+1)}_{(2} = f^{(k+1)}_{1}</math><br />
<br />
при этом<br />
<br />
<math>a^{(k+1)}_{1} = - a^{(k)}_{2}(a^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>b^{(k+1)}_{1} = b^{(k)}_{2} - c^{(k)}_{2}(a^{(k)}_{1}/b^{(k)}_{1}) - a^{(k)}_{2}(c^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>f^{(k+1)}_{1} = f^{(k)}_{2} - c^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1} - a^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1}</math><br />
<br />
<br />
<math>n-1</math>-е уравнение заменяется на <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-3)/2} + b^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-1)/2} = f^{(k+1)}_{(n-1)/2}</math><br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} = - c^{(k)}_{n-1}(c^{(k)}_{n-2}/b^{(k)}_{n-2})</math>,<br />
<br />
<math>b^{(k+1)}_{(n-1)/2} = b^{(k)}_{n-1} - c^{(k)}_{n-1}(a^{(k)}_{n-2}/b^{(k)}_{n-2}) - a^{(k)}_{n-1}(c^{(k)}_{n}/b^{(k)}_{n})</math>,<br />
<br />
<math>f^{(k+1)}_{(n-1)/2} = f^{(k)}_{n-1} - c^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2} - a^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2}</math>.<br />
<br />
По окончании всех этих манипуляций размерность k+1-й СЛАУ оказывается равной <math>(n-1)/2</math>.<br />
<br />
Шаги повторяются до тех пор, пока после <math>m-1</math> шагов редукции размерность СЛАУ не становится равной 1 и остаётся одно уравнение<br />
<br />
<math>b^{(m-1)}_{1} x^{(m-1)}_{1} = f^{(m-1)}_{1}</math><br />
<br />
==== Обратный ход редукции ====<br />
<br />
[[file:CycRedMicroRev.png|thumb|right|150px|Рисунок 3. Микрограф "узла" обратного хода алгоритма циклической редукции ]]<br />
<br />
Из последнего уравнения, полученного прямым ходом, вычисляется <br />
<br />
<math>x^{(m-1)}_{1} = f^{(m-1)}_{1}/b^{(m-1)}_{1}</math><br />
<br />
Теперь, последовательно уменьшая верхние индексы неизвестных, используется нечётные уравнения каждого шага для вычисления неизвестных с соотвествующими нечётными номерами. Чётные неизвестные получаются из тождеств <math>x^{(k)}_{i} = x^{(k+1)}_{i/2}</math>, а для нечётных <math>i</math> <br />
<br />
<math>x^{(k)}_{i} = f^{(k)}_{i}/b^{(k)}_{i} - (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} - (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} </math><br />
<br />
c "левого края" системы будет <br />
<br />
<math>x^{(k)}_{1} = f^{(k)}_{1}/b^{(k)}_{1} - (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2}</math><br />
<br />
а с "правого"<br />
<br />
<math>x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n} - (c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1}</math><br />
<br />
После вычисления всех <math>x^{(0)}_{i}</math> значения искомых неизвестных <math>x_{i} = x^{(k)}_{i}</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Видно, что, поскольку вычисляемые на каждом шаге прямого хода редукции при преобразовании нечётных уравнений отношения коэффициентов <br />
<br />
<math> c^{(k)}_{i}/b^{(k)}_{i} , a^{(k)}_{i}/b^{(k)}_{i} , f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
почти все используются для преобразований двух чётных уравнений, то при выделении "микровычислений", из которых следует составить шаги редукции и которые составляют его ядро, лучше отнести вычисления этих отношений к предыдущему шагу редукции. Таким образом, на "подготовительном шаге" микроядро будет для каждого нечётного <math>i</math> состоять только из трёх делений (кроме <math>2</math> и <math>n</math> - там будет по 2 деления), а затем на каждом последующем шаге редукции для каждого нечётного <math>i</math> - из двух умножений и двух вычислений выражений типа <math>a-bc-de</math>, с последующими тремя делениями (на "краях" часть этих операций отсутствует или урезана, но общую картину это не очень меняет). Для чётных <math>i</math> деления в микроядре будут отсутствовать.<br />
<br />
Что касается шагов обратного хода, то там для каждого <math>i</math> рано или поздно выполняется одна операция типа <math>a-bc-de</math> (на "краях" - типа <math>a-bc</math>).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[file:CycRedDirect.png|thumb|right|400px|Рисунок 4. Граф алгоритма прямого хода циклической редукции при n=15. Светло-зелёным обозначены микроядра "подготовительного шага" с тремя (или меньше) делениями, синим - микроядра нечётных узов, сине-зелёным - ядра чётных узлов (без делений).]]<br />
<br />
[[file:CycRedRev.png|thumb|right|400px|Рисунок 5. Граф алгоритма обратного хода циклической редукции при n=15. В вершинах - операции вида a-bc-de, в вершинах с 1 входящей дугой - вида a-bc. Показаны только дуги, передающие значения найденных неизвестных.]]<br />
<br />
Если выразить макроструктуру алгоритма циклической редукции в терминах её "микроядер", то прямой ход редукции несколько схож на схемы сдваивания, но отличается от неё не только количеством входящих в макровершины дуг, из-за чего её схема - скорее "страивание", но и зацеплением соседних ветвей. Поэтому, в отличие от схем сдваивания, для которых характерно полностью независимое исполнение ветвей до определённого момента, в схеме редукции это зацепление ветвей не даёт им выполняться полностью независимо. <br />
<br />
Обратный ход циклической редукции - уже практически полное "обратное" сдваивание, и на этом этапе разделение на независимые ветви может быть проделано после старта, если размножить необходимые данные.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Метод циклической редукции изначально спроектирован для параллельного исполнения, поскольку является по отношению к, например, классической прогонке, алгоритмом с избыточными вычислениями. Поэтому смысла в его последовательной реализации обычно не видят и они не встречаются в библиотеках программ. Тем не менее, из-за того, что современная архитектура даже одиночных узлов и персональных компьютеров не вполне последовательна, то этот смысл, как вполне может оказаться<ref name="FAVT-2016" />, уже появился, поскольку, кроме простого количества операций, производительность вычислений на компьютере определяется и другими параметрами алгоритма, в частности, локальностью вычислений.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Поскольку алгоритм циклической редукции обычно не предназначен для последовательного исполнения, то его "последовательную сложность" скорее следует считать только оценкой общего количества операций. Если взять только главный член, линейный по размеру, и опустить меньшие по порядку (логарифмические и константы), то получается, что в циклической редукции ''3n'' делений, ''8n'' умножений и ''6n'' вычитаний/сложений. Таким образом, при классификации по последовательной сложности, алгоритм циклической редукции относится к алгоритмам ''с линейной сложностью''.<br />
<br />
Сравнение сложности с алгоритмом [[Прогонка, точечный вариант|прогонки]] показывает также, что циклическая редукция - алгоритм с избыточными вычислениями: избыточность по сравнению с прогонкой более чем в 2 раза.<br />
<br />
=== Информационный граф ===<br />
<br />
У прямого хода циклической редукции макрограф представлен на Рис. 4, при этом графы разные макровершин представлены на Рис. 1, 2a, 2b. Как видно, после первого шага схему можно условно назвать "схемой страивания", поскольку связанные с каждым уравнением, остающимся в редуцированной СЛАУ, коэффициенты вычисляются по формулам из коэффициентов '''трёх''' соседних уравнений в нередуцированной СЛАУ. <br />
<br />
Обратный ход имеет макрограф, представленный на Рис. 5, при этом графы макровершин представлены на Рис. 3 (крайние макровершины, имеющие только одну входящую дугу на макрографе, замещают недостающие данные нулями, соответственно уменьшая вычисления). Этот макрограф, в принципе, имеет макроструктуру, обратную к схеме сдваивания, поэтому её можно условно назвать "схемой раздваивания", с одной поправкой: все макровершины, кроме крайних, имеют по две входящих дуги, которые немного изменяют обычное "раздваивание".<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для выполнения циклической редукции в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>log_2 n</math> ярусов делений, из них самый широкий ярус - первый, в нём <math>3n/2</math> делений. <br />
* <math>2 log_2 n</math> ярусов умножений и <math>4 log_2 n</math>сложений/вычитаний.<br />
<br />
Ширина ярусов экспоненциально, с показателем 2, убывает с их номерами, а потом, на обратном ходе, экспоненциально растёт, также с показателем 2. Эта неоднородность приводит к тому, что при практическом полном распараллеливании большую часть времени большинство устройств будет простаивать. <br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам с ''логарифмической сложностью''. При классификации по ширине ЯПФ её сложность будет ''линейной''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является отношением размера задачи к его логарифму. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно циклическая редукция используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
В случае, когда требуется решение нескольких СЛАУ с одной и той же матрицей, большую часть прямого хода вычислений (см. рисунки с графом алгоритма) можно не повторять.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Из-за избыточности вычислений на компьютерах без параллельных устройств обычно используют более простую монотонную или встречную [[Прогонка, точечный вариант|прогонку]], а циклическую редукцию в последовательном варианте обычно просто не реализуют. Этот выбор, однако, не так очевиден на современных "однопроцессорных" суперскалярных компьютерах, поскольку в циклической редукции нет такой избыточной локальности, как в [[Прогонка, точечный вариант|прогонке]]. Однако, эта сторона циклической редукции ещё не исследована и ждёт тех, кто проведёт соответствующую работу.<br />
<br />
Приведем пример подпрограммы, реализующей циклическую прогонку СЛАУ с числом уравнений "степень двойки минус 1", где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы. Следует обратить внимание на то, что данная версия циклической редукции непригодна для повторного решения СЛАУ с той же матрицей и новой правой частью, поскольку при повторении нужно несколько большее количество промежуточных данных. <br />
<br />
<source lang="fortran"><br />
subroutine cprogm (a,x,N,m) ! N=2^m-1<br />
real a(3,N), x(N)<br />
<br />
k=1<br />
k2=2<br />
kk=1 <br />
<br />
a(2,1)=1./a(2,1)<br />
a(2,N)=1./a(2,N)<br />
a(3,1)=a(3,1)*a(2,1)<br />
a(1,N)=a(1,N)*a(2,N)<br />
x(1) = x(1)*a(2,1)<br />
x(N) = x(N)*a(2,N)<br />
do i=3,N-2,2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
do j=1,m-2<br />
<br />
k=k2 ! k=2^j<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
a(3,k) = -a(3,k)*a(3,k+kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(1,N+1-k)*a(3,N+1-k-kk)<br />
a(1,N+1-k)= - a(1,N+1-k)*a(1,N+1-k-kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k-kk)*a(1,N+1-k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k) - x(k+kk)*a(3,k+kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(3,N+1-k)*a(1,N+1-k+kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k+kk)*a(3,N+1-k+kk) <br />
<br />
k2=k2*2 ! k2=2^(j+1)<br />
<br />
do i = k2, N+1-k2, k<br />
a(2,i) = a(2,i) - a(1,i)*a(3,i-kk)<br />
x(i) = x(i)- x(i-kk)*a(1,i-kk)<br />
a(3,i) = -a(3,i)*a(3,i+kk)<br />
a(1,i)= - a(1,i)*a(1,i-kk) <br />
a(2,i) = a(2,i) - a(3,i)*a(1,i+kk)<br />
x(i) = x(i)- x(i+kk)*a(3,i+kk)<br />
end do <br />
<br />
a(2,k)=1./a(2,k)<br />
a(2,N+1-k)=1./a(2,N+1-k)<br />
a(3,k)=a(3,k)*a(2,k)<br />
a(1,N+1-k)=a(1,N+1-k)*a(2,N+1-k)<br />
x(k) = x(k)*a(2,k)<br />
x(N+1-k) = x(N+1-k)*a(2,N+1-k) <br />
<br />
do i = k2+k, N+1-k2-k, k2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
<br />
kk=k ! budet kk=2^(j-1)<br />
<br />
end do <br />
<br />
c m-1 shag & start of reverse<br />
<br />
k=k2 ! k=2^(m-1) i kk=2^(m-2)<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k)- x(k+kk)*a(3,k+kk)<br />
a(2,k) = 1./a(2,k)<br />
x(k) = x(k)*a(2,k)<br />
<br />
c start reverse <br />
<br />
x(kk) = x(kk) - a(3,kk)*x(k)<br />
<br />
x(k+kk) = x(k+kk) - a(1,k+kk)*x(k)<br />
<br />
do j=m-2, 1, -1<br />
<br />
k2 = k ! k2 = 2^(j+1)<br />
k = kk ! k = 2^j<br />
kk = kk/2 ! kk = 2^(j-1)<br />
<br />
x (kk) = x (kk) - a(3,kk)*x(kk+kk)<br />
x (N+1-kk) = x (N+1-kk) - a(1,N+1-kk)*x(N+1-k) <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(1,i)*x(i-kk) <br />
end do <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(3,i)*x(i+kk) <br />
end do <br />
end do<br />
<br />
return<br />
end<br />
</source><br />
<br />
Здесь необходимо отметить, что, несмотря на последовательность этой реализации, она вполне может быть исполнена и параллельно, нужно только как-то скомандовать имеющемуся в наличии компилятору, что циклы по i параллельны (например, спецкомментариями OpenMP).<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
По графу видно, что, хотя и налицо "местная" локальность по вычислениям, но от яруса к ярусу расстояние между запрашиваемыми данными растёт от первых и последних ярусов алгоритма к его середине, являющимся не только узким местом алгоритма, но и имеющим наибольшую длину передачи данных между устройствами. Поэтому, хотя в циклической редукции нет той избыточной локальности, которая притормаживает работу прогонки, это преимущество, как показали замеры<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref> (см. Рисунок 6), не способно компенсировать недостатки общей локальности графа циклической редукции.<br />
<br />
[[File:I3-cyc.png|thumb|center|1000px|Рисунок 6. Отношение времени исполнения циклической редукции и монотонных прогонок на ПК. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение циклической редукции, к времени, затраченному на выполнение монотонной повторной прогонки. Система Core i3 + Windows 8, сборка кода компилятором GNU Fortran. На других системах получена качественно примерно такая же картина, с различием по выходу за границы кэша. Между синей и красной линиями - примерная область взаимной компенсации неарифметических факторов. При малых размерах сказывается неиспользование прогонками суперскалярности процессора.]]<br />
<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:cyclic_reduction_1.png|thumb|center|700px|Рисунок 7. Полный метод циклической редукции. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации полного метода циклической редукции. Данный профиль достаточно интересно. Как нам известно из описания самого алгоритма, он состоит из двух частей – прямого и обратного хода. На общем профиле синяя вертикальная черта отделяет эти два этапа. Рассмотрим их в отдельности.<br />
<br />
На первом этапе явно видны отдельные итерации (первые 3 итерации разделены желтыми линиями), каждая из которых состоит из 4 параллельно выполняемых переборов данных. Отметим, что на каждой итерации в рамках каждого перебора задействованы данные из одного и того же диапазона. Число обращений в память в каждой итерации различается, и это позволяет предположить, что характер переборов также может отличаться. <br />
<br />
Рассмотрим фрагменты двух итераций более детально. На рис. 2 и 3 соответственно представлены фрагменты 1 и 2, обозначенные на рис. 1 зеленым. Данные фрагменты содержат по 1000 обращений. Можно увидеть, что в целом все переборы в двух фрагментах выполняются с регулярным и достаточно небольшим шагом по памяти, однако интенсивность обращений в каждом случае разная. Причем при смене итерации меняется и интенсивность обращений к одному и тому диапазону данных. <br />
<br />
[[file:cyclic_reduction_2.png|thumb|center|500px|Рисунок 8. Профиль обращений, фрагмент 1]]<br />
<br />
[[file:cyclic_reduction_3.png|thumb|center|500px|Рисунок 9. Профиль обращений, фрагмент 2]]<br />
<br />
Рассмотрим еще более детально небольшие части данных фрагментов, чтобы дальше проанализировать структуру представленных переборов. На рис. 4 представлены небольшие локальные области, выделенные зеленым на рис. 2 и 3. Можно увидеть, что локальная структура обращений в целом отличается. В частности, в области, выделенной на рис. 3, шаг по памяти больше, а также выполняется меньше повторных обращений, что говорит о более низкой пространственной локальности. <br />
<br />
[[file:cyclic_reduction_4.png|thumb|center|700px|Рисунок 10. Локальная структура фрагментов 2 и 3]]<br />
<br />
В целом можно сказать, что структура итераций похожа, однако локальность несколько отличается в силу указанных выше причин. Если же говорить в среднем, такое строение итераций характеризуется от средней до высокой (в зависимости от локальной структуры) пространственной локальностью и низкой временной локальностью, поскольку данные перебираются подряд (обычно с небольшим шагом по памяти), но при этом повторно почти не используются. Отметим, что размер итераций достаточно велик, так что повторное обращение к данным на следующей итерации не приводит к улучшению локальности.<br />
<br />
Отдельно рассмотрим самый конец первого этапа (фрагмент 3 на рис. 1), который показан на рис. 5. Здесь локальность обращений в память самая низкая, поскольку шаг по памяти в переборах становится наибольшим. В центре рисунка можно заметить область особенно низкой локальности (наиболее разрозненные обращения). Хотя данный фрагмент небольшого размера, он может серьезно снижать общую производительность взаимодействия с памятью.<br />
<br />
[[file:cyclic_reduction_5.png|thumb|center|500px|Рисунок 11. Профиль обращений, фрагмент 3]]<br />
<br />
Второй этап также имеет регулярную структуру. Рассмотрим на рис. 6 небольшую область более детально (фрагмент 4 на рис. 1). Из данного рисунка видно, что здесь также выполняются последовательные переборы, локальная структура которых может отличаться. Как и в рассмотренном на первом этапе случае, это приводит к различиям по пространственной и временной локальности. Стоит отметить, что угол наклона переборов на втором этапе в целом больше, чем на первом этапе (см. рис 1), что говорит о большем шаге по памяти, и, как следствие, более низкой пространственной локальности, однако это различие может нивелироваться различиями в локальной структуре.<br />
<br />
[[file:cyclic_reduction_6.png|thumb|center|500px|Рисунок 12. Профиль обращений, фрагмент 4]]<br />
<br />
В целом можно сказать, что общий профиль обладает низкой временной локальностью. Пространственная локальность, скорее всего, не очень высокая, в частности, из-за неэффективной области в конце первого этапа. Однако делать предположения относительно пространственной локальности в данном случае достаточно сложно ввиду большого разнообразия локальных структур переборов.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 7 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае низка и лишь чуть выше значения daps для такой неэффективной с точки зрения работы с памятью программы как тест RandomAccess (rand). В целом это соотносится с выводами, которые были сделаны нами ранее<br />
<br />
<br />
[[file:cyclic_reduction_daps.png|thumb|center|700px|Рисунок 13. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Исходя из таких свойств алгоритма циклической редукции, как плохая локальность и наличие избыточности, логично было бы использовать его не в "чистом виде", а, проведя несколько этапов редукции, решать оставшуюся СЛАУ более простым методом вроде прогонки.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
При оценке масштабируемости этого алгоритма, как и всех алгоритмов с избыточными вычислениями, следует учитывать, что сравнение по быстродействию и эффективности нужно проводить не с однопроцессорным вариантом исполнения самого алгоритма, а с алгоритмом [[Прогонка, точечный вариант|прогонки]]. <br />
<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации циклической редукции согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [2 : 256] с шагом степени двойки;<br />
* размер матрицы [64 : 33554432] с шагом степени двойки.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 3.89e-09%;<br />
* максимальная эффективность реализации 0.00163%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации циклической редукции в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:Cyclic reduction performance.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:Cyclic reduction eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[https://github.com/yflim/Code-sample--parallel-tridiagonal-solver Исследованная параллельная реализация на языке C]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В силу того, что граф алгоритма несколько схож по структуре на сдваивание, он лучше всего отображался бы на архитектуры типа гиперкуб, однако в последнее десятилетие стали ясны физические и технологические сложности для проектирования таких систем. Вместе с тем, его разработанность отдельными группами исследователей (в блочных вариантах, в том числе) вполне позволяет применять циклическую редукцию и на обычных массово параллельных компьютерах. Особенно рекомендуется не доводить циклическую редукцию до предела, а на отдельных узлах использовать такие старые методы, как монотонная и встречная прогонки.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Обычно циклическую редукцию как в блочном, так и в точечном варианте реализуют матфизики, решающие задачи с задачами, содержащими, например, дискретизацию оператора Лапласа. Её, несмотря на популярность, редко включают в пакеты программ. В основном это связано с тем, что простота схемы циклической редукции остаётся только для определённых размерностей задач, а универсальные решатели на её основе никто не делает. Для других размерностей могут быть придуманы разные, в том числе и более быстрые варианты циклической редукции<ref>А.В.Фролов "О коэффициенте при логарифме в критическом пути графа циклической редукции" // Суперкомпьютерные дни в России: Труды международной конференции (26-27 сентября 2016 г., г. Москва). – М.: Изд-во МГУ, 2016. с. 307-313.</ref>, но у них очень сложная схема, и каждый раз нужно выполнять анализ на устойчивость.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
{{algorithm}}<br />
[[Категория:Алгоритмы с избыточными вычислениями]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%B0%D1%8F_%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%87%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21033Повторная встречная прогонка, точечный вариант2016-12-16T10:32:48Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Встречная повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>2.5n-1</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Встречная повторная прогонка''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где уже однажды СЛАУ с такой же матрицей была решена методом встречной прогонки<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
'''Встречная повторная прогонка''', как и [[Классическая монотонная прогонка, повторный вариант|повторная монотонная]], заключается в исключении из уравнений неизвестных, однако, в отличие от монотонной, в ней исключение ведут одновременно с обоих "краёв" СЛАУ (верхнего и нижнего). В принципе, её можно считать простейшим вариантом [[Метод редукции|метода редукции]] (при m=1 и встречных направлениях монотонных прогонок).<br />
<br />
=== Математическое описание алгоритма ===<br />
<math>m</math> здесь - номер уравнения, на котором "встречаются" две ветви прямого хода - "сверху" и "снизу". <br />
<br />
В приведенных обозначениях во встречной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
"сверху":<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}, </math><br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i}), \quad i = 1, 2, \cdots , m-1.<br />
<br />
</math><br />
<br />
и "снизу":<br />
<br />
<math>\eta_{N} = f_{N}/c_{N}</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})/(c_{i}-b_{i}\xi_{i+1})</math>, <math>\quad i = N-1, N-2, \cdots , m.</math><br />
<br />
(все отношения <math>1/c_{0}</math>, <math>1/c_{N}</math>, <math>1/(c_{i}-a_{i}\alpha_{i})</math>, <math>1/(c_{i}-b_{i}\xi_{i+1})</math> при этом вычислены при выполнении первой встречной прогонки) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}, \quad i = m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}, \quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
<br />
В приводимых обычно<ref name="SETKI" /> формулах встречной прогонки нет формулы для компоненты <math>y_{m-1}</math>, которая вычисляется позже в обратном ходе. Однако это удлиняет критический путь графа как в случае с чётным числом переменных, откладывая вычисление <math>y_{m-1}</math> на момент, когда уже будет вычислена <math>y_{m}</math>, хотя обе компоненты могут быть вычислены одновременно почти независимо друг от друга, так и в случае с нечётным числом переменных, когда для вычисления на "месте встречи" нужно подождать дополнительно одно вычисление коэффициентов либо "сверху" от него, либо "снизу".<br />
<br />
Поэтому в более поздних источниках<ref name="IK">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г. ,208 с.</ref> приводятся формулы, которые более оптимальны для нечётного количества неизвестных. В них старт обратного хода заменяется на формулу (в наших обозначениях)<br />
<br />
<math>y_{m} = (f_{m}+b_{m}\eta_{m+1}+a_{m}\beta_{m})/(c_{m}-a_{m}\alpha_{m}-b_{m}\xi_{m+1})</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно, как и в случае [[Классическая монотонная прогонка, повторный вариант|повторной классической монотонной прогонки]], представить состоящим из двух частей - прямого и обратного хода; однако их ширина вдвое больше, чем в монотонном случае. В прямом ходе ядро составляют две независимые последовательности операций двух умножений и сложения/вычитания. В обратном ходе в ядре остаются только две независимые последовательности операций умножения и сложения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - прямой ход правой и левой повторных прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ. Обратный ход также может быть разложен на две макроединицы - обратный ход правой и левой прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
[[file:VstrProgonkaInv.png|thumb|left|600px|Рисунок 1. Детальный граф алгоритма встречной прогонки с однократным вычислением обратных чисел при n=6 без отображения входных и выходных данных. '''inv''' - вычисление обратного числа, '''mult''' - операция перемножения чисел. Без обращений - ветви, повторяющиеся при замене правой части СЛАУ в '''повторной встречной прогонке'''.]]<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0})</math>, <br />
<br />
<math>\eta_{1} = f_{N}(1/c_{N})</math>.<br />
<br />
2. Последовательно выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>, <math>\quad i = 1, 2, \cdots , m-1</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})(1/(c_{i}-b_{i}\xi_{i+1}))</math>, <math>\quad i = N-1, N-2, \cdots , m</math>.<br />
<br />
<br />
3. Инициализируется обратный ход:<br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>, <br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>.<br />
<br />
4. Последовательно выполняются формулы обратного хода:<br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = m-1, m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}</math>, <math>\quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения встречной прогонки в трёхдиагональной СЛАУ из n уравнений с n неизвестными в последовательном (наиболее быстром) варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности встречная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
Информационный граф встречной прогонки представлен на рис.1. Как видно, он параллелен со степенью не более 2. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названию "обратный ход". <br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Обе ветви прямого хода можно выполнять одновременно, если <math>N=2m-1</math>, т.е. <math>n=2m</math>. В этом случае встречная прогонка требует последовательного выполнения следующих ярусов:<br />
<br />
* по <math>3m-2</math> ярусов умножений и <math>2m-1</math> ярусов сложений/вычитаний (по две операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ встречная прогонка относится к алгоритмам с ''линейной'' сложностью. При классификации по ширине ЯПФ сложность этого алгоритма равна <math>2</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': преобразованная первой встречной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''константой'' (2). <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных – тоже ''константа''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно повторная встречная прогонка, как и повторная монотонная, используется для решения СЛАУ с диагональным преобладанием. Тогда гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от нужд вычислений, возможны как разные способы хранения матрицы СЛАУ (в виде одного массива с 3 строками или в виде 3 разных массивов), так и разные способы хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно).<br />
<br />
Приведем пример подпрограммы, реализующей встречную прогонку СЛАУ с нечётным числом уравнений, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы.<br />
<br />
<source lang="fortran"><br />
subroutine vprogmr (a,x,N) ! N=2m-1<br />
real a(3,N), x(N)<br />
<br />
m=(N+1)/2<br />
<br />
x(1)=x(1)*a(2,1) ! beta 2<br />
x(N)=x(N)*a(2,N) ! eta N<br />
<br />
do 10 i=2,m-1<br />
<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
x(N+1-i)=(x(N+1-i)-a(3,N+1-i)*x(N+2-i))* a(2,N+1-i) ! eta N-i<br />
<br />
10 continue<br />
<br />
x(m) =(x(m)-a(1,m)*x(m-1)-a(3,m)*x(m+1))*a(2,m) ! y m<br />
<br />
do 20 i=m+1,N<br />
x(i)=a(1,i)*x(i-1)+x(i) ! y i up<br />
x(N+1-i)=a(3,N+1-i)*x(N+2-i)+x(N+1-i) ! y i down<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, что нужны операциям, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом" и "нижнем правом" "углах" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, что бы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:countersweep_repeated_1.png|thumb|center|700px|Рисунок 2. Встречная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации повторного варианта встречной прогонки. Данный профиль состоит из набора параллельно выполняемых последовательных переборов, как возрастающих, так и убывающих. В общем случае такой профиль характеризуется высокой пространственной локальностью, поскольку соседние обращения выполняются к близким по памяти данным, а также низкой временной локальностью, так как данные практически не используются повторно. Это подтверждает тот факт, что общее число обращений в память менее чем в два раза превышает число задействованных данных – достаточно плохой показатель производительности работы с памятью.<br />
<br />
Из рисунка можно предположить, что переборы, скорее всего, устроены достаточно регулярно и не меняют своего поведения в течение программы. Однако нужно детально разобрать, как устроены эти переборы; для этого рассмотрим некоторые наиболее интересные фрагменты профиля более детально. <br />
<br />
На рис. 2 приведен фрагмент 1 (выделен на рис. 1 зеленым). Видна регулярность обращений к памяти, которая нарушается только один раз, примерно по центру рисунка, где происходит смена этапов работы программы. На первом этапе параллельно выполняются два возрастающих и два убывающих последовательных перебора с небольшим шагом по памяти; на втором этапе число переборов возрастает в 1.5 раза, однако характер обращений остается примерно тем же. Подобный фрагмент, в отличие от обычного последовательного перебора, обладает более высокой временной локальностью, поскольку данные используются повторно по несколько раз, при этом большая часть повторных обращений расположена близко друг к другу.<br />
<br />
[[file:countersweep_repeated_2.png|thumb|center|500px|Рисунок 3. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим фрагмент 2 (рис. 3). Здесь профиль устроен очень просто – параллельно выполняются возрастающий и убывающий переборы данных с небольшим шагом по памяти. Подобное поведение можно увидеть также во всех переборах, расположенных ниже фрагмента 2 на рис. 1.<br />
<br />
[[file:countersweep_repeated_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, фрагмент 2]]<br />
<br />
В целом можно сказать, что данный профиль действительно состоит из небольшого числа возрастающих и убывающих последовательных переборов с небольшим шагом по памяти. Такое строение характеризуется высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 4 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что реализация данного алгоритма показывает достаточно неплохую производительность работы с памятью. Результаты daps примерно совпадают с результатами для реализации метод Холецкого и обгоняют большинство других алгоритмов, построенных на основе переборов элементов массива, таких как вычисление скалярного произведения, реализация повторного варианта монотонной прогонки и т.д.<br />
<br />
[[file:countersweep_repeated_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Встречная повторная прогонка задумана изначально для случая, когда нужно найти только какую-то близкую к "середине" компоненту вектора решения, а остальные не нужны (решение т.н. "частичной задачи"). При появлении параллельных компьютерных устройств оказалось, что у встречной прогонки есть небольшой ресурс параллелизма и она убыстряет счёт, если её верхнюю и нижнюю ветви "раскидать" на 2 процессора. Однако для получения массового параллелизма встречная прогонка непригодна из-за низкой ширины своей [[Глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] (равной 2).<br />
<br />
С появлением суперскалярных процессоров оказалось, что для получения выигрыша (около 20%) перед монотонной повторной прогонкой встречную даже необязательно "раскидывать" на 2 процессора или даже на 2 ядра. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 2. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой повторной встречной прогонки, как почти непараллельного алгоритма, говорить нельзя в принципе, за исключением разве что двухпроцессорных систем. Понятие масштабируемости неприменимо, поскольку описываемый алгоритм не предполагает параллельной реализации.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная встречная прогонка - метод для архитектуры классического, фон-неймановского типа. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует взять какой-либо её параллельный заменитель, например, наиболее распространённую [[Метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный метод]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм повторной встречной прогонки настолько прост, что, в тех случаях, когда он по каким-либо причинам понадобился, большинство использующих его исследователей-прикладников просто пишут соответствующий фрагмент программы самостоятельно. Поэтому встречную прогонку в пакеты программ обычно не включают.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Законченные статьи]]<br />
[[Категория:Алгоритмы с небольшим уровнем параллелизма]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B,_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21032Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант2016-12-16T10:32:19Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = QR-разложение методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{4 n^3}{3}</math><br />
| pf_height = <math>n^2</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 1)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для разложения матриц в виде <math>A=QR</math> (<math>Q</math> - унитарная, <math>R</math> — правая треугольная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием минимального одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QR</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце. Таким образом, после <math>n-1</math> шагов преобразований получается матрица <math>R</math> из <math>QR</math>-разложения.<br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma =1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф шага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего или из входных данных), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего, красных и жёлтых кружков, а на последнем шаге и голубого - выходные для алгоритма.]]<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. При этом, однако, строгая последовательность выполнения этих трёх подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>2n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На Рисунке 1 приведён шаг графа алгоритма метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом. Операции привязаны к обрабатываемым элементам матрицы. Для получения полного графа графы шагов нужно положить друг на друга последовательными слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>\frac{n^2}{2}</math> умножений и <math>\frac{n^2}{2}</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''. Однако все эти широко распространённые методы пока не дали возможности снизить критический путь метода Хаусхолдера до ''линейного'' (как, скажем, у метода Гивенса).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': правая треугольная матрица <math>R</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>n^2+n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) QR-разложения квадратной вещественной матрицы на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SX(K)=2.<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
В этом варианте R расположена в верхнем правом треугольнике массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов, а их первые элементы - в элементах массива SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qr_1.png|thumb|center|700px|Рисунок 2. Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации вещественного варианта метода Хаусхолдера (отражений) QR-разложения квадратной матрицы. Из данного рисунка можно сделать несколько выводов. Во-первых, видно, что в среднем к каждому элементу выполняется примерно 90 обращений (отношение общего числа обращений к числу задействованных данных), что является достаточно хорошим показателем в плане повторного использования данных. Далее, явно видна итерационная структура профиля, причем на каждой следующей итерации обращения к небольшой части первых элементов перестают выполняться. Это говорит о том, что ближе к концу профиля задействовано меньше данных, что хорошо сказывается на пространственной локальности. Однако в то же время видно, что на каждой следующей итерации выполняется меньше обращений к одним и тем же локальным областям, то есть временная локальность, по всей вероятности, становится ниже. Рассмотрим подробнее первую итерацию, что более детально оценить их строение.<br />
<br />
На рис. 2 представлена одна итерация общего профиля (выделенный зеленый фрагмент на рис. 1). Ее можно условно разбить на 5 фрагментов, каждую из которых рассмотреть отдельно. Фрагменты 2 и 5 устроены просто – элементы перебираются с большим шагом по памяти, при достижении конца данных перебор повторяется, только с небольшим сдвигом. Про такие фрагменты можно сказать, что они обладают низкой пространственной локальностью, поскольку шаг по памяти достаточно большой, и низкой временной локальностью, так как данные повторно не используются. Однако отметим, что данные фрагменты состоят из небольшого числа обращений, что означает, что они не так сильно будут снижать общую локальность профиля.<br />
<br />
[[file:householder_qr_2.png|thumb|center|700px|Рисунок 3. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагмент 1 и 3 устроены практически идентично (основное отличие – два основных этапа переставлены местами), поэтому рассмотрим только один из них. На рис. 3 представлен фрагмент 3. Здесь хорошо видно, как устроены эти этапы. На первом этапе выполняется перебор элементов в обратном порядке с шагом 1, при этом к каждому элементу подряд обращаются несколько раз. Такой фрагмент обладает очень высокой пространственной локальностью и достаточно высокой временной локальностью. <br />
<br />
[[file:householder_qr_3.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 3]]<br />
<br />
На втором этапе также выполняется последовательный перебор, но уже с небольшим шагом, и к каждому элементу обращаются только по 1 разу на каждой итерации. Однако, поскольку в каждой небольшой итерации на втором этапе всего около 30 обращений, повторное обращение к каждому элементу происходит недалеко от предыдущего, что говорит о достаточно высокой временной локальности. Пространственная локальность также высока, поскольку шаг по памяти невелик.<br />
<br />
Далее рассмотрим фрагмент 4. Чтобы лучше понять локальную структуру, детально изучим небольшую его часть (рис. 4). Из данного рисунка хорошо видно, что здесь профиль устроен очень просто и представляет собой обычный последовательный перебор с шагом 1 (с небольшими и нечастыми сдвигами, которые не влияют на локальность этого фрагмента). Такой фрагмент обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:householder_qr_4.png|thumb|center|500px|Рисунок 5. Профиль обращений, одна итерация, часть фрагмента 4]]<br />
<br />
В целом можно сказать, что одна итерация обладает высокой пространственной и средней временной локальностью, хотя она несколько снижается из-за наличия фрагментов 2, 4 и 5. При условии, что итерации подобны, выводы относительно локальности одной итерации можно перенести и на общий профиль.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном примере высока. Значение daps для него находится на одном уровне с тестом Linpack и лишь немногим ниже, чем, например, самые эффективные варианты перемножения плотных матриц. Это означает, что негативное влияние фрагментов 2 и 5 оказывается достаточно слабым, вероятно (как и предполагалось ранее) из-за небольшого их размера.<br />
<br />
<br />
[[file:householder_qr_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BC%D0%BE%D0%BD%D0%BE%D1%82%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21031Классическая монотонная прогонка, повторный вариант2016-12-16T10:31:54Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>5n-4</math><br />
| pf_width = <math>1</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
[[file:ProgonkaRep.png|thumb|right|200px|Рисунок 1. Граф алгоритма повторной прогонки при n=7 без отображения входных и выходных данных. '''/''' - деление, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Повторная прогонка''' - один из вариантов метода исключения неизвестных, применяемого к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где как минимум один раз уже была решена прогонкой другая СЛАУ с той же матрицей<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
Здесь рассматривается тот вариант прогонки, когда обрабатывается вся СЛАУ сверху вниз и обратно - так называемая '''правая прогонка'''. Суть метода состоит в исключении из уравнений неизвестных, сначала - сверху вниз - под диагональю, а затем - снизу вверх - над диагональю. Вариант, когда СЛАУ "проходится" наоборот, снизу вверх и обратно вниз - '''левая прогонка''' - принципиально ничем не отличается от рассматриваемого, поэтому нет смысла включать его отдельное описание.<br />
<br />
[[file:ProgonkaRepMul.png|thumb|left|200px|Рисунок 2. Граф алгоритма повторной прогонки с заранее выполненным предвычислением обратных чисел при n=7 без отображения входных и выходных данных. '''m''' - умножение на предвычисленное обратное число, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В приведённых выше обозначениях в повторной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}</math>, <br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i})</math>, <math>\quad i = 1, 2, \cdots , N</math>.<br />
<br />
(при этом отношения <math>1/c_{0}</math> и <math>1/(c_{i}-a_{i}\alpha_{i})</math> уже предвычислены при решении первой системы первой прогонкой) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{N} = \beta_{N+1}</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = N-1, N-2, \cdots , 1, 0</math>.<br />
<br />
В литературе<ref name="SETKI" /> указывается, что данные формулы эквивалентны использованию предвычисленного при полной прогонке одного из вариантов <math>LU</math>-разложения матрицы системы для последующего решения двухдиагональных систем посредством прямой и обратной подстановок.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно считать состоящим из двух частей - прямого и обратного хода прогонки. Как в прямом ходе, так и в обратном, вычислительное ядро составляют последовательности операций умножения (в прямом ходе их две за шаг; в принципе, можно воспользоваться ассоциативностью и вычислять сразу оба произведения, но "промежуточный" результат нужен для обратного хода) и сложения/вычитания. <br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - треугольного разложения матрицы и прямого хода решения двухдиагональной СЛАУ, которые выполняются "одновременно", т.е. параллельно друг другу. При этом решение двухдиагональной СЛАУ использует результаты треугольного разложения.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход прогонки:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0}) </math><br />
<br />
2. Последовательно для всех <math>i</math> от <math>1</math> до <math>N-1</math> выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>.<br />
<br />
3. Инициализируется обратный ход прогонки:<br />
<br />
<math>y_{N} = (f_{N}+a_{N}\beta_{N})(1/(c_{N}-a_{N}\alpha_{N}))</math><br />
<br />
4. Последовательно для всех <math>i</math> с убыванием от <math>N-1</math> до <math>0</math> выполняются формулы обратного хода:<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в последовательном варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности, повторная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
Информационный граф повторной прогонки в случае первой прогонки без предвычислений обратных чисел на рис.1. Информационный граф повторной прогонки в случае первой прогонки с предвычислениями обратных чисел - на рис.2. Как следует из анализа графа, он является полностью последовательным. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названиям "прямой и обратный ход".<br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>3n - 2</math> ярусов умножений и <math>2n - 2</math> сложений/вычитаний (в ярусах - по <math>1</math> операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам со сложностью <math>O(n)</math>. При классификации по ширине ЯПФ её сложность будет равна <math>1</math> (чисто последовательный алгоритм).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Предварительно обработанная полной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, равно <math>1</math>. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных также является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно прогонка используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от способа хранения матрицы СЛАУ (в виде одного массива с <math>3</math> строками или в виде <math>3</math> разных массивов) и способа хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно) возможны различные реализации алгоритма.<br />
<br />
Приведем пример подпрограммы на языке Fortran, реализующей прогонку, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые в процессе первой прогонки коэффициенты --- на месте элементов исходной матрицы. <br />
<br />
<source lang="fortran"><br />
subroutine progmr (a,x,N)<br />
real a(3,0:N), x(0:N)<br />
x(0)=x(0)*a(2,0) ! beta 1<br />
do 10 i=1,N-1<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
10 continue<br />
<br />
x(N)=(x(N)-a(1,N)*x(N-1))*a(2,N) ! y N<br />
do 20 i=N-1,0,-1<br />
x(i)=a(3,i)*x(i+1)+x(i) ! y i<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, которые необходимы для операций, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом углу" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, чтобы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
При этом, однако, повторная прогонка относится к таким последовательным алгоритмам, в которых локальность вычислений настолько велика, что является даже излишней<ref>Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ'2016): труды международной научной конференции (г. Архангельск, 28 марта - 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref>. Из-за того, что данные, необходимые для выполнения основных операций прогонки, вычисляются в непосредственно предшествующим им операциях, возможность использования суперскалярности вычислительных ядер процессоров практически сводится на нет, что ухудшает эффективность выполнения прогонки даже на современных однопроцессорных и одноядерных системах. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 3. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:monosweep_repeated_1.png|thumb|center|700px|Рисунок 4. Классическая монотонная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен общий профиль обращений в память для реализации повторного варианта классической монотонной прогонки. Явно видно два этапа в работе программы – на первом этапе выполняется перебор 4 массивов в прямой последовательности, а на втором этапе 2 массива перебираются в обратной последовательности. Профиль состоит из фрагментов, каждый из которых очень похож на обычный последовательный перебор, который обладает высокой пространственной и низкой временной локальностями. Однако необходимо более детальное рассмотрение профиля, чтобы понять, как именно этот перебор устроен.<br />
<br />
На рис. 2 представлен фрагмент 1 (выделен на рис. 1 зеленым). В данном фрагменте происходит смена двух этапов, а также входят обращения ко всем массивам. Из рис. 2 видно, что перебор 4 массивов в нижней части рисунка состоит из единичных обращений; в данном случае фрагменты представляют собой обычный последовательный перебор элементов массива, возможно с некоторым небольшим шагом по памяти. Однако самая верхняя часть фрагмента отличается от остальных, более того, можно увидеть, что характер обращений на разных этапах отличается. Рассмотрим данную часть более детально.<br />
<br />
[[file:monosweep_repeated_2.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 1]]<br />
<br />
На рис.3 представлена часть фрагмента 1, выделенная зеленым на рис. 2. Можно увидеть, что, действительно, профили на разных этапах отличаются. На первом этапе выполняются два параллельно последовательных перебора, при этом сдвиг по памяти между ними очень невелик. На втором этапе параллельно выполняется уже три последовательных перебора, причем сдвиг по памяти также очень мал. При таких условиях пространственная локальность высока, поскольку данные перебираются почти последовательно. Временная локальность для данной части относительно невысока, поскольку повторных обращений к данным немного, хотя они расположены очень близко друг к другу.<br />
<br />
Отметим, однако, что, во-первых, переборы других массивов реже используют данные повторно, и, во-вторых, данные также повторно используются на разных этапах далеко друг от друга, что приводит к достаточно серьезному снижению временной локальности при рассмотрении общего профиля.<br />
<br />
[[file:monosweep_repeated_3.png|thumb|center|500px|Рисунок 6. Фрагмент 1, верхняя часть]]<br />
<br />
В целом можно сказать, что общий профиль обладает высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 4 приведены значения daps для реализаций некоторых распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно невысока и находится на уровне таких алгоритмов, как вычисление нормы вектора и решение уравнения Пуассона. По всей видимости, это связано с достаточно низкой временной локальностью, как было сказано выше.<br />
<br />
Однако отметим, что в целом данный профиль обладает более высокой производительностью работы с памятью по сравнению с большинством алгоритмов, которые представляют собой совокупность различных последовательных переборов, таких как вычисление нормы вектора, реализация схемы Горнера и т.д.<br />
<br />
[[file:monosweep_repeated_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Как видно из анализа графа алгоритма, его (без существенных модификаций) практически невозможно распараллелить. Поэтому есть два способа использования прогонок для параллельных вычислительных систем: либо разбивать задачу, где используются прогонки, так, чтобы их было достаточно много, например, так, чтобы на каждую из прогонок приходился 1 процессор (1 ядро), либо использовать вместо прогонки её параллельные варианты (циклическую редукцию, последовательно-параллельные варианты и т.п.).<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой прогонки, как полностью непараллельного алгоритма, говорить не имеет смысла. Однако необходимо отметить, что анализ масштабируемости параллельных вариантов прогонки должен проводиться относительно однопроцессорной реализации описанного классического варианта прогонки, а не относительно однопроцессорных расчетов для её параллельных вариантов.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная монотонная прогонка - метод для архитектуры классического, фон-неймановского типа, устаревший даже для эффективной загрузки одноядерных систем, поддерживающих суперскалярность, где проигрывает повторной встречной прогонке. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует использовать какой-либо её параллельный заменитель, например, наиболее распространённую [[Повторный метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный вариант]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм прогонки является настолько простым, что, несмотря на его "дежурное" присутствие в стандартных пакетах программ решения задач линейной алгебры, большинство использующих его исследователей-прикладников часто пишут соответствующий фрагмент программы самостоятельно. Однако надо отметить, что, как правило, метод прогонки в пакетах реализуют не в его "чистом виде", а в виде пары "разложение на две двухдиагональные матрицы" и "решение СЛАУ с предварительно факторизованной трёхдиагональной матрицей". Так обстоит дело, например, в пакете SCALAPACK и его предшественниках.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[En:Two-sided elimination method, pointwise version]]<br />
[[Категория:Алгоритмы с низким уровнем параллелизма]]<br />
[[Категория:Законченные статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21030Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T10:00:20Z<p>VadimVV: /* Структура обращений в память и качественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 2 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 1 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
На рис. 3 показан отдельно фрагмент 6, выделенный на рис. 2. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 2. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (представлен на рис. 4, выделен на рис. 2 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 3) по строению очень похожа на первую часть; вторая (область 2 на рис. 3) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B,_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21029Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант2016-12-16T09:55:05Z<p>VadimVV: /* Количественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = QR-разложение методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{4 n^3}{3}</math><br />
| pf_height = <math>n^2</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 1)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для разложения матриц в виде <math>A=QR</math> (<math>Q</math> - унитарная, <math>R</math> — правая треугольная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием минимального одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QR</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце. Таким образом, после <math>n-1</math> шагов преобразований получается матрица <math>R</math> из <math>QR</math>-разложения.<br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma =1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф шага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего или из входных данных), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего, красных и жёлтых кружков, а на последнем шаге и голубого - выходные для алгоритма.]]<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. При этом, однако, строгая последовательность выполнения этих трёх подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>2n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На Рисунке 1 приведён шаг графа алгоритма метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом. Операции привязаны к обрабатываемым элементам матрицы. Для получения полного графа графы шагов нужно положить друг на друга последовательными слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>\frac{n^2}{2}</math> умножений и <math>\frac{n^2}{2}</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''. Однако все эти широко распространённые методы пока не дали возможности снизить критический путь метода Хаусхолдера до ''линейного'' (как, скажем, у метода Гивенса).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': правая треугольная матрица <math>R</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>n^2+n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) QR-разложения квадратной вещественной матрицы на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SX(K)=2.<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
В этом варианте R расположена в верхнем правом треугольнике массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов, а их первые элементы - в элементах массива SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qr_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации вещественного варианта метода Хаусхолдера (отражений) QR-разложения квадратной матрицы. Из данного рисунка можно сделать несколько выводов. Во-первых, видно, что в среднем к каждому элементу выполняется примерно 90 обращений (отношение общего числа обращений к числу задействованных данных), что является достаточно хорошим показателем в плане повторного использования данных. Далее, явно видна итерационная структура профиля, причем на каждой следующей итерации обращения к небольшой части первых элементов перестают выполняться. Это говорит о том, что ближе к концу профиля задействовано меньше данных, что хорошо сказывается на пространственной локальности. Однако в то же время видно, что на каждой следующей итерации выполняется меньше обращений к одним и тем же локальным областям, то есть временная локальность, по всей вероятности, становится ниже. Рассмотрим подробнее первую итерацию, что более детально оценить их строение.<br />
<br />
На рис. 2 представлена одна итерация общего профиля (выделенный зеленый фрагмент на рис. 1). Ее можно условно разбить на 5 фрагментов, каждую из которых рассмотреть отдельно. Фрагменты 2 и 5 устроены просто – элементы перебираются с большим шагом по памяти, при достижении конца данных перебор повторяется, только с небольшим сдвигом. Про такие фрагменты можно сказать, что они обладают низкой пространственной локальностью, поскольку шаг по памяти достаточно большой, и низкой временной локальностью, так как данные повторно не используются. Однако отметим, что данные фрагменты состоят из небольшого числа обращений, что означает, что они не так сильно будут снижать общую локальность профиля.<br />
<br />
[[file:householder_qr_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагмент 1 и 3 устроены практически идентично (основное отличие – два основных этапа переставлены местами), поэтому рассмотрим только один из них. На рис. 3 представлен фрагмент 3. Здесь хорошо видно, как устроены эти этапы. На первом этапе выполняется перебор элементов в обратном порядке с шагом 1, при этом к каждому элементу подряд обращаются несколько раз. Такой фрагмент обладает очень высокой пространственной локальностью и достаточно высокой временной локальностью. <br />
<br />
[[file:householder_qr_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 3]]<br />
<br />
На втором этапе также выполняется последовательный перебор, но уже с небольшим шагом, и к каждому элементу обращаются только по 1 разу на каждой итерации. Однако, поскольку в каждой небольшой итерации на втором этапе всего около 30 обращений, повторное обращение к каждому элементу происходит недалеко от предыдущего, что говорит о достаточно высокой временной локальности. Пространственная локальность также высока, поскольку шаг по памяти невелик.<br />
<br />
Далее рассмотрим фрагмент 4. Чтобы лучше понять локальную структуру, детально изучим небольшую его часть (рис. 4). Из данного рисунка хорошо видно, что здесь профиль устроен очень просто и представляет собой обычный последовательный перебор с шагом 1 (с небольшими и нечастыми сдвигами, которые не влияют на локальность этого фрагмента). Такой фрагмент обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:householder_qr_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, часть фрагмента 4]]<br />
<br />
В целом можно сказать, что одна итерация обладает высокой пространственной и средней временной локальностью, хотя она несколько снижается из-за наличия фрагментов 2, 4 и 5. При условии, что итерации подобны, выводы относительно локальности одной итерации можно перенести и на общий профиль.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном примере высока. Значение daps для него находится на одном уровне с тестом Linpack и лишь немногим ниже, чем, например, самые эффективные варианты перемножения плотных матриц. Это означает, что негативное влияние фрагментов 2 и 5 оказывается достаточно слабым, вероятно (как и предполагалось ранее) из-за небольшого их размера.<br />
<br />
<br />
[[file:householder_qr_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B,_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21028Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант2016-12-16T09:54:46Z<p>VadimVV: /* Количественная оценка локальности */</p>
<hr />
<div>{{algorithm<br />
| name = QR-разложение методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{4 n^3}{3}</math><br />
| pf_height = <math>n^2</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 1)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для разложения матриц в виде <math>A=QR</math> (<math>Q</math> - унитарная, <math>R</math> — правая треугольная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием минимального одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QR</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце. Таким образом, после <math>n-1</math> шагов преобразований получается матрица <math>R</math> из <math>QR</math>-разложения.<br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma =1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф шага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего или из входных данных), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего, красных и жёлтых кружков, а на последнем шаге и голубого - выходные для алгоритма.]]<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. При этом, однако, строгая последовательность выполнения этих трёх подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>2n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На Рисунке 1 приведён шаг графа алгоритма метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом. Операции привязаны к обрабатываемым элементам матрицы. Для получения полного графа графы шагов нужно положить друг на друга последовательными слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>\frac{n^2}{2}</math> умножений и <math>\frac{n^2}{2}</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''. Однако все эти широко распространённые методы пока не дали возможности снизить критический путь метода Хаусхолдера до ''линейного'' (как, скажем, у метода Гивенса).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': правая треугольная матрица <math>R</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>n^2+n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) QR-разложения квадратной вещественной матрицы на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SX(K)=2.<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
В этом варианте R расположена в верхнем правом треугольнике массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов, а их первые элементы - в элементах массива SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qr_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации вещественного варианта метода Хаусхолдера (отражений) QR-разложения квадратной матрицы. Из данного рисунка можно сделать несколько выводов. Во-первых, видно, что в среднем к каждому элементу выполняется примерно 90 обращений (отношение общего числа обращений к числу задействованных данных), что является достаточно хорошим показателем в плане повторного использования данных. Далее, явно видна итерационная структура профиля, причем на каждой следующей итерации обращения к небольшой части первых элементов перестают выполняться. Это говорит о том, что ближе к концу профиля задействовано меньше данных, что хорошо сказывается на пространственной локальности. Однако в то же время видно, что на каждой следующей итерации выполняется меньше обращений к одним и тем же локальным областям, то есть временная локальность, по всей вероятности, становится ниже. Рассмотрим подробнее первую итерацию, что более детально оценить их строение.<br />
<br />
На рис. 2 представлена одна итерация общего профиля (выделенный зеленый фрагмент на рис. 1). Ее можно условно разбить на 5 фрагментов, каждую из которых рассмотреть отдельно. Фрагменты 2 и 5 устроены просто – элементы перебираются с большим шагом по памяти, при достижении конца данных перебор повторяется, только с небольшим сдвигом. Про такие фрагменты можно сказать, что они обладают низкой пространственной локальностью, поскольку шаг по памяти достаточно большой, и низкой временной локальностью, так как данные повторно не используются. Однако отметим, что данные фрагменты состоят из небольшого числа обращений, что означает, что они не так сильно будут снижать общую локальность профиля.<br />
<br />
[[file:householder_qr_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагмент 1 и 3 устроены практически идентично (основное отличие – два основных этапа переставлены местами), поэтому рассмотрим только один из них. На рис. 3 представлен фрагмент 3. Здесь хорошо видно, как устроены эти этапы. На первом этапе выполняется перебор элементов в обратном порядке с шагом 1, при этом к каждому элементу подряд обращаются несколько раз. Такой фрагмент обладает очень высокой пространственной локальностью и достаточно высокой временной локальностью. <br />
<br />
[[file:householder_qr_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 3]]<br />
<br />
На втором этапе также выполняется последовательный перебор, но уже с небольшим шагом, и к каждому элементу обращаются только по 1 разу на каждой итерации. Однако, поскольку в каждой небольшой итерации на втором этапе всего около 30 обращений, повторное обращение к каждому элементу происходит недалеко от предыдущего, что говорит о достаточно высокой временной локальности. Пространственная локальность также высока, поскольку шаг по памяти невелик.<br />
<br />
Далее рассмотрим фрагмент 4. Чтобы лучше понять локальную структуру, детально изучим небольшую его часть (рис. 4). Из данного рисунка хорошо видно, что здесь профиль устроен очень просто и представляет собой обычный последовательный перебор с шагом 1 (с небольшими и нечастыми сдвигами, которые не влияют на локальность этого фрагмента). Такой фрагмент обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:householder_qr_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, часть фрагмента 4]]<br />
<br />
В целом можно сказать, что одна итерация обладает высокой пространственной и средней временной локальностью, хотя она несколько снижается из-за наличия фрагментов 2, 4 и 5. При условии, что итерации подобны, выводы относительно локальности одной итерации можно перенести и на общий профиль.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном примере высока. Значение daps для него находится на одном уровне с тестом Linpack и лишь немногим ниже, чем, например, самые эффективные варианты перемножения плотных матриц. Это означает, что негативное влияние фрагментов 2 и 5 оказывается достаточно слабым, вероятно (как и предполагалось ранее) из-за небольшого их размера.<br />
<br />
<br />
[[file:householder_qr_daps.png|thumb|center|700px|Рисунок 3. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9A%D0%BB%D0%B0%D1%81%D1%81%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B0%D1%8F_%D0%BC%D0%BE%D0%BD%D0%BE%D1%82%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D0%BF%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21018Классическая монотонная прогонка, повторный вариант2016-12-16T08:19:06Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>5n-4</math><br />
| pf_width = <math>1</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
[[file:ProgonkaRep.png|thumb|right|200px|Рисунок 1. Граф алгоритма повторной прогонки при n=7 без отображения входных и выходных данных. '''/''' - деление, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Повторная прогонка''' - один из вариантов метода исключения неизвестных, применяемого к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где как минимум один раз уже была решена прогонкой другая СЛАУ с той же матрицей<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
Здесь рассматривается тот вариант прогонки, когда обрабатывается вся СЛАУ сверху вниз и обратно - так называемая '''правая прогонка'''. Суть метода состоит в исключении из уравнений неизвестных, сначала - сверху вниз - под диагональю, а затем - снизу вверх - над диагональю. Вариант, когда СЛАУ "проходится" наоборот, снизу вверх и обратно вниз - '''левая прогонка''' - принципиально ничем не отличается от рассматриваемого, поэтому нет смысла включать его отдельное описание.<br />
<br />
[[file:ProgonkaRepMul.png|thumb|left|200px|Рисунок 2. Граф алгоритма повторной прогонки с заранее выполненным предвычислением обратных чисел при n=7 без отображения входных и выходных данных. '''m''' - умножение на предвычисленное обратное число, '''''f''''' - операция '''''a+bc'''''.]]<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В приведённых выше обозначениях в повторной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}</math>, <br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i})</math>, <math>\quad i = 1, 2, \cdots , N</math>.<br />
<br />
(при этом отношения <math>1/c_{0}</math> и <math>1/(c_{i}-a_{i}\alpha_{i})</math> уже предвычислены при решении первой системы первой прогонкой) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{N} = \beta_{N+1}</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = N-1, N-2, \cdots , 1, 0</math>.<br />
<br />
В литературе<ref name="SETKI" /> указывается, что данные формулы эквивалентны использованию предвычисленного при полной прогонке одного из вариантов <math>LU</math>-разложения матрицы системы для последующего решения двухдиагональных систем посредством прямой и обратной подстановок.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно считать состоящим из двух частей - прямого и обратного хода прогонки. Как в прямом ходе, так и в обратном, вычислительное ядро составляют последовательности операций умножения (в прямом ходе их две за шаг; в принципе, можно воспользоваться ассоциативностью и вычислять сразу оба произведения, но "промежуточный" результат нужен для обратного хода) и сложения/вычитания. <br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - треугольного разложения матрицы и прямого хода решения двухдиагональной СЛАУ, которые выполняются "одновременно", т.е. параллельно друг другу. При этом решение двухдиагональной СЛАУ использует результаты треугольного разложения.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход прогонки:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0}) </math><br />
<br />
2. Последовательно для всех <math>i</math> от <math>1</math> до <math>N-1</math> выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>.<br />
<br />
3. Инициализируется обратный ход прогонки:<br />
<br />
<math>y_{N} = (f_{N}+a_{N}\beta_{N})(1/(c_{N}-a_{N}\alpha_{N}))</math><br />
<br />
4. Последовательно для всех <math>i</math> с убыванием от <math>N-1</math> до <math>0</math> выполняются формулы обратного хода:<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в последовательном варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности, повторная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
Информационный граф повторной прогонки в случае первой прогонки без предвычислений обратных чисел на рис.1. Информационный граф повторной прогонки в случае первой прогонки с предвычислениями обратных чисел - на рис.2. Как следует из анализа графа, он является полностью последовательным. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названиям "прямой и обратный ход".<br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Для выполнения повторной прогонки в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>3n - 2</math> ярусов умножений и <math>2n - 2</math> сложений/вычитаний (в ярусах - по <math>1</math> операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам со сложностью <math>O(n)</math>. При классификации по ширине ЯПФ её сложность будет равна <math>1</math> (чисто последовательный алгоритм).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Предварительно обработанная полной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, равно <math>1</math>. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных также является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно прогонка используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от способа хранения матрицы СЛАУ (в виде одного массива с <math>3</math> строками или в виде <math>3</math> разных массивов) и способа хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно) возможны различные реализации алгоритма.<br />
<br />
Приведем пример подпрограммы на языке Fortran, реализующей прогонку, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые в процессе первой прогонки коэффициенты --- на месте элементов исходной матрицы. <br />
<br />
<source lang="fortran"><br />
subroutine progmr (a,x,N)<br />
real a(3,0:N), x(0:N)<br />
x(0)=x(0)*a(2,0) ! beta 1<br />
do 10 i=1,N-1<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
10 continue<br />
<br />
x(N)=(x(N)-a(1,N)*x(N-1))*a(2,N) ! y N<br />
do 20 i=N-1,0,-1<br />
x(i)=a(3,i)*x(i+1)+x(i) ! y i<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, которые необходимы для операций, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом углу" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, чтобы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
При этом, однако, повторная прогонка относится к таким последовательным алгоритмам, в которых локальность вычислений настолько велика, что является даже излишней<ref>Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ'2016): труды международной научной конференции (г. Архангельск, 28 марта - 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref>. Из-за того, что данные, необходимые для выполнения основных операций прогонки, вычисляются в непосредственно предшествующим им операциях, возможность использования суперскалярности вычислительных ядер процессоров практически сводится на нет, что ухудшает эффективность выполнения прогонки даже на современных однопроцессорных и одноядерных системах. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 2. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:monosweep_repeated_1.png|thumb|center|700px|Рисунок 1. Классическая монотонная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен общий профиль обращений в память для реализации повторного варианта классической монотонной прогонки. Явно видно два этапа в работе программы – на первом этапе выполняется перебор 4 массивов в прямой последовательности, а на втором этапе 2 массива перебираются в обратной последовательности. Профиль состоит из фрагментов, каждый из которых очень похож на обычный последовательный перебор, который обладает высокой пространственной и низкой временной локальностями. Однако необходимо более детальное рассмотрение профиля, чтобы понять, как именно этот перебор устроен.<br />
<br />
На рис. 2 представлен фрагмент 1 (выделен на рис. 1 зеленым). В данном фрагменте происходит смена двух этапов, а также входят обращения ко всем массивам. Из рис. 2 видно, что перебор 4 массивов в нижней части рисунка состоит из единичных обращений; в данном случае фрагменты представляют собой обычный последовательный перебор элементов массива, возможно с некоторым небольшим шагом по памяти. Однако самая верхняя часть фрагмента отличается от остальных, более того, можно увидеть, что характер обращений на разных этапах отличается. Рассмотрим данную часть более детально.<br />
<br />
[[file:monosweep_repeated_2.png|thumb|center|500px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
На рис.3 представлена часть фрагмента 1, выделенная зеленым на рис. 2. Можно увидеть, что, действительно, профили на разных этапах отличаются. На первом этапе выполняются два параллельно последовательных перебора, при этом сдвиг по памяти между ними очень невелик. На втором этапе параллельно выполняется уже три последовательных перебора, причем сдвиг по памяти также очень мал. При таких условиях пространственная локальность высока, поскольку данные перебираются почти последовательно. Временная локальность для данной части относительно невысока, поскольку повторных обращений к данным немного, хотя они расположены очень близко друг к другу.<br />
<br />
Отметим, однако, что, во-первых, переборы других массивов реже используют данные повторно, и, во-вторых, данные также повторно используются на разных этапах далеко друг от друга, что приводит к достаточно серьезному снижению временной локальности при рассмотрении общего профиля.<br />
<br />
[[file:monosweep_repeated_3.png|thumb|center|500px|Рисунок 3. Фрагмент 1, верхняя часть]]<br />
<br />
В целом можно сказать, что общий профиль обладает высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 4 приведены значения daps для реализаций некоторых распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно невысока и находится на уровне таких алгоритмов, как вычисление нормы вектора и решение уравнения Пуассона. По всей видимости, это связано с достаточно низкой временной локальностью, как было сказано выше.<br />
<br />
Однако отметим, что в целом данный профиль обладает более высокой производительностью работы с памятью по сравнению с большинством алгоритмов, которые представляют собой совокупность различных последовательных переборов, таких как вычисление нормы вектора, реализация схемы Горнера и т.д.<br />
<br />
[[file:monosweep_repeated_daps.png|thumb|center|700px|Рисунок 4. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Как видно из анализа графа алгоритма, его (без существенных модификаций) практически невозможно распараллелить. Поэтому есть два способа использования прогонок для параллельных вычислительных систем: либо разбивать задачу, где используются прогонки, так, чтобы их было достаточно много, например, так, чтобы на каждую из прогонок приходился 1 процессор (1 ядро), либо использовать вместо прогонки её параллельные варианты (циклическую редукцию, последовательно-параллельные варианты и т.п.).<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой прогонки, как полностью непараллельного алгоритма, говорить не имеет смысла. Однако необходимо отметить, что анализ масштабируемости параллельных вариантов прогонки должен проводиться относительно однопроцессорной реализации описанного классического варианта прогонки, а не относительно однопроцессорных расчетов для её параллельных вариантов.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная монотонная прогонка - метод для архитектуры классического, фон-неймановского типа, устаревший даже для эффективной загрузки одноядерных систем, поддерживающих суперскалярность, где проигрывает повторной встречной прогонке. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует использовать какой-либо её параллельный заменитель, например, наиболее распространённую [[Повторный метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный вариант]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм прогонки является настолько простым, что, несмотря на его "дежурное" присутствие в стандартных пакетах программ решения задач линейной алгебры, большинство использующих его исследователей-прикладников часто пишут соответствующий фрагмент программы самостоятельно. Однако надо отметить, что, как правило, метод прогонки в пакетах реализуют не в его "чистом виде", а в виде пары "разложение на две двухдиагональные матрицы" и "решение СЛАУ с предварительно факторизованной трёхдиагональной матрицей". Так обстоит дело, например, в пакете SCALAPACK и его предшественниках.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[En:Two-sided elimination method, pointwise version]]<br />
[[Категория:Алгоритмы с низким уровнем параллелизма]]<br />
[[Категория:Законченные статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4_%D1%86%D0%B8%D0%BA%D0%BB%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B9_%D1%80%D0%B5%D0%B4%D1%83%D0%BA%D1%86%D0%B8%D0%B8&diff=21017Полный метод циклической редукции2016-12-16T08:14:34Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Циклическая редукция для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>17n + o(n)</math><br />
| pf_height = <math>7 log_2 n</math><br />
| pf_width = <math>3n/2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Циклическая редукция''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где <br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ2}}<br />
<br />
'''Циклическая редукция''', как и все варианты прогонки, заключается <ref name="IK3d">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г., 208 с.</ref><ref name="FAVT-2016">Фролов А.В., Антонов А.С., Воеводин Вл.В., Теплов А.М. Сопоставление разных методов решения одной задачи по методике проекта Algowiki // Параллельные вычислительные технологии (ПаВТ’2016): труды международной научной конференции (г. Архангельск, 28 марта – 1 апреля 2016 г.). Челябинск: Издательский центр ЮУрГУ, 2016. С. 347-360.</ref> в исключении из уравнений неизвестных, однако, в отличие от них, в ней исключение ведут одновременно по всей СЛАУ. В принципе, её можно считать вариантом [[Метод редукции|метода редукции]], выполняемого максимально возможное для данной СЛАУ число раз.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Лучше всего схема циклической редукции<ref name="IK3d" /> разработана для случая <math>n = 2^{m}-1</math>. Эта схема состоит из прямого и обратного ходов. <br />
Прямой ход состоит из последовательного уменьшения в СЛАУ количества уравнений почти в 2 раза (за счёт подстановки из уравнений с нечётными номерами заменяются уравнения с чётными), пока не останется одно уравнение, обратный - в получении всё большего количества компонент решения исходной СЛАУ. Оба хода - как прямой, так и обратный - разбиты на шаги. Здесь мы приведём тот вариант алгоритма, в котором операции экономятся за счёт предварительной нормировки уравнений, используемых для исключения неизвестных. <br />
<br />
==== Прямой ход редукции ====<br />
<br />
В начале считается, что все <br />
<math>c^{(0)}_{i} = c_{i}, b^{(0)}_{i} = b_{i}, a^{(0)}_{i} = a_{i}, f^{(0)}_{i} = f_{i}, x^{(0)}_{i} = x_{i}</math><br />
<br />
На k-м шаге теперь выполняем процедуру редукции системы уравнений размерности n.<br />
<br />
Для каждого из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math> <br />
<br />
с нечётными <math>i</math> с помощью деления уравнения на <math>b^{(k)}_{i}</math> выполняется его замена на уравнение <br />
<br />
<math> (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} + x^{(k)}_{i} + (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} = f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
[[file:CycRedMicro0.png|thumb|right|150px|Рисунок 1. Микрограф "узла" подготовительного шага прямого хода алгоритма циклической редукции ]]<br />
<br />
Уравнение <br />
<br />
<math>b^{(k)}_{1} x^{(k)}_{1} + a^{(k)}_{1} x^{(k)}_{2} = f^{(k)}_{1}</math><br />
<br />
аналогично меняется на уравнение <br />
<br />
<math>x^{(k)}_{1} + (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2} = f^{(k)}_{1}/b^{(k)}_{1}</math>:<br />
<br />
а уравнение <br />
<br />
<math>c^{(k)}_{n} x^{(k)}_{n-1} + b^{(k)}_{n} x^{(k)}_{n} = f^{(k)}_{n}</math> <br />
<br />
меняется на уравнение <br />
<br />
<math>(c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1} + x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n}</math>:<br />
<br />
[[file:CycRedMicroDirect.png|thumb|right|400px|Рисунок 2a. Микрограф "узла" с нечётным номером прямого хода алгоритма циклической редукции]]<br />
<br />
Для каждого же из уравнений <br />
<br />
<math>c^{(k)}_{i} x^{(k)}_{i-1} + b^{(k)}_{i} x^{(k)}_{i} + a^{(k)}_{i} x^{(k)}_{i+1} = f^{(k)}_{i}</math><br />
<br />
с чётными <math>i</math> (кроме <math>2</math> и <math>n-2</math>) выполняется, с учётом <math>x^{(k+1)}_{i/2} = x^{(k)}_{i}</math> его замена на уравнение <br />
<br />
<math>c^{(k+1)}_{i/2} x^{(k+1)}_{(i-2)/2} + b^{(k+1)}_{i/2} x^{(k+1)}_{i/2} + a^{(k+1)}_{i/2} x^{(k+1)}_{(i+2)/2} = f^{(k+1)}_{i/2}</math><br />
<br />
[[file:CycRedMicroDirect2.png|thumb|right|400px|Рисунок 2b. Микрограф "узла" с чётным номером прямого хода алгоритма циклической редукции ]]<br />
<br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{i/2} = - c^{(k)}_{i}(c^{(k)}_{i-1}/b^{(k)}_{i-1})</math>, <br />
<br />
<math>a^{(k+1)}_{i/2} = - a^{(k)}_{i}(a^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>b^{(k+1)}_{i/2} = b^{(k)}_{i} - c^{(k)}_{i}(a^{(k)}_{i-1}/b^{(k)}_{i-1}) - a^{(k)}_{i}(c^{(k)}_{i+1}/b^{(k)}_{i+1})</math>,<br />
<br />
<math>f^{(k+1)}_{i/2} = f^{(k)}_{i} - c^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1} - a^{(k)}_{i}f^{(k)}_{i-1}/b^{(k)}_{i-1}</math>.<br />
<br />
Для 2го уравнения выполняется его замена на уравнение<br />
<br />
<math>b^{(k+1)}_{1} x^{(k+1)}_{1} + a^{(k+1)}_{1} x^{(k+1)}_{(2} = f^{(k+1)}_{1}</math><br />
<br />
при этом<br />
<br />
<math>a^{(k+1)}_{1} = - a^{(k)}_{2}(a^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>b^{(k+1)}_{1} = b^{(k)}_{2} - c^{(k)}_{2}(a^{(k)}_{1}/b^{(k)}_{1}) - a^{(k)}_{2}(c^{(k)}_{3}/b^{(k)}_{3})</math>,<br />
<br />
<math>f^{(k+1)}_{1} = f^{(k)}_{2} - c^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1} - a^{(k)}_{2}f^{(k)}_{1}/b^{(k)}_{1}</math><br />
<br />
<br />
<math>n-1</math>-е уравнение заменяется на <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-3)/2} + b^{(k+1)}_{(n-1)/2} x^{(k+1)}_{(n-1)/2} = f^{(k+1)}_{(n-1)/2}</math><br />
<br />
при этом <br />
<br />
<math>c^{(k+1)}_{(n-1)/2} = - c^{(k)}_{n-1}(c^{(k)}_{n-2}/b^{(k)}_{n-2})</math>,<br />
<br />
<math>b^{(k+1)}_{(n-1)/2} = b^{(k)}_{n-1} - c^{(k)}_{n-1}(a^{(k)}_{n-2}/b^{(k)}_{n-2}) - a^{(k)}_{n-1}(c^{(k)}_{n}/b^{(k)}_{n})</math>,<br />
<br />
<math>f^{(k+1)}_{(n-1)/2} = f^{(k)}_{n-1} - c^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2} - a^{(k)}_{n-1}f^{(k)}_{n-2}/b^{(k)}_{n-2}</math>.<br />
<br />
По окончании всех этих манипуляций размерность k+1-й СЛАУ оказывается равной <math>(n-1)/2</math>.<br />
<br />
Шаги повторяются до тех пор, пока после <math>m-1</math> шагов редукции размерность СЛАУ не становится равной 1 и остаётся одно уравнение<br />
<br />
<math>b^{(m-1)}_{1} x^{(m-1)}_{1} = f^{(m-1)}_{1}</math><br />
<br />
==== Обратный ход редукции ====<br />
<br />
[[file:CycRedMicroRev.png|thumb|right|150px|Рисунок 3. Микрограф "узла" обратного хода алгоритма циклической редукции ]]<br />
<br />
Из последнего уравнения, полученного прямым ходом, вычисляется <br />
<br />
<math>x^{(m-1)}_{1} = f^{(m-1)}_{1}/b^{(m-1)}_{1}</math><br />
<br />
Теперь, последовательно уменьшая верхние индексы неизвестных, используется нечётные уравнения каждого шага для вычисления неизвестных с соотвествующими нечётными номерами. Чётные неизвестные получаются из тождеств <math>x^{(k)}_{i} = x^{(k+1)}_{i/2}</math>, а для нечётных <math>i</math> <br />
<br />
<math>x^{(k)}_{i} = f^{(k)}_{i}/b^{(k)}_{i} - (c^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i-1} - (a^{(k)}_{i}/b^{(k)}_{i}) x^{(k)}_{i+1} </math><br />
<br />
c "левого края" системы будет <br />
<br />
<math>x^{(k)}_{1} = f^{(k)}_{1}/b^{(k)}_{1} - (a^{(k)}_{1}/b^{(k)}_{1}) x^{(k)}_{2}</math><br />
<br />
а с "правого"<br />
<br />
<math>x^{(k)}_{n} = f^{(k)}_{n}/b^{(k)}_{n} - (c^{(k)}_{n}/b^{(k)}_{n}) x^{(k)}_{n-1}</math><br />
<br />
После вычисления всех <math>x^{(0)}_{i}</math> значения искомых неизвестных <math>x_{i} = x^{(k)}_{i}</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Видно, что, поскольку вычисляемые на каждом шаге прямого хода редукции при преобразовании нечётных уравнений отношения коэффициентов <br />
<br />
<math> c^{(k)}_{i}/b^{(k)}_{i} , a^{(k)}_{i}/b^{(k)}_{i} , f^{(k)}_{i}/b^{(k)}_{i}</math><br />
<br />
почти все используются для преобразований двух чётных уравнений, то при выделении "микровычислений", из которых следует составить шаги редукции и которые составляют его ядро, лучше отнести вычисления этих отношений к предыдущему шагу редукции. Таким образом, на "подготовительном шаге" микроядро будет для каждого нечётного <math>i</math> состоять только из трёх делений (кроме <math>2</math> и <math>n</math> - там будет по 2 деления), а затем на каждом последующем шаге редукции для каждого нечётного <math>i</math> - из двух умножений и двух вычислений выражений типа <math>a-bc-de</math>, с последующими тремя делениями (на "краях" часть этих операций отсутствует или урезана, но общую картину это не очень меняет). Для чётных <math>i</math> деления в микроядре будут отсутствовать.<br />
<br />
Что касается шагов обратного хода, то там для каждого <math>i</math> рано или поздно выполняется одна операция типа <math>a-bc-de</math> (на "краях" - типа <math>a-bc</math>).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[file:CycRedDirect.png|thumb|right|400px|Рисунок 4. Граф алгоритма прямого хода циклической редукции при n=15. Светло-зелёным обозначены микроядра "подготовительного шага" с тремя (или меньше) делениями, синим - микроядра нечётных узов, сине-зелёным - ядра чётных узлов (без делений).]]<br />
<br />
[[file:CycRedRev.png|thumb|right|400px|Рисунок 5. Граф алгоритма обратного хода циклической редукции при n=15. В вершинах - операции вида a-bc-de, в вершинах с 1 входящей дугой - вида a-bc. Показаны только дуги, передающие значения найденных неизвестных.]]<br />
<br />
Если выразить макроструктуру алгоритма циклической редукции в терминах её "микроядер", то прямой ход редукции несколько схож на схемы сдваивания, но отличается от неё не только количеством входящих в макровершины дуг, из-за чего её схема - скорее "страивание", но и зацеплением соседних ветвей. Поэтому, в отличие от схем сдваивания, для которых характерно полностью независимое исполнение ветвей до определённого момента, в схеме редукции это зацепление ветвей не даёт им выполняться полностью независимо. <br />
<br />
Обратный ход циклической редукции - уже практически полное "обратное" сдваивание, и на этом этапе разделение на независимые ветви может быть проделано после старта, если размножить необходимые данные.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Метод циклической редукции изначально спроектирован для параллельного исполнения, поскольку является по отношению к, например, классической прогонке, алгоритмом с избыточными вычислениями. Поэтому смысла в его последовательной реализации обычно не видят и они не встречаются в библиотеках программ. Тем не менее, из-за того, что современная архитектура даже одиночных узлов и персональных компьютеров не вполне последовательна, то этот смысл, как вполне может оказаться<ref name="FAVT-2016" />, уже появился, поскольку, кроме простого количества операций, производительность вычислений на компьютере определяется и другими параметрами алгоритма, в частности, локальностью вычислений.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Поскольку алгоритм циклической редукции обычно не предназначен для последовательного исполнения, то его "последовательную сложность" скорее следует считать только оценкой общего количества операций. Если взять только главный член, линейный по размеру, и опустить меньшие по порядку (логарифмические и константы), то получается, что в циклической редукции ''3n'' делений, ''8n'' умножений и ''6n'' вычитаний/сложений. Таким образом, при классификации по последовательной сложности, алгоритм циклической редукции относится к алгоритмам ''с линейной сложностью''.<br />
<br />
Сравнение сложности с алгоритмом [[Прогонка, точечный вариант|прогонки]] показывает также, что циклическая редукция - алгоритм с избыточными вычислениями: избыточность по сравнению с прогонкой более чем в 2 раза.<br />
<br />
=== Информационный граф ===<br />
<br />
У прямого хода циклической редукции макрограф представлен на Рис. 4, при этом графы разные макровершин представлены на Рис. 1, 2a, 2b. Как видно, после первого шага схему можно условно назвать "схемой страивания", поскольку связанные с каждым уравнением, остающимся в редуцированной СЛАУ, коэффициенты вычисляются по формулам из коэффициентов '''трёх''' соседних уравнений в нередуцированной СЛАУ. <br />
<br />
Обратный ход имеет макрограф, представленный на Рис. 5, при этом графы макровершин представлены на Рис. 3 (крайние макровершины, имеющие только одну входящую дугу на макрографе, замещают недостающие данные нулями, соответственно уменьшая вычисления). Этот макрограф, в принципе, имеет макроструктуру, обратную к схеме сдваивания, поэтому её можно условно назвать "схемой раздваивания", с одной поправкой: все макровершины, кроме крайних, имеют по две входящих дуги, которые немного изменяют обычное "раздваивание".<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для выполнения циклической редукции в трёхдиагональной СЛАУ из <math>n</math> уравнений с <math>n</math> неизвестными в параллельном варианте требуется последовательно выполнить следующие ярусы:<br />
<br />
* <math>log_2 n</math> ярусов делений, из них самый широкий ярус - первый, в нём <math>3n/2</math> делений. <br />
* <math>2 log_2 n</math> ярусов умножений и <math>4 log_2 n</math>сложений/вычитаний.<br />
<br />
Ширина ярусов экспоненциально, с показателем 2, убывает с их номерами, а потом, на обратном ходе, экспоненциально растёт, также с показателем 2. Эта неоднородность приводит к тому, что при практическом полном распараллеливании большую часть времени большинство устройств будет простаивать. <br />
<br />
Таким образом, при классификации по высоте ЯПФ, прогонка относится к алгоритмам с ''логарифмической сложностью''. При классификации по ширине ЯПФ её сложность будет ''линейной''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Объём входных данных''': <math>4n-2</math>.<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является отношением размера задачи к его логарифму. <br />
<br />
При этом вычислительная мощность алгоритма как отношение числа операций к суммарному объему входных и выходных данных является ''константой''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно циклическая редукция используется для решения СЛАУ с диагональным преобладанием. В этом случае гарантируется устойчивость алгоритма.<br />
<br />
В случае, когда требуется решение нескольких СЛАУ с одной и той же матрицей, большую часть прямого хода вычислений (см. рисунки с графом алгоритма) можно не повторять.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Из-за избыточности вычислений на компьютерах без параллельных устройств обычно используют более простую монотонную или встречную [[Прогонка, точечный вариант|прогонку]], а циклическую редукцию в последовательном варианте обычно просто не реализуют. Этот выбор, однако, не так очевиден на современных "однопроцессорных" суперскалярных компьютерах, поскольку в циклической редукции нет такой избыточной локальности, как в [[Прогонка, точечный вариант|прогонке]]. Однако, эта сторона циклической редукции ещё не исследована и ждёт тех, кто проведёт соответствующую работу.<br />
<br />
Приведем пример подпрограммы, реализующей циклическую прогонку СЛАУ с числом уравнений "степень двойки минус 1", где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы. Следует обратить внимание на то, что данная версия циклической редукции непригодна для повторного решения СЛАУ с той же матрицей и новой правой частью, поскольку при повторении нужно несколько большее количество промежуточных данных. <br />
<br />
<source lang="fortran"><br />
subroutine cprogm (a,x,N,m) ! N=2^m-1<br />
real a(3,N), x(N)<br />
<br />
k=1<br />
k2=2<br />
kk=1 <br />
<br />
a(2,1)=1./a(2,1)<br />
a(2,N)=1./a(2,N)<br />
a(3,1)=a(3,1)*a(2,1)<br />
a(1,N)=a(1,N)*a(2,N)<br />
x(1) = x(1)*a(2,1)<br />
x(N) = x(N)*a(2,N)<br />
do i=3,N-2,2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
do j=1,m-2<br />
<br />
k=k2 ! k=2^j<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
a(3,k) = -a(3,k)*a(3,k+kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(1,N+1-k)*a(3,N+1-k-kk)<br />
a(1,N+1-k)= - a(1,N+1-k)*a(1,N+1-k-kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k-kk)*a(1,N+1-k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k) - x(k+kk)*a(3,k+kk)<br />
a(2,N+1-k) = a(2,N+1-k) - a(3,N+1-k)*a(1,N+1-k+kk)<br />
x(N+1-k) = x(N+1-k)- x(N+1-k+kk)*a(3,N+1-k+kk) <br />
<br />
k2=k2*2 ! k2=2^(j+1)<br />
<br />
do i = k2, N+1-k2, k<br />
a(2,i) = a(2,i) - a(1,i)*a(3,i-kk)<br />
x(i) = x(i)- x(i-kk)*a(1,i-kk)<br />
a(3,i) = -a(3,i)*a(3,i+kk)<br />
a(1,i)= - a(1,i)*a(1,i-kk) <br />
a(2,i) = a(2,i) - a(3,i)*a(1,i+kk)<br />
x(i) = x(i)- x(i+kk)*a(3,i+kk)<br />
end do <br />
<br />
a(2,k)=1./a(2,k)<br />
a(2,N+1-k)=1./a(2,N+1-k)<br />
a(3,k)=a(3,k)*a(2,k)<br />
a(1,N+1-k)=a(1,N+1-k)*a(2,N+1-k)<br />
x(k) = x(k)*a(2,k)<br />
x(N+1-k) = x(N+1-k)*a(2,N+1-k) <br />
<br />
do i = k2+k, N+1-k2-k, k2<br />
a(2,i) = 1./a(2,i)<br />
a(1,i) = a(1,i)*a(2,i)<br />
a(3,i) = a(3,i)*a(2,i)<br />
x(i) = x(i)*a(2,i)<br />
end do<br />
<br />
kk=k ! budet kk=2^(j-1)<br />
<br />
end do <br />
<br />
c m-1 shag & start of reverse<br />
<br />
k=k2 ! k=2^(m-1) i kk=2^(m-2)<br />
<br />
a(2,k) = a(2,k) - a(1,k)*a(3,k-kk)<br />
x(k) = x(k)- x(k-kk)*a(1,k-kk)<br />
a(2,k) = a(2,k) - a(3,k)*a(1,k+kk)<br />
x(k) = x(k)- x(k+kk)*a(3,k+kk)<br />
a(2,k) = 1./a(2,k)<br />
x(k) = x(k)*a(2,k)<br />
<br />
c start reverse <br />
<br />
x(kk) = x(kk) - a(3,kk)*x(k)<br />
<br />
x(k+kk) = x(k+kk) - a(1,k+kk)*x(k)<br />
<br />
do j=m-2, 1, -1<br />
<br />
k2 = k ! k2 = 2^(j+1)<br />
k = kk ! k = 2^j<br />
kk = kk/2 ! kk = 2^(j-1)<br />
<br />
x (kk) = x (kk) - a(3,kk)*x(kk+kk)<br />
x (N+1-kk) = x (N+1-kk) - a(1,N+1-kk)*x(N+1-k) <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(1,i)*x(i-kk) <br />
end do <br />
do i = k2+k, N+1-k-kk, k2<br />
x(i) = x(i) - a(3,i)*x(i+kk) <br />
end do <br />
end do<br />
<br />
return<br />
end<br />
</source><br />
<br />
Здесь необходимо отметить, что, несмотря на последовательность этой реализации, она вполне может быть исполнена и параллельно, нужно только как-то скомандовать имеющемуся в наличии компилятору, что циклы по i параллельны (например, спецкомментариями OpenMP).<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
По графу видно, что, хотя и налицо "местная" локальность по вычислениям, но от яруса к ярусу расстояние между запрашиваемыми данными растёт от первых и последних ярусов алгоритма к его середине, являющимся не только узким местом алгоритма, но и имеющим наибольшую длину передачи данных между устройствами. Поэтому, хотя в циклической редукции нет той избыточной локальности, которая притормаживает работу прогонки, это преимущество, как показали замеры<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref> (см. Рисунок 6), не способно компенсировать недостатки общей локальности графа циклической редукции.<br />
<br />
[[File:I3-cyc.png|thumb|center|1000px|Рисунок 6. Отношение времени исполнения циклической редукции и монотонных прогонок на ПК. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение циклической редукции, к времени, затраченному на выполнение монотонной повторной прогонки. Система Core i3 + Windows 8, сборка кода компилятором GNU Fortran. На других системах получена качественно примерно такая же картина, с различием по выходу за границы кэша. Между синей и красной линиями - примерная область взаимной компенсации неарифметических факторов. При малых размерах сказывается неиспользование прогонками суперскалярности процессора.]]<br />
<br />
<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:cyclic_reduction_1.png|thumb|center|700px|Рисунок 1. Полный метод циклической редукции. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации полного метода циклической редукции. Данный профиль достаточно интересно. Как нам известно из описания самого алгоритма, он состоит из двух частей – прямого и обратного хода. На общем профиле синяя вертикальная черта отделяет эти два этапа. Рассмотрим их в отдельности.<br />
<br />
На первом этапе явно видны отдельные итерации (первые 3 итерации разделены желтыми линиями), каждая из которых состоит из 4 параллельно выполняемых переборов данных. Отметим, что на каждой итерации в рамках каждого перебора задействованы данные из одного и того же диапазона. Число обращений в память в каждой итерации различается, и это позволяет предположить, что характер переборов также может отличаться. <br />
<br />
Рассмотрим фрагменты двух итераций более детально. На рис. 2 и 3 соответственно представлены фрагменты 1 и 2, обозначенные на рис. 1 зеленым. Данные фрагменты содержат по 1000 обращений. Можно увидеть, что в целом все переборы в двух фрагментах выполняются с регулярным и достаточно небольшим шагом по памяти, однако интенсивность обращений в каждом случае разная. Причем при смене итерации меняется и интенсивность обращений к одному и тому диапазону данных. <br />
<br />
[[file:cyclic_reduction_2.png|thumb|center|500px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
[[file:cyclic_reduction_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, фрагмент 2]]<br />
<br />
Рассмотрим еще более детально небольшие части данных фрагментов, чтобы дальше проанализировать структуру представленных переборов. На рис. 4 представлены небольшие локальные области, выделенные зеленым на рис. 2 и 3. Можно увидеть, что локальная структура обращений в целом отличается. В частности, в области, выделенной на рис. 3, шаг по памяти больше, а также выполняется меньше повторных обращений, что говорит о более низкой пространственной локальности. <br />
<br />
[[file:cyclic_reduction_4.png|thumb|center|700px|Рисунок 4. Локальная структура фрагментов 2 и 3]]<br />
<br />
В целом можно сказать, что структура итераций похожа, однако локальность несколько отличается в силу указанных выше причин. Если же говорить в среднем, такое строение итераций характеризуется от средней до высокой (в зависимости от локальной структуры) пространственной локальностью и низкой временной локальностью, поскольку данные перебираются подряд (обычно с небольшим шагом по памяти), но при этом повторно почти не используются. Отметим, что размер итераций достаточно велик, так что повторное обращение к данным на следующей итерации не приводит к улучшению локальности.<br />
<br />
Отдельно рассмотрим самый конец первого этапа (фрагмент 3 на рис. 1), который показан на рис. 5. Здесь локальность обращений в память самая низкая, поскольку шаг по памяти в переборах становится наибольшим. В центре рисунка можно заметить область особенно низкой локальности (наиболее разрозненные обращения). Хотя данный фрагмент небольшого размера, он может серьезно снижать общую производительность взаимодействия с памятью.<br />
<br />
[[file:cyclic_reduction_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 3]]<br />
<br />
Второй этап также имеет регулярную структуру. Рассмотрим на рис. 6 небольшую область более детально (фрагмент 4 на рис. 1). Из данного рисунка видно, что здесь также выполняются последовательные переборы, локальная структура которых может отличаться. Как и в рассмотренном на первом этапе случае, это приводит к различиям по пространственной и временной локальности. Стоит отметить, что угол наклона переборов на втором этапе в целом больше, чем на первом этапе (см. рис 1), что говорит о большем шаге по памяти, и, как следствие, более низкой пространственной локальности, однако это различие может нивелироваться различиями в локальной структуре.<br />
<br />
[[file:cyclic_reduction_6.png|thumb|center|500px|Рисунок 6. Профиль обращений, фрагмент 4]]<br />
<br />
В целом можно сказать, что общий профиль обладает низкой временной локальностью. Пространственная локальность, скорее всего, не очень высокая, в частности, из-за неэффективной области в конце первого этапа. Однако делать предположения относительно пространственной локальности в данном случае достаточно сложно ввиду большого разнообразия локальных структур переборов.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 7 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае низка и лишь чуть выше значения daps для такой неэффективной с точки зрения работы с памятью программы как тест RandomAccess (rand). В целом это соотносится с выводами, которые были сделаны нами ранее<br />
<br />
<br />
[[file:cyclic_reduction_daps.png|thumb|center|700px|Рисунок 7. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Исходя из таких свойств алгоритма циклической редукции, как плохая локальность и наличие избыточности, логично было бы использовать его не в "чистом виде", а, проведя несколько этапов редукции, решать оставшуюся СЛАУ более простым методом вроде прогонки.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
При оценке масштабируемости этого алгоритма, как и всех алгоритмов с избыточными вычислениями, следует учитывать, что сравнение по быстродействию и эффективности нужно проводить не с однопроцессорным вариантом исполнения самого алгоритма, а с алгоритмом [[Прогонка, точечный вариант|прогонки]]. <br />
<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации циклической редукции согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [2 : 256] с шагом степени двойки;<br />
* размер матрицы [64 : 33554432] с шагом степени двойки.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 3.89e-09%;<br />
* максимальная эффективность реализации 0.00163%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации циклической редукции в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:Cyclic reduction performance.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:Cyclic reduction eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[https://github.com/yflim/Code-sample--parallel-tridiagonal-solver Исследованная параллельная реализация на языке C]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В силу того, что граф алгоритма несколько схож по структуре на сдваивание, он лучше всего отображался бы на архитектуры типа гиперкуб, однако в последнее десятилетие стали ясны физические и технологические сложности для проектирования таких систем. Вместе с тем, его разработанность отдельными группами исследователей (в блочных вариантах, в том числе) вполне позволяет применять циклическую редукцию и на обычных массово параллельных компьютерах. Особенно рекомендуется не доводить циклическую редукцию до предела, а на отдельных узлах использовать такие старые методы, как монотонная и встречная прогонки.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Обычно циклическую редукцию как в блочном, так и в точечном варианте реализуют матфизики, решающие задачи с задачами, содержащими, например, дискретизацию оператора Лапласа. Её, несмотря на популярность, редко включают в пакеты программ. В основном это связано с тем, что простота схемы циклической редукции остаётся только для определённых размерностей задач, а универсальные решатели на её основе никто не делает. Для других размерностей могут быть придуманы разные, в том числе и более быстрые варианты циклической редукции<ref>А.В.Фролов "О коэффициенте при логарифме в критическом пути графа циклической редукции" // Суперкомпьютерные дни в России: Труды международной конференции (26-27 сентября 2016 г., г. Москва). – М.: Изд-во МГУ, 2016. с. 307-313.</ref>, но у них очень сложная схема, и каждый раз нужно выполнять анализ на устойчивость.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
{{algorithm}}<br />
[[Категория:Алгоритмы с избыточными вычислениями]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%B4%D0%BB%D1%8F_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D0%BC%D0%BC%D0%B5%D1%82%D1%80%D0%B8%D1%87%D0%BD%D1%8B%D1%85_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86_%D0%BA_%D1%82%D1%80%D1%91%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC%D1%83_%D0%B2%D0%B8%D0%B4%D1%83&diff=21016Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду2016-12-16T08:06:59Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение симметричной вещественной матрицы к трёхдиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>O(n^3)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2/2</math><br />
| input_data = <math>(n^2+n)/2</math><br />
| output_data = <math>(n^2+3n)/2</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к трёхдиагональному виду, или, что то же самое, для разложения <math>A=QTQ^T</math> (<math>Q</math> - ортогональная, <math>T</math> — симметричная трёхдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QTQ^T</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений) с последующим умножением на те же матрицы справа. <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-м столбце. После умножения на эту же матрицу отражения справа автоматически убираются и ненулевые наддиагональные элементы, начиная с <math>i+2</math>-го в <math>i</math>-й строке, а полученная модификация снова приобретает симметричный вид. <br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Потом по аналогичным формулам модифицируются строки ниже ведущей. Благодаря ассоциативности этих операций после вычисления вектора <math>v</math> можно сразу выписать формулы модификации всех элементов справа и снизу от ведущих столбца и строки. Оказывается, что если для каждого столбца матрицы <math>x^{(j)}</math> с номером <math>j</math> известно <math>\beta_{j}=\frac{(x^{(j)},v)}{\gamma}</math>, то для модифицируемого элемента матрицы <math>y</math> в позиции <math>(i,j)</math> выполняется модификация <math>y' = y - \beta_{i} v_{i} - \beta_{j} v_{j} - \Delta v_{i} v_{j}</math>, где <math>\Delta = \frac{(\beta,v)}{\gamma}</math>. При этом все эти модификации на каждом конкретном шаге алгоритма для разных пар <math>(i,j)</math> можно выполнять независимо друг от друга, в том числе и параллельно.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
[[File:HausLeftSym1.png|thumb|right|800px|Рисунок 1. Граф первой половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-4</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Зелёные кружки - операция вида <math>a+b^2</math>, салатовые - операция вида <math>a+bc</math>. Синий кружок - вычисление параметров матрицы отражения, светло-красные - вычисление коэффициентов <math>\beta_i</math>для следующего полушага, жёлтые - вычисление вектора <math>v</math>.]]<br />
[[File:HousLeftSym2.png|thumb|right|600px|Рисунок 2. Граф второй половины шага алгоритма приведения к трёхдиагональному виду с отображением входных данных. Изображён граф первой половины шага с номером <math>n-3</math>. Квадраты - результаты выполнения предыдущего шага. Если шаг первый, то это входные данные. Синий, жёлтые и светло-красные кружки выполняются на первом полушаге (см. Рисунок 1). Голубые кружки - операции <math>a+bc</math>, зелёное - умножение, чёрные - операции <math>a+bc+de+fce</math>.]]<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также проводимые над нижним правым квадратом матрицы операции вида <math>y'=y-ab-cd-fbd</math>, с учётом симметрии матрицы.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также массовые покомпонентные операции <math>y'=y-ab-cd-fbd</math>. При этом, однако, строгая последовательность выполнения первых двух подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них коэффициентов модификации. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) одновременное умножение слева матрицы отражения <math>U_{i}</math> и справа матрицы отражения <math>U_{i}^T</math>на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также массовых модификаций элементов вида <math>y'=y-\alpha v - \beta w - \Delta vw</math>. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>O(n^3)</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На рисунках 1 и 2 приведён граф алгоритма шага метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в симметричном приведении матрицы порядка <math>n</math> к трёхдиагональной методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>2(n-i)</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. <br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная симметричная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>(n^2+n)/2</math>.<br />
<br />
'''Выходные данные''': трёхдиагональная матрица <math>D</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>(n^2+3n)/2</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной симметричной вещественной матрицы к трёхдиагональному виду на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-2<br />
<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
SX(I)=SX(I)+A(J,I)*A(J,I)<br />
END DO <br />
DO K = I+1, N<br />
DO J = N-1, K, -1<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
DO J = K-1,I+1,-1<br />
SX(K)=SX(K)+A(J,I)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I+1,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I+1,I)*BETA+SIGN(1.,A(I+1,I)) <br />
A(I+1,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N<br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2<br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I+1,I)=ALPHA<br />
G=1.! 1/gamma<br />
SX2=0.<br />
DO K = I+2, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(K,I+1),SX(I))<br />
SX2=SX(K)*A(K,I)+SX2 <br />
END DO<br />
SX2=G*SX2<br />
DO K = I+2, N <br />
A(K,K) = A(K,K)-2*A(K,I)*SX(K)+SX2*A(K,I)**2 <br />
DO J = K+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)-A(K,I)*SX(I)+SX2*A(J,I)*A(K,I)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1.<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
Здесь симметричная трёхдиагональная матрица хранится в диагонали и нижней кодиагонали массива A, вектора v - в поддиагональной части массива, за исключением первых их элементов, для которых выделен массив SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qtq_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) для приведения симметричных матриц к трёхдиагональному виду. Данный профиль обладает явной итерационной структурой, при этом видно, что итерации очень похожи. Основное их отличие заключается в наборе используемых данных – на каждой следующей итерации несколько первых элементов отбрасываются их рассмотрения, то есть чем позже итерация, тем меньше данных в ней задействовано. Также можно отметить, что число обращений в каждой последующей итерации немного уменьшается. Для того чтобы проанализировать общий профиль, в таком случае чаще всего достаточно изучить одну итерацию. Рассмотрим самую первую из них более детально.<br />
<br />
На рис. 2 показан набор обращений в рамках первой итерации (выделен на рис. 1 зеленым). Его можно разбить на несколько фрагментов и рассмотреть их отдельно. Строение фрагментов 2-5 можно оценить по общему графику для итерации. Фрагмент 2 представляет собой набор последовательных переборов в обратном порядке с небольшим шагом, причем число элементов в переборе постепенно уменьшается. Такое строение характеризуется достаточно высокой пространственной локальностью, поскольку часто происходит обращений к близко расположенным данным, однако низкой временной, поскольку данные повторно не используются. То же самое верно и для фрагмента 4, основное отличие которого заключается в том, что переборы выполняются в прямом порядке. Отметим, что некоторое искривление данного фрагмента связано не со строением самого фрагмента, а с разным числом параллельно выполняющихся обращений (см., например, правую область фрагмента 6), чего не наблюдается при выполнении фрагмента 2.<br />
<br />
[[file:householder_qtq_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагменты 3 и 5 состоят из небольшого числа обращений к данным, расположенным далеко друг от друга. Такие фрагменты характеризуются низкой пространственной и временной локальностью.<br />
<br />
Оставшиеся фрагменты требуют более детального рассмотрения. Перейдем к изучению фрагмента 1, который целиком представлен на рис. 3. Здесь мы можем видеть несколько этапов, каждый из которых представляет собой последовательный перебор элементов с шагом по памяти 1, причем в некоторых случаях данные много раз используются повторно (особенно это заметно в правой нижней области рисунка, где выполняется множество обращений к одному и тому же элементу). При условии, что в данном фрагменте задействовано всего около 40 элементов, можно говорить, что данный набор обращений обладает высокой пространственной и временной локальностью. <br />
<br />
[[file:householder_qtq_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 1]]<br />
<br />
Далее рассмотрим детально фрагмент 6 (рис. 4). Здесь можно увидеть, что повторные обращения к данным выполняются не подряд, а через некоторые промежутки, однако число задействованных элементов также очень невелико, так что и в этом случае локальность (и пространственная, и временная) будет высокой.<br />
<br />
[[file:householder_qtq_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
В целом можно сказать, что рассматриваемая итерация обладает достаточно высокой пространственной и временной локальностью. Поскольку остальные итерации устроены подобным образом, данные вывод можно перенести и на общий профиль обращений.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно высока – значение daps лишь немногим уступает тесту Linpack, который обладает высокой эффективностью работы с памятью, и немного превосходит, например, реализацию метода Якоби или метода Гаусса.<br />
<br />
[[file:householder_qtq_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_QR-%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B,_%D0%B2%D0%B5%D1%89%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D0%B9_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21015Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный точечный вариант2016-12-16T08:00:44Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = QR-разложение методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{4 n^3}{3}</math><br />
| pf_height = <math>n^2</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 1)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для разложения матриц в виде <math>A=QR</math> (<math>Q</math> - унитарная, <math>R</math> — правая треугольная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрица <math>Q</math> хранится и используется не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием минимального одномерного дополнительного массива. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения <math>QR</math>-разложения матрицы используются умножения слева её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце. Таким образом, после <math>n-1</math> шагов преобразований получается матрица <math>R</math> из <math>QR</math>-разложения.<br />
<br />
На каждом из шагов метода матрицу отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma =1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math>справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф шага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего или из входных данных), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего, красных и жёлтых кружков, а на последнем шаге и голубого - выходные для алгоритма.]]<br />
<br />
Как уже сказано в описании ядра, основная часть - вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. При этом, однако, строгая последовательность выполнения этих трёх подшагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца и заканчивая предпоследним <math>(n-1)</math>-м.<br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{i}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>2n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
На Рисунке 1 приведён шаг графа алгоритма метода Хаусхолдера в наиболее его быстром (с параллельной точки зрения) варианте, использующем то, что с точностью до множителя ведущий вектор матрицы отражения отличается отличается от подстолбца, где выполняется очередное исключение, только одним элементом. Операции привязаны к обрабатываемым элементам матрицы. Для получения полного графа графы шагов нужно положить друг на друга последовательными слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>\frac{n^2}{2}</math> умножений и <math>\frac{n^2}{2}</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''. Однако все эти широко распространённые методы пока не дали возможности снизить критический путь метода Хаусхолдера до ''линейного'' (как, скажем, у метода Гивенса).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': правая треугольная матрица <math>R</math> (ненулевые элементы <math>r_{ij}</math> в последовательном варианте хранятся в элементах исходной матрицы <math>a_{ij}</math>), унитарная (ортогональная) матрица Q - как произведение матриц Хаусхолдера (отражения) (их вектора нормалей к плоскостям отражения в последовательном варианте хранятся в поддиагональных элементах исходной матрицы <math>a_{ij}</math> и в одном дополнительном столбце размерности n).<br />
<br />
'''Объём выходных данных''': <math>n^2+n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) QR-разложения квадратной вещественной матрицы на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SX(K)=2.<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
<br />
END DO<br />
</source><br />
<br />
В этом варианте R расположена в верхнем правом треугольнике массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов, а их первые элементы - в элементах массива SX.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qr_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации вещественного варианта метода Хаусхолдера (отражений) QR-разложения квадратной матрицы. Из данного рисунка можно сделать несколько выводов. Во-первых, видно, что в среднем к каждому элементу выполняется примерно 90 обращений (отношение общего числа обращений к числу задействованных данных), что является достаточно хорошим показателем в плане повторного использования данных. Далее, явно видна итерационная структура профиля, причем на каждой следующей итерации обращения к небольшой части первых элементов перестают выполняться. Это говорит о том, что ближе к концу профиля задействовано меньше данных, что хорошо сказывается на пространственной локальности. Однако в то же время видно, что на каждой следующей итерации выполняется меньше обращений к одним и тем же локальным областям, то есть временная локальность, по всей вероятности, становится ниже. Рассмотрим подробнее первую итерацию, что более детально оценить их строение.<br />
<br />
На рис. 2 представлена одна итерация общего профиля (выделенный зеленый фрагмент на рис. 1). Ее можно условно разбить на 5 фрагментов, каждую из которых рассмотреть отдельно. Фрагменты 2 и 5 устроены просто – элементы перебираются с большим шагом по памяти, при достижении конца данных перебор повторяется, только с небольшим сдвигом. Про такие фрагменты можно сказать, что они обладают низкой пространственной локальностью, поскольку шаг по памяти достаточно большой, и низкой временной локальностью, так как данные повторно не используются. Однако отметим, что данные фрагменты состоят из небольшого числа обращений, что означает, что они не так сильно будут снижать общую локальность профиля.<br />
<br />
[[file:householder_qr_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
Далее, фрагмент 1 и 3 устроены практически идентично (основное отличие – два основных этапа переставлены местами), поэтому рассмотрим только один из них. На рис. 3 представлен фрагмент 3. Здесь хорошо видно, как устроены эти этапы. На первом этапе выполняется перебор элементов в обратном порядке с шагом 1, при этом к каждому элементу подряд обращаются несколько раз. Такой фрагмент обладает очень высокой пространственной локальностью и достаточно высокой временной локальностью. <br />
<br />
[[file:householder_qr_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 3]]<br />
<br />
На втором этапе также выполняется последовательный перебор, но уже с небольшим шагом, и к каждому элементу обращаются только по 1 разу на каждой итерации. Однако, поскольку в каждой небольшой итерации на втором этапе всего около 30 обращений, повторное обращение к каждому элементу происходит недалеко от предыдущего, что говорит о достаточно высокой временной локальности. Пространственная локальность также высока, поскольку шаг по памяти невелик.<br />
<br />
Далее рассмотрим фрагмент 4. Чтобы лучше понять локальную структуру, детально изучим небольшую его часть (рис. 4). Из данного рисунка хорошо видно, что здесь профиль устроен очень просто и представляет собой обычный последовательный перебор с шагом 1 (с небольшими и нечастыми сдвигами, которые не влияют на локальность этого фрагмента). Такой фрагмент обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:householder_qr_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, часть фрагмента 4]]<br />
<br />
В целом можно сказать, что одна итерация обладает высокой пространственной и средней временной локальностью, хотя она несколько снижается из-за наличия фрагментов 2, 4 и 5. При условии, что итерации подобны, выводы относительно локальности одной итерации можно перенести и на общий профиль.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном примере высока. Значение daps для него находится на одном уровне с тестом Linpack и лишь немногим ниже, чем, например, самые эффективные варианты перемножения плотных матриц. Это означает, что негативное влияние фрагментов 2 и 5 оказывается достаточно слабым, вероятно (как и предполагалось ранее) из-за небольшого их размера.<br />
<br />
<br />
[[file:dqds_daps.png|thumb|center|700px|Рисунок 3. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для QR-разложения матриц именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%A5%D0%B0%D1%83%D1%81%D1%85%D0%BE%D0%BB%D0%B4%D0%B5%D1%80%D0%B0_(%D0%BE%D1%82%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9)_%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BA_%D0%B4%D0%B2%D1%83%D1%85%D0%B4%D0%B8%D0%B0%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%84%D0%BE%D1%80%D0%BC%D0%B5&diff=21014Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме2016-12-16T07:55:02Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>{{algorithm<br />
| name = Приведение матрицы к двудиагональному виду методом Хаусхолдера (отражений)<br />
| serial_complexity = <math>\frac{8 n^3}{3}+O(n^2)</math><br />
| pf_height = <math>2n^2+O(n)</math><br />
| pf_width = <math>n^2</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n(n + 2)</math><br />
}}<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]]<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Метод Хаусхолдера''' (в советской математической литературе чаще называется '''методом отражений''') используется для приведения симметричных вещественных матриц к двухдиагональному виду, или, что то же самое, для разложения <math>A=QDU^T</math> (<math>Q, U</math> - ортогональные, <math>D</math> — правая двухдиагональная матрица)<ref>В.В.Воеводин, Ю.А.Кузнецов. Матрицы и вычисления. М.: Наука, 1984.</ref>. При этом матрицы <math>Q, U</math> хранятся и используются не в своём явном виде, а в виде произведения матриц отражения<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref>. Каждая из матриц отражения может быть определена одним вектором. Это позволяет в классическом исполнении метода отражений хранить результаты разложения на месте матрицы A с использованием двух одномерных дополнительных массивов. <br />
<br />
В данной статье рассматривается именно классическое исполнение, в котором не используются приёмы типа сдваивания при вычислениях скалярных произведений.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
В методе Хаусхолдера для выполнения разложения матрицы в произведение двухдиагональной и двух ортогональных используются попеременные умножения слева и справа её текущих модификаций на матрицы Хаусхолдера (отражений). <br />
<br />
{{Шаблон:Матрица отражений}}<br />
<br />
На <math>i</math>-м шаге метода с помощью преобразования отражения "убираются" ненулевые поддиагональные элементы в <math>i</math>-м столбце, а потом (кроме шага с номером <math>i=n-1</math>) с помощью преобразования отражения "убираются" ненулевые наддиагональные элементы в <math>i</math>-м столбце, кроме самого левого из них. Таким образом, после <math>n-1</math> шагов преобразований получается правая двудиагональная <math>D</math> из разложения матрицы в произведение двудиагональной и двух ортогональных.<br />
<br />
На каждом из шагов метода матрицы отражений обычно представляют не в стандартном виде, а в виде <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца так:<br />
<br />
<math>s</math> - вектор размерности <math>n+1-i</math>, составленный из элементов <math>i</math>-го столбца, начиная с <math>i</math>-го.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i</math>, <math>v_{j}=u_{j-i+1}</math> при <math>j>i</math>, а <math>v_{i}=1</math>, если <math>u_{1}=0</math> и <math>v_{i}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i}|</math>.<br />
<br />
После вычисления вектора <math>v</math> подстолбцы справа от ведущего модифицируются по формулам <math>x'=x-\frac{(x,v)}{\gamma}v</math>.<br />
<br />
Аналогично для правых матриц отражений <math>U=E-\frac{1}{\gamma}vv^*</math> <math>v</math> находится через координаты текущей <math>i</math>-й строки так:<br />
<br />
<math>s</math> - вектор размерности <math>n-i</math>, составленный из элементов <math>i</math>-й строки, начиная с <math>i+1</math>-го элемента.<br />
<br />
Если <math>(s,s)=0</math>, то <math>v=e_{i+1}</math>, <math>\gamma = \frac{1}{2}</math>.<br />
<br />
В остальных случаях по алгоритму вычисляется <math>u = \frac{1}{\sqrt{(s,s)}}s</math>, и далее <math>v_{j}=0</math> при <math>j<i+1</math>, <math>v_{j}=u_{j-i+2}</math> при <math>j>i+1</math>, а <math>v_{i+1}=1</math>, если <math>u_{1}=0</math> и <math>v_{i+1}=\frac{u_{1}}{|u_{1}|}(1+|u_{1}|)</math> для остальных значений. При этом <math>\gamma = 1+|u_{1}|=|v_{i+1}|</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основную часть алгоритма составляют вычисления на каждом шагу скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстолбцов <math>x</math> справа от текущего, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>, и затем вычисления на каждом шагу (кроме шага с номером <math>i=n-1</math>) скалярных произведений <math>(s,s)</math> и <math>(x,v)</math> для всех подстрок <math>x</math> снизу от текущей, а также векторные операции <math>x'=x-\frac{(x,v)}{\gamma}v</math>. Это используется при программировании метода во многих библиотеках для его конструирования из стандартных подпрограмм (например, из BLAS).<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Всё приведение состоит из <math>2n-3</math> полушагов. Каждый из них состоит из вычисления некоторой матрицы отражений (Хаусхолдера) и умножения на неё матрицы. Строгая последовательность выполнения этих частей полушагов не обязательна, в силу связи получаемых векторов <math>s</math> и <math>v</math> можно одновременно с <math>(s,s)</math> вычислять и произведения <math>(x,s)</math> с последующим выражением через них <math>(x,v)</math>. Это позволяет почти вдвое уменьшать критический путь графа алгоритма.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Последовательность выполнения алгоритма обычно записывается как последовательное "обнуление" поддиагональных элементов столбцов, начиная с 1-го столбца, заканчивая <math>(n-1)</math>-м, попеременно с последовательным "обнулением" наддиагональных (кроме первой кодиагонали) элементов строк, начиная с 1й строки и заканчивая <math>(n-2)</math>-й <br />
<br />
При этом в каждом "обнуляемом" <math>i</math>-м столбце "обнуляются" сразу все его поддиагональные элементы одновременно, с <math>(i+1)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-го столбца состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i-1}</math> такой, чтобы при умножении на неё слева "обнулились" все поддиагональные его элементы;<br />
б) умножение слева матрицы отражения <math>U_{2i-1}</math> на текущую версию матрицы.<br />
<br />
В каждой "обнуляемой" <math>i</math>-й строке "обнуляются" сразу все его наддиагональные элементы одновременно, с <math>(i+2)</math>-го до <math>n</math>-го. <br />
<br />
Каждое "обнуление" <math>i</math>-й строки состоит из двух шагов: <br />
а) вычисление параметров матрицы отражения <math>U_{2i}</math> такой, чтобы при умножении на неё справа "обнулились" все (кроме первого) наддиагональные её элементы;<br />
б) умножение справа матрицы отражения <math>U_{2i}</math> на текущую версию матрицы.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
В последовательной версии основная сложность алгоритма определяется прежде всего вычислениями скалярных произведений векторов, а также модификаций векторов вида <math>x'=x-\alpha v</math>, причем над векторами убывающей по ходу алгоритма размерности. Они, если не учитывать возможную разреженность, составляют (в главном члене) по <math>4n^3/3</math> операций действительного умножения и сложения/вычитания.<br />
<br />
При классификации по последовательной сложности, таким образом, метод Хаусхолдера относится к алгоритмам ''с кубической сложностью''.<br />
<br />
=== Информационный граф ===<br />
<br />
[[File:HausLeft1.png|thumb|right|600px|Рисунок 1. Граф нечётного полушага (обнуление <math>i</math>го столбца) алгоритма. Квадратики - входные данные шага (берутся с предыдущего полушага), кружки - операции. Зелёным выделены операции типа a+bb, салатовым и голубым - типа a+bc, тёмно-синим - вычисление <math>\gamma , v_{1}</math>, жёлтым - умножения (или деления). Обведённая группа операций повторяется независимо n-i раз. Результаты синего и жёлтых кружков - выходные для алгоритма.]]<br />
<br />
На рисунке 1 изображён граф нечётного (<math>(2i-1)</math>го) полушага в привязке к элементам обрабатываемой матрицы. Граф чётного (<math>2i</math>го) полушага почти аналогичен, только длина ветвей (нарисована слева) будет меньше на 1 (<math>n-i</math>), и для привязки к элементам матрицы его надо отразить относительно диагонали матрицы. После этого отражения графы полушагов можно положить друг на друга слоями, при этом правые нижние углы должны быть друг над другом.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Для понимания ресурса параллелизма в разложении матрицы порядка <math>n</math> методом Хаусхолдера нужно рассмотреть критический путь графа. <br />
<br />
Как видно из описания разных вершин, вычисления при "обнулении" <math>i</math>-го столбца параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций. Вычисления при "обнулении" <math>i</math>-й строки параметров отражения и скалярных произведений состоят из основной части - ветви длиной по <math>n-i-1</math> умножений и сложений - и коррекции вычислений, которые составляют <math>O(1)</math> операций.<br />
<br />
Поэтому по грубой (без членов низших порядков) оценке критический путь метода Хаусхолдера будет идти через <math>n^2</math> умножений и <math>n^2</math> сложений/вычитаний. <br />
<br />
Поэтому в параллельном варианте, как и в последовательном, основную долю требуемого для выполнения алгоритма времени будут определять операции вида <math>a+bc</math>. <br />
<br />
При классификации по высоте ЯПФ, таким образом, метод Хаусхолдера относится к алгоритмам ''с квадратичной сложностью''. При классификации по ширине ЯПФ его сложность будет также ''квадратичной'' (без расширения ряда ярусов, связанных с векторными операциями сложения, пришлось бы увеличить вдвое длину критического пути; при таком расширении сложность по ширине ЯПФ станет ''линейной'').<br />
<br />
Надо сказать, что здесь в оценках речь идёт именно о классическом способе реализации метода Хаусхолдера. Даже использование схем сдваивания или последовательно-параллельных для вычисления скалярных произведений уменьшает критический путь с квадратичного до ''степени 3/2'' или ''линейно-логарифмического''.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> (элементы <math>a_{ij}</math>).<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Объём выходных данных''': <math>n^2+2n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''линейным'', что даёт определённый стимул для распараллеливания. Однако у наискорейшей ЯПФ ширина ''квадратична'', что указывает на дисбаланс между загруженностями устройств при попытке её реально запрограммировать. Поэтому более практично даже при хорошей (быстрой) вычислительной сети оставить количество устройств (например, узлов кластера) ''линейным'' по размеру матрицы, что удвоит критический путь реализуемой ЯПФ. <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных, ''линейна''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Вычислительная погрешность в методе отражений (Хаусхолдера) растет ''линейно'', как и в методе Гивенса (вращений).<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В варианте с кратчайшим критическим путём графа алгоритма (с использованием зависимости между обнуляемым вектором и направляющим вектором отражения) метод Хаусхолдера (отражений) приведения квадратной вещественной матрицы к двудиагональной форме на Фортране 77 можно записать так:<br />
<br />
<source lang="fortran"><br />
DO I = 1, N-1<br />
DO K = I, N<br />
SX(K)=A(N,I)*A(N,K)<br />
END DO<br />
DO J = N-1, I, -1<br />
DO K = I, N<br />
SX(K)=SX(K)+A(J,I)*A(J,K)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SX(I))<br />
IF (A(I,I).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = A(I,I)*BETA+SIGN(1.,A(I,I)) <br />
A(I,I)=ALPHA<br />
G=1./ABS(SX(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+1, N<br />
A(J,I)=A(J,I)*BETA<br />
END DO<br />
SX(I) = -1. <br />
A(I,I)=ALPHA<br />
G=1.! 1/gamma<br />
DO K = I+1, N<br />
SX(K)=SX(K)*BETA*G+SIGN(A(I,K),SX(I))<br />
A(I,K) = A(I,K)+SX(K)*SX(I)<br />
DO J = I+1, N<br />
A (J,K) = A(J,K)-A(J,I)*SX(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SX(I)=1<br />
G=2<br />
DO K = I+1, N<br />
SX(K)=2<br />
A(I,K) = A(I,K)-SX(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
IF (I.LT.N-1) THEN<br />
<br />
DO K = I, N<br />
SL(K)=A(I,N)*A(K,N)<br />
END DO<br />
DO J = N-1, I+1, -1<br />
DO K = I, N<br />
SL(K)=SL(K)+A(I,J)*A(K,J)<br />
END DO<br />
END DO<br />
<br />
ALPHA = SQRT (SL(I))<br />
IF (A(I,I+1).NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = A(I,I+1)*BETA+SIGN(1.,A(I,I+1)) <br />
A(I,I+1)=ALPHA<br />
G=1./ABS(SL(I)) ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SX(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
IF (ALPHA.NE.0.) THEN<br />
BETA = 1./ALPHA<br />
DO J = I+2, N<br />
A(I,J)=A(I,J)*BETA<br />
END DO<br />
SL(I) = -1. <br />
A(I,I+1)=ALPHA<br />
G=1. ! 1/gamma<br />
DO K = I+1, N<br />
SL(K)=SL(K)*BETA*G+SIGN(A(K,I+1),SL(I))<br />
A(K,I+1) = A(K,I+1)+SL(K)*SL(I)<br />
DO J = I+2, N<br />
A (K,J) = A(K,J)-A(I,J)*SL(K)<br />
END DO<br />
END DO<br />
ELSE<br />
SL(I)=1<br />
G=2.<br />
DO K = I+1, N<br />
SL(K)=2.<br />
A(K,I+1) = A(K,I+1)-SL(K)<br />
END DO<br />
END IF<br />
END IF <br />
<br />
END IF<br />
END DO<br />
</source><br />
<br />
В этом варианте D расположена в диагонали и верхней кодиагонали массива A, направляющие вектора матриц отражений размещены в поддиагональных элементах соответствующих столбцов или надддиагональных - строк, а их первые элементы - в элементах массивов SX и SL.<br />
<br />
Обычно же в последовательных версиях коэффициенты модификаций столбцов вычисляются целиком через скалярные произведения после вычислений параметров матрицы отражения. При этом схема чуть проще. Удлиняется критический путь графа, но для последовательных реализаций это неважно.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
К сожалению, в графе, как видно по рисунку, в наличии пучки рассылок, в них неизбежно часть дуг остаются длинными, что отрицательно влияет на локальность вычислений по пространству. <br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:householder_qdu_1.png|thumb|center|700px|Рисунок 1. Метод Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации метода Хаусхолдера (отражений) приведения матрицы к двухдиагональной форме. Данный профиль обладает явной итерационной структурой, причем видно, что на каждой следующей итерации из рассмотрения отбрасывается небольшая часть элементов из начала массива данных. Также можно заметить, что основная часть данных используется достаточно равномерно в рамках одной итерации, однако в нижней и верхней частях итерации видны небольшие блоки с очень высокой локальностью обращений. Итерации явно устроены по одному принципу, поэтому стоит рассмотреть одну из них более детально.<br />
<br />
На рис. 2 показаны обращения в рамках первой итерации общего профиля (выделена на рис. 1 зеленым). Эти обращения можно условно разбить на несколько фрагментов. Фрагменты 1, 2 и 6 практически идентичны (различие заключается только в перестановке местами двух частей этих фрагментов), рассмотрим один из них.<br />
<br />
[[file:householder_qdu_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, одна итерация]]<br />
<br />
На рис. 3 показан отдельно фрагмент 6, выделенный на рис. 2. Теперь можно определить структуру каждой из двух частей более точно. Первая часть представляет собой последовательный перебор элементов в обратном порядке, причем к каждому элементу выполняется несколько обращений подряд. Во второй части также наблюдается последовательный перебор, однако с другими свойствами: элементы перебираются в прямом порядке, к каждому элементу выполняется только 1 обращение, однако данный перебор повторяется несколько раз подряд, причем каждый раз задействованы одни и те же элементы. Все это позволяет говорить о высокой пространственной локальности фрагмента (поскольку шаг по памяти каждый раз небольшой) и достаточно высокой временной локальности (поскольку данные часто используются повторно).<br />
<br />
[[file:householder_qdu_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, одна итерация, фрагмент 6]]<br />
<br />
Свойства локальности во фрагментах 3 и 5 можно оценить и без более детального рассмотрения. Данные в обоих фрагментах перебираются с достаточно большим шагом по памяти, при этом повторно они почти не используются, что говорит о низкой локальности в обоих случаях (хотя надо отметить, что локальность фрагмента 5 немного выше из-за более высокой пространственной локальности).<br />
<br />
Теперь перейдем к рассмотрению фрагмента 4, расположенного в центре рис. 2. Поскольку он обладает регулярной структурой, достаточно более детально изучить небольшой фрагмент (выделен на рис. 2 зеленым). Здесь можно увидеть, что первая часть фрагмента представляет собой практически стандартный последовательный перебор (с небольшими нечастыми сдвигами, которые не влияют на локальность). Такой набор обращений обладает высокой пространственной и низкой временной локальностью. Вторая часть данного фрагмента устроена несколько сложнее – она состоит из L-образных наборов обращений. Одна его область (часть 1 на рис. 3) по строению очень похожа на первую часть; вторая (область 2 на рис. 3) представляет собой набор обращений к одному и тому же элементу, что достаточно сильно повышает локальность данного фрагмента. Из этого можно сделать вывод, что фрагмент 4 обладает высокой пространственной и средней временной локальностью.<br />
<br />
[[file:householder_qdu_4.png|thumb|center|500px|Рисунок 4. Профиль обращений, одна итерация, часть фрагмент 4]]<br />
<br />
Суммируя выше сказанное, можно сказать, что фрагменты 1, 2 и 6 обладают высокой локальностью; фрагменты 3 и 5 – низкой локальностью; фрагмент 4 – высокой пространственной и средней временной локальностью. Можно предположить, что в итоге локальность общего профиля будет не очень высокой, в основном из-за более низкой временной локальности. Однако такое достаточно сложное строение общего профиля затрудняет оценку локальности общего профиля на основе визуального анализа. <br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что оценка производительности в данном случае не очень высока, что подтверждает сделанные ранее выводы. В частности, значение daps для этого примера показывает более низкую производительность по сравнению с реализациями других методов Хаусхолдера (householder_qr householder_qtq) и находится примерно на уровне оценки для реализации метода Холецкого.<br />
<br />
[[file:householder_qdu_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
==== Масштабируемость реализации алгоритма ====<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
<br />
В сравнении с методом Гивенса, который имеет естественное двумерное блочное разбиение на основе точечного метода, метод Хаусхолдера из-за худших характеристик локальности (наличие пучков рассылок) и меньшего количества независимых обобщённых развёрток графа не так хорош для реализаций на системах с распределённой памятью, как для систем с общей памятью. Поэтому на массово параллельных системах с распределённой памятью следует применять метод Хаусхолдера (если уж именно его нужно реализовать) не в точечной версии, а в разрабатываемых исследователями блочных вариантах. Следует отметить, что эти варианты - не блочная нарезка описанного метода, а самостоятельные методы. Особенно их применение рекомендуется в случаях с большой разрежённостью матрицы.<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Большинство пакетов от LINPACKа и LAPACKa до SCALAPACKa используют для приведения матриц к двудиагональному виду именно метод Хаусхолдера, правда, в различных модификациях (обычно с использованием BLAS). Существует большая подборка исследовательских работ по блочным версиям.<br />
<br />
== Литература ==<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]<br />
[[Категория:Разложения матриц]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9F%D0%BE%D0%B2%D1%82%D0%BE%D1%80%D0%BD%D0%B0%D1%8F_%D0%B2%D1%81%D1%82%D1%80%D0%B5%D1%87%D0%BD%D0%B0%D1%8F_%D0%BF%D1%80%D0%BE%D0%B3%D0%BE%D0%BD%D0%BA%D0%B0,_%D1%82%D0%BE%D1%87%D0%B5%D1%87%D0%BD%D1%8B%D0%B9_%D0%B2%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82&diff=21013Повторная встречная прогонка, точечный вариант2016-12-16T07:47:39Z<p>VadimVV: /* Локальность реализации алгоритма */</p>
<hr />
<div>{{algorithm<br />
| name = Встречная повторная прогонка для трёхдиагональной матрицы,<br /> точечный вариант<br />
| serial_complexity = <math>5n-4</math><br />
| pf_height = <math>2.5n-1</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>4n-2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Frolov|А.В.Фролов]].<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
'''Встречная повторная прогонка''' - один из вариантов метода исключения неизвестных в приложении к решению СЛАУ<ref name="VOLA">Воеводин В.В. Вычислительные основы линейной алгебры. М.: Наука, 1977.</ref><ref name="MIV">Воеводин В.В., Кузнецов Ю.А. Матрицы и вычисления. М.: Наука, 1984.</ref> вида <math>Ax = b</math>, где уже однажды СЛАУ с такой же матрицей была решена методом встречной прогонки<br />
<br />
{{Шаблон:Трёхдиагональная СЛАУ}}<br />
<br />
'''Встречная повторная прогонка''', как и [[Классическая монотонная прогонка, повторный вариант|повторная монотонная]], заключается в исключении из уравнений неизвестных, однако, в отличие от монотонной, в ней исключение ведут одновременно с обоих "краёв" СЛАУ (верхнего и нижнего). В принципе, её можно считать простейшим вариантом [[Метод редукции|метода редукции]] (при m=1 и встречных направлениях монотонных прогонок).<br />
<br />
=== Математическое описание алгоритма ===<br />
<math>m</math> здесь - номер уравнения, на котором "встречаются" две ветви прямого хода - "сверху" и "снизу". <br />
<br />
В приведенных обозначениях во встречной прогонке сначала выполняют её прямой ход - вычисляют коэффициенты<br />
<br />
"сверху":<br />
<br />
<math>\beta_{1} = f_{0}/c_{0}, </math><br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})/(c_{i}-a_{i}\alpha_{i}), \quad i = 1, 2, \cdots , m-1.<br />
<br />
</math><br />
<br />
и "снизу":<br />
<br />
<math>\eta_{N} = f_{N}/c_{N}</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})/(c_{i}-b_{i}\xi_{i+1})</math>, <math>\quad i = N-1, N-2, \cdots , m.</math><br />
<br />
(все отношения <math>1/c_{0}</math>, <math>1/c_{N}</math>, <math>1/(c_{i}-a_{i}\alpha_{i})</math>, <math>1/(c_{i}-b_{i}\xi_{i+1})</math> при этом вычислены при выполнении первой встречной прогонки) после чего вычисляют решение с помощью обратного хода<br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})/(1-\xi_{m}\alpha_{m})</math>, <br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}, \quad i = m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}, \quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
<br />
В приводимых обычно<ref name="SETKI" /> формулах встречной прогонки нет формулы для компоненты <math>y_{m-1}</math>, которая вычисляется позже в обратном ходе. Однако это удлиняет критический путь графа как в случае с чётным числом переменных, откладывая вычисление <math>y_{m-1}</math> на момент, когда уже будет вычислена <math>y_{m}</math>, хотя обе компоненты могут быть вычислены одновременно почти независимо друг от друга, так и в случае с нечётным числом переменных, когда для вычисления на "месте встречи" нужно подождать дополнительно одно вычисление коэффициентов либо "сверху" от него, либо "снизу".<br />
<br />
Поэтому в более поздних источниках<ref name="IK">Ильин В.П., Кузнецов Ю.И. Трехдиагональные матрицы и их приложения. М.: Наука. Главная редакция физико-математической литературы, 1985г. ,208 с.</ref> приводятся формулы, которые более оптимальны для нечётного количества неизвестных. В них старт обратного хода заменяется на формулу (в наших обозначениях)<br />
<br />
<math>y_{m} = (f_{m}+b_{m}\eta_{m+1}+a_{m}\beta_{m})/(c_{m}-a_{m}\alpha_{m}-b_{m}\xi_{m+1})</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительное ядро алгоритма можно, как и в случае [[Классическая монотонная прогонка, повторный вариант|повторной классической монотонной прогонки]], представить состоящим из двух частей - прямого и обратного хода; однако их ширина вдвое больше, чем в монотонном случае. В прямом ходе ядро составляют две независимые последовательности операций двух умножений и сложения/вычитания. В обратном ходе в ядре остаются только две независимые последовательности операций умножения и сложения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
В дополнение к возможности представления макроструктуры алгоритма как совокупности прямого и обратного хода, прямой ход также может быть разложен на две макроединицы - прямой ход правой и левой повторных прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ. Обратный ход также может быть разложен на две макроединицы - обратный ход правой и левой прогонок, выполняемых "одновременно", т.е., параллельно друг другу, для разных половин СЛАУ.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
[[file:VstrProgonkaInv.png|thumb|left|600px|Рисунок 1. Детальный граф алгоритма встречной прогонки с однократным вычислением обратных чисел при n=6 без отображения входных и выходных данных. '''inv''' - вычисление обратного числа, '''mult''' - операция перемножения чисел. Без обращений - ветви, повторяющиеся при замене правой части СЛАУ в '''повторной встречной прогонке'''.]]<br />
<br />
Последовательность исполнения метода следующая: <br />
<br />
1. Инициализируется прямой ход:<br />
<br />
<math>\beta_{1} = f_{0}(1/c_{0})</math>, <br />
<br />
<math>\eta_{1} = f_{N}(1/c_{N})</math>.<br />
<br />
2. Последовательно выполняются формулы прямого хода:<br />
<br />
<math>\beta_{i+1} = (f_{i}+a_{i}\beta_{i})(1/(c_{i}-a_{i}\alpha_{i}))</math>, <math>\quad i = 1, 2, \cdots , m-1</math>, <br />
<br />
<math>\eta_{i} = (f_{i}+b_{i}\eta_{i+1})(1/(c_{i}-b_{i}\xi_{i+1}))</math>, <math>\quad i = N-1, N-2, \cdots , m</math>.<br />
<br />
<br />
3. Инициализируется обратный ход:<br />
<br />
<math>y_{m-1} = (\beta_{m}+\alpha_{m}\eta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>, <br />
<br />
<math>y_{m} = (\eta_{m}+\xi_{m}\beta_{m})(1/(1-\xi_{m}\alpha_{m}))</math>.<br />
<br />
4. Последовательно выполняются формулы обратного хода:<br />
<br />
<math>y_{i} = \alpha_{i+1} y_{i+1} + \beta_{i+1}</math>, <math>\quad i = m-1, m-2, \cdots , 1, 0</math>, <br />
<br />
<math>y_{i+1} = \xi_{i+1} y_{i} + \eta_{i+1}</math>, <math>\quad i = m, m+1, \cdots , N-1</math>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения встречной прогонки в трёхдиагональной СЛАУ из n уравнений с n неизвестными в последовательном (наиболее быстром) варианте требуется:<br />
<br />
* <math>2n-2</math> сложений/вычитаний,<br />
* <math>3n-2</math> умножений.<br />
<br />
Таким образом, при классификации по последовательной сложности встречная прогонка относится к алгоритмам ''с линейной сложностью''.<br />
<br />
=== Информационный граф ===<br />
Информационный граф встречной прогонки представлен на рис.1. Как видно, он параллелен со степенью не более 2. Из рисунка видно, что не только математическая суть обработки элементов векторов, но даже структура графа алгоритма и направление потоков данных в нём вполне соответствуют названию "обратный ход". <br />
<br />
=== Описание ресурса параллелизма алгоритма ===<br />
<br />
Обе ветви прямого хода можно выполнять одновременно, если <math>N=2m-1</math>, т.е. <math>n=2m</math>. В этом случае встречная прогонка требует последовательного выполнения следующих ярусов:<br />
<br />
* по <math>3m-2</math> ярусов умножений и <math>2m-1</math> ярусов сложений/вычитаний (по две операции).<br />
<br />
Таким образом, при классификации по высоте ЯПФ встречная прогонка относится к алгоритмам с ''линейной'' сложностью. При классификации по ширине ЯПФ сложность этого алгоритма равна <math>2</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': преобразованная первой встречной прогонкой трёхдиагональная матрица <math>A</math> (элементы <math>a_{ij}</math>), вектор <math>b</math> (элементы <math>b_{i}</math>).<br />
<br />
'''Выходные данные''': вектор <math>x</math> (элементы <math>x_{i}</math>).<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности, как хорошо видно, является ''константой'' (2). <br />
<br />
При этом вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных – тоже ''константа''.<br />
<br />
Алгоритм в рамках выбранной версии полностью детерминирован.<br />
<br />
Обычно повторная встречная прогонка, как и повторная монотонная, используется для решения СЛАУ с диагональным преобладанием. Тогда гарантируется устойчивость алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
В зависимости от нужд вычислений, возможны как разные способы хранения матрицы СЛАУ (в виде одного массива с 3 строками или в виде 3 разных массивов), так и разные способы хранения вычисляемых коэффициентов (на месте уже использованных элементов матрицы либо отдельно).<br />
<br />
Приведем пример подпрограммы, реализующей встречную прогонку СЛАУ с нечётным числом уравнений, где все элементы матрицы хранятся в одном массиве, причём соседние элементы матричной строки размещаются рядом, а вычисляемые коэффициенты - на месте уже ненужных элементов исходной матрицы.<br />
<br />
<source lang="fortran"><br />
subroutine vprogmr (a,x,N) ! N=2m-1<br />
real a(3,N), x(N)<br />
<br />
m=(N+1)/2<br />
<br />
x(1)=x(1)*a(2,1) ! beta 2<br />
x(N)=x(N)*a(2,N) ! eta N<br />
<br />
do 10 i=2,m-1<br />
<br />
x(i)=(x(i)-a(1,i)*x(i-1))*a(2,i) ! beta i+1<br />
x(N+1-i)=(x(N+1-i)-a(3,N+1-i)*x(N+2-i))* a(2,N+1-i) ! eta N-i<br />
<br />
10 continue<br />
<br />
x(m) =(x(m)-a(1,m)*x(m-1)-a(3,m)*x(m+1))*a(2,m) ! y m<br />
<br />
do 20 i=m+1,N<br />
x(i)=a(1,i)*x(i-1)+x(i) ! y i up<br />
x(N+1-i)=a(3,N+1-i)*x(N+2-i)+x(N+1-i) ! y i down<br />
20 continue<br />
return<br />
end<br />
</source><br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Как видно по графу алгоритма, локальность данных по пространству хорошая - все аргументы, что нужны операциям, вычисляются "рядом". Однако по времени локальность вычислений не столь хороша. Если данные задачи не помещаются в кэш, то вычисления в "верхнем левом" и "нижнем правом" "углах" СЛАУ будут выполняться с постоянными промахами кэша. Отсюда может следовать одна из рекомендаций прикладникам, использующим прогонку, - нужно организовать все вычисления так, что бы прогонки были "достаточно коротки" для помещения данных в кэш.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:countersweep_repeated_1.png|thumb|center|700px|Рисунок 1. Встречная прогонка, повторный вариант. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации повторного варианта встречной прогонки. Данный профиль состоит из набора параллельно выполняемых последовательных переборов, как возрастающих, так и убывающих. В общем случае такой профиль характеризуется высокой пространственной локальностью, поскольку соседние обращения выполняются к близким по памяти данным, а также низкой временной локальностью, так как данные практически не используются повторно. Это подтверждает тот факт, что общее число обращений в память менее чем в два раза превышает число задействованных данных – достаточно плохой показатель производительности работы с памятью.<br />
<br />
Из рисунка можно предположить, что переборы, скорее всего, устроены достаточно регулярно и не меняют своего поведения в течение программы. Однако нужно детально разобрать, как устроены эти переборы; для этого рассмотрим некоторые наиболее интересные фрагменты профиля более детально. <br />
<br />
На рис. 2 приведен фрагмент 1 (выделен на рис. 1 зеленым). Видна регулярность обращений к памяти, которая нарушается только один раз, примерно по центру рисунка, где происходит смена этапов работы программы. На первом этапе параллельно выполняются два возрастающих и два убывающих последовательных перебора с небольшим шагом по памяти; на втором этапе число переборов возрастает в 1.5 раза, однако характер обращений остается примерно тем же. Подобный фрагмент, в отличие от обычного последовательного перебора, обладает более высокой временной локальностью, поскольку данные используются повторно по несколько раз, при этом большая часть повторных обращений расположена близко друг к другу.<br />
<br />
[[file:countersweep_repeated_2.png|thumb|center|500px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим фрагмент 2 (рис. 3). Здесь профиль устроен очень просто – параллельно выполняются возрастающий и убывающий переборы данных с небольшим шагом по памяти. Подобное поведение можно увидеть также во всех переборах, расположенных ниже фрагмента 2 на рис. 1.<br />
<br />
[[file:countersweep_repeated_3.png|thumb|center|500px|Рисунок 3. Профиль обращений, фрагмент 2]]<br />
<br />
В целом можно сказать, что данный профиль действительно состоит из небольшого числа возрастающих и убывающих последовательных переборов с небольшим шагом по памяти. Такое строение характеризуется высокой пространственной и достаточно низкой временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 4 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что реализация данного алгоритма показывает достаточно неплохую производительность работы с памятью. Результаты daps примерно совпадают с результатами для реализации метод Холецкого и обгоняют большинство других алгоритмов, построенных на основе переборов элементов массива, таких как вычисление скалярного произведения, реализация повторного варианта монотонной прогонки и т.д.<br />
<br />
[[file:countersweep_repeated_daps.png|thumb|center|700px|Рисунок 4. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Встречная повторная прогонка задумана изначально для случая, когда нужно найти только какую-то близкую к "середине" компоненту вектора решения, а остальные не нужны (решение т.н. "частичной задачи"). При появлении параллельных компьютерных устройств оказалось, что у встречной прогонки есть небольшой ресурс параллелизма и она убыстряет счёт, если её верхнюю и нижнюю ветви "раскидать" на 2 процессора. Однако для получения массового параллелизма встречная прогонка непригодна из-за низкой ширины своей [[Глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] (равной 2).<br />
<br />
С появлением суперскалярных процессоров оказалось, что для получения выигрыша (около 20%) перед монотонной повторной прогонкой встречную даже необязательно "раскидывать" на 2 процессора или даже на 2 ядра. Последнее показано сравнением времени исполнения монотонной повторной прогонки и встречной монотонной прогонки<ref>Фролов Н.А., Фролов А.В. Экспериментальные исследования влияния степени локальности алгоритмов на их быстродействие на примере решения трёхдиагональных СЛАУ // Труды 59й научной конференции МФТИ (21–26 ноября 2016 г., гг. Москва-Долгопрудный).</ref>, которая благодаря наличию двух ветвей вычислений даёт выигрыш по времени около 20% в сравнении с первой даже на персональных компьютерах (см. Рисунок 2). <br />
<br />
[[File:Repvstrprog.png|thumb|center|800px|Рисунок 2. Отношение времени исполнения повторной встречной и монотонных прогонок. По оси асбсцисс отложено <math>m</math> (порядок СЛАУ равен <math>2^m - 1</math>), по оси ординат - отношение времени, затраченного на исполнение встречной повторной прогонки, к времени, затраченному на выполнение монотонной повторной прогонки. Цветами выделены графики для разных систем (Pentium D + Windows XP, Core i5 + Windows 8, Core i7 + Windows 7), на всех их использована одна и та же сборка кода компилятором GNU Fortran.]]<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
О масштабируемости самой повторной встречной прогонки, как почти непараллельного алгоритма, говорить нельзя в принципе, за исключением разве что двухпроцессорных систем. Понятие масштабируемости неприменимо, поскольку описываемый алгоритм не предполагает параллельной реализации.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
В силу существенно последовательной природы алгоритма и его избыточной локальности, исследование его динамических характеристик представляется малоценным.<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Повторная встречная прогонка - метод для архитектуры классического, фон-неймановского типа. Для распараллеливания решения СЛАУ с трёхдиагональной матрицей следует взять какой-либо её параллельный заменитель, например, наиболее распространённую [[Метод циклической редукции|циклическую редукцию]] или уступающий ей по критическому пути графа, но имеющий более регулярную структуру графа новый [[Последовательно-параллельный вариант решения трёхдиагональной СЛАУ с LU-разложением и обратными подстановками|последовательно-параллельный метод]].<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
Алгоритм повторной встречной прогонки настолько прост, что, в тех случаях, когда он по каким-либо причинам понадобился, большинство использующих его исследователей-прикладников просто пишут соответствующий фрагмент программы самостоятельно. Поэтому встречную прогонку в пакеты программ обычно не включают.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Законченные статьи]]<br />
[[Категория:Алгоритмы с небольшим уровнем параллелизма]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0_dqds&diff=21012Итерация алгоритма dqds2016-12-16T07:39:25Z<p>VadimVV: /* Количественная оценка локальности */</p>
<hr />
<div><br />
{{algorithm<br />
| name = Алгоритм dqds нахождения<br /> сингулярных чисел двухдиагональной матрицы<br />
| serial_complexity = <math>5n-4</math> <br />
| pf_height = <math>4n-3</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>2n</math><br />
| output_data = <math>2n</math><br />
}}<br />
<br />
<br />
Основные авторы описания: [[Участник:Chernyavskiy|А.Ю.Чернявский]]<br />
<br />
== Свойства и структура алгоритмов ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Итерация алгоритма dqds''' является одним шагом алгоритма dqds нахождения сингулярных чисел двухдиагональной матрицы.<br />
<br />
Сам алгоритм '''dqds''' (''differential quotient-difference algorithm with shifts'')<ref name="vla">Деммель Д. Вычислительная линейная алгебра. – М : Мир, 2001.</ref><ref name="hola">Hogben L. (ed.). Handbook of linear algebra. – CRC Press, 2006.</ref> строит последовательность двухдиагональных матриц, сходящуюся к диагональной матрице, содержащей квадраты искомых сингулярных чисел. Его особенностью является высокая точность решения задачи. <br />
Вычислительным ядром алгоритма является именно внутренняя итерация, вне итераций происходит подбор сдвига <math>\delta</math> (параметер итерации, см. [[#Математическое описание алгоритма|математическое описание алгоритма]]), отслеживание сходимости, а также применение различных оптимизационных "хитростей". Отметим, что внеитерационная часть алгоритма не существенна с точки зрения структуры вычислений, т.к. основные затраты ложатся на вычисления внутри итерации. Подробности и варианты внеитерационной части, а также анализ сходимости можно найти в соответствующей литературе<br />
<ref>Fernando K. V., Parlett B. N. Accurate singular values and differential qd algorithms //Numerische Mathematik. – 1994. – Т. 67. – №. 2. – С. 191-229.</ref><br />
<ref>Parlett B. N., Marques O. A. An implementation of the dqds algorithm (positive case) //Linear Algebra and its Applications. – 2000. – Т. 309. – №. 1. – С. 217-259.</ref> <br />
<ref>Aishima K. et al. On convergence of the DQDS algorithm for singular value computation //SIAM Journal on Matrix Analysis and Applications. – 2008. – Т. 30. – №. 2. – С. 522-537.</ref>.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== Вспомогательные сведения ====<br />
Для понимания математических основ dqds-итерации полезно рассмотреть кратко её вывод, частично отражающий и историю возникновения алгоритма (подробности можно найти в <ref name="vla"/>).<br />
За основу dqds-алгоритма удобно взять так называемую LR-итерацию, предшествующую хорошо-известной QR-итерации. LR-алгоритм, начиная с входной матрицы <math>T_0>0,</math> строит сходящуюся последовательность подобных <math>T_0</math> матриц <math>T_i>0,</math> итерационно используя следующие три шага:<br />
#Выбрать сдвиг <math>\tau_i</math> меньший младшего собственного значения <math>T_i.</math><br />
#Вычислить разложение Холецкого <math>T_i-\tau^2_iI=B_i^TB_i,</math> где <math>B_i</math> - верхняя треугольная матрица с положительной диагональю.<br />
#<math>T_{i+1}=B_iB_i^T+\tau_i^2I.</math><br />
<br />
Отметим, что два шага LR-итерации с нулевым сдвигом эквивалентны одному шагу QR-итерации. Итерационная процедура приводит матрицу к диагональному виду, тем самым вычисляя собственные значения исходной матрицы. LR-алгоритм достаточно легко может быть переформулирован с заметными упрощениями для задачи поиска сингулярных значений двухдиагональных матриц. А именно, будем вычислять последовательность двухдиагональных матриц <math>B_i</math> без непосредственного вычисления <math>T_i</math>(которые в данном случае будут трехдиагональными). Пусть матрица <math>B_i</math> имеет диагональные элементы <math>a_1 \ldots a_n</math> и наддиагональные элементы <math>b_1 \ldots b_{n-1}</math>, а матрица <math>B_{i+1}</math> - диагональные элементы <math>\widehat{a}_1 \ldots \widehat{a}_n</math> и наддиагональные элементы <math>\widehat{b}_1 \ldots \widehat{b}_{n-1}.</math> Тогда шаг LR-итерации в терминах матриц <math>B_i</math> можно привести к простому циклу, пробегающему значения <math>j</math> от <math>1</math> до <math>n-1:</math><br />
<br />
:<math><br />
\widehat{a}^2_j = a^2_j+b^2_j-\widehat{b}^2_{j-1}-\delta<br />
</math><br />
:<math><br />
\widehat{b}^2_j = b^2_j\dot (a^2_{j+1}/a^2_j)<br />
</math><br />
и вычислению <math>\widehat{a}^2_n = a^2_n-\widehat{b}^2_{n-1}-\delta.</math> Очевидно, что работу с извлечением квадратов выгодно вести лишь после окончания работы алгоритма, поэтому можно ввести замену <math>q_j=a^2_j,\; e_j=b^2,</math> что в итоге приводит к так называемому алгоритму qds.<br />
Формулы алгоритма следующие:<br />
<br />
<br />
:<math><br />
\widehat{q}_j = q_j + e_j - \widehat{e}_{j-1} - \delta, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{e}_j = e_j \cdot q_{j+1} / \widehat{q}_j, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{q}_n = q_n - \widehat{e}_{n-1} - \delta. <br />
</math><br />
<br />
<br />
Здесь <math>q_j, \; j \in [1,n]</math> и <math>e_j, \; j \in [1,n-1]</math> - квадраты элемнтов главной и верхней побочной диагонали соответственно. Крышка означает выходные переменные, а <br />
<br />
<math>\delta</math> - сдвиг (параметр алгоритма).<br />
Такая математическая запись наиболее компактна и соответствует так называемой qds-итерации.<br />
<br />
==== Математическое описание итерации алгоритма dqds====<br />
Представим теперь математическую запись, приближенную к dqds-итерации (с математической точки зрения qds и dqds-итерации эквивалентны) с введенными вспомогательными переменными <br />
<br />
<math>t_j</math> и <math>d_j.</math> Итерация алгоритма dqds преобразует входную двухдиагональную матрицу <math>B</math> в выходную <math>\widehat{B}.</math> <br />
<br />
Входные и выходные данные: <math>q_{j}, \; j\in [1,n], \; e_{k}, \; k\in [1,n-1] </math> - квадраты элементов главной и побочной диагонали входной матрицы <math>B</math>, <math> \widehat{q}_j , \; \widehat{e}_k </math> - то же для вычисляемой матрицы <math>\widehat{B}.</math>. <br />
<br />
Формулы метода выглядят следующим образом:<br />
<br />
<br />
:<math><br />
d_1 = q_1 - \delta, \; q_n = d_n <br />
</math><br />
:для<math><br />
\quad j\in [1,n-1]: <br />
</math><br />
:<math><br />
\widehat{q}_j = d_j + e_j<br />
</math><br />
:<math><br />
t_j = q_{j+1}/\widehat{q}_j<br />
</math><br />
:<math><br />
\widehat{e}_j=e_j \cdot t_j <br />
</math><br />
:<math><br />
d_{j+1} = d \cdot t - \delta<br />
</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром алгоритма является последовательный расчёт квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы. Учитывая использование вспомогательных переменных расчёт каждой новой пары содержит по одной операции сложения, вычитания и деления, а также две операции умножения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Алгоритм состоит из отдельного вычисления начального значения вспомогательной переменной <math>d,</math> последующим (n-1)-кратным выполнением повторяющейся последовательности из 5 операций (+,/,*,*,-) для вычисления квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы и завершающего вычислением крайнего значения <math>\widehat{q}_n</math>.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Отметим, что выходные данные сразу могут быть записаны на место входных (это учтено в схеме), также для хранения вспомогательных переменных <math>t_j</math> и <math>d_j</math> достаточно двух перезаписываемых переменных. Таким образом элементы главной (<math>q_j</math>) и побочной (<math>e_k</math>) диагонали входной матрицы последовательно перезаписываются соответствующими элементами выходной матрицы.<br />
<br />
Последовательность исполнения метода следующая:<br />
<br />
1. Вычисляется начальное значение вспомогательной переменной <math>d = q_1-\delta.</math><br />
<br />
2. Производится цикл по j от 1 до n-1, состоящий из:<br />
<br />
:2.1 Вычисляется значение <math>q_j = d + e_j;</math><br />
:2.2 Вычисляется значение вспомогательной переменной <math>t = q_{j+1}/q_j;</math><br />
:2.3 Вычисляется значение <math>e_j = e_j \cdot t;</math><br />
:2.4 Вычисляется значение вспомогательной переменной <math>d = d \cdot t - \delta.</math><br />
:<br />
<br />
3. Вычисляется <math>q_n = d.</math><br />
<br />
<br />
Легко заметить, что можно представить вычисления в другой форме, например, в виде qds-итерации (см. [[Итерация алгоритма dqds#Математическое описание алгоритма|Математическое описание dqds-итерации]]), однако, именно dqds реализация вычисления позволяет достичь высокой точности<ref name="vla"></ref>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения одной итерации dqds необходимо выполнить:<br />
<br />
* <math>n-1</math> делений,<br />
* <math>2n-2</math> умножений,<br />
* <math>2n-1</math> сложений/вычитаний.<br />
<br />
Таким образом одна dqds-итерация имеет ''линейную сложность''.<br />
<br />
=== Информационный граф ===<br />
[[file:dqds.png|thumb|center|600px|Рисунок 1. Граф алгоритма для n=4 без отображения входных и выходных данных.]]<br />
<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Как видно из информационного графа алгоритма, на каждом шаге основного цикла возможно лишь параллельное выполнение операции умножения (2.2) и умножения+сложения (2.4). Это позволяет сократить число ярусов на одной итерации цикла c 5 до 4, а общее число ярусов алгоритма с 5n-4 до 4n-3. Ярусы с операциями умножения состоят из двух операций, остальные же из одной.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Квадраты элементов основной и верхней побочной диагонали двухдиагональной матрицы (вектора <math>q</math> длины n и <math>e</math> длины n-1), а также параметр сдвига <math>\delta</math>.<br />
<br />
'''Объём входных данных''': <math>2n</math>. <br />
<br />
'''Выходные данные''': Квадраты элементов основной и верхней побочной диагонали выходной двухдиагональной матрицы.<br />
<br />
'''Объём выходных данных''': <math>2n-1</math>. <br />
<br />
=== Свойства алгоритма===<br />
<br />
Соотношение последовательной и параллельной сложности при наличии возможности параллельного выполнения операций умножения составляет <math>\frac{5n-4}{4n-3}</math>, т.е. алгоритм плохо распараллеливается. <br />
<br />
Вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных - константа.<br />
<br />
Описываемый алгоритм является полностью детерминированным. <br />
<br />
<br />
== Программная реализация алгоритмов ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Алгоритм на языке Matlab может быть записать так:<br />
<source lang="matlab"><br />
<br />
d = q(1)-delta;<br />
for j = 1:n-1<br />
q(j)=d+e(j);<br />
t=q(j+1)/q(j);<br />
e(j) = e(j)*t;<br />
d = d*t-delta;<br />
end<br />
q(n) = d;<br />
<br />
</source><br />
<br />
Как говорилось в [[#Схема реализации последовательного алгоритма|cхеме реализации последовательного алгоритма]], вычисляемые данные записываются сразу на место входных.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Легко видеть, что локальность данных высока. Её легко повысить, размещая рядом соответствующие элементы массивов e и q, однако, это не оказывает существенного влияния на производительность в силу константного количества операций относительно объема обрабатываемых данных.<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:dqds_1.png|thumb|center|700px|Рисунок 1. Итерация алгоритма dqds. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации итерации алгоритма dqds. Данный профиль внешне устроен очень просто и состоит из двух параллельно выполняемых последовательных переборов. Отметим, что общее число обращений в память всего в 3 раза больше числа задействованных данных, что говорит о том, что данные повторно используют редко. Зачастую это сигнализирует о достаточно невысокой локальности.<br />
<br />
Рассмотрим более детально строение одного из переборов (второй устроен практически идентично). На рис. 2 показан выделенный на рис. 1 небольшой фрагмент 1. Видно, что на каждом шаге в данном переборе выполняется три обращения в память; подобное строение говорит о высокой пространственной локальности, однако низкой временной, поскольку данные повторно используются только по 3 раза. <br />
<br />
Поскольку данная структура обращений и составляет общий профиль, то же самое можно говорить и о локальности всего профиля.<br />
<br />
[[file:dqds_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
<br />
===== Количественная оценка локальности =====<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 3 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Результат получен достаточно неожиданный – производительность работы с памятью очень невелика. Оценка daps для данного профиля сравнима с реализациями самых неэффективных алгоритмов в части работы с памятью – тестов RandomAccess и неэффективных вариантов обычного перемножения матриц. Отчасти это может объясняться низкой временной локальностью. Однако в данном случае причина может также заключаться в том, что в данной реализации объем вычислений на одно обращение в память достаточно велик, что может приводить к недостаточной загруженности подсистемы памяти. В таком случае, несмотря на в целом неплохую эффективность работы с памятью, производительность будет достаточно низка.<br />
<br />
[[file:dqds_daps.png|thumb|center|700px|Рисунок 3. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Итерация dqds практически полностью последовательна. Единственная возможность - одновременное выполнение операции умножения (2.3) и операции (2.4) умножения и сложения, что дает небольшой выигрыш в производительности.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
Алгоритм не является масштабируемым, максимального эффекта ускорения можно добиться на двух независимых процессорах.<br />
<br />
Проведём исследование масштабируемости вширь реализации согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров 1;<br />
* размер матрицы [1000 : 260000] с шагом 500.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 2.559e-06%;<br />
* максимальная эффективность реализации 2.492e-08%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации DQDS в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:DQDS Perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:DQDS eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[http://git.algowiki-project.org/Teplov/Scalability/tree/master/DQDS/dqds.c Исследованная параллельная реализация на языке C]<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Эффективное выполнение алгоритма возможно только на вычислительных устройствах с одним или двумя ядрами. <br />
<br />
=== Существующие реализации алгоритма ===<br />
Сам алгоритм dqds реализован в функции xBDSQR пакета LAPACK и используется при её вызове без расчёта сингулярных векторов.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&diff=21011Алгоритм Борувки2016-12-16T07:39:08Z<p>VadimVV: /* Количественная оценка локальности */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Борувки'''<ref>Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.</ref><ref>Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.</ref> предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть задан связный неориентированный граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math>. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).<br />
<br />
Алгоритм Борувки основан на следующих двух свойствах задачи:<br />
* '''Минимальное ребро фрагмента'''. Пусть <math>F</math> – фрагмент минимального остовного дерева и <math>e_F</math> – ребро наименьшего веса, исходящее из <math>F</math> (т.е. ровно один его конец является вершиной из <math>F</math>). Если ребро <math>e_F</math> единственно, то оно принадлежит минимальному остовному дереву.<br />
* '''Схлопывание фрагментов'''. Пусть <math>F</math> – фрагмент минимального остовного дерева графа <math>G</math>, а граф <math>G'</math> получен из <math>G</math> склеиванием вершин, принадлежащих <math>F</math>. Тогда объединение <math>F</math> и минимального остовного дерева графа <math>G'</math> даёт минимальное остовное дерево исходного графа <math>G</math>.<br />
<br />
В начале работы алгоритма каждая вершина графа <math>G</math> является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основными операциями являются:<br />
# Поиск минимального по весу исходящего ребра в каждом фрагменте.<br />
# Объединение фрагментов.<br />
<br />
=== Макроструктура алгоритма ===<br />
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. <br />
<br />
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.<br />
<br />
Пусть задан связный граф <math>G=(V,E)</math> с вершинами <math>V = ( v_{1}, v_{2}, ..., v_{n} )</math> и рёбрами <math>E = ( e_{1}, e_{2}, ..., e_{m} )</math>. Каждому ребру <math>e \in E</math> приписан вес <math>w(e)</math>. <br />
<br />
Требуется построить дерево <math>T^* \subseteq E</math>, связывающее все вершины, и имеющее наименьший возможный вес среди всех таких деревьев:<br />
<br />
<math> w(T^* )= \min_T( w(T)) </math><br />
<br />
где вес множества рёбер есть сумма их весов:<br />
<br />
<math>w(T)=\sum_{e \in T} (w(T))</math><br />
<br />
Если граф <math>G</math> не является связным, то дерева, связывающего все вершины, не существует. <br />
<br />
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности <math>G</math>. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).<br />
<br />
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====<br />
<br />
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:<br />
<br />
1. <math>FIND(v) = w</math> – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда <math>FIND(u) = FIND(v)</math>.<br />
<br />
2. <math>MERGE(u, v)</math> – объединяет два фрагмента, которым принадлежат вершины <math>u</math> и <math>v.</math> (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.<br />
<br />
==== Последовательная версия====<br />
<br />
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя <math>parent(v)</math>.<br />
<br />
1. Изначально <math>parent(v) := v</math> для всех вершин.<br />
<br />
2. <math>FIND(v)</math> выполняется следующим образом: полагаем <math>u := v</math>, и далее следуем по указателям <math>u := parent(u)</math> до тех пор, пока не станет <math>u = parent(u)</math>. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: <math>parent(u_i) := u</math>, либо производить схлопывание по пути: <math>parent(u) := parent(parent(u)))</math>.<br />
<br />
3. <math>MERGE(u, v)</math> выполняется следующим образом: вначале находим корневые вершины <math>u := FIND(u), v := FIND(v)</math>. Если <math>u = v</math>, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из <math>parent(u) := v</math> или <math>parent(v) := u</math>. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.<br />
<br />
1. В самом начале каждая вершина является отдельным фрагментом.<br />
<br />
2. На каждом шаге:<br />
<br />
* Для каждого фрагмента определяется минимальное по весу исходящее ребро.<br />
<br />
* Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.<br />
<br />
3. Алгоритм останавливается, когда остаётся только один фрагмент, либо когда ни у одного из фрагментов нет исходящих рёбер.<br />
<br />
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).<br />
<br />
Объединение фрагментов также может быть реализовано параллельно, с использованием описанной выше параллельной версии структуры Union-Find.<br />
<br />
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:<br />
<br />
1. В начале итерации счётчик активных фрагментов обнуляется.<br />
<br />
2. На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.<br />
<br />
3. На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция <math>MERGE(u, v)</math> вернула значение истина.<br />
<br />
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.<br />
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Последовательная сложность алгоритма Борувки для графа с <math>n</math> вершинами и <math>m</math> рёбрами составляет <math>O(m \ln(n))</math> операций.<br />
<br />
=== Информационный граф ===<br />
<br />
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний), и параллелизм в алгоритме обработка графа, не помещающегося в память.<br />
<br />
'''Нижний уровень параллелизма''': поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно, с использованием описанной структуры Union-Find. <br />
<br />
'''Верхний уровень параллелизма''': построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. <br />
<br />
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]<br />
<br />
Рассмотрим информационные графы и подробное описание каждого из них. Так же можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.<br />
<br />
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующим операциям параллельного поиска минимальных исходящих ребер, а так же уровнях {6, 7, 8}, соответствующим операциям параллельного объединения деревьев. Так же, различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла, производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе аналогичная следующая итерация. <br />
<br />
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального основного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные compute mst. Затем, после параллельных вычислений mst, происходит вычисление итого основного дерево, после чего после чего полученный результат сохраняется (save results).<br />
<br />
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Алгоритму Борувки обладает большим потенциалом параллелизма, так как его основная операция (выбор минимального исходящего ребра во фрагменте) может исполнятся независимо для каждого фрагмента.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j},<br />
v^{(2)}_{j})</math> с весами <math>f_j</math>).<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''': список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма ===<br />
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.<br />
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более <math>\log_2 n</math>. Отсюда следует и оценка сложности алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
На этапе поиска минимального ребра происходят следующие обращения к памяти:<br />
# Чтение информации о рёбрах. Может производится последовательно.<br />
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива <math>parent(u)</math> с вероятным промахой по кэшу.<br />
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.<br />
<br />
На этапе схлопывания фрагментов требуется атомарно обновить массив <math>parent(u)</math> для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:boruvka_1.png|thumb|center|700px|Рисунок 1. Алгоритм Борувки. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.<br />
<br />
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 1 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.<br />
<br />
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 1), которая показана на рис. 2. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 2 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.<br />
<br />
[[file:boruvka_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
Далее перейдем к изучению фрагмента 2 (рис. 3). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.<br />
<br />
[[file:boruvka_3.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 2]]<br />
<br />
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 4 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.<br />
<br />
[[file:boruvka_4.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 3]]<br />
<br />
Отдельное рассмотрение фрагмента 4 (рис. 5) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:boruvka_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 4]]<br />
<br />
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.<br />
<br />
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм Борувки, состоит из двух частей: <br />
<br />
1. части, отвечающей за общую координацию вычислений<br />
<br />
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.<br />
<br />
<br />
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации <math>MERGE</math> результаты операций <math>FIND(u)</math> и <math>FIND(v)</math> могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.<br />
<br />
1. Каждой вершине v соответствует запись <math>A[v] = { parent, rank }</math>. Изначально <math>A[v] := { v, 0 }</math>.<br />
<br />
2. Вспомогательная операция <math>UPDATE(v, rank_v, u, rank_u)</math>:<br />
<br />
old := A[v]<br />
<br />
if old.parent != v or old.rank != rank_v then return false<br />
<br />
new := { u, rank_u }<br />
<br />
return CAS(A[v], old, new)<br />
<br />
3. Операция <math>FIND(v)</math>:<br />
<br />
while v != A[v].parent do<br />
u := A[v].parent<br />
CAS(A[v].parent, u, A[u].parent)<br />
v := A[u].parent<br />
return v<br />
<br />
4. Операция UNION(u, v):<br />
<br />
while true do<br />
(u, v) := (FIND(u), FIND(v))<br />
if u = v then return false<br />
(rank_u, rank_v) := (A[u].rank, A[v].rank)<br />
if (A[u].rank, u) > (A[v].rank, v) then<br />
swap((u, rank_u), (v, rank_v))<br />
if UPDATE(u, rank_u, v, rank_u) then<br />
if rank_u = rank_v then<br />
UPDATE(v, rank_v, v, rank_v + 1)<br />
return true<br />
<br />
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются<br />
# пропускная способность памяти при чтении данных графа<br />
# соперничество потоков при выполнении атомарных операций с памятью<br />
# барьерная синхронизация после каждого подшага алгоритма.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера<br />
<br />
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]<br />
<br />
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]</code> сочетают алгоритм Борувки и [[алгоритм Крускала]].<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%B5%D0%BB%D0%BB%D0%BC%D0%B0%D0%BD%D0%B0-%D0%A4%D0%BE%D1%80%D0%B4%D0%B0&diff=21010Алгоритм Беллмана-Форда2016-12-16T07:36:42Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Беллмана-Форда'''<ref>Bellman, Richard. “On a Routing Problem.” Quarterly of Applied Mathematics 16 (1958): 87–90.</ref><ref>Ford, L R. Network Flow Theory. Rand.org, RAND Corporation, 1958.</ref><ref>Moore, Edward F. “The Shortest Path Through a Maze,” International Symposium on the Theory of Switching, 285–92, 1959.</ref> предназначен для решения [[Поиск кратчайшего пути от одной вершины (SSSP)|задачи поиска кратчайшего пути на графе]]. Для заданного ориентированного взвешенного графа алгоритм находит кратчайшие расстояния от выделенной вершины-источника до всех остальных вершин графа. Алгоритм Беллмана-Форда масштабируется хуже других алгоритмов решения указанной задачи (сложность <math>O(mn)</math> против <math>O(m + n\ln n)</math> у [[Алгоритм Дейкстры|алгоритма Дейкстры]]), однако его отличительной особенностью является применимость к графам с произвольными, в том числе отрицательными, весами.<br />
<br />
=== Математическое описание алгоритма ===<br />
Пусть задан граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math> и выделенной вершиной-источником <math>u</math>. Обозначим через <math>d(v)</math> кратчайшее расстояние от источника <math>u</math> до вершины <math>v</math>.<br />
<br />
Алгоритм Беллмана-Форда ищет функцию <math>d(v)</math> как единственное решение уравнения<br />
:<math><br />
d(v) = \min \{ d(w) + f(e) \mid e = (w, v) \in E \}, \quad \forall v \ne u,<br />
</math><br />
с начальным условием <math>d(u) = 0</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основной операцией алгоритма является релаксация ребра: если <math>e = (w, v) \in E</math> и <math>d(v) > d(w) + f(e)</math>, то производится присваивание <math>d(v) \leftarrow d(w) + f(e)</math>.<br />
<br />
=== Макроструктура алгоритма ===<br />
Алгоритм последовательно уточняет значения функции <math>d(v)</math>.<br />
* В самом начале производится присваивание <math>d(u) = 0</math>, <math>d(v) = \infty</math>, <math>\forall v \ne u</math>.<br />
* Далее происходит <math>n-1</math> итерация, в ходе каждой из которых производится релаксация всех рёбер графа.<br />
<br />
Структуру можно описать следующим образом:<br />
<br />
1. Инициализация: всем вершинам присваивается предполагаемое расстояние <math>t(v)=\infty</math>, кроме вершины-источника, для которой <math>t(u)=0</math> .<br />
<br />
2. Релаксация множества рёбер <math>E</math><br />
<br />
а) Для каждого ребра <math>e=(v,z) \in E</math> вычисляется новое предполагаемое расстояние <math>t^' (z)=t(v)+ w(e)</math>.<br />
<br />
б) Если <math>t^' (z)< t(z)</math>, то происходит присваивание <math>t(z) := t' (z)</math> (релаксация ребра <math>e</math> ).<br />
<br />
3. Алгоритм производит релаксацию всех рёбер графа до тех пор, пока на очередной итерации происходит релаксация хотя бы одного ребра.<br />
<br />
Если на <math>n</math>-й итерации всё ещё производилась релаксацию рёбер, то в графе присутствует цикл отрицательной длины. Ребро <math>e=(v,z)</math>, лежащее на таком цикле, может быть найдено проверкой следующего условия (проверяется для всех рёбер за линейное время): <math>t(v)+w(e)<d(z)</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Последовательный алгоритм реализуется следующим псевдокодом:<br />
<br />
'''Входные данные''':<br />
граф с вершинами ''V'', рёбрами ''E'' с весами ''f''(''e'');<br />
вершина-источник ''u''.<br />
'''Выходные данные''': расстояния ''d''(''v'') до каждой вершины ''v'' ∈ ''V'' от вершины ''u''.<br />
<br />
'''for each''' ''v'' ∈ ''V'' '''do''' ''d''(''v'') := ∞<br />
''d''(''u'') = 0<br />
<br />
'''for''' ''i'' '''from''' 1 '''to''' |''V''| - 1:<br />
'''for each''' ''e'' = (''w'', ''v'') ∈ ''E'':<br />
'''if''' ''d''(''v'') > ''d''(''w'') + ''f''(''e''):<br />
''d''(''v'') := ''d''(''w'') + ''f''(''e'')<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Алгоритм выполняет <math>n-1</math> итерацию, на каждой из которых происходит релаксация <math>m</math> рёбер. Таким образом, общий объём работы составляет <math>O(mn)</math> операций.<br />
<br />
Константа в оценке сложности может быть уменьшена за счёт использования следующих двух стандартных приёмов.<br />
<br />
# Если на очередной итерации не произошло ни одной успешной релаксации, то алгоритм завершает работу.<br />
# На очередной итерации рассматриваются не все рёбра, а только выходящие из вершин, для которых на прошлой итерации была выполнена успешная релаксация (на первой итерации – только рёбра, выходящие из источника).<br />
<br />
=== Информационный граф ===<br />
На рисунке 1 представлен информационный граф алгоритма, демонстрирующий описанные уровни параллелизма. На приведенном далее информационном графе нижний уровень параллелизма обозначен в горизонтальных плоскостях. Множество всех плоскостей представляет собой верхний уровень параллелизма (операции в каждой плоскости могут выполняться параллельно).<br />
<br />
Нижний уровень параллелизма на графе алгоритма расположен на уровнях {2 и 3}, соответствующим операциям инициализации массива дистанций (2) и обновления массива c использованием данных массива ребер {3}. Операция {4} - проверка того, были ли изменения на последней итерации и выход из цикла, если таковых не было.<br />
<br />
[[file:APSP.png|thumb|center|700px|Рисунок 1. Информационный граф обобщенного алгоритма Беллмана-Форда.]]<br />
<br />
Верхний уровень параллелизма, как уже говорилось, заключается в параллельном подсчете дистанций для различных вершин-источников, и на рисунке отмечен разными плоскостями.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
При использовании атомарных операций для вычисления минимума релаксация рёбер может производится параллельно. В этом случае потребуется <math>O(n)</math> шагов при использовании <math>O(m)</math> процессоров.<br />
<br />
[[Алгоритм Δ-шагания]] может рассматриваться как параллельная версия алгоритма Беллмана-Форда.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j}, v^{(2)}_{j})</math> с весами <math>f_j</math>), вершина-источник <math>u</math>.<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''' (возможные варианты):<br />
# для каждой вершины <math>v</math> исходного графа – последнее ребро <math>e^*_v = (w, v)</math>, лежащее на кратчайшем пути от вершины <math>u</math> к <math>v</math>, или соответствующая вершина <math>w</math>;<br />
# для каждой вершины <math>v</math> исходного графа – суммарный вес <math>f^*(v)</math> кратчайшего пути от от вершины <math>u</math> к <math>v</math>.<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма===<br />
<br />
Алгоритм может распознавать наличие отрицательных циклов в графе. Ребро <math>e = (v, w)</math> лежит на таком цикле, если вычисленные алгоритмом кратчайшие расстояния <math>d(v)</math> удовлетворяют условию<br />
:<math><br />
d(v) + f(e) < d(w),<br />
</math><br />
где <math>f(e)</math> – вес ребра <math>e</math>. Условие может быть проверено для всех рёбер графа за время <math>O(m)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:bellman_ford_1.png|thumb|center|700px|Рисунок 1. Алгоритм Беллмана-Форда. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации алгоритма Беллмана-Форда. Первое, что сразу стоит отметить – число обращений в память гораздо больше числа задействованных данных. Это говорит о частом повторном использовании одних и тех же данных, что обычно приводит к высокой временной локальности. Далее, видна явная регулярная структура производимых обращений в память, видны повторяющиеся итерации работы алгоритма. Практически все обращения образуют фрагменты, похожие на последовательный перебор, кроме самой верхней части, где наблюдается более сложная структура.<br />
<br />
Рассмотрим более детально отдельные фрагменты общего профиля, чтобы лучше разобраться в его структуре. На рис. 2 представлен фрагмент 1 (выделен на рис. 1), на котором показаны первые 500 обращений в память (отметим, что другой наклон двух последовательных переборов связан с измененным отношением сторон в рассматриваемой области). Данный рисунок показывает, что выделенные желтым части 1 и 2 являются практические идентичными последовательными переборами; отличие между ними только в том, что в части 1 обращения выполняются в два раза чаще, поэтому на рис. 2 эта часть представлена большим числом точек. Как мы знаем, подобные профили характеризуются высокой пространственной и низкой временной локальностью.<br />
<br />
[[file:bellman_ford_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
Далее рассмотрим более интересный фрагмент 2, отмеченный на рис. 1. Здесь можно снова увидеть подтверждение регулярности обращений в нижней области профиля, однако верхняя область явно устроена гораздо сложнее; хотя и здесь просматривается регулярность. В частности, также видны те же самые итерации, в которых здесь можно выделить большие последовательности обращений к одним и тем же данным. Пример такого поведения, оптимального с точки зрения локальности, выделен на рисунке желтым. <br />
<br />
[[file:bellman_ford_3.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 2]]<br />
<br />
Чтобы понять структуру обращений в память в верхней части, можно рассмотреть ее еще подробнее. Приведем визуализацию небольшой области фрагмента 1, выделенную на рис. 3 зеленым. Однако в данном случае дальнейшее приближение не привносит большей ясности: видна нерегулярная структура внутри итерации, характер которой достаточно сложно описать. Но в данном случае этого и не требуется – можно заметить, что по вертикали отложено всего 15 элементов, при этом обращений к ним выполняется гораздо больше. Независимо от структуры обращений, такой профиль обладает очень высокой как пространственной, так и временной локальностью.<br />
<br />
[[file:bellman_ford_4.png|thumb|center|700px|Рисунок 4. Небольшая часть фрагмента 1]]<br />
<br />
А так как основная масса обращений приходится именно на фрагмент 2, можно утверждать, что и весь общий профиль обладает высокой пространственной и временной локальностью.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 5 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что по производительности работы с памятью данная реализация алгоритма показывает очень хорошие результаты. В частности, значение daps сравнимо с оценкой для теста Linpack, который известен высокой эффективностью взаимодействия с подсистемой памяти. <br />
<br />
<br />
[[file:bellman_ford_daps.png|thumb|center|700px|Рисунок 5. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм поиска кратчайших путей, состоит из двух частей: части, отвечающей за общую координацию вычислений, а так же параллельные вычисления на многоядерных CPU, и GPU части, отвечающей только за вычисления на графическом ускорителе.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Алгоритм обладает значительным потенциалом масштабируемости, так как каждое ребро обрабатывается независимо и можно поручить каждому вычислительному процессу свою часть рёбер графа. Узким местом является доступ к разделяемому всеми процессами массиву расстояний. Алгоритм позволяет ослабить требования к синхронизации данных этого массива между процессами (когда один процесс может не сразу увидеть новое значение расстояния, записанное другим процессом), за счёт, может быть, большего количества глобальных итераций.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Беллмана-Форда согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости вширь реализации алгоритма Беллмана-Форда.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера.<br />
<br />
[[file:APSP scaling wide.png|thumb|center|700px|Рисунок 2. Параллельная реализация алгоритма Беллмана-Форда масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++: [http://www.boost.org/libs/graph/doc/ Boost Graph Library] (функция <code>[http://www.boost.org/libs/graph/doc/bellman_ford_shortest.html bellman_ford_shortest]</code>).<br />
* Python: [https://networkx.github.io NetworkX] (функция <code>[http://networkx.github.io/documentation/networkx-1.9.1/reference/generated/networkx.algorithms.shortest_paths.weighted.bellman_ford.html bellman_ford]</code>).<br />
* Java: [http://jgrapht.org JGraphT] (класс <code>[http://jgrapht.org/javadoc/org/jgrapht/alg/BellmanFordShortestPath.html BellmanFordShortestPath]</code>).<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%91%D0%BE%D1%80%D1%83%D0%B2%D0%BA%D0%B8&diff=21009Алгоритм Борувки2016-12-16T07:29:37Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div>== Свойства и структура алгоритма ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Алгоритм Борувки'''<ref>Borůvka, Otakar. “O Jistém Problému Minimálním.” Práce Moravské Přírodovědecké Společnosti III, no. 3 (1926): 37–58.</ref><ref>Jarník, Vojtěch. “O Jistém Problému Minimálním (Z Dopisu Panu O. Borůvkovi).” Práce Moravské Přírodovědecké Společnosti 6, no. 4 (1930): 57–63.</ref> предназначен для решения [[Построение минимального остовного дерева (MST)|задачи о построении минимального остовного дерева]] во взвешенном неориентированном графе. Алгоритм хорошо параллелизуется и является основой для распределённого [[Алгоритм GHS|алгоритма GHS]].<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
Пусть задан связный неориентированный граф <math>G = (V, E)</math> с весами рёбер <math>f(e)</math>. Предполагается, что веса всех рёбер различны (если это не так, то можно упорядочить рёбра сначала по весу, а потом по номеру).<br />
<br />
Алгоритм Борувки основан на следующих двух свойствах задачи:<br />
* '''Минимальное ребро фрагмента'''. Пусть <math>F</math> – фрагмент минимального остовного дерева и <math>e_F</math> – ребро наименьшего веса, исходящее из <math>F</math> (т.е. ровно один его конец является вершиной из <math>F</math>). Если ребро <math>e_F</math> единственно, то оно принадлежит минимальному остовному дереву.<br />
* '''Схлопывание фрагментов'''. Пусть <math>F</math> – фрагмент минимального остовного дерева графа <math>G</math>, а граф <math>G'</math> получен из <math>G</math> склеиванием вершин, принадлежащих <math>F</math>. Тогда объединение <math>F</math> и минимального остовного дерева графа <math>G'</math> даёт минимальное остовное дерево исходного графа <math>G</math>.<br />
<br />
В начале работы алгоритма каждая вершина графа <math>G</math> является отдельным фрагментом. На очередном шаге у каждого фрагмента выбирается исходящее ребро минимального веса (если такое ребро существует). Выбранные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты склеиваются.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Основными операциями являются:<br />
# Поиск минимального по весу исходящего ребра в каждом фрагменте.<br />
# Объединение фрагментов.<br />
<br />
=== Макроструктура алгоритма ===<br />
В задаче требуется указать в данном связном взвешенном графе дерево, соединяющее все его вершины и имеющее наименьший возможный суммарный вес рёбер. <br />
<br />
Классический пример (из статьи Борувки) – спроектировать наиболее дешёвую электрическую сеть, зная стоимость устройства каждого участка электрической линии.<br />
<br />
Пусть задан связный граф <math>G=(V,E)</math> с вершинами <math>V = ( v_{1}, v_{2}, ..., v_{n} )</math> и рёбрами <math>E = ( e_{1}, e_{2}, ..., e_{m} )</math>. Каждому ребру <math>e \in E</math> приписан вес <math>w(e)</math>. <br />
<br />
Требуется построить дерево <math>T^* \subseteq E</math>, связывающее все вершины, и имеющее наименьший возможный вес среди всех таких деревьев:<br />
<br />
<math> w(T^* )= \min_T( w(T)) </math><br />
<br />
где вес множества рёбер есть сумма их весов:<br />
<br />
<math>w(T)=\sum_{e \in T} (w(T))</math><br />
<br />
Если граф <math>G</math> не является связным, то дерева, связывающего все вершины, не существует. <br />
<br />
В этом случае необходимо найти минимальной остовное дерево для каждой компоненты связности <math>G</math>. Набор таких деревьев называется минимальным остовным лесом (сокращённо MSF – Minimum Spanning Forest).<br />
<br />
==== Вспомогательный алгоритм: система непересекающихся множеств (Union-Find)====<br />
<br />
Во всех алгоритмах решения задачи требуется отслеживать, каким уже построенным фрагментам дерева принадлежат те или иные вершины графа. Для этого используется структура данных «система непересекающихся множеств» (Union-Find). Данная структура поддерживает две операции:<br />
<br />
1. <math>FIND(v) = w</math> – по вершине v возвращает вершину w – «корень» фрагмента, которому принадлежит вершина v. При этом гарантируется, что вершины u и v принадлежат одному и тому же фрагменту, тогда и только тогда, когда <math>FIND(u) = FIND(v)</math>.<br />
<br />
2. <math>MERGE(u, v)</math> – объединяет два фрагмента, которым принадлежат вершины <math>u</math> и <math>v.</math> (Если они уже лежат в одном фрагменте, то ничего не происходит.) При практической реализации удобно, чтобы данная операция возвращала значение истина, если объединение фрагментов имело место, и ложь в противном случае.<br />
<br />
==== Последовательная версия====<br />
<br />
Классический последовательный алгоритм Union-Find описан в статье Тарьяна. Каждой вершине v приписывается указатель на вершину-родителя <math>parent(v)</math>.<br />
<br />
1. Изначально <math>parent(v) := v</math> для всех вершин.<br />
<br />
2. <math>FIND(v)</math> выполняется следующим образом: полагаем <math>u := v</math>, и далее следуем по указателям <math>u := parent(u)</math> до тех пор, пока не станет <math>u = parent(u)</math>. Это и будет результат операции. Дополнительно можно «схлопывать» дерево: присвоить всем посещённым вершинами: <math>parent(u_i) := u</math>, либо производить схлопывание по пути: <math>parent(u) := parent(parent(u)))</math>.<br />
<br />
3. <math>MERGE(u, v)</math> выполняется следующим образом: вначале находим корневые вершины <math>u := FIND(u), v := FIND(v)</math>. Если <math>u = v</math>, то исходные вершины принадлежат одному фрагменту и объединения фрагментов не происходит. В противном случае полагаем одно из <math>parent(u) := v</math> или <math>parent(v) := u</math>. Дополнительно можно отслеживать количество вершин в каждом из фрагментов, чтобы меньший фрагмент подсоединять к большему, а не наоборот (оценки сложности доказываются именно при такой реализации, однако на практике алгоритм хорошо работает и без подсчёта количества вершин).<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
В алгоритме Борувки фрагменты минимального остовного дерева наращиваются постепенно присоединением минимального ребра, выходящего из каждого фрагмента.<br />
<br />
1. В самом начале каждая вершина является отдельным фрагментом.<br />
<br />
2. На каждом шаге:<br />
<br />
* Для каждого фрагмента определяется минимальное по весу исходящее ребро.<br />
<br />
* Минимальные рёбра добавляются в минимальное остовное дерево, а соответствующие фрагменты объединяются.<br />
<br />
3. Алгоритм останавливается, когда остаётся только один фрагмент, либо когда ни у одного из фрагментов нет исходящих рёбер.<br />
<br />
Поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента. Таким образом, данную стадию вычислений можно эффективно параллелизовать (в том числе с использованием массового параллелизма графических ускорителей).<br />
<br />
Объединение фрагментов также может быть реализовано параллельно, с использованием описанной выше параллельной версии структуры Union-Find.<br />
<br />
Аккуратный подсчёт количества активных фрагментов позволяет остановить алгоритм Борувки на один шаг раньше обычного:<br />
<br />
1. В начале итерации счётчик активных фрагментов обнуляется.<br />
<br />
2. На этапе поиска минимальных рёбер счётчик увеличивается на единицу для каждого фрагмента, у которого были исходящие рёбра.<br />
<br />
3. На этапе объединения фрагментов счётчик уменьшается на единицу каждый раз, когда операция <math>MERGE(u, v)</math> вернула значение истина.<br />
<br />
Если в конце итерации счётчик равен 0 или 1, то вычисления останавливаются.<br />
Параллелизм возможен на этапе сортировки рёбер по весу, однако основной ход алгоритма является последовательным.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Последовательная сложность алгоритма Борувки для графа с <math>n</math> вершинами и <math>m</math> рёбрами составляет <math>O(m \ln(n))</math> операций.<br />
<br />
=== Информационный граф ===<br />
<br />
В описанном подходе существует два уровня параллелизма: параллелизм в классическом алгоритме Борувки (нижний), и параллелизм в алгоритме обработка графа, не помещающегося в память.<br />
<br />
'''Нижний уровень параллелизма''': поиск минимальных исходящих рёбер может выполняться независимо для каждого фрагмента, благодаря чему данную стадию вычислений можно эффективно параллелизовать (как на GPU, так и на CPU). Объединение фрагментов также может быть реализовано параллельно, с использованием описанной структуры Union-Find. <br />
<br />
'''Верхний уровень параллелизма''': построение отдельных минимальных основных деревьев для каждого из списков ребер может производиться параллельно. Например, список ребер может разбиваться на две части, одна из которых обрабатывается на GPU, а вторая параллельно на CPU. <br />
<br />
[[file:MST low.png|thumb|center|1000px|Рисунок 1. Информационный граф нижнего уровня параллелизма]]<br />
<br />
Рассмотрим информационные графы и подробное описание каждого из них. Так же можно считать, что на рисунке 1 представлен информационный граф классического алгоритма Борувки, а на рисунке 2 — алгоритма обработки графа.<br />
<br />
Нижний уровень параллелизма на графе алгоритма (рисунок 1) расположен на уровнях {3, 4, 5}, соответствующим операциям параллельного поиска минимальных исходящих ребер, а так же уровнях {6, 7, 8}, соответствующим операциям параллельного объединения деревьев. Так же, различные операции копирования {1, 2, 8, 9} выполняются параллельно. После выполнения тела цикла, производится проверка {12} того, сколько деревьев осталось на текущем шаге, и если данное число не изменилось, то происходит выход из цикла, иначе аналогичная следующая итерация. <br />
<br />
Верхний уровень параллелизма (рисунок 2), как уже говорилось, заключается в параллельном вычислении минимального основного дерева (compute mst) для различных частей графа. Перед этим производится процесс инициализации (init process), данные которого используют последующие параллельные compute mst. Затем, после параллельных вычислений mst, происходит вычисление итого основного дерево, после чего после чего полученный результат сохраняется (save results).<br />
<br />
[[file:MST up.png|thumb|center|1000px|Рисунок 2. Информационный граф верхнего уровня параллелизма]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Алгоритму Борувки обладает большим потенциалом параллелизма, так как его основная операция (выбор минимального исходящего ребра во фрагменте) может исполнятся независимо для каждого фрагмента.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
'''Входные данные''': взвешенный граф <math>(V, E, W)</math> (<math>n</math> вершин <math>v_i</math> и <math>m</math> рёбер <math>e_j = (v^{(1)}_{j},<br />
v^{(2)}_{j})</math> с весами <math>f_j</math>).<br />
<br />
'''Объём входных данных''': <math>O(m + n)</math>.<br />
<br />
'''Выходные данные''': список рёбер минимального остовного дерева (для несвязного графа – список минимальных остовных деревьев для всех компонент связности).<br />
<br />
'''Объём выходных данных''': <math>O(n)</math>.<br />
<br />
=== Свойства алгоритма ===<br />
# Алгоритм останавливается за конечное число шагов, поскольку на каждом шаге становится по крайней мере на один фрагмент меньше.<br />
# Более того, число фрагментов на каждом шаге уменьшается как минимум вдвое, так что общее число шагов составляет не более <math>\log_2 n</math>. Отсюда следует и оценка сложности алгоритма.<br />
<br />
== Программная реализация алгоритма ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
На этапе поиска минимального ребра происходят следующие обращения к памяти:<br />
# Чтение информации о рёбрах. Может производится последовательно.<br />
# Проверка принадлежности ребра одному и тому же фрагменту - два чтения массива <math>parent(u)</math> с вероятным промахой по кэшу.<br />
# Чтение и обновление минимального веса ребра фрагмента. Данная информация может быть закеширована, особенно на поздних шагах, однако обновление необходимо производить атомарно, что требует инвализации кэша.<br />
<br />
На этапе схлопывания фрагментов требуется атомарно обновить массив <math>parent(u)</math> для каждого добавляемого в MST ребра. В зависимости от реализации параллельной структуры Union-Find корни фрагментов могут находиться ближе к началу массива, что позволяет закешировать эту наиболее часто читаемую область. Требование атомарности, однако, ограничивает эффект от такого кэширования.<br />
<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:boruvka_1.png|thumb|center|700px|Рисунок 1. Алгоритм Борувки. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации алгоритма Борувки. Этот алгоритм, как и большинство графовых алгоритмов, обладает нерегулярной структурой. Сразу нужно отметить, что локальность реализаций таких алгоритмов во многом зависит от структуры входного графа и может существенно меняться. В данном случае мы рассматриваем лишь один из возможных вариантов.<br />
<br />
Можно увидеть, что общий профиль состоит из 4 достаточно схожих этапов (разделены на рис. 1 вертикальными линиями). Однако поскольку этот профиль не обладает регулярной структурой, лучше рассмотреть все этапы.<br />
<br />
Начнем с изучения верхней части профиля (фрагмент 1 на рис. 1), которая показана на рис. 2. На каждом этапе большую часть обращений занимает последовательный перебор всех элементов данного фрагмента (выделен на рис. 2 желтым). Остальные обращения на разных этапах устроены по-разному. Если на первом этапе эти обращения разбросаны достаточно далеко друг от друга, что приводит к низкой пространственной и временной локальности, то на последнем этапе почти все обращения (не считая последовательного перебора) выполняются к одному и тому же элементу, что, естественно, характеризуется очень высокой локальностью. Подобное строение всего фрагмента приводит, скорее всего, к средним значениям и по пространственной, и по временной локальности.<br />
<br />
[[file:boruvka_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
Далее перейдем к изучению фрагмента 2 (рис. 3). Здесь можно увидеть, что строение каждого из 4 этапов отличается достаточно сильно. Как и в случае с фрагментом 1, каждый следующий этап обладает более высокой локальностью, однако здесь это заметно сильнее. При этом отметим, что данный фрагмент задействует всего около 60 элементов, а обращений к ним выполняется достаточно много, так что локальность в данном случае будет высока.<br />
<br />
[[file:boruvka_3.png|thumb|center|700px|Рисунок 3. Профиль обращений, фрагмент 2]]<br />
<br />
В целом похожая картинка наблюдается и во фрагменте 3. На рис. 4 видны 4 этапа со схожей структурой, и также задействовано около 60 элементов, что позволяет говорить о высокой локальности данного фрагмента.<br />
<br />
[[file:boruvka_4.png|thumb|center|700px|Рисунок 4. Профиль обращений, фрагмент 3]]<br />
<br />
Отдельное рассмотрение фрагмента 4 (рис. 5) позволяет увидеть, что локальность здесь определяется 4 последовательными переборами всех элементов данного фрагмента. Эти переборы обладают стандартной структурой – шаг по памяти 1, только 1 обращение к каждому элементу; небольшое искривление данных переборов вызвано нерегулярной активностью в других фрагментах, которая приводит к искажению визуального представления профиля. Подобный набор обращений обладает высокой пространственной, но низкой временной локальностью.<br />
<br />
[[file:boruvka_5.png|thumb|center|500px|Рисунок 5. Профиль обращений, фрагмент 4]]<br />
<br />
Таким образом, фрагменты 2 и 3 характеризуются высокой локальностью, другие 2 фрагмента – средней локальностью. А поскольку большая часть обращений приходится именно на фрагменты 2 и 3, можно предположить, что общая локальность должна быть достаточно высока.<br />
<br />
===== Количественная оценка локальности =====<br />
<br />
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 6 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Можно увидеть, что производительность работы с памятью в данном случае достаточно неплоха – значение daps сравнимо, например, со значением для реализации метода Холецкого. Однако это значение заметно ниже самых производительных реализаций алгоритмов (например, теста Linpack), что в целом неудивительно в случае графовых алгоритмов, традиционно неэффективно работающих с памятью.<br />
<br />
[[file:boruvka_daps.png|thumb|center|700px|Рисунок 6. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
Программа, реализующая алгоритм Борувки, состоит из двух частей: <br />
<br />
1. части, отвечающей за общую координацию вычислений<br />
<br />
2. части, отвечающей за параллельные вычисления на многоядерных CPU или GPU.<br />
<br />
<br />
Описанный выше последовательный алгоритм не может применяться в параллельной программе: в реализации <math>MERGE</math> результаты операций <math>FIND(u)</math> и <math>FIND(v)</math> могут постоянно меняться, что приведёт к race condition. Параллельный вариант алгоритма описан в статье.<br />
<br />
1. Каждой вершине v соответствует запись <math>A[v] = { parent, rank }</math>. Изначально <math>A[v] := { v, 0 }</math>.<br />
<br />
2. Вспомогательная операция <math>UPDATE(v, rank_v, u, rank_u)</math>:<br />
<br />
old := A[v]<br />
<br />
if old.parent != v or old.rank != rank_v then return false<br />
<br />
new := { u, rank_u }<br />
<br />
return CAS(A[v], old, new)<br />
<br />
3. Операция <math>FIND(v)</math>:<br />
<br />
while v != A[v].parent do<br />
u := A[v].parent<br />
CAS(A[v].parent, u, A[u].parent)<br />
v := A[u].parent<br />
return v<br />
<br />
4. Операция UNION(u, v):<br />
<br />
while true do<br />
(u, v) := (FIND(u), FIND(v))<br />
if u = v then return false<br />
(rank_u, rank_v) := (A[u].rank, A[v].rank)<br />
if (A[u].rank, u) > (A[v].rank, v) then<br />
swap((u, rank_u), (v, rank_v))<br />
if UPDATE(u, rank_u, v, rank_u) then<br />
if rank_u = rank_v then<br />
UPDATE(v, rank_v, v, rank_v + 1)<br />
return true<br />
<br />
Для описанной версии алгоритма гарантируется свойство wait-free. На практике может использоваться упрощённая версия без подсчёта рангов, обладающая более слабым свойством lock-free, но в ряде случаев выигрывающая по скорости.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
Возможность обрабатывать фрагменты независимо означает хорошую масштабируемость алгоритма. Сдерживающими факторами являются<br />
# пропускная способность памяти при чтении данных графа<br />
# соперничество потоков при выполнении атомарных операций с памятью<br />
# барьерная синхронизация после каждого подшага алгоритма.<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации алгоритма Борувки согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2 [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [1 : 28] с шагом 1;<br />
* размер графа [2^20 : 2^27].<br />
<br />
Проведем отдельные исследования сильной масштабируемости и масштабируемости вширь реализации алгоритма Борувки.<br />
<br />
Производительность определена как TEPS (от англ. Traversed Edges Per Second), то есть число ребер графа, который алгоритм обрабатывает в секунду. С помощью данной метрики можно сравнивать производительность для различных размеров графа, оценивая, насколько понижается эффективность обработки графа при увеличении его размера<br />
<br />
[[file:MST scaling strong.png|thumb|center|700px|Рисунок 3. Параллельная реализация алгоритма Борувки масштабируемость CPU версии: производительность в зависимости от числа запущенных CPU-потоков.]]<br />
<br />
[[file:MST scaling wide.png|thumb|center|700px|Рисунок 4. Параллельная реализация алгоритма Борувки масштабируемость различных версий реализации алгоритма: производительность в зависимости от размера графа]]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
<br />
* C++, MPI: [http://www.boost.org/libs/graph_parallel/doc/html/index.html Parallel Boost Graph Library]; функции <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#dense-boruvka-minimum-spanning-tree dense_boruvka_minimum_spanning_tree]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-then-merge boruvka_then_merge]</code>, <code>[http://www.boost.org/libs/graph_parallel/doc/html/dehne_gotz_min_spanning_tree.html#boruvka-mixed-merge boruvka_mixed_merge]</code> сочетают алгоритм Борувки и [[алгоритм Крускала]].<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Начатые статьи]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%98%D1%82%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F_%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D0%B0_dqds&diff=21008Итерация алгоритма dqds2016-12-16T07:24:08Z<p>VadimVV: /* Локальность данных и вычислений */</p>
<hr />
<div><br />
{{algorithm<br />
| name = Алгоритм dqds нахождения<br /> сингулярных чисел двухдиагональной матрицы<br />
| serial_complexity = <math>5n-4</math> <br />
| pf_height = <math>4n-3</math><br />
| pf_width = <math>2</math><br />
| input_data = <math>2n</math><br />
| output_data = <math>2n</math><br />
}}<br />
<br />
<br />
Основные авторы описания: [[Участник:Chernyavskiy|А.Ю.Чернявский]]<br />
<br />
== Свойства и структура алгоритмов ==<br />
=== Общее описание алгоритма ===<br />
<br />
'''Итерация алгоритма dqds''' является одним шагом алгоритма dqds нахождения сингулярных чисел двухдиагональной матрицы.<br />
<br />
Сам алгоритм '''dqds''' (''differential quotient-difference algorithm with shifts'')<ref name="vla">Деммель Д. Вычислительная линейная алгебра. – М : Мир, 2001.</ref><ref name="hola">Hogben L. (ed.). Handbook of linear algebra. – CRC Press, 2006.</ref> строит последовательность двухдиагональных матриц, сходящуюся к диагональной матрице, содержащей квадраты искомых сингулярных чисел. Его особенностью является высокая точность решения задачи. <br />
Вычислительным ядром алгоритма является именно внутренняя итерация, вне итераций происходит подбор сдвига <math>\delta</math> (параметер итерации, см. [[#Математическое описание алгоритма|математическое описание алгоритма]]), отслеживание сходимости, а также применение различных оптимизационных "хитростей". Отметим, что внеитерационная часть алгоритма не существенна с точки зрения структуры вычислений, т.к. основные затраты ложатся на вычисления внутри итерации. Подробности и варианты внеитерационной части, а также анализ сходимости можно найти в соответствующей литературе<br />
<ref>Fernando K. V., Parlett B. N. Accurate singular values and differential qd algorithms //Numerische Mathematik. – 1994. – Т. 67. – №. 2. – С. 191-229.</ref><br />
<ref>Parlett B. N., Marques O. A. An implementation of the dqds algorithm (positive case) //Linear Algebra and its Applications. – 2000. – Т. 309. – №. 1. – С. 217-259.</ref> <br />
<ref>Aishima K. et al. On convergence of the DQDS algorithm for singular value computation //SIAM Journal on Matrix Analysis and Applications. – 2008. – Т. 30. – №. 2. – С. 522-537.</ref>.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== Вспомогательные сведения ====<br />
Для понимания математических основ dqds-итерации полезно рассмотреть кратко её вывод, частично отражающий и историю возникновения алгоритма (подробности можно найти в <ref name="vla"/>).<br />
За основу dqds-алгоритма удобно взять так называемую LR-итерацию, предшествующую хорошо-известной QR-итерации. LR-алгоритм, начиная с входной матрицы <math>T_0>0,</math> строит сходящуюся последовательность подобных <math>T_0</math> матриц <math>T_i>0,</math> итерационно используя следующие три шага:<br />
#Выбрать сдвиг <math>\tau_i</math> меньший младшего собственного значения <math>T_i.</math><br />
#Вычислить разложение Холецкого <math>T_i-\tau^2_iI=B_i^TB_i,</math> где <math>B_i</math> - верхняя треугольная матрица с положительной диагональю.<br />
#<math>T_{i+1}=B_iB_i^T+\tau_i^2I.</math><br />
<br />
Отметим, что два шага LR-итерации с нулевым сдвигом эквивалентны одному шагу QR-итерации. Итерационная процедура приводит матрицу к диагональному виду, тем самым вычисляя собственные значения исходной матрицы. LR-алгоритм достаточно легко может быть переформулирован с заметными упрощениями для задачи поиска сингулярных значений двухдиагональных матриц. А именно, будем вычислять последовательность двухдиагональных матриц <math>B_i</math> без непосредственного вычисления <math>T_i</math>(которые в данном случае будут трехдиагональными). Пусть матрица <math>B_i</math> имеет диагональные элементы <math>a_1 \ldots a_n</math> и наддиагональные элементы <math>b_1 \ldots b_{n-1}</math>, а матрица <math>B_{i+1}</math> - диагональные элементы <math>\widehat{a}_1 \ldots \widehat{a}_n</math> и наддиагональные элементы <math>\widehat{b}_1 \ldots \widehat{b}_{n-1}.</math> Тогда шаг LR-итерации в терминах матриц <math>B_i</math> можно привести к простому циклу, пробегающему значения <math>j</math> от <math>1</math> до <math>n-1:</math><br />
<br />
:<math><br />
\widehat{a}^2_j = a^2_j+b^2_j-\widehat{b}^2_{j-1}-\delta<br />
</math><br />
:<math><br />
\widehat{b}^2_j = b^2_j\dot (a^2_{j+1}/a^2_j)<br />
</math><br />
и вычислению <math>\widehat{a}^2_n = a^2_n-\widehat{b}^2_{n-1}-\delta.</math> Очевидно, что работу с извлечением квадратов выгодно вести лишь после окончания работы алгоритма, поэтому можно ввести замену <math>q_j=a^2_j,\; e_j=b^2,</math> что в итоге приводит к так называемому алгоритму qds.<br />
Формулы алгоритма следующие:<br />
<br />
<br />
:<math><br />
\widehat{q}_j = q_j + e_j - \widehat{e}_{j-1} - \delta, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{e}_j = e_j \cdot q_{j+1} / \widehat{q}_j, \quad j \in [1,n-1]<br />
</math><br />
:<math><br />
\widehat{q}_n = q_n - \widehat{e}_{n-1} - \delta. <br />
</math><br />
<br />
<br />
Здесь <math>q_j, \; j \in [1,n]</math> и <math>e_j, \; j \in [1,n-1]</math> - квадраты элемнтов главной и верхней побочной диагонали соответственно. Крышка означает выходные переменные, а <br />
<br />
<math>\delta</math> - сдвиг (параметр алгоритма).<br />
Такая математическая запись наиболее компактна и соответствует так называемой qds-итерации.<br />
<br />
==== Математическое описание итерации алгоритма dqds====<br />
Представим теперь математическую запись, приближенную к dqds-итерации (с математической точки зрения qds и dqds-итерации эквивалентны) с введенными вспомогательными переменными <br />
<br />
<math>t_j</math> и <math>d_j.</math> Итерация алгоритма dqds преобразует входную двухдиагональную матрицу <math>B</math> в выходную <math>\widehat{B}.</math> <br />
<br />
Входные и выходные данные: <math>q_{j}, \; j\in [1,n], \; e_{k}, \; k\in [1,n-1] </math> - квадраты элементов главной и побочной диагонали входной матрицы <math>B</math>, <math> \widehat{q}_j , \; \widehat{e}_k </math> - то же для вычисляемой матрицы <math>\widehat{B}.</math>. <br />
<br />
Формулы метода выглядят следующим образом:<br />
<br />
<br />
:<math><br />
d_1 = q_1 - \delta, \; q_n = d_n <br />
</math><br />
:для<math><br />
\quad j\in [1,n-1]: <br />
</math><br />
:<math><br />
\widehat{q}_j = d_j + e_j<br />
</math><br />
:<math><br />
t_j = q_{j+1}/\widehat{q}_j<br />
</math><br />
:<math><br />
\widehat{e}_j=e_j \cdot t_j <br />
</math><br />
:<math><br />
d_{j+1} = d \cdot t - \delta<br />
</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Вычислительным ядром алгоритма является последовательный расчёт квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы. Учитывая использование вспомогательных переменных расчёт каждой новой пары содержит по одной операции сложения, вычитания и деления, а также две операции умножения.<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Алгоритм состоит из отдельного вычисления начального значения вспомогательной переменной <math>d,</math> последующим (n-1)-кратным выполнением повторяющейся последовательности из 5 операций (+,/,*,*,-) для вычисления квадратов диагональных (<math>\widehat{q}_j</math>) и внедиагональных (<math>\widehat{e}_k</math>) элементов выходной матрицы и завершающего вычислением крайнего значения <math>\widehat{q}_n</math>.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Отметим, что выходные данные сразу могут быть записаны на место входных (это учтено в схеме), также для хранения вспомогательных переменных <math>t_j</math> и <math>d_j</math> достаточно двух перезаписываемых переменных. Таким образом элементы главной (<math>q_j</math>) и побочной (<math>e_k</math>) диагонали входной матрицы последовательно перезаписываются соответствующими элементами выходной матрицы.<br />
<br />
Последовательность исполнения метода следующая:<br />
<br />
1. Вычисляется начальное значение вспомогательной переменной <math>d = q_1-\delta.</math><br />
<br />
2. Производится цикл по j от 1 до n-1, состоящий из:<br />
<br />
:2.1 Вычисляется значение <math>q_j = d + e_j;</math><br />
:2.2 Вычисляется значение вспомогательной переменной <math>t = q_{j+1}/q_j;</math><br />
:2.3 Вычисляется значение <math>e_j = e_j \cdot t;</math><br />
:2.4 Вычисляется значение вспомогательной переменной <math>d = d \cdot t - \delta.</math><br />
:<br />
<br />
3. Вычисляется <math>q_n = d.</math><br />
<br />
<br />
Легко заметить, что можно представить вычисления в другой форме, например, в виде qds-итерации (см. [[Итерация алгоритма dqds#Математическое описание алгоритма|Математическое описание dqds-итерации]]), однако, именно dqds реализация вычисления позволяет достичь высокой точности<ref name="vla"></ref>.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Для выполнения одной итерации dqds необходимо выполнить:<br />
<br />
* <math>n-1</math> делений,<br />
* <math>2n-2</math> умножений,<br />
* <math>2n-1</math> сложений/вычитаний.<br />
<br />
Таким образом одна dqds-итерация имеет ''линейную сложность''.<br />
<br />
=== Информационный граф ===<br />
[[file:dqds.png|thumb|center|600px|Рисунок 1. Граф алгоритма для n=4 без отображения входных и выходных данных.]]<br />
<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Как видно из информационного графа алгоритма, на каждом шаге основного цикла возможно лишь параллельное выполнение операции умножения (2.2) и умножения+сложения (2.4). Это позволяет сократить число ярусов на одной итерации цикла c 5 до 4, а общее число ярусов алгоритма с 5n-4 до 4n-3. Ярусы с операциями умножения состоят из двух операций, остальные же из одной.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
'''Входные данные''': Квадраты элементов основной и верхней побочной диагонали двухдиагональной матрицы (вектора <math>q</math> длины n и <math>e</math> длины n-1), а также параметр сдвига <math>\delta</math>.<br />
<br />
'''Объём входных данных''': <math>2n</math>. <br />
<br />
'''Выходные данные''': Квадраты элементов основной и верхней побочной диагонали выходной двухдиагональной матрицы.<br />
<br />
'''Объём выходных данных''': <math>2n-1</math>. <br />
<br />
=== Свойства алгоритма===<br />
<br />
Соотношение последовательной и параллельной сложности при наличии возможности параллельного выполнения операций умножения составляет <math>\frac{5n-4}{4n-3}</math>, т.е. алгоритм плохо распараллеливается. <br />
<br />
Вычислительная мощность алгоритма, как отношение числа операций к суммарному объему входных и выходных данных - константа.<br />
<br />
Описываемый алгоритм является полностью детерминированным. <br />
<br />
<br />
== Программная реализация алгоритмов ==<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Алгоритм на языке Matlab может быть записать так:<br />
<source lang="matlab"><br />
<br />
d = q(1)-delta;<br />
for j = 1:n-1<br />
q(j)=d+e(j);<br />
t=q(j+1)/q(j);<br />
e(j) = e(j)*t;<br />
d = d*t-delta;<br />
end<br />
q(n) = d;<br />
<br />
</source><br />
<br />
Как говорилось в [[#Схема реализации последовательного алгоритма|cхеме реализации последовательного алгоритма]], вычисляемые данные записываются сразу на место входных.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
Легко видеть, что локальность данных высока. Её легко повысить, размещая рядом соответствующие элементы массивов e и q, однако, это не оказывает существенного влияния на производительность в силу константного количества операций относительно объема обрабатываемых данных.<br />
<br />
==== Локальность реализации алгоритма ====<br />
<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
<br />
[[file:dqds_1.png|thumb|center|700px|Рисунок 1. Итерация алгоритма dqds. Общий профиль обращений в память]]<br />
<br />
На рис. 1 представлен профиль обращений в память для реализации итерации алгоритма dqds. Данный профиль внешне устроен очень просто и состоит из двух параллельно выполняемых последовательных переборов. Отметим, что общее число обращений в память всего в 3 раза больше числа задействованных данных, что говорит о том, что данные повторно используют редко. Зачастую это сигнализирует о достаточно невысокой локальности.<br />
<br />
Рассмотрим более детально строение одного из переборов (второй устроен практически идентично). На рис. 2 показан выделенный на рис. 1 небольшой фрагмент 1. Видно, что на каждом шаге в данном переборе выполняется три обращения в память; подобное строение говорит о высокой пространственной локальности, однако низкой временной, поскольку данные повторно используются только по 3 раза. <br />
<br />
Поскольку данная структура обращений и составляет общий профиль, то же самое можно говорить и о локальности всего профиля.<br />
<br />
[[file:dqds_2.png|thumb|center|700px|Рисунок 2. Профиль обращений, фрагмент 1]]<br />
<br />
<br />
===== Количественная оценка локальности =====<br />
Первая оценка выполняется на основе характеристики daps, которая оценивает число выполненных обращений (чтений и записей) в память в секунду. Данная характеристика является аналогом оценки flops применительно к работе с памятью и является в большей степени оценкой производительности взаимодействия с памятью, чем оценкой локальности. Однако она служит хорошим источником информации, в том числе для сравнения с результатами по следующей характеристике cvg.<br />
<br />
На рисунке 3 приведены значения daps для реализаций распространенных алгоритмов, отсортированные по возрастанию (чем больше daps, тем в общем случае выше производительность). Результат получен достаточно неожиданный – производительность работы с памятью очень невелика. Оценка daps для данного профиля сравнима с реализациями самых неэффективных алгоритмов в части работы с памятью – тестов RandomAccess и неэффективных вариантов обычного перемножения матриц. Отчасти это может объясняться низкой временной локальностью. Однако в данном случае причина может также заключаться в том, что в данной реализации объем вычислений на одно обращение в память достаточно велик, что может приводить к недостаточной загруженности подсистемы памяти. В таком случае, несмотря на в целом неплохую эффективность работы с памятью, производительность будет достаточно низка.<br />
<br />
[[file:dqds_daps.png|thumb|center|700px|Рисунок 3. Сравнение значений оценки daps]]<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Итерация dqds практически полностью последовательна. Единственная возможность - одновременное выполнение операции умножения (2.3) и операции (2.4) умножения и сложения, что дает небольшой выигрыш в производительности.<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
Алгоритм не является масштабируемым, максимального эффекта ускорения можно добиться на двух независимых процессорах.<br />
<br />
Проведём исследование масштабируемости вширь реализации согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов-2" [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров 1;<br />
* размер матрицы [1000 : 260000] с шагом 500.<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 2.559e-06%;<br />
* максимальная эффективность реализации 2.492e-08%.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности выбранной реализации DQDS в зависимости от изменяемых параметров запуска.<br />
<br />
[[file:DQDS Perf.png|thumb|center|700px|Рисунок 8. Параллельная реализация циклической редукции. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[file:DQDS eff.png|thumb|center|700px|Рисунок 9. Параллельная реализация циклической редукции. Изменение эффективности в зависимости от числа процессоров и размера матрицы.]]<br />
<br />
[http://git.algowiki-project.org/Teplov/Scalability/tree/master/DQDS/dqds.c Исследованная параллельная реализация на языке C]<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
Эффективное выполнение алгоритма возможно только на вычислительных устройствах с одним или двумя ядрами. <br />
<br />
=== Существующие реализации алгоритма ===<br />
Сам алгоритм dqds реализован в функции xBDSQR пакета LAPACK и используется при её вызове без расчёта сингулярных векторов.<br />
<br />
== Литература ==<br />
<br />
<references /><br />
<br />
[[Категория:Статьи в работе]]</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Elijah/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F&diff=19587Участник:Elijah/Нахождение собственных чисел квадратной матрицы методом QR разложения2016-11-30T06:33:07Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
<br />
{{algorithm<br />
| name = Нахождение собственных чисел квадратной матрицы методом QR разложения<br />
| serial_complexity = <math>N * O(n^3)</math><br />
| pf_height = <math> N * (12n - 15) </math><br />
| pf_width = <math> O(n^2) </math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: <br><br />
[[Участник:Elijah|И.В.Афанасьев]], разделы [[#Вычислительное ядро алгоритма|1.3]] - [[#Свойства алгоритма|1.10]] (базовый алгоритм), [[#Особенности реализации последовательного алгоритма|2.1]], [[#Возможные способы и особенности параллельной реализации алгоритма|2.3]], [[#Масштабируемость алгоритма и его реализации|2.4]], [[#Существующие реализации алгоритма|2.7]] <br><br />
<br />
[[Участник:Vladbrim|В.А.Шишватов]], разделы [[#Общее описание алгоритма|1.1]], [[#Математическое описание алгоритма|1.2]], [[#Вычислительное ядро алгоритма|1.3]] - [[#Свойства алгоритма|1.10]] ((алгоритм с преобразованием к матрице Хессенберга) <br><br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
QR-алгоритм — это численный метод в линейной алгебре, предназначенный для решения полной проблемы собственных значений, то есть отыскания всех собственных чисел и собственных векторов матрицы. Был разработан в конце 1950-х годов независимо В. Н. Кублановской и Дж. Фрэнсисом. [https://ru.wikipedia.org/wiki/QR-алгоритм] <br><br />
<br />
Данный алгоритм хорошо подходит для поиска собственных значений матриц общего вида, порядок которых составляет порядке тысячи. <br />
<br />
Суть алгоритма состоит в итеративном приведении матрицы A к верхнетреугольном виду с помощью последовательных QR разложений и матричный умножений. Данные матрицы на каждой итерации подобны между собой, так как данные умножения равносильны ортогональным преобразованиям. В следствии подобия, собственные значения этих матриц равны между собой.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== QR-алгоритм нахождения собственных чисел====<br />
<br />
Пусть <math>A</math> — вещественная матрица, для которой мы хотим найти собственные числа. Положим <math>A_0 = A</math>. На <math>k</math>-ом шаге (начиная с <math>k = 0</math>) вычислим QR-разложение <math>A_k = Q_k R_k</math>, где <math>Q_k</math> — ортогональная матрица (то есть <math>Q_k^\text{T} = Q_k^{-1}</math>), а <math>R_k</math> — верхняя треугольная матрица. Затем мы определяем <math>A_{k+1} = R_k Q_k</math>.<br />
<br />
Заметим, что<br />
: <math> A_{k+1} = R_k Q_k = Q_k^{-1} Q_k R_k Q_k = Q_k^{-1} A_k Q_k = Q_k^{T} A_k Q_k, </math><br />
то есть все матрицы <math>A_k</math> являются подобными, то есть их собственные значения равны.<br />
<br />
Пусть все диагональные миноры матрицы <math>A</math> вырождены. Тогда последовательность матриц <math>A</math> при <math> k \rightarrow \infty</math> сходится по форме к клеточному правому треугольному виду, соответствующему клеткам с одинаковыми по модулю собственными значениями.<br />
<ref>Численные методы / Н. С. Бахвалов, Н. П. Жидков, Г. М. Кобельков. — 3-е изд. — М: БИНОМ, Лаборатория знаний, 2004. — С. 321. — 636 с.</ref><br />
<br />
==== Сходимость QR-алгоритма нахождения собственных чисел ====<br />
<br />
Сходимость QR-алгоритма определяется как сходимость поддиагональной подматрицы матрицы <math>A_k</math> (<math>A_k = R_{k-1}Q_{k-1}</math>) к нулевой матрице.<br />
<br />
Предположим, что для матрицы <math>A \in \mathbb{C}^{n \times n}</math> выполнены следующие условия:<br />
<br />
# <math>A = X \Lambda X^{-1}, \quad \Lambda = \begin{bmatrix} \Lambda_1 & 0 \\ 0 & \Lambda_2 \end{bmatrix}, \quad \Lambda_1 \in \mathbb{C}^{m \times m}, \quad \Lambda_2 \in \mathbb{C}^{r \times r}</math>;<br />
# <math>\left | \lambda_1 \right | \ge \ldots \ge \left | \lambda_m \right | > \left | \lambda_{m+1} \right | \ge \ldots \ge \left | \lambda_{m+r} \right | > 0, \quad \{ \lambda_1, \ldots, \lambda_m \} = \lambda(\Lambda_1), \quad \{ \lambda_{m+1}, \ldots, \lambda_{m+r} \} = \lambda(\Lambda_2)</math>;<br />
# ведущая подматрица порядка <math>m</math> в <math>X^{-1}</math> невырожденная.<br />
<br />
Пусть QR-алгоритм порождает последовательность матриц вида:<br />
<br />
:<math>A_k = \begin{bmatrix} A_{11}^{(k)} & A_{12}^{(k)} \\ A_{21}^{(k)} & A_{22}^{(k)} \end{bmatrix}</math>.<br />
<br />
Тогда <math>A_{21}^{(k)} \rightarrow O</math> при <math> k \rightarrow \infty</math>. При достижении необходимой точности матрицу <math>A_{21}^{(k)}</math> можно заменит нулевой матрицей. В таком случае собственные значения необходимо искать для матриц <math>A_{11}^{(k)}</math> и <math>A_{22}^{(k)}</math>.<br />
<br />
Если условия 1-3 выполнены для всех <math>m \in \{1, ..., n-1\}</math>, то все поддиагональные матрицы сходятся к нулевым матрицам. Таким образом, все элементы, лежащие ниже главной диагонали стремятся к нулю, и диагональные элементы матриц <math>A_k</math> сходятся к искомым собственным значениям матрицы <math>A</math><ref>Тыртышников Е.Е. Методы численного анализа. — М.: Академия, 2007. — 320 c.</ref><br />
<br />
==== Приведение матрицы к матрице Хессенберга ====<br />
<br />
Для преобразования матрицы к матрице Хессенберга<ref>[https://en.wikipedia.org/wiki/Hessenberg_matrix| Hessenberg matrix]</ref> используется преобразование Хаусхолдера<ref>[https://en.wikipedia.org/wiki/Householder_transformation#Tridiagonalization| Tridiagonalization using Householder transformation]</ref>.<br />
<br />
Для каждого <math> k = 1,2,... n-1 </math><br />
<br />
:<math> \displaystyle \alpha = -\operatorname{sgn}(a^k_{k+1,k})\sqrt{\sum_{j=k+1}^{n}(a^k_{jk})^2} </math>;<br />
<br />
:<math> r = \sqrt{\frac{1}{2}(\alpha^{2}-a^k_{k+1,k}\alpha)} </math>;<br />
<br />
:<math>v^k_1 = v^{k}_2 = \cdots = v^k_k=0;</math><br />
<br />
:<math> v^{k}_{k+1} = \frac{a^{k}_{k+1,k}-\alpha}{2r}</math><br />
<br />
:<math> v^{k}_j = \frac{a^{k}_{jk}}{2r}</math>, для <math>j = k+2; k+3, ..., n</math><br />
<br />
:<math> \displaystyle P^{k} = I - 2v^{(k)}(v^{(k)})^\text{T}</math><br />
<br />
:<math>\displaystyle A^{(k+1)} = P^{k}A^{(k)}(P^{k})^\text{T}</math><br />
<br />
При этом матрица <math>P</math> не вычисляется и используется напрямую. Вместо этого используются следующие вычисления<br />
:<math>\displaystyle B^{(k)} = P^{k}A^{(k)} = (I - 2v^{(k)}(v^{(k)})^\text{T}) A^{(k+1)} = A^{(k+1)} - 2A^{(k+1)}v^{(k)}(v^{(k)})^\text{T}</math><br />
:<math>\displaystyle A^{(k+1)} <br />
= P^{k}A^{(k)}(P^{k})^\text{T} <br />
= B^{(k)}(P^{k})^\text{T} <br />
= ((B^{(k)})^\text{T})^\text{T}(P^{k})^\text{T} <br />
= (P^{k}(B^{(k)})^\text{T})^\text{T} <br />
= ((B^{(k)})^\text{T} - 2B^{(k)}v^{(k)}(v^{(k)})^\text{T})^\text{T}</math><br />
<br />
Таким образом перемножение матриц <math>P^{k}A^{(k)}</math> и <math>B^{k}P^{(k)}</math> имеет сложность <math>O(n^2) </math>(умножения матрицы на вектор) вместо <math>O(n^3)</math> (перемножение матриц)<br />
<br />
==== Использование сдвигов ====<br />
<br />
К сожалению, скорость сходимости алгоритма зависит от отношения модулей собственных значений. <br />
Поддиагональная подматрица <math>A_k</math> сходится к нулевой со скоростью <math>O(|\lambda_{k}| / |\lambda_{k+1}|)</math>. <br />
Таким образом, если у матрицы есть два очень близких значения, то соответсвующая подматрица будет очень медленно сходиться.<br />
<br />
По этой причине для увеличения скорости сходимости алгоритма переходят от матрицы <math>A</math> к матрице <math>(A - sI)</math>, где <math>s \in \mathbb{C}</math>, а <math>I</math> — единичная матрица, в надежде, что <math>|\lambda_{k} - s| / |\lambda_{k+1} - s| \gg |\lambda_k| / |\lambda_{k+1}|</math>.<br />
<br />
Такой подход называется QR-алгоритмом со сдвигами. Пусть <math>A_0 = A</math> — исходная матрица. <br />
Тогда для <math>k = 1, 2, \ldots</math>:<br />
* <math>A_{k-1} - s_kI= Q_kR_k</math> (QR-разложение);<br />
* <math>A_k = R_kQ_k + s_kI</math>.<br />
<br />
QR-алгоритм со сдвигами является частным случаем обобщенного QR-алгоритма, в котором на каждой итерации используется некоторый полином <math>f_k</math>:<br />
* <math>f_k(A_{k-1}) = Q_kR_k</math>;<br />
* <math>A_k = Q_k^{-1}A_{k-1}Q_k</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
==== Базовый алгоритм ====<br />
<br />
У базового QR алгоритма есть два вычислительных ядра:<br />
# операция поиска QR разложения с использованием метода Гивенса<br />
# матричное умножение полученных матриц<br />
<br />
Для получения <math>QR</math>-разложения методом Гивенса <math>A</math> матрицу приводят к правой треугольной <math>R</math> последовательностью умножений её слева на матрицы вращения <math>T_{1 2}, T_{1 3}, ..., T_{1 n}, T_{2 3}, T_{2 4}, ..., T_{2 n}, ... , T_{n-2 n}, T_{n-1 n}</math>. <br />
<br />
Матричное умножение может быть выполнено любым классическим способом: для плотных матриц <math>A</math> (элементы <math>a_{ij}</math>) и <math>B</math> (элементы <math>b_{ij}</math>) вычисляется плотная матрица <math>C</math>с элементами <math>c_{ij}</math> по формуле:<br />
<br />
Формулы метода:<br />
:<math><br />
\begin{align}<br />
c_{ij} = \sum_{k = 1}^{n} a_{ik} b_{kj}, \quad i \in [1, m], \quad i \in [1, l].<br />
\end{align}<br />
</math><br />
<br />
==== Алгоритм с преобразованием к матрице Хессенберга ====<br />
<br />
Для QR алгоритма с использования матрицы Хессенберга:<br />
# получение матрицы Хессенберга из исходной матрицы<br />
# операция поиска QR разложения с использованием модифицированного метода Гивенса<br />
<br />
Для преобразования исходной матрицы к матрице Хессенберга используются преобразования Хаусхолдера (см. [[#Приведение матрицы к матрице Хессенберга| раздел 1.2.3]])<br />
<br />
Для получения <math>QR</math>-разложения методом Гивенса для матрицы Хессенберга <math>A</math>, матрицу приводят к правой треугольной <math>R</math> последовательностью умножений её слева на матрицы вращения <math>T_{1 2}, T_{2 3}, T_{3 4}, ..., T_{(n-1)\ n}</math>. <br> <br><br />
<br />
Более подробное описание вычислительных ядер можно найти по соответствующим ссылкам в [[#Макроструктура алгоритма| разделе 1.4]].<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже описано в описании ядра алгоритма, базовая версия QR-алгоритма на каждой итерации использует следующие алгоритмы: <br />
# [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]] или [[Метод_Хаусхолдера_(отражений)_QR-разложения_квадратной_матрицы,_вещественный_вариант|Метод Хаусхолдера(отражений) QR-разложения_квадратной_матрицы]]<br />
# [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]<br />
<br />
В случае использования матрицы Хессенберга:<br />
# Получение матрицы Хессенберга из исходной матрицы с помощью преобразований Хаусхолдера<ref>[https://en.wikipedia.org/wiki/Householder_transformation| Householder transformation]</ref>.<br />
# [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]. При этом количество элементов матрицы, которые нужно занулять, на порядок меньше, чем в базовом варианте.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
==== Базовый алгоритм ====<br />
<br />
Последовательная реализация простейшего алгоритма состоит из некоторого числа итераций<br />
<br />
Каждая итерация состоит из следующих действий (с начальным условием <math> A_0 = A</math>):<br />
# Для матрицы <math> A_{n} </math> строится QR разложение любым доступным последовательным алгоритмом на матрицы <math> Q_{n} </math> и <math> R_{n} </math> <br><br />
# Матрицы <math> R_{n} </math> и <math> Q_{n} </math> перемножаются, таким образом получается матрица <math> A_{n+1} = R_{n} * Q_{n} </math> для следующей итерации <math> n + 1 </math> <br />
# Проверятся, приведена ли матрица к верхнетреугольной форме, и если да, то производится выход из цикла.<br />
<br />
==== Алгоритм с преобразованием к матрице Хессенберга ====<br />
<br />
На первом этапе исходная матрица <math>A</math> преобразуется к матрице Хессенберга <math>H</math>, которая будет иметь такие же собственные значения, что и изначальная матрица.<br />
<br />
Затем производится некоторое число итераций, состоящих из следующих действий (с начальным условием <math> A_0 = H</math>): <br />
# Для матрицы <math> A_{n} </math> строится QR разложение с помощью метода вращения Гивенса. При этом занулять нужно не все элементы, а только <math> a_{i + 1, i} </math>, где <math>i \in {1, n - 1}</math>. Полученная матрица будет опять являться матрицей Хессенберга.<br />
# Проверятся, приведена ли матрица к верхнетреугольной форме, и если да, то производится выход из цикла.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
==== Последовательная сложность базового алгоритма ====<br />
Рассчитаем последовательную сложность базового алгоритма. Пусть <math style="vertical-align:0%"> A \in \mathbb{R}^{n \times n}</math>, и для приведения матрицы к треугольной форме необходимо произвести N итераций алгоритма. <br><br />
На каждой итерации алгоритма производится QR разложение (сложностью <math style="vertical-align:0%"> 2 * n^3 </math>) и матричное умножение (сложностью <math style="vertical-align:0%"> n^3 </math>). Проверка того, имеет ли матрица треугольную форму, может быть проведена за <math style="vertical-align:0%"> n^2 </math> операций. <br><br />
Таким образом итоговая сложность одной итерации составляет: <math> 3*n^3 + n^2 = O(n^3)</math> <br><br />
Общая сложность алгоритма при <math> N </math> итерациях составляет <math> N * O(n^3) </math><br />
<br />
==== Последовательная сложность алгоритма с приведением матрицы к матрице Хессенбрга ====<br />
<br />
Данный алгоритм состоит из двух этапов:<br />
# преобразование исходной матрицы к матрице Хессенберга<br />
# QR-разложение методом Гивенса<br />
<br />
===== Сложность алгоритма приведения матрицы к матрице Хессенбрга =====<br />
<br />
На каждой итерации алгоритма приведения матрицы к матрице Хессенбрга требуется вычислить: <br />
# <math>\alpha</math> со сложность <math>O(n)</math><br />
# <math>r</math> со сложностью <math>O(1)</math><br />
# <math>v^k</math> со сложностью <math>O(n)</math><br />
# <math>A^{(k+1)}</math> со сложностью <math>O(n^2)</math><br />
Таким образом вычислительная сложность одной итерации равна <math>O(n^2)</math>.<br />
Всего таких итераций <math>n</math>, поэтому общая сложность алгоритма равна <math>O(n^3)</math>.<br />
<br />
===== Сложность алгоритма QR-разложения методом Гивенса =====<br />
<br />
Метод Гивенса использует матрицы вращения, чтобы занулить ненулевые элементы матрицы под главной диагональю на каждой итерации. Умножение на одну матрицу вращения имеет вычислительную сложность <math>O(n)</math>. Для случая произвольной матрицы в худшем случае потребуется занулить <math>\frac{n^2 - n}{2}</math> элементов. Если матрица является матрицей Хессенберга, то таких элементов будет всего лишь <math>n - 1</math>.<br />
<br />
Таким образом, для произвольной матрицы одна итерация алгоритма QR-разложения имеет вычислительную сложность равную <math>O(n^3)</math>, для матрицы Хессенберга - <math>O(n^2)</math>. Проверка на то, является ли матрица треугольной, имеет сложность <math>O(n^2)</math>, что не влияет на порядок сложности.<br />
<br />
Если всего требуется <math>N</math> итераций для преобразования матрицы к треугольному виду, то для для произвольной матрицы сложность будет <math>N * O(n^3)</math>, для матрицы Хессенберга - <math>N * O(n^2)</math><br />
<br />
===== Итоговая сложность =====<br />
<br />
Таким образом, общая вычислительная сложность алгоритма вычисления собственных значений с приведением матрицы к матрице Хессенбрга равна <math>O(n^3) + N * O(n^2)</math><br />
<br />
=== Информационный граф ===<br />
<br />
Макрограф алгоритма изображён на рисунке 1. Он представляет из себя верхний уровень алгоритма, на котором в цикле итеративно вызываются следующие операции: <br><br />
<br />
# Инициализация матриц ('''init matrices''')<br />
# QR разложение ('''QR decomposition''')<br />
# Матричное умножение ('''matrix multiplication''')<br />
# Проверка, приведена ли матрица к верхней треугольной форме ('''check if matrix is upper triangular''')<br />
# Получение собственных значений из диагонали матрицы ('''get eigenvalues from matrix diagonal''')<br />
<br />
Информационные графы [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|QR разложения]] и [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|матричного умножения]] могут быть найдены по приведенным ссылкам. Информационный граф проверки того, приведена ли матрица к верхнетреугольной форме, будет приведен далее на рисунке 2. Информационные графы инициализации и получения собственных значений представляют собой работу с вводом и выводом входных/выходных данных, и, поэтому, рассматриваться не будут. <br />
<br />
[[Файл:Screen_Shot_2016-10-15_at_12.43.32_PM.png|thumb|center|600px|Рисунок 1. Информационный граф базовой версии QR-алгоритма без отображения входных и выходных данных. N итераций.]]<br />
<br />
[[Файл:Check_if_matrix_is_upper_triangular.png|thumb|center|300px|Рисунок 2. Информационный граф проверки приведенности матрицы к верхнетреугольной форме.]] <br><br />
<br />
В случае оптимизации алгоритма с приведением матрицы к форме Хессенберга, инициализация матрицы (int matrices (1)), будет включать в себя преобразование хаусхолдера, имеющее следующий граф алгоритма:<br />
<br />
[[Файл:Hessenberg_transformation.png|thumb|center|500px|Рисунок 3. Информационный граф проверки хаусхолдера.]] <br><br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Все итерации базового QR-алгоритма производятся последовательно, поэтому на верхнем уровне алгоритм чисто последователен. <br><br />
<br />
Основной ресурс параллелизма представлен на нижнем уровне, при реализации различных операций, используемых в алгоритме, таких как QR разложение методом вращений и матричное умножение. <br> <br><br />
<br />
==== Параллельная сложность базового QR-алгоритма ====<br />
Посчитаем параллельную сложность каждой операции по отдельности, а затем, по полученным данным, и всего алгоритма целиком: <br><br />
# Параллельная сложность QR разложения методом вращений составляет <math> 11n - 16 </math> <ref name="QRGivens">[[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]</ref> <br><br />
# Параллельная сложность матричного умножения составляет <math> n </math><ref name="MatrixMultiplication">[[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]</ref>.<br><br />
# Параллельная сложность проверки, является ли матрица верхнетреугольной, равна n. <br><br />
Таким образом параллельная сложность каждой итерации составляет <math> 13n - 15 </math>. При N итерациях, которые необходимо производить последовательно, итоговая сложность базового алгоритма составляет <math> N * (13n - 15) </math>, что в упрощенном виде равно <math> N * O(n) </math> <br><br />
<br />
==== Параллельная сложность алгоритма с приведением матрицы к форме Хессенберга: ====<br />
Снова вычислим параллельную сложность каждой операции по отдельности, а затем, по полученным данным, и всего алгоритма целиком: <br><br />
# Параллельная сложность приведения матрицы к форме Хессенберга составляет <math> O(n^2) </math><br />
# Параллельная сложность каждой итерации с приведенной матрицы составляет <math> O(n) </math><br />
<br />
Таким образом параллельная сложность всего алгоритма при N итерациях составляет <math> O(n^2) + N*O(n) </math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
* '''Входные данные''': плотная квадратная матрица <math style="vertical-align:0%">A</math> (элементы <math>a_{ij}</math>).<br />
<br />
* '''Объём входных данных''': <math style="vertical-align:0%">n^2</math>.<br />
<br />
* '''Выходные данные''': <math>n</math> вещественных собственных чисел <math> | l_{i} | </math> матрицы <math style="vertical-align:0%">A</math> <br />
<br />
* '''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности для базового является квадратичным, что даёт хороший стимул для распараллеливания. Для оптимизированного алгоритма данное соотношение линейно.<br />
<br />
Вычислительная мощность, равная отношению числа операций <math> N * O(n^3) </math> к суммарному объему входных и выходных данных <math> n^2 + n </math>, для каждой итерации линейна, а для всего алгоритма в целом равна <math> N*n </math>, что позволяет сделать вывод о том, что перемещение данных для обработки не играет важной роли в данном алгоритме. <br />
<br />
Для оптимизированного алгоритма вычислительная мощность так же линейна (приведение матрицы к форме Хессенберга имеет сложность <math> O(n^3) </math>, а объем выходных данных <math> O(n^2) </math>).<br />
<br />
Вычислительная погрешность растет линейно, из-за использования метода вращений для QR разложения. <br />
<br />
Данный алгоритм недетерминирован, так как число итераций зависит от значений элементов матрицы и выход из основного цикла происходит по достижению некоторой точности. В зависимости от этого, вычислительный поток имеет различную длину. <br />
На каждой отдельной итерации в свою очередь алгоритм полностью детерминирован, так как детерминированы его обе базовые макрооперации - QR разложение и матричное умножение.<br />
<br />
== Программная реализация ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Основным типом данных для QR алгоритма является матрица. В данной статье представлена реализация на языке C++, что накладывает определенные ограничения на формат хранени матриц: все матрицы хранятся в одномерном массиве в построчном (ROW_MAJOR) формате. <br />
<br />
В простейшем случае QR-алгоритм выглядит следующим образом:<br />
<br />
<source lang="C++"><br />
<br />
int sequential_QR_algorithm(float *A, int size)<br />
{<br />
float *Q = new double[size * size];<br />
float *R = new double[size * size];<br />
while(!check_if_upper_triangular(A, size)))<br />
{<br />
sgeqrf (ROW_MAJOR, size, size, A, size, tau);<br />
set_Q_and_R(A, Q, R, size);<br />
sorgqr(ROW_MAJOR, size, size, size, Q, size, tau);<br />
sgemm (RowMajor, NoTrans, NoTrans, size, size, size, 1, R, size, Q, size, 0, A, size);<br />
}<br />
}<br />
</source><br />
<br />
В последовательном случае функции dgeqrf, dorgqr и dgemm представляют собой последовательные функции стандарта BLAS, вычисляющие QR разложение(geqrf), матрицу Q после разложения (dorgqr) и матричное умножение (dgemm). <br />
Так же здесь используются простейшие функции set_Q_and_R и check_if_upper_triangular. Функция set_Q_and_R получает после QR разложения из матрицы A данные о матрицах Q и R. Функция check_if_upper_triangular проверяет, приведена ли матрица A к треугольном виду, и если да, происходит выход из цикла.<br />
<br />
Для работы программы в памяти необходимо хранить 3 матрицы, таким образом общий объем составляет 3 * n^2. Так же необходим вспомогательный массив tau для QR разложения, однако его размер составляет всего n. Так же важно заметить, что различные реализации функций dgeqrf, dorgqr и dgemm обычно используют внутренние вспомогательные массивы для блочной обработки, однако, их размер не превышает n * C, где C - некоторая архитектурно зависимая константа. <br />
<br />
Все вычисления рекомендуется производить в одинарной точности.<br />
<br />
Как уже говорилось ранее, внешний цикл представленной программы сугубо последователен (итеративен), и основной ресурс параллелизма расположен в вызываемых функциях (geqrf, gemm). Их реализации бывают крайне различны, поэтому данный алгоритм можно легко преобразовать для любой архитектуры, используя набор стандартных библиотечных функций. К примеру, для реализации алгоритма на GPU можно использовать библиотеку MAGMA, на многоядерных CPU - Intel MKL.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Как уже говорилось в [[#Особенности реализации последовательного алгоритма| разделе 2.1]], при использовании различных библиотек можно реализовать данный алгоритм практически на всех архитектурах. Далее более подробно будут рассмотрены примеры для некоторых из них. Особенности параллельных реализаций для базовых макроопераций (QR разложения и матричного умножения), уже описаны в соответствующих статьях (см. ссылки в [[#Макроструктура алгоритма| разделе 1.4]]) <br />
<br />
'''Реализация на многоядерных CPU (одном узле)'''<br />
<br />
Для распарарллеливания на многоядерных CPU используются функции LAPACKE_sgeqrf, LAPACKE_sorgqr и cblas_sgemm. Распараллеливание оставшихся функций может быть произведено с использованием openMP, так как внутренние циклы в них независимы по данным.<br />
<br />
Исходный код предложенной реализации представлен далее:<br />
<br />
<source lang="C++"><br />
<br />
int CPU_QR_algorithm(float *A, int size)<br />
{<br />
float *Q = new double[size * size];<br />
float *R = new double[size * size];<br />
while(!check_if_upper_triangular(A, size)))<br />
{<br />
LAPACKE_sgeqrf (LAPACK_ROW_MAJOR, size, size, A, size, tau);<br />
set_Q_and_R(A, Q, R, size);<br />
LAPACKE_sorgqr(LAPACK_ROW_MAJOR, size, size, size, Q, size, tau);<br />
cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, size, size, size, 1, R, size, Q, size, 0, A, size);<br />
}<br />
}<br />
</source><br />
<br />
'''Реализация на GPU (и multi-GPU)'''<br />
<br />
Для реализации алгоритма на GPU и multi-GPU можно использовать аналогичные функции dgemm и geqrf из библиотеки [[ Magma. http://icl.cs.utk.edu/magma/ | magma]].<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
Для данного алгоритма была проверена масштабируемость оптимизированной версии с приведением матрицы к форме Хессенберга. Тесты проводились для случайно сгенерированной матрицы размера 8000x8000 на 1-14 параллельных вычислительных потоках.<br />
<br />
Код, для которого проверлась масштабируемость, реализован с помощью функций библиотеки intel MKL, выглядит следующим образом: <br><br />
<br />
<source lang="C++"><br />
<br />
#include "stdio.h"<br />
#include "stdlib.h"<br />
#include "mkl.h"<br />
#include "math.h"<br />
#include <iostream><br />
<br />
using namespace std;<br />
<br />
int main(int argc, char* argv[])<br />
{<br />
int max_threads;<br />
float *h, *Wr, *Wi, *z, *scalar, *h_copy;<br />
double s_initial, s_elapsed;<br />
lapack_int n = atoi(argv[1]); //size=n<br />
<br />
h=(float*)mkl_malloc(n*n*sizeof(float),32);<br />
h_copy=(float*)mkl_malloc(n*n*sizeof(float),32);<br />
z=(float*)mkl_malloc(n*n*sizeof(float),32); //z will not be referenced due to set compz="N"<br />
Wr=(float*)mkl_malloc(n*sizeof(float),32);<br />
Wi=(float*)mkl_malloc(n*sizeof(float),32);<br />
scalar=(float*)mkl_malloc((n-1)*sizeof(float),32);<br />
if(h==NULL) //initilization for hessenberg matrix<br />
{<br />
for(int i=0;i<n*n;i++)<br />
{<br />
h[i]=(float)rand()*2000;<br />
h_copy[i]=h[i];<br />
}<br />
}<br />
for(int j=0;j<n*n;j++)<br />
{<br />
z[j]=0.0;<br />
}<br />
max_threads=mkl_get_max_threads();<br />
<br />
for(int t = 1;t <= max_threads;t++)<br />
{<br />
double t1 = dsecnd();<br />
mkl_set_num_threads(t);<br />
LAPACKE_sgehrd(LAPACK_ROW_MAJOR, n, 1, n, h, n, scalar);<br />
LAPACKE_shseqr(LAPACK_ROW_MAJOR, 'E', 'N', n, 1, n, h, n, Wr, Wi, z, n);<br />
double t2 = dsecnd();<br />
<br />
cout << "total time for " << t << " threads: " << t2 - t1 << endl << endl;<br />
<br />
for(int k=0;k<n*n;k++)<br />
{<br />
h[k]=h_copy[k];<br />
}<br />
}<br />
return 0;<br />
}<br />
<br />
</source><br />
<br />
Далее приводятся график сильной масштабируемости. Данные результаты были получены на суперкомпьютере "Ломоносов-2", каждый узел которого оборудован 14-ядерным процессором Intel Xeon E5 v3. Тестирование производилось на одном узле данного типа, с использованием библиотеки MKL версии 11.1.3. Для компиляции использовался компилятор intel icc версии 15.0.3 с опцией оптимизации -O2.<br />
<br />
[[file:QR_algorithm_strong_scalability.png|thumb|center|500px|Рис.4 Сильная масштабируемость оптимизированного QR алгоритма с приведением матрицы к форме Хессенберга]]<br />
<br />
Из данных графика видно, что алгоритм хорошо масштабируется при небольшом числе процессов (1-4), однако дальнейшее прибавление процессов не ведет к линейному ускорению.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
'''Последовательный''' QR алгоритм, а так же различные базовые функции для его реализации, можно найти в [http://www.netlib.org/scalapack/ библиотеке LAPACK] и её аналогах и расширениях.<br />
<br />
Базовая версия QR алгоритма может быть реализована описанным в [[#Особенности реализации последовательного алгоритма| разделе 2.1]] способом при помощи функций:<br />
# '''?geqrf''' - вычисляет QR разложение произвольной матрицы<br />
# '''?gemm''' - вычисляет произведение двух плотных матриц<br />
<br />
'''?steqr''' - вычисляет собственные числа и собственные вектора для случая тридиагональной матрицы. <br />
<br />
Для реализации оптимизированного алгоритма с приведением матрицы к матрице Хессенберга можно использовать следующие функции:<br />
# '''?gehrd''' - приводят матрицу общего вида к верхней матрице Хессенберга с использованием ортогональных/унитарных трансформаций<br />
# '''?hseqr''' - вычисляют собственные значения матрицы в верхней матрице Хессенберга с использованием QR алгоритма<br />
<br />
Подробная документация по упомянутым функциям может быть найдена [http://physics.oregonstate.edu/~landaur/nacphy/lapack/eigen.html здесь].<br />
<br />
'''Параллельный''' QR алгоритм может быть реализован с помощью библиотек [http://www.netlib.org/scalapack/ ScaLAPACK] и [http://www.cs.utexas.edu/users/plapack/ PlaLAPACK].<br />
<br />
'''p?geqrf''', '''p?gemm''', '''p?hseqr''' - аналогичные функции ScaLAPACK.<br />
<br />
'''PLA_QR''', '''PLA_Gemm''' - аналогичные функции из PlaLAPACK.<br />
<br />
== Примечания ==<br />
<br />
== Литература ==</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Elijah&diff=19586Обсуждение участника:Elijah2016-11-30T06:32:56Z<p>VadimVV: </p>
<hr />
<div><br />
<br />
== Статья [[Участник:Elijah/Нахождение собственных чисел квадратной матрицы методом QR разложения]] ==<br />
<br />
=== По существу ===<br />
<br />
* Кроме приведения к хессенбергу, типовые алгоритмы из библиотек используют также сдвиги. Они здесь не описаны, а сделать это необходимо. --[[Участник:Frolov|Фролов А.В.]] ([[Обсуждение участника:Frolov|обсуждение]]) 12:55, 10 ноября 2016 (MSK)<br />
<br />
=== Замечания ===</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:F-morozov/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_(4)&diff=19333Участник:F-morozov/Нахождение собственных чисел квадратной матрицы методом QR разложения (4)2016-11-28T06:50:57Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
{{algorithm<br />
| name = Нахождение собственных чисел квадратной матрицы методом QR-разложения<br />
| serial_complexity = <math>O(n^3 + N \times n^2)</math><br />
| pf_height = <math>N \times O(n)</math><br />
| pf_width = <math>O(n^2)</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:f-morozov|Ф.&nbsp;В.&nbsp;Морозов]], [[Участник:Nataliya|Н.&nbsp;Ф.&nbsp;Пащенко]]<br />
<br />
Описание составлено совместно, точно выделить вклад отдельных авторов невозможно.<br />
<br />
= Свойства и структура алгоритма =<br />
<br />
== Общее описание алгоритма ==<br />
<br />
Для решения ряда задач механики, физики, химии требуется получение всех собственных значений (собственных чисел), а иногда и всех собственных векторов некоторых матриц. Эту задачу называют полной проблемой собственных значений<ref name="Бахвалов">Бахвалов&nbsp;Н.&nbsp;С., Жидков&nbsp;Н.&nbsp;П., Кобельков&nbsp;Г.&nbsp;М. Численные методы. — 6-е изд. — М.: БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref>. Если рассматриваются матрицы общего вида, порядок которых не больше тысячи (нескольких тысяч), то для вычисления всех собственных значений (и собственных векторов) можно рекомендовать '''QR-алгоритм'''. Он был разработан в начале 1960-х годов независимо В.&nbsp;Н.&nbsp;Кублановской (Россия) и Дж.&nbsp;Фрэнсисом (Великобритания)<ref name="Тыртышников">Тыртышников&nbsp;Е.&nbsp;Е. Методы численного анализа. — М.: Академия, 2007. — 320 c.</ref>.<br />
<br />
Нахождение собственных чисел матрицы <math>A</math> методом QR-разложения (QR-алгоритм) заключается в построении последовательности матриц, сходящейся по форме к клеточному правому треугольному виду. Матрицы <math>A_k</math> данной последовательности строятся с использованием QR-разложения таким образом, что они подобны между собой и подобны исходной матрице <math>A</math>, поэтому их собственные значения равны. Характеристический многочлен клеточной правой треугольной матрицы равен произведению характеристических многочленов ее диагональных клеток<ref name="Бахвалов"/>. Его корни — собственные значения матрицы, которые согласно вышесказанному и являются искомыми.<br />
<br />
== Математическое описание алгоритма ==<br />
<br />
Известно, что произвольная квадратная матрица может быть представлена в виде произведения унитарной (''в вещественном случае ортогональной'') и верхней треугольной матриц. Такое разложение называется QR-разложением.<br />
<br />
=== QR-алгоритм ===<br />
<br />
Пусть <math>A_0 = A</math> — исходная матрица.<br />
Для <math>k = 1, 2, \ldots</math>:<br />
* <math>A_{k-1} = Q_kR_k</math>, где <math>Q_k</math> — унитарная (''ортогональная'') матрица, <math>R_k</math> — верхняя треугольная матрица;<br />
* <math>A_k = R_kQ_k</math>.<br />
<br />
Заметим, что <math>A_k = R_kQ_k = Q_k^{-1}A_{k-1}Q_k</math>, то есть матрицы <math>A_k</math> и <math>A_{k-1}</math> подобны для любого <math>k</math>.<br />
Таким образом, матрицы <math>A_1, A_2, \ldots</math> подобны исходной матрице <math>A</math> и имеют те же собственные значения.<br />
При выполнении [[#Сходимость QR-алгоритма|некоторых условий]] последовательность <math>\{A_k\}</math> сходится к верхней треугольной матрице <math>\hat{A}</math>, на диагонали которой расположены искомые собственные значения.<br />
<br />
=== Сходимость QR-алгоритма ===<br />
<br />
Сходимость QR-алгоритма трактуется как сходимость к нулю поддиагонального блока матрицы <math>A_k</math>.<br />
<br />
Предположим, что для матрицы <math>A \in \mathbb{C}^{n \times n}</math> выполнены следующие условия:<br />
<br />
# <math>A = X \Lambda X^{-1}, \quad \Lambda = \begin{bmatrix} \Lambda_1 & 0 \\ 0 & \Lambda_2 \end{bmatrix}, \quad \Lambda_1 \in \mathbb{C}^{m \times m}, \quad \Lambda_2 \in \mathbb{C}^{r \times r}</math>;<br />
# <math>\left | \lambda_1 \right | \ge \ldots \ge \left | \lambda_m \right | > \left | \lambda_{m+1} \right | \ge \ldots \ge \left | \lambda_{m+r} \right | > 0, \quad \{ \lambda_1, \ldots, \lambda_m \} = \lambda(\Lambda_1), \quad \{ \lambda_{m+1}, \ldots, \lambda_{m+r} \} = \lambda(\Lambda_2)</math>;<br />
# ведущая подматрица порядка <math>m</math> в <math>X^{-1}</math> невырожденная.<br />
<br />
И пусть QR-алгоритм порождает последовательность матриц<br />
<br />
:<math>A_k = \begin{bmatrix}A_{11}^{(k)} & A_{12}^{(k)} \\ A_{21}^{(k)} & A_{22}^{(k)}\end{bmatrix}</math>.<br />
<br />
Тогда <math>A_{21}^{(k)} \rightarrow O</math> при <math> k \rightarrow \infty</math>.<br />
<br />
Если блок <math>A_{21}^{(k)}</math> достаточно мал, то он заменяется нулевым, и собственные значения ищутся для диагональных блоков <math>A_{11}^{(k)}</math> и <math>A_{22}^{(k)}</math>. <br />
<br />
Если условия 1-3 выполнены для всех <math>1 \le m \le n - 1</math>, то к нулю сходятся все поддиагональные блоки. А это значит, что все поддиагональные элементы матриц <math>A_k</math> стремятся к нулю, и диагональные элементы матриц <math>A_k</math> сходятся к искомым собственным значениям матрицы <math>A</math><ref name="Тыртышников"/>.<br />
<br />
=== Сокращение затрат на одну итерацию ===<br />
<br />
Одна QR-итерация для матрицы общего вида требует <math>O(n^3)</math> арифметических операций. Это очень большие затраты, даже если итераций не очень много (обычно их требуется не больше 5 на каждое собственное значение). <br />
Для решения данной проблемы используют тот факт, что с помощью отражений или вращений матрицу <math>A</math> можно привести к унитарно подобной верхней хессенберговой (почти треугольной) матрице:<br />
* <math>H = (h_{ij}), \quad h_{ij} = 0</math> при <math>i > j + 1</math>;<br />
* <math>H = PAP^*</math>, где <math>P</math> — произведение конечного числа отражений (или вращений). <br />
Затем QR-алгоритм применяется к матрице <math>A_0 = H</math>.<br />
<br />
Приведение к хессенберговой форме требует <math>O(n^3)</math> операций, но после него любая QR-итерация будет выполняться за <math>O(n^2)</math> операций. Сокращение затрат на одну итерацию объясняется инвариантностью хессенберговой формы по отношению к QR-итерациям<ref name="Тыртышников"/>.<br />
<br />
=== Уменьшение числа итераций ===<br />
<br />
Скорость сходимости алгоритма зависит от отношения модулей собственных значений: чем больше отношение <math>|\lambda_{m+1}| / |\lambda_m|</math>, тем медленнее убывает поддиагональный <math>(n-m) \times m </math> блок.<br />
Для увеличения скорости сходимости алгоритма можно перейти от матрицы <math>A</math> к матрице <math>(A - sI)</math>, где <math>s \in \mathbb{C}</math>, а <math>I</math> — единичная матрица, в надежде, что <math>|\lambda_{m+1} - s| / |\lambda_m - s| \ll |\lambda_{m+1}| / |\lambda_m|</math>.<br />
<br />
Такой подход называется QR-алгоритмом со сдвигами и имеет следуюший вид:<br />
<br />
<math>A_0 = A</math> — исходная матрица.<br />
Для <math>k = 1, 2, \ldots</math>:<br />
* <math>A_{k-1} - s_kI= Q_kR_k</math> (QR-разложение);<br />
* <math>A_k = R_kQ_k + s_kI</math>.<br />
<br />
QR-алгоритм со сдвигами является частным случаем обобщенного QR-алгоритма (QR-алгоритма с мультисдвигами), в котором на каждой итерации используется полином <math>f_k</math>:<br />
* <math>f_k(A_{k-1}) = Q_kR_k</math>;<br />
* <math>A_k = Q_k^{-1}A_{k-1}Q_k</math>.<br />
<br />
== Вычислительное ядро алгоритма ==<br />
<br />
Основное время работы алгоритма приходится на вычисление QR-разложения на итерациях алгоритма. <br />
<br />
Для получения QR-разложения могут использоваться [[Метод_Хаусхолдера_(отражений)_QR-разложения_матрицы|метод Хаусхолдера]], [[Метод_Гивенса_(вращений)_QR-разложения_матрицы|метод Гивенса]] или процесс ортогонализации Грама-Шмидта.<br />
<br />
При использовании хессенберговой формы приведение к ней имеет сложность, сравнимую с последующими итерациями QR-алгоритма.<br />
<br />
== Макроструктура алгоритма ==<br />
Основные элементы алгоритма:<br />
* (опционально) приведение к хессенберговой форме;<br />
* QR-разложение;<br />
* умножение плотных матриц.<br />
<br />
== Схема реализации последовательного алгоритма ==<br />
Пример реализации на языке Python:<br />
<br />
<syntaxhighlight lang="python"><br />
def get_eigenvalues(A, use_hessenberg_form):<br />
"""Вычисление собственных значений матрицы методом QR-разложения<br />
<br />
Параметры:<br />
A -- квадратная матрица<br />
use_hessenberg_form -- использовать ли приведение к хессенберговой форме<br />
"""<br />
if use_hessenberg_form:<br />
A = to_hessenberg_form(A) #Матрица приводится к хессенберговой форме<br />
while not is_upper_triangular(A): #Является ли матрица верхней треугольной<br />
Q, R = QR_decomposition(A) #Производится QR-разложение матрицы<br />
A = R * Q<br />
#Собственными значениями являются диагональные элементы полученной матрицы<br />
return diagonal_items(A)<br />
</syntaxhighlight><br />
<br />
== Последовательная сложность алгоритма ==<br />
<br />
Рассмотрим сложность отдельных элементов алгоритма:<br />
* QR-разложение имеет сложность <math>O(n^3)</math> арифметических операций;<br />
* сложность перемножения квадратных матриц равна <math>n^3 + O(n^2)</math>;<br />
* для проверки, является ли матрица верхней треугольной, необходимо <math>O(n^2)</math> операций.<br />
Таким образом, каждая итерация имеет кубическую сложность и, если алгоритм сходится через <math>N</math> итераций, общая сложность составит <math>N \times O(n^3)</math> арифметических операций.<br />
<br />
Сложность построения хессенберговой формы составляет <math>O(n^3)</math>, одна итерация алгоритма для хессенберговой матрицы имеет сложность <math>O(n^2)</math>. Общая сложность QR-алгоритма с предварительным приведением матрицы к хессенберговой форме равна <math>O(n^3 + N \times n^2)</math> арифметических операций, где <math>N</math> — число итераций алгоритма.<br />
<br />
== Информационный граф ==<br />
[[Файл:qr_algorithm_graph.png|thumb|center|640px|Рисунок 1. Информационный граф.]]<br />
Узлы графа:<br />
* <math>QR</math> — QR-разложение;<br />
* <math>\times</math> — перемножение матриц;<br />
* <math>isUT</math> — проверка, является ли матрица верхней треугольной.<br />
<br />
== Ресурс параллелизма алгоритма ==<br />
<br />
Несмотря на то что QR-алгоритм является последовательным, действия, выполняемые на каждой итерации, могут быть эффективно выполнены параллельно:<br />
* параллельная сложность QR-разложения составляет <math>O(n)</math> при использовании [[Метод_Гивенса_(вращений)_QR-разложения_матрицы|метода Гивенса]] или <math>O(n^2)</math> при использовании [[Метод_Хаусхолдера_(отражений)_QR-разложения_матрицы|метода Хаусхолдера]];<br />
* параллельная сложность умножения матриц равна <math>O(n)</math>;<br />
* для проверки, является ли матрица верхней треугольной, достаточно одного яруса.<br />
Таким образом, при классификации по высоте ярусно-параллельной формы сложность равна <math>N \times O(n)</math> или <math>N \times O(n^2)</math> в зависимости от используемого метода QR-разложения.<br />
<br />
При классификации по ширине ярусно-параллельной формы, сложность QR-алгоритма составляет <math>O(n^2)</math>.<br />
<br />
== Входные и выходные данные алгоритма ==<br />
<br />
'''Входные данные:''' плотная квадратная матрица <math>A</math> порядка <math>n</math>.<br />
<br />
'''Объем входных данных:''' <math>n^2</math>.<br />
<br />
'''Выходные данные:''' собственные числа матрицы <math>A</math>.<br />
<br />
'''Объем выходных данных:''' <math>n</math>.<br />
<br />
== Свойства алгоритма ==<br />
<br />
* Соотношение последовательной и параллельной сложности является квадратичным или линейным в зависимости от метода QR-разложения.<br />
* [[Глоссарий#Вычислительная мощность|Вычислительная мощность]] алгоритма равна <math>N \times O(n)</math>.<br />
* Число итераций <math>N</math> заранее неизвестно, то есть алгоритм недетерминирован.<br />
* Если рассмотренные в разделе «[[#Сходимость QR-алгоритма|Сходимость QR-алгоритма]]» условия 1-3 не выполнены, то, возможно, ни один из поддиагональных блоков не сходится к нулю. Это означает, что в общем случае QR-алгоритм не обязан сходиться. Однако внеся в элементы матрицы сколь угодно малые возмущения, всегда можно удовлетворить указанным условиям.<br />
* Скорость сходимости алгоритма зависит от отношения собственных значений, чем ближе по модулю собственные значения, тем медленее скорость сходимости.<br />
* Если матрица <math>A</math> эрмитова, то хессенбергова матрица <math>H</math> будет трехдиагональной, и сложность одной QR-итерации составит <math>O(n)</math> операций.<br />
<br />
= Программная реализация алгоритма =<br />
<br />
== Особенности реализации последовательного алгоритма ==<br />
<br />
== Локальность данных и вычислений ==<br />
<br />
== Возможные способы и особенности параллельной реализации алгоритма ==<br />
<br />
== Масштабируемость алгоритма и его реализации ==<br />
Исследование проводилось на узлах раздела test суперкомпьютера "Ломоносов"<ref name="Lomonosov">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Исследовалась реализация QR-алгоритма из библиотеки [https://software.intel.com/en-us/node/521079 Intel MKL] версии 11.2.0 (функции LAPACKE_sgehrd и LAPACKE_shseqr). Была написана программа на языке C, использовался компилятор [https://software.intel.com/en-us/c-compilers Intel C++]. Пример компиляции программы и запуска для матрицы размера <math>8000 \times 8000</math>:<br />
$ mpicc qr.c -o _scratch/qr -L${MKLROOT}/lib/intel64 -lmkl_rt -lpthread -lm -ldl<br />
$ sbatch -n 1 -p test run _scratch/qr 8000<br />
<br />
Оценивалось время вычисления собственных чисел произвольной квадратной матрицы для различных значений порядка матрицы (Matrix size) и числа потоков (Threads). Ввиду того, что количество операций зависит от числа итераций алгоритма, оценить [[Глоссарий#Производительность|производительность]] не удается.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число потоков [1 : 8];<br />
* размер матрицы [2000 : 8000] с шагом 500.<br />
<br />
На рисунке приведен график времени работы выбранной реализации QR-алгоритма в зависимости от изменяемых параметров запуска. <br />
<br />
[[Файл:Qr_algorithm_lapack.png|thumb|center|700px|Рисунок 2. Производительность QR-алгоритма.]]<br />
<br />
Из рисунка видно, что алгоритм хорошо масштабируется. Время выполнения в зависимости от размера задачи растет значительно быстрее при использовании одного потока по сравнению с использованиемм нескольких потоков. Увеличение числа потоков при фиксированном размере задачи приводит к уменьшению времени выполнения. Для матрицы размера <math>8000 \times 8000</math> время работы однопоточной реализации более чем в два раза превосходит время работы параллельной.<br />
<br />
Исходный код программы на языке C и скрипт для обработки результатов доступны на [https://github.com/f-morozov/algowiki-qr github].<br />
<br />
== Динамические характеристики и эффективность реализации алгоритма ==<br />
<br />
== Выводы для классов архитектур ==<br />
<br />
== Существующие реализации алгоритма ==<br />
<br />
#[http://www.netlib.org/lapack/ LAPACK] (Fortran, C). Имеется [https://software.intel.com/en-us/node/521079 реализация в Intel MKL]:<br />
#* ?geev - нахождение собственных значений матрицы;<br />
#* ?gehrd - приведение к хессенберговой форме;<br />
#* ?hseqr - нахождение собственных значений матрицы в хессенберговой форме.<br />
#[http://www.netlib.org/scalapack/ ScaLAPACK] (Fortran, C). Имеется [https://software.intel.com/en-us/node/521526 реализация в Intel MKL]:<br />
#* p?gehrd- приведение к хессенберговой форме;<br />
#* p?hseqr - нахождение собственных значений матрицы в хессенберговой форме.<br />
#[http://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eigvals.html numpy.linalg.eigvals] (Python) - использует ?geev из LAPACK.<br />
#[http://eigen.tuxfamily.org/dox/group__Eigenvalues__Module.html Eigen] (C++).<br />
#[http://www-03.ibm.com/systems/power/software/essl/ IBM ESSL] (Fortran, C, C++).<br />
<br />
= Литература =</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:F-morozov&diff=19332Обсуждение участника:F-morozov2016-11-28T06:50:48Z<p>VadimVV: </p>
<hr />
<div><br />
== Статья [[Нахождение_собственных_чисел_квадратной_матрицы_методом_QR_разложения_(4)]] ==<br />
<br />
=== По существу ===<br />
<br />
* Во вкладке для оценки общего числа операций дана цифра для неускоренного алгоритма. --[[Участник:Frolov|Фролов А.В.]] ([[Обсуждение участника:Frolov|обсуждение]]) 12:45, 10 ноября 2016 (MSK)<br />
* Приведен только приём ускорения в отношении одной итерации. Нет метода сдвигов для уменьшения итераций. --[[Участник:Frolov|Фролов А.В.]] ([[Обсуждение участника:Frolov|обсуждение]]) 12:45, 10 ноября 2016 (MSK)<br />
<br />
=== Замечания ===</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Elijah/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F&diff=19138Участник:Elijah/Нахождение собственных чисел квадратной матрицы методом QR разложения2016-11-25T07:34:01Z<p>VadimVV: </p>
<hr />
<div>{{algorithm<br />
| name = Нахождение собственных чисел квадратной матрицы методом QR разложения<br />
| serial_complexity = <math>N * O(n^3)</math><br />
| pf_height = <math> N * (12n - 15) </math><br />
| pf_width = <math> O(n^2) </math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: <br><br />
[[Участник:Elijah|И.В.Афанасьев]], разделы [[#Вычислительное ядро алгоритма|1.3]] - [[#Свойства алгоритма|1.10]] (базовый алгоритм), [[#Особенности реализации последовательного алгоритма|2.1]], [[#Возможные способы и особенности параллельной реализации алгоритма|2.3]], [[#Масштабируемость алгоритма и его реализации|2.4]], [[#Существующие реализации алгоритма|2.7]] <br><br />
<br />
[[Участник:Vladbrim|В.А.Шишватов]], разделы [[#Общее описание алгоритма|1.1]], [[#Математическое описание алгоритма|1.2]], [[#Вычислительное ядро алгоритма|1.3]] - [[#Свойства алгоритма|1.10]] ((алгоритм с преобразованием к матрице Хессенберга) <br><br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
QR-алгоритм — это численный метод в линейной алгебре, предназначенный для решения полной проблемы собственных значений, то есть отыскания всех собственных чисел и собственных векторов матрицы. Был разработан в конце 1950-х годов независимо В. Н. Кублановской и Дж. Фрэнсисом. [https://ru.wikipedia.org/wiki/QR-алгоритм] <br><br />
<br />
Данный алгоритм хорошо подходит для поиска собственных значений матриц общего вида, порядок которых составляет порядке тысячи. <br />
<br />
Суть алгоритма состоит в итеративном приведении матрицы A к верхнетреугольном виду с помощью последовательных QR разложений и матричный умножений. Данные матрицы на каждой итерации подобны между собой, так как данные умножения равносильны ортогональным преобразованиям. В следствии подобия, собственные значения этих матриц равны между собой.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== QR-алгоритм нахождения собственных чисел====<br />
<br />
Пусть <math>A</math> — вещественная матрица, для которой мы хотим найти собственные числа. Положим <math>A_0 = A</math>. На <math>k</math>-ом шаге (начиная с <math>k = 0</math>) вычислим QR-разложение <math>A_k = Q_k R_k</math>, где <math>Q_k</math> — ортогональная матрица (то есть <math>Q_k^\text{T} = Q_k^{-1}</math>), а <math>R_k</math> — верхняя треугольная матрица. Затем мы определяем <math>A_{k+1} = R_k Q_k</math>.<br />
<br />
Заметим, что<br />
: <math> A_{k+1} = R_k Q_k = Q_k^{-1} Q_k R_k Q_k = Q_k^{-1} A_k Q_k = Q_k^{T} A_k Q_k, </math><br />
то есть все матрицы <math>A_k</math> являются подобными, то есть их собственные значения равны.<br />
<br />
Пусть все диагональные миноры матрицы <math>A</math> вырождены. Тогда последовательность матриц <math>A</math> при <math> k \rightarrow \infty</math> сходится по форме к клеточному правому треугольному виду, соответствующему клеткам с одинаковыми по модулю собственными значениями.<br />
<ref>Численные методы / Н. С. Бахвалов, Н. П. Жидков, Г. М. Кобельков. — 3-е изд. — М: БИНОМ, Лаборатория знаний, 2004. — С. 321. — 636 с.</ref><br />
<br />
==== Сходимость QR-алгоритма нахождения собственных чисел ====<br />
<br />
Сходимость QR-алгоритма определяется как сходимость поддиагональной подматрицы матрицы <math>A_k</math> (<math>A_k = R_{k-1}Q_{k-1}</math>) к нулевой матрице.<br />
<br />
Предположим, что для матрицы <math>A \in \mathbb{C}^{n \times n}</math> выполнены следующие условия:<br />
<br />
# <math>A = X \Lambda X^{-1}, \quad \Lambda = \begin{bmatrix} \Lambda_1 & 0 \\ 0 & \Lambda_2 \end{bmatrix}, \quad \Lambda_1 \in \mathbb{C}^{m \times m}, \quad \Lambda_2 \in \mathbb{C}^{r \times r}</math>;<br />
# <math>\left | \lambda_1 \right | \ge \ldots \ge \left | \lambda_m \right | > \left | \lambda_{m+1} \right | \ge \ldots \ge \left | \lambda_{m+r} \right | > 0, \quad \{ \lambda_1, \ldots, \lambda_m \} = \lambda(\Lambda_1), \quad \{ \lambda_{m+1}, \ldots, \lambda_{m+r} \} = \lambda(\Lambda_2)</math>;<br />
# ведущая подматрица порядка <math>m</math> в <math>X^{-1}</math> невырожденная.<br />
<br />
Пусть QR-алгоритм порождает последовательность матриц вида:<br />
<br />
:<math>A_k = \begin{bmatrix} A_{11}^{(k)} & A_{12}^{(k)} \\ A_{21}^{(k)} & A_{22}^{(k)} \end{bmatrix}</math>.<br />
<br />
Тогда <math>A_{21}^{(k)} \rightarrow O</math> при <math> k \rightarrow \infty</math>. При достижении необходимой точности матрицу <math>A_{21}^{(k)}</math> можно заменит нулевой матрицей. В таком случае собственные значения необходимо искать для матриц <math>A_{11}^{(k)}</math> и <math>A_{22}^{(k)}</math>.<br />
<br />
Если условия 1-3 выполнены для всех <math>m \in \{1, ..., n-1\}</math>, то все поддиагональные матрицы сходятся к нулевым матрицам. Таким образом, все элементы, лежащие ниже главной диагонали стремятся к нулю, и диагональные элементы матриц <math>A_k</math> сходятся к искомым собственным значениям матрицы <math>A</math><ref>Тыртышников Е.Е. Методы численного анализа. — М.: Академия, 2007. — 320 c.</ref><br />
<br />
==== Приведение матрицы к матрице Хессенберга ====<br />
<br />
Для преобразования матрицы к матрице Хессенберга<ref>[https://en.wikipedia.org/wiki/Hessenberg_matrix| Hessenberg matrix]</ref> используется преобразование Хаусхолдера<ref>[https://en.wikipedia.org/wiki/Householder_transformation#Tridiagonalization| Tridiagonalization using Householder transformation]</ref>.<br />
<br />
Для каждого <math> k = 1,2,... n-1 </math><br />
<br />
:<math> \displaystyle \alpha = -\operatorname{sgn}(a^k_{k+1,k})\sqrt{\sum_{j=k+1}^{n}(a^k_{jk})^2} </math>;<br />
<br />
:<math> r = \sqrt{\frac{1}{2}(\alpha^{2}-a^k_{k+1,k}\alpha)} </math>;<br />
<br />
:<math>v^k_1 = v^{k}_2 = \cdots = v^k_k=0;</math><br />
<br />
:<math> v^{k}_{k+1} = \frac{a^{k}_{k+1,k}-\alpha}{2r}</math><br />
<br />
:<math> v^{k}_j = \frac{a^{k}_{jk}}{2r}</math>, для <math>j = k+2; k+3, ..., n</math><br />
<br />
:<math> \displaystyle P^{k} = I - 2v^{(k)}(v^{(k)})^\text{T}</math><br />
<br />
:<math>\displaystyle A^{(k+1)} = P^{k}A^{(k)}(P^{k})^\text{T}</math><br />
<br />
При этом матрица <math>P</math> не вычисляется и используется напрямую. Вместо этого используются следующие вычисления<br />
:<math>\displaystyle B^{(k)} = P^{k}A^{(k)} = (I - 2v^{(k)}(v^{(k)})^\text{T}) A^{(k+1)} = A^{(k+1)} - 2A^{(k+1)}v^{(k)}(v^{(k)})^\text{T}</math><br />
:<math>\displaystyle A^{(k+1)} <br />
= P^{k}A^{(k)}(P^{k})^\text{T} <br />
= B^{(k)}(P^{k})^\text{T} <br />
= ((B^{(k)})^\text{T})^\text{T}(P^{k})^\text{T} <br />
= (P^{k}(B^{(k)})^\text{T})^\text{T} <br />
= ((B^{(k)})^\text{T} - 2B^{(k)}v^{(k)}(v^{(k)})^\text{T})^\text{T}</math><br />
<br />
Таким образом перемножение матриц <math>P^{k}A^{(k)}</math> и <math>B^{k}P^{(k)}</math> имеет сложность <math>O(n^2) </math>(умножения матрицы на вектор) вместо <math>O(n^3)</math> (перемножение матриц)<br />
<br />
==== Использование сдвигов ====<br />
<br />
К сожалению, скорость сходимости алгоритма зависит от отношения модулей собственных значений. <br />
Поддиагональная подматрица <math>A_k</math> сходится к нулевой со скоростью <math>O(|\lambda_{k}| / |\lambda_{k+1}|)</math>. <br />
Таким образом, если у матрицы есть два очень близких значения, то соответсвующая подматрица будет очень медленно сходиться.<br />
<br />
По этой причине для увеличения скорости сходимости алгоритма переходят от матрицы <math>A</math> к матрице <math>(A - sI)</math>, где <math>s \in \mathbb{C}</math>, а <math>I</math> — единичная матрица, в надежде, что <math>|\lambda_{k} - s| / |\lambda_{k+1} - s| \gg |\lambda_k| / |\lambda_{k+1}|</math>.<br />
<br />
Такой подход называется QR-алгоритмом со сдвигами. Пусть <math>A_0 = A</math> — исходная матрица. <br />
Тогда для <math>k = 1, 2, \ldots</math>:<br />
* <math>A_{k-1} - s_kI= Q_kR_k</math> (QR-разложение);<br />
* <math>A_k = R_kQ_k + s_kI</math>.<br />
<br />
QR-алгоритм со сдвигами является частным случаем обобщенного QR-алгоритма, в котором на каждой итерации используется некоторый полином <math>f_k</math>:<br />
* <math>f_k(A_{k-1}) = Q_kR_k</math>;<br />
* <math>A_k = Q_k^{-1}A_{k-1}Q_k</math>.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
==== Базовый алгоритм ====<br />
<br />
У базового QR алгоритма есть два вычислительных ядра:<br />
# операция поиска QR разложения с использованием метода Гивенса<br />
# матричное умножение полученных матриц<br />
<br />
Для получения <math>QR</math>-разложения методом Гивенса <math>A</math> матрицу приводят к правой треугольной <math>R</math> последовательностью умножений её слева на матрицы вращения <math>T_{1 2}, T_{1 3}, ..., T_{1 n}, T_{2 3}, T_{2 4}, ..., T_{2 n}, ... , T_{n-2 n}, T_{n-1 n}</math>. <br />
<br />
Матричное умножение может быть выполнено любым классическим способом: для плотных матриц <math>A</math> (элементы <math>a_{ij}</math>) и <math>B</math> (элементы <math>b_{ij}</math>) вычисляется плотная матрица <math>C</math>с элементами <math>c_{ij}</math> по формуле:<br />
<br />
Формулы метода:<br />
:<math><br />
\begin{align}<br />
c_{ij} = \sum_{k = 1}^{n} a_{ik} b_{kj}, \quad i \in [1, m], \quad i \in [1, l].<br />
\end{align}<br />
</math><br />
<br />
==== Алгоритм с преобразованием к матрице Хессенберга ====<br />
<br />
Для QR алгоритма с использования матрицы Хессенберга:<br />
# получение матрицы Хессенберга из исходной матрицы<br />
# операция поиска QR разложения с использованием модифицированного метода Гивенса<br />
<br />
Для преобразования исходной матрицы к матрице Хессенберга используются преобразования Хаусхолдера (см. [[#Приведение матрицы к матрице Хессенберга| раздел 1.2.3]])<br />
<br />
Для получения <math>QR</math>-разложения методом Гивенса для матрицы Хессенберга <math>A</math>, матрицу приводят к правой треугольной <math>R</math> последовательностью умножений её слева на матрицы вращения <math>T_{1 2}, T_{2 3}, T_{3 4}, ..., T_{(n-1)\ n}</math>. <br> <br><br />
<br />
Более подробное описание вычислительных ядер можно найти по соответствующим ссылкам в [[#Макроструктура алгоритма| разделе 1.4]].<br />
<br />
=== Макроструктура алгоритма ===<br />
<br />
Как уже описано в описании ядра алгоритма, базовая версия QR-алгоритма на каждой итерации использует следующие алгоритмы: <br />
# [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]] или [[Метод_Хаусхолдера_(отражений)_QR-разложения_квадратной_матрицы,_вещественный_вариант|Метод Хаусхолдера(отражений) QR-разложения_квадратной_матрицы]]<br />
# [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]<br />
<br />
В случае использования матрицы Хессенберга:<br />
# Получение матрицы Хессенберга из исходной матрицы с помощью преобразований Хаусхолдера<ref>[https://en.wikipedia.org/wiki/Householder_transformation| Householder transformation]</ref>.<br />
# [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]. При этом количество элементов матрицы, которые нужно занулять, на порядок меньше, чем в базовом варианте.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
==== Базовый алгоритм ====<br />
<br />
Последовательная реализация простейшего алгоритма состоит из некоторого числа итераций<br />
<br />
Каждая итерация состоит из следующих действий (с начальным условием <math> A_0 = A</math>):<br />
# Для матрицы <math> A_{n} </math> строится QR разложение любым доступным последовательным алгоритмом на матрицы <math> Q_{n} </math> и <math> R_{n} </math> <br><br />
# Матрицы <math> R_{n} </math> и <math> Q_{n} </math> перемножаются, таким образом получается матрица <math> A_{n+1} = R_{n} * Q_{n} </math> для следующей итерации <math> n + 1 </math> <br />
# Проверятся, приведена ли матрица к верхнетреугольной форме, и если да, то производится выход из цикла.<br />
<br />
==== Алгоритм с преобразованием к матрице Хессенберга ====<br />
<br />
На первом этапе исходная матрица <math>A</math> преобразуется к матрице Хессенберга <math>H</math>, которая будет иметь такие же собственные значения, что и изначальная матрица.<br />
<br />
Затем производится некоторое число итераций, состоящих из следующих действий (с начальным условием <math> A_0 = H</math>): <br />
# Для матрицы <math> A_{n} </math> строится QR разложение с помощью метода вращения Гивенса. При этом занулять нужно не все элементы, а только <math> a_{i + 1, i} </math>, где <math>i \in {1, n - 1}</math>. Полученная матрица будет опять являться матрицей Хессенберга.<br />
# Проверятся, приведена ли матрица к верхнетреугольной форме, и если да, то производится выход из цикла.<br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
==== Последовательная сложность базового алгоритма ====<br />
Рассчитаем последовательную сложность базового алгоритма. Пусть <math style="vertical-align:0%"> A \in \mathbb{R}^{n \times n}</math>, и для приведения матрицы к треугольной форме необходимо произвести N итераций алгоритма. <br><br />
На каждой итерации алгоритма производится QR разложение (сложностью <math style="vertical-align:0%"> 2 * n^3 </math>) и матричное умножение (сложностью <math style="vertical-align:0%"> n^3 </math>). Проверка того, имеет ли матрица треугольную форму, может быть проведена за <math style="vertical-align:0%"> n^2 </math> операций. <br><br />
Таким образом итоговая сложность одной итерации составляет: <math> 3*n^3 + n^2 = O(n^3)</math> <br><br />
Общая сложность алгоритма при <math> N </math> итерациях составляет <math> N * O(n^3) </math><br />
<br />
==== Последовательная сложность алгоритма с приведением матрицы к матрице Хессенбрга ====<br />
<br />
Данный алгоритм состоит из двух этапов:<br />
# преобразование исходной матрицы к матрице Хессенберга<br />
# QR-разложение методом Гивенса<br />
<br />
===== Сложность алгоритма приведения матрицы к матрице Хессенбрга =====<br />
<br />
На каждой итерации алгоритма приведения матрицы к матрице Хессенбрга требуется вычислить: <br />
# <math>\alpha</math> со сложность <math>O(n)</math><br />
# <math>r</math> со сложностью <math>O(1)</math><br />
# <math>v^k</math> со сложностью <math>O(n)</math><br />
# <math>A^{(k+1)}</math> со сложностью <math>O(n^2)</math><br />
Таким образом вычислительная сложность одной итерации равна <math>O(n^2)</math>.<br />
Всего таких итераций <math>n</math>, поэтому общая сложность алгоритма равна <math>O(n^3)</math>.<br />
<br />
===== Сложность алгоритма QR-разложения методом Гивенса =====<br />
<br />
Метод Гивенса использует матрицы вращения, чтобы занулить ненулевые элементы матрицы под главной диагональю на каждой итерации. Умножение на одну матрицу вращения имеет вычислительную сложность <math>O(n)</math>. Для случая произвольной матрицы в худшем случае потребуется занулить <math>\frac{n^2 - n}{2}</math> элементов. Если матрица является матрицей Хессенберга, то таких элементов будет всего лишь <math>n - 1</math>.<br />
<br />
Таким образом, для произвольной матрицы одна итерация алгоритма QR-разложения имеет вычислительную сложность равную <math>O(n^3)</math>, для матрицы Хессенберга - <math>O(n^2)</math>. Проверка на то, является ли матрица треугольной, имеет сложность <math>O(n^2)</math>, что не влияет на порядок сложности.<br />
<br />
Если всего требуется <math>N</math> итераций для преобразования матрицы к треугольному виду, то для для произвольной матрицы сложность будет <math>N * O(n^3)</math>, для матрицы Хессенберга - <math>N * O(n^2)</math><br />
<br />
===== Итоговая сложность =====<br />
<br />
Таким образом, общая вычислительная сложность алгоритма вычисления собственных значений с приведением матрицы к матрице Хессенбрга равна <math>O(n^3) + N * O(n^2)</math><br />
<br />
=== Информационный граф ===<br />
<br />
Макрограф алгоритма изображён на рисунке 1. Он представляет из себя верхний уровень алгоритма, на котором в цикле итеративно вызываются следующие операции: <br><br />
<br />
# Инициализация матриц ('''init matrices''')<br />
# QR разложение ('''QR decomposition''')<br />
# Матричное умножение ('''matrix multiplication''')<br />
# Проверка, приведена ли матрица к верхней треугольной форме ('''check if matrix is upper triangular''')<br />
# Получение собственных значений из диагонали матрицы ('''get eigenvalues from matrix diagonal''')<br />
<br />
Информационные графы [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|QR разложения]] и [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|матричного умножения]] могут быть найдены по приведенным ссылкам. Информационный граф проверки того, приведена ли матрица к верхнетреугольной форме, будет приведен далее на рисунке 2. Информационные графы инициализации и получения собственных значений представляют собой работу с вводом и выводом входных/выходных данных, и, поэтому, рассматриваться не будут. <br />
<br />
[[Файл:Screen_Shot_2016-10-15_at_12.43.32_PM.png|thumb|center|600px|Рисунок 1. Информационный граф базовой версии QR-алгоритма без отображения входных и выходных данных. N итераций.]]<br />
<br />
[[Файл:Check_if_matrix_is_upper_triangular.png|thumb|center|300px|Рисунок 2. Информационный граф проверки приведенности матрицы к верхнетреугольной форме.]] <br><br />
<br />
В случае оптимизации алгоритма с приведением матрицы к форме Хессенберга, инициализация матрицы (int matrices (1)), будет включать в себя преобразование хаусхолдера, имеющее следующий граф алгоритма:<br />
<br />
[[Файл:Hessenberg_transformation.png|thumb|center|500px|Рисунок 3. Информационный граф проверки хаусхолдера.]] <br><br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
<br />
Все итерации базового QR-алгоритма производятся последовательно, поэтому на верхнем уровне алгоритм чисто последователен. <br><br />
<br />
Основной ресурс параллелизма представлен на нижнем уровне, при реализации различных операций, используемых в алгоритме, таких как QR разложение методом вращений и матричное умножение. <br> <br><br />
<br />
==== Параллельная сложность базового QR-алгоритма ====<br />
Посчитаем параллельную сложность каждой операции по отдельности, а затем, по полученным данным, и всего алгоритма целиком: <br><br />
# Параллельная сложность QR разложения методом вращений составляет <math> 11n - 16 </math> <ref name="QRGivens">[[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]</ref> <br><br />
# Параллельная сложность матричного умножения составляет <math> n </math><ref name="MatrixMultiplication">[[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]</ref>.<br><br />
# Параллельная сложность проверки, является ли матрица верхнетреугольной, равна n. <br><br />
Таким образом параллельная сложность каждой итерации составляет <math> 13n - 15 </math>. При N итерациях, которые необходимо производить последовательно, итоговая сложность базового алгоритма составляет <math> N * (13n - 15) </math>, что в упрощенном виде равно <math> N * O(n) </math> <br><br />
<br />
==== Параллельная сложность алгоритма с приведением матрицы к форме Хессенберга: ====<br />
Снова вычислим параллельную сложность каждой операции по отдельности, а затем, по полученным данным, и всего алгоритма целиком: <br><br />
# Параллельная сложность приведения матрицы к форме Хессенберга составляет <math> O(n^2) </math><br />
# Параллельная сложность каждой итерации с приведенной матрицы составляет <math> O(n) </math><br />
<br />
Таким образом параллельная сложность всего алгоритма при N итерациях составляет <math> O(n^2) + N*O(n) </math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
<br />
* '''Входные данные''': плотная квадратная матрица <math style="vertical-align:0%">A</math> (элементы <math>a_{ij}</math>).<br />
<br />
* '''Объём входных данных''': <math style="vertical-align:0%">n^2</math>.<br />
<br />
* '''Выходные данные''': <math>n</math> вещественных собственных чисел <math> | l_{i} | </math> матрицы <math style="vertical-align:0%">A</math> <br />
<br />
* '''Объём выходных данных''': <math>n</math>.<br />
<br />
=== Свойства алгоритма ===<br />
<br />
Соотношение последовательной и параллельной сложности для базового является квадратичным, что даёт хороший стимул для распараллеливания. Для оптимизированного алгоритма данное соотношение линейно.<br />
<br />
Вычислительная мощность, равная отношению числа операций <math> N * O(n^3) </math> к суммарному объему входных и выходных данных <math> n^2 + n </math>, для каждой итерации линейна, а для всего алгоритма в целом равна <math> N*n </math>, что позволяет сделать вывод о том, что перемещение данных для обработки не играет важной роли в данном алгоритме. <br />
<br />
Для оптимизированного алгоритма вычислительная мощность так же линейна (приведение матрицы к форме Хессенберга имеет сложность <math> O(n^3) </math>, а объем выходных данных <math> O(n^2) </math>).<br />
<br />
Вычислительная погрешность растет линейно, из-за использования метода вращений для QR разложения. <br />
<br />
Данный алгоритм недетерминирован, так как число итераций зависит от значений элементов матрицы и выход из основного цикла происходит по достижению некоторой точности. В зависимости от этого, вычислительный поток имеет различную длину. <br />
На каждой отдельной итерации в свою очередь алгоритм полностью детерминирован, так как детерминированы его обе базовые макрооперации - QR разложение и матричное умножение.<br />
<br />
== Программная реализация ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
Основным типом данных для QR алгоритма является матрица. В данной статье представлена реализация на языке C++, что накладывает определенные ограничения на формат хранени матриц: все матрицы хранятся в одномерном массиве в построчном (ROW_MAJOR) формате. <br />
<br />
В простейшем случае QR-алгоритм выглядит следующим образом:<br />
<br />
<source lang="C++"><br />
<br />
int sequential_QR_algorithm(float *A, int size)<br />
{<br />
float *Q = new double[size * size];<br />
float *R = new double[size * size];<br />
while(!check_if_upper_triangular(A, size)))<br />
{<br />
sgeqrf (ROW_MAJOR, size, size, A, size, tau);<br />
set_Q_and_R(A, Q, R, size);<br />
sorgqr(ROW_MAJOR, size, size, size, Q, size, tau);<br />
sgemm (RowMajor, NoTrans, NoTrans, size, size, size, 1, R, size, Q, size, 0, A, size);<br />
}<br />
}<br />
</source><br />
<br />
В последовательном случае функции dgeqrf, dorgqr и dgemm представляют собой последовательные функции стандарта BLAS, вычисляющие QR разложение(geqrf), матрицу Q после разложения (dorgqr) и матричное умножение (dgemm). <br />
Так же здесь используются простейшие функции set_Q_and_R и check_if_upper_triangular. Функция set_Q_and_R получает после QR разложения из матрицы A данные о матрицах Q и R. Функция check_if_upper_triangular проверяет, приведена ли матрица A к треугольном виду, и если да, происходит выход из цикла.<br />
<br />
Для работы программы в памяти необходимо хранить 3 матрицы, таким образом общий объем составляет 3 * n^2. Так же необходим вспомогательный массив tau для QR разложения, однако его размер составляет всего n. Так же важно заметить, что различные реализации функций dgeqrf, dorgqr и dgemm обычно используют внутренние вспомогательные массивы для блочной обработки, однако, их размер не превышает n * C, где C - некоторая архитектурно зависимая константа. <br />
<br />
Все вычисления рекомендуется производить в одинарной точности.<br />
<br />
Как уже говорилось ранее, внешний цикл представленной программы сугубо последователен (итеративен), и основной ресурс параллелизма расположен в вызываемых функциях (geqrf, gemm). Их реализации бывают крайне различны, поэтому данный алгоритм можно легко преобразовать для любой архитектуры, используя набор стандартных библиотечных функций. К примеру, для реализации алгоритма на GPU можно использовать библиотеку MAGMA, на многоядерных CPU - Intel MKL.<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
Как уже говорилось в [[#Особенности реализации последовательного алгоритма| разделе 2.1]], при использовании различных библиотек можно реализовать данный алгоритм практически на всех архитектурах. Далее более подробно будут рассмотрены примеры для некоторых из них. Особенности параллельных реализаций для базовых макроопераций (QR разложения и матричного умножения), уже описаны в соответствующих статьях (см. ссылки в [[#Макроструктура алгоритма| разделе 1.4]]) <br />
<br />
'''Реализация на многоядерных CPU (одном узле)'''<br />
<br />
Для распарарллеливания на многоядерных CPU используются функции LAPACKE_sgeqrf, LAPACKE_sorgqr и cblas_sgemm. Распараллеливание оставшихся функций может быть произведено с использованием openMP, так как внутренние циклы в них независимы по данным.<br />
<br />
Исходный код предложенной реализации представлен далее:<br />
<br />
<source lang="C++"><br />
<br />
int CPU_QR_algorithm(float *A, int size)<br />
{<br />
float *Q = new double[size * size];<br />
float *R = new double[size * size];<br />
while(!check_if_upper_triangular(A, size)))<br />
{<br />
LAPACKE_sgeqrf (LAPACK_ROW_MAJOR, size, size, A, size, tau);<br />
set_Q_and_R(A, Q, R, size);<br />
LAPACKE_sorgqr(LAPACK_ROW_MAJOR, size, size, size, Q, size, tau);<br />
cblas_sgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans, size, size, size, 1, R, size, Q, size, 0, A, size);<br />
}<br />
}<br />
</source><br />
<br />
'''Реализация на GPU (и multi-GPU)'''<br />
<br />
Для реализации алгоритма на GPU и multi-GPU можно использовать аналогичные функции dgemm и geqrf из библиотеки [[ Magma. http://icl.cs.utk.edu/magma/ | magma]].<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
<br />
Для данного алгоритма была проверена масштабируемость оптимизированной версии с приведением матрицы к форме Хессенберга. Тесты проводились для случайно сгенерированной матрицы размера 8000x8000 на 1-14 параллельных вычислительных потоках.<br />
<br />
Код, для которого проверлась масштабируемость, реализован с помощью функций библиотеки intel MKL, выглядит следующим образом: <br><br />
<br />
<source lang="C++"><br />
<br />
#include "stdio.h"<br />
#include "stdlib.h"<br />
#include "mkl.h"<br />
#include "math.h"<br />
#include <iostream><br />
<br />
using namespace std;<br />
<br />
int main(int argc, char* argv[])<br />
{<br />
int max_threads;<br />
float *h, *Wr, *Wi, *z, *scalar, *h_copy;<br />
double s_initial, s_elapsed;<br />
lapack_int n = atoi(argv[1]); //size=n<br />
<br />
h=(float*)mkl_malloc(n*n*sizeof(float),32);<br />
h_copy=(float*)mkl_malloc(n*n*sizeof(float),32);<br />
z=(float*)mkl_malloc(n*n*sizeof(float),32); //z will not be referenced due to set compz="N"<br />
Wr=(float*)mkl_malloc(n*sizeof(float),32);<br />
Wi=(float*)mkl_malloc(n*sizeof(float),32);<br />
scalar=(float*)mkl_malloc((n-1)*sizeof(float),32);<br />
if(h==NULL) //initilization for hessenberg matrix<br />
{<br />
for(int i=0;i<n*n;i++)<br />
{<br />
h[i]=(float)rand()*2000;<br />
h_copy[i]=h[i];<br />
}<br />
}<br />
for(int j=0;j<n*n;j++)<br />
{<br />
z[j]=0.0;<br />
}<br />
max_threads=mkl_get_max_threads();<br />
<br />
for(int t = 1;t <= max_threads;t++)<br />
{<br />
double t1 = dsecnd();<br />
mkl_set_num_threads(t);<br />
LAPACKE_sgehrd(LAPACK_ROW_MAJOR, n, 1, n, h, n, scalar);<br />
LAPACKE_shseqr(LAPACK_ROW_MAJOR, 'E', 'N', n, 1, n, h, n, Wr, Wi, z, n);<br />
double t2 = dsecnd();<br />
<br />
cout << "total time for " << t << " threads: " << t2 - t1 << endl << endl;<br />
<br />
for(int k=0;k<n*n;k++)<br />
{<br />
h[k]=h_copy[k];<br />
}<br />
}<br />
return 0;<br />
}<br />
<br />
</source><br />
<br />
Далее приводятся график сильной масштабируемости. Данные результаты были получены на суперкомпьютере "Ломоносов-2", каждый узел которого оборудован 14-ядерным процессором Intel Xeon E5 v3. Тестирование производилось на одном узле данного типа, с использованием библиотеки MKL версии 11.1.3.<br />
<br />
[[file:QR_algorithm_strong_scalability.png|thumb|center|500px|Рис.4 Сильная масштабируемость оптимизированного QR алгоритма с приведением матрицы к форме Хессенберга]]<br />
<br />
Из данных графика видно, что алгоритм хорошо масштабируется при небольшом числе процессов (1-4), однако дальнейшее прибавление процессов не ведет к линейному ускорению.<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
<br />
'''Последовательный''' QR алгоритм, а так же различные базовые функции для его реализации, можно найти в [http://www.netlib.org/scalapack/ библиотеке LAPACK] и её аналогах и расширениях.<br />
<br />
Базовая версия QR алгоритма может быть реализована описанным в [[#Особенности реализации последовательного алгоритма| разделе 2.1]] способом при помощи функций:<br />
# '''?geqrf''' - вычисляет QR разложение произвольной матрицы<br />
# '''?gemm''' - вычисляет произведение двух плотных матриц<br />
<br />
'''?steqr''' - вычисляет собственные числа и собственные вектора для случая тридиагональной матрицы. <br />
<br />
Для реализации оптимизированного алгоритма с приведением матрицы к матрице Хессенберга можно использовать следующие функции:<br />
# '''?gehrd''' - приводят матрицу общего вида к верхней матрице Хессенберга с использованием ортогональных/унитарных трансформаций<br />
# '''?hseqr''' - вычисляют собственные значения матрицы в верхней матрице Хессенберга с использованием QR алгоритма<br />
<br />
Подробная документация по упомянутым функциям может быть найдена [http://physics.oregonstate.edu/~landaur/nacphy/lapack/eigen.html здесь].<br />
<br />
'''Параллельный''' QR алгоритм может быть реализован с помощью библиотек [http://www.netlib.org/scalapack/ ScaLAPACK] и [http://www.cs.utexas.edu/users/plapack/ PlaLAPACK].<br />
<br />
'''p?geqrf''', '''p?gemm''', '''p?hseqr''' - аналогичные функции ScaLAPACK.<br />
<br />
'''PLA_QR''', '''PLA_Gemm''' - аналогичные функции из PlaLAPACK.<br />
<br />
== Примечания ==<br />
<br />
== Литература ==</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Elijah&diff=19137Обсуждение участника:Elijah2016-11-25T07:33:53Z<p>VadimVV: </p>
<hr />
<div><br />
<br />
== Статья [[Участник:Elijah/Нахождение собственных чисел квадратной матрицы методом QR разложения]] ==<br />
<br />
=== По существу ===<br />
<br />
* Кроме приведения к хессенбергу, типовые алгоритмы из библиотек используют также сдвиги. Они здесь не описаны, а сделать это необходимо. --[[Участник:Frolov|Фролов А.В.]] ([[Обсуждение участника:Frolov|обсуждение]]) 12:55, 10 ноября 2016 (MSK)<br />
<br />
=== Замечания ===<br />
* П. 2.4 - необходимо указать характеристики программно-аппаратной платформы, на которой проводились эксперименты ('''какой компилятор и с какими опциями использовался''' (если специальных ключей компиляции не было, надо указать это явно) ). --[[Участник:VadimVV|VadimVV]] ([[Обсуждение участника:VadimVV|обсуждение]]) 10:33, 25 ноября 2016 (MSK)</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:VadimVV&diff=18914Участник:VadimVV2016-11-22T07:50:03Z<p>VadimVV: </p>
<hr />
<div>Воеводин Вадим Владимирович, к.ф.-м.н, с.н.с. лаборатории Параллельных информационных технологий Научно-исследовательского вычислительного центра Московского государственного университета имени М.В.Ломоносова.</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:SKirill/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=18913Участник:SKirill/Метод Ньютона решения систем нелинейных уравнений2016-11-22T07:49:39Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
<br />
{{algorithm<br />
| name = Метод Ньютона решения систем нелинейных уравнений<br />
| serial_complexity = <math>O(L \cdot n^3)</math>, если для решения СЛАУ использовать метод Гаусса, алгоритм сойдется за <math>L</math> итераций, вычисление значения каждой функции системы и их производных в точке имеет оценку сложности <math>O(n)</math><br />
| input_data = <math>n</math> функций от <math>n</math> переменных, <math>n^2</math> частных производных этих функций по каждой переменной, <math>n</math>-мерный вектор(начальное приближение решения), <math>\varepsilon</math> - требуемая точность, <math>max\_iter</math> - максимальное число итераций<br />
| output_data = <math>n</math>-мерный вектор<br />
}}<br />
Авторы: [[Участник:SKirill|Шохин К.О.]](1.1,1.2,1.4,1.6,1.8,1.10,2.7), [[Участник:Лебедев Артём|Лебедев А.А.]](1.1,1.3,1.5,1.7,1.9,2.7)<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
<br />
Метод Ньютона<ref name="Tyrtysh">Тыртышников Е. Е. "Методы численного анализа" — М., Академия, 2007. - 320 c.</ref><ref name="Bahvalov">Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. "Численные Методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref> решения систем нелинейных уравнений является обобщением метода Ньютона решения нелинейных уравнений, который основан на идее линеаризации. Пусть <math> F(x) : \R^1 \to \R^1</math> - дифференцируемая функция и необходимо решить уравнение <br />
<math>F(x) = 0</math>.<br />
<br />
Взяв некоторое <math>x_0</math> в качестве начального приближения решения, мы можем построить линейную аппроксимацию <br />
<br />
<math>F(x)</math> в окрестности <math>x_0 : F(x_0+h) \approx F(x_0)+F^'(x_0)h</math> и решить получающееся линейное уравнение <math>F(x_0 )+F^' (x_0 )h =0</math>.<br />
<br />
<br />
Таким образом получаем итеративный метод : <math> x_{k+1} = x_k - {F^'(x_k)}^{-1}F(x_k) , k = 0,1,\ldots </math> .<br />
<br />
<br />
Данный метод был предложен Ньютоном в 1669 году<ref name = Polyak>Поляк Б. Т. "Метод Ньютона и его роль в оптимизации и вычислительной математике" - Труды ИСА РАН 2006. Т. 28</ref>. Более точно, Ньютон оперировал только с полиномами; в выражении для <math>F(x+h)</math> он отбрасывал члены более высокого порядка по h , чем линейные. Ученик Ньютона Рафсон в 1690 г. предложил общую форму метода (т. е. не предполагалось что <math>F(x)</math> обязательно полином и использовалось понятие производной), поэтому часто говорят о методе Ньютона—Рафсона.<br />
Дальнейшее развитие исследований связано с именами таких известных математиков, как Фурье, Коши и другие. Например, Фурье доказал в 1818 г., что метод сходится квадратично в окрестности корня, а Коши (1829, 1847) предложил многомерное обобщение метода и использовал метод для доказательства существования решения уравнения.<br />
<br />
=== Математическое описание алгоритма ===<br />
Пусть дана система из <math>n</math> нелинейных уравнений с <math>n</math> неизвестными.<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
f_1(x_1, \ldots, x_n) = 0,<br />
\\ f_2(x_1, \ldots, x_n) = 0,<br />
\\ \vdots<br />
\\ f_n(x_1, \ldots, x_n) = 0.<br />
\end{matrix}\right.<br />
</math><br />
, где <math>f_i(x_1, \ldots,x_n) : \R^n \to \R, i = 1, \ldots ,n</math> - нелинейные функции, определенные и непрерывно дифференцируемые в некоторой <br />
<br />
<br />
области <math>G \subset \R^n</math>.<br />
<br />
Запишем ее в векторном виде: <br />
<br />
<math>\overline{x} = {(x_1,x_2,\ldots,x_n)}^T, F(x) ={[f_1(x),f_2(x),\ldots,f_n(x)]}^T, F(x)=0</math><br />
<br />
Требуется найти такой вектор <math>\overline{x^*} = {(x^*_1,x^*_2,\ldots,x^*_n)}^T</math>, который, при подстановке в исходную систему, превращает каждое уравнение в верное числовое равенство.<br />
<br />
<br />
При таком подходе формула для нахождения решения является естественным обобщением формулы одномерного итеративного метода:<br />
<br />
<math>x^{(k+1)} = x^{(k)} - W^{-1}(x^{(k)})\cdot F(x^{(k)}) , k=0,1,2,\ldots</math>, где <br />
<br />
<math> W = \begin{pmatrix} <br />
\frac{\partial{f_1(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_1(x_n)}}{\partial{x_n}}<br />
\\ \vdots & \ddots & \vdots<br />
\\ \frac{\partial{f_n(x_1)}}{\partial{x_1}} & \cdots & \frac{\partial{f_n(x_n)}}{\partial{x_n}}<br />
\end{pmatrix}</math> – матрица Якоби.<br />
<br />
В рассмотренных предположениях относительно функции <math>F(\cdot)</math> при выборе начального приближения <math>x^{(0)}</math> из достаточно малой окрестности решения <math>\overline{x^*}</math> имеет место сходимость последовательности <math>\{x^{(k)}\}</math>. При дополнительном предположении <math>F(\cdot) \in C^2</math> имеет место квадратичная сходимость метода.<br />
<br />
В качестве критерия окончания процесса итераций обычно берут условие <math>\left \| x^{(k+1)} - x^{(k)} \right \| < \varepsilon</math>, где <math>\varepsilon</math> - требуемая точность решения.<br />
<br />
Основная сложность метода Ньютона заключается в обращении матрицы Якоби. Вводя обозначение <math>\Delta x^{(k)} = x^{(k+1)} - x^{(k)}</math> получаем СЛАУ для вычисления <math>\Delta x^{(k)}:</math><br />
<math>\frac{\partial{F(x^{(k)})}}{\partial{x}} = -F(x^{(k)})</math><br />
<br />
Тогда <math>x^{(k+1)} = x^{(k)}+ \Delta x^{(k)}.</math><br />
<br />
Часто метод Ньютона модифицируют следующим образом. По ходу вычислений или заранее выбирают возрастающую последовательность чисел <math>n_0=0, n_1,\ldots</math><br />
<br />
При <math>n_i \le k < n_{i+1}</math> вычисление <math>\Delta x^{(k)}</math> осуществляют по следующей формуле:<br />
<br />
<math>\frac{\partial{F(x^{n_i})}}{\partial{x}} = -F(x^{(k)}).</math><br />
<br />
Увеличение числа итераций, сопровождающее такую модификацию, компенсируется «дешевизной» одного шага итерации.<br />
<br />
=== Вычислительное ядро алгоритма ===<br />
<br />
Основная вычислительная нагрузка алгоритма заключается в решении СЛАУ:<br />
<br />
<math>\frac{\partial F(x^{(k)})}{\partial x}\Delta x^{(k)} = -F(x^{(k)}) \qquad(*)</math><br />
<br />
Для нахождения <math>\Delta x^{(k)}</math>, по которому вычисляется значение вектора <math>\overline{x}</math> на очередной итерации: <math>x^{(k+1)} = x^{(k)} + \Delta x^{(k)}.</math><br />
<br />
=== Макроструктура алгоритма ===<br />
Как уже было сказано, основную часть каждой итерации метода составляет нахождение матрицы Якоби и решение СЛАУ <math>(*)</math> для нахождения <math>\Delta x^{(k)}.</math><br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<br />
Формулы метода описаны выше. ниже представлена схема алгоритма, в которой:<br />
<br />
# n - число уравнений в СЛАУ<br />
# Max - предельное число итераций<br />
# <math>\varepsilon</math> - точность вычислений<br />
# <math>x^{(0)}</math> - начальное приближение<br />
<br />
[[file:NewtonScheme.png|thumb|center|700px|Рис.1 Схема реализации последовательного алгоритма]]<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Пусть вычисление значения первой производной функции <math>f_i(x_1,x_2,\ldots,x_n)</math> в точке <math>x^{(k)}</math> имеет оценку сложности <math>D_i</math>, решение СЛАУ <math>(*)</math> имеет оценку сложности <math>S</math>(в эту оценку также включаются операции по нахождению элементов матрицы Якоби), а оценка сложности сложения векторов – <math>V</math>. Тогда оценка сложности одной итерации представляется в виде:<br />
<br />
<math>\sum^{n}_{i=1} {D_i} + S + V</math><br />
<br />
Количество итераций определяется скоростью сходимости алгоритма.<br />
Скорость сходимости метода Ньютона в общем случае является квадратичной. Однако успех или неуспех применения алгоритма во многом определяется выбором начального приближения решения. Если начальное приближение выбрано достаточно хорошо и матрица системы линейных уравнений на каждой итерации хорошо обусловлена и имеет обратную матрицу, то метод Ньютона сходится к единственному в данной окрестности решению. На практике критерием работоспособности метода является число итераций: если оно оказывается большим (для большинства задач >100), то начальное приближение выбрано плохо.<br />
<br />
В качестве примера можно рассмотреть алгоритм Ньютона, использующий для решения СЛАУ метод Гаусса. Рассмотрим решение системы, для которой справедливы следующие предположения:<br />
<br />
* Вычисление значения каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
* Вычисление значения производной каждой функции в заданной точке требует <math>O(n)</math> операций.<br />
<br />
<br />
В таких предположениях указанное выражение для вычисления сложности примет следующий вид:<br />
<br />
<math>O(n^2) + O(n^3) + O(n) = O(n^3)</math><br />
<br />
<br />
Предположим также, что выполнены все условия для квадратичной сходимости метода, и для решения системы потребуется небольшое(<100) количество итераций.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot n^3)</math>.<br />
<br />
=== Информационный граф ===<br />
[[file:infograph.png|thumb|center|700px|Рис.2 Информационный граф алгоритма]]<br />
<br />
На рисунке изображена структура информационного графа алгоритма. Блок IN представляет собой выбор начального приближения, необходимой точности. Каждый блок IT соответствует одной итерации алгоритма. Блок OUT соответствует успешному окончанию алгоритма. <br />
<br />
Как видно, алгоритм является строго последовательным, каждая итерация зависит от результата предыдущей.<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
Хоть сам по себе алгоритм и является строго последовательным, в нем все же присутствует возможность ускорить выполнение за счет распараллеливания.<br />
Основной ресурс параллелизма заключен в решении СЛАУ <math>(*)</math>. Применять можно любой из известных алгоритмов, ориентируясь на известные особенности структуры матрицы Якоби исходной системы.<br />
<br />
В качестве примера рассмотрим метод Ньютона, использующий для решения СЛАУ параллельный вариант метода Гаусса, в котором между процессорами распределяются строки исходной матрицы. Оставаясь в тех же предположениях, что были сделаны при вычислении оценки последовательной сложности, получим оценку параллельной сложности алгоритма.<br />
<br />
Пусть задача решается на <math>p</math> процессорах, тогда сложность решения СЛАУ имеет оценку <math>O(\frac{n^3}{p})</math>.<br />
<br />
Теперь каждому процессору нужно вычислить не <math>n</math>, а <math>\frac{n}{p}</math> функций, поэтому для вычисления элементов матрицы Якоби и правой части СЛАУ потребуется <math>O(\frac{n^2}{p})</math> операций.<br />
<br />
Таким образом, получаем следующую оценку параллельной сложности одной итерации алгоритма: <math>O(\frac{n^2}{p}) + O(\frac{n^3}{p}) + O(n) = O(\frac{n^3}{p})</math>.<br />
<br />
Пусть алгоритм сойдется за <math>L</math> итераций, тогда итоговая сложность будет равна <math>O(L \cdot \frac{n^3}{p})</math>.<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
При определении объема входных и выходных данных алгоритма будем придерживаться следующих предположений:<br />
<br />
*Рассматривается 64-битная система, таким образом каждый адрес занимает 64 бита.<br />
*Все числа, с которыми оперирует алгоритм также занимают 64 бита. <br />
*Каждая функция будет представлена своим адресом, таким образом вклад функций в объем входных данных будет равен <math>n</math> чисел.<br />
*Производная каждой функции также представлена своим адресом, и вклад производных в объем входных данных будет равен <math>n^2</math> чисел.<br />
<br />
Входными данными алгоритма являются:<br />
# Функции, образующие систему уравнений.<br />
# Производные функций, образующих систему уравнений, если их можно задать аналитически.<br />
# Точность, с которой требуется найти решение.<br />
# Максимальное число итераций, которое может быть проделано.<br />
# Начальное приближение <math>x^{(0)}</math>.<br />
Объем входных данных алгоритма равен <math>n ^2 + 2\cdot n + 2 </math> в случае, когда на вход алгоритму передаются частные производные (<math>n^2</math> адресов частных производных <math>n</math> функций по <math>n</math> переменным, <math>n</math> адресов функций, образующих систему уравнений, <math>n</math>-мерный вектор начального приближения, требуемая точность и максимальное количество итераций), когда частные производные приближенно вычисляются непосредственно в программе, объем входных данных равен <math>2\cdot n + 2.</math><br />
<br />
Выходными данными алгоритма в случае успеха является вектор, который удовлетворяет решению СЛАУ с заданной точностью, в случае выхода количества итераций за заданное ограничение, считается, что алгоритм не смог найти удовлетворяющее ограничениям решение, и выдается сообщение об ошибке.<br />
<br />
Объем выходных данных: <math>n.</math><br />
<br />
=== Свойства алгоритма ===<br />
Соотношение последовательной и параллельной сложностей зависит от того, какие алгоритмы применяются для решения СЛАУ <math>(*)</math>.<br />
Результат работы алгоритма сильно зависит от выбора начального приближения решения. При удачном выборе алгоритм достаточно быстро сходится к искомому решению. Глобальная сходимость для многих задач отсутствует. <br />
<br />
Существует теорема о достаточном условии сходимости метода Ньютона:<br />
<br />
Пусть функция <math>F(x)</math> непрерывно дифференцируема в открытом выпуклом множестве <math>G \subset \R^n</math>. Предположим, что существуют <math>\overline{x^*} \in \R^n</math> и <math>r,\beta > 0 </math>, такие что <math>N(\overline{x^*},r) \subset G , F(\overline{x^*}) = 0</math> , и существует <math>W^{-1}(\overline{x^*})</math>, причем <math>\left \| W^{-1}(\overline{x^*})\right \| \le \beta </math> и <math>W(x) \in Lip_{\gamma} (N(\overline{x^*},r))</math>. Тогда существует <math>\varepsilon > 0</math> такое, что для всех <math>x^{(0)} \in N(\overline{x^*}, \varepsilon)</math> последовательность <math>x^{(1)},x^{(2)},\ldots</math> порождаемая итерационным процессом сходится к <math>\overline{x^*}</math> и удовлетворяет неравенству <math>\left \|x^{(k+1)} - \overline{x^*}\right \| \le \beta\cdot\gamma\cdot{\left \| x^{(k)}- \overline{x^*}\right \|}^2</math>.<br />
<br />
Использовались следующие обозначения:<br />
<br />
*<math>N(x,r)</math> - открытая окрестность радиуса <math>r</math> с центром в точке <math>x</math>;<br />
<br />
*<math>W(x) \in Lip_{\gamma}(N(x,r))</math> означает, что <math>W(x)</math> непрерывна по Липшицу с константой <math>\gamma</math> в области <math>N(x,r)</math>.<br />
<br />
Оставаясь в предположениях, описанных при вычислении объема входных данных, имеем объем входных данных равный <math>n^2 + 2\cdot n + 2 = O(n^2)</math> чисел.<br />
<br />
Оценку числа операций возьмем для реализации метода Ньютона с помощью метода Гаусса: <math>O(n^3)</math>. Тогда вычислительная мощность есть <math>O(n)</math>.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
<br />
=== Локальность данных и вычислений ===<br />
<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
Так как алгоритм по сути своей является последовательным, то масштабируемость конкретной реализации алгоритма определяется масштабируемостью реализации алгоритма решения СЛАУ, используемого для нахождения изменения текущего решения на очередной итерации.<br />
==== Масштабируемость реализации алгоритма ====<br />
В исследуемой реализации для решения СЛАУ использовался метод Гаусса. Исследование проводилось для систем размером от 1024 до 4096 уравнений, с шагом 512. Исследуемая система имеет следующий вид:<br />
<br />
<math><br />
\left\{\begin{matrix}<br />
cos(x_1) - 1 = 0,<br />
\\ cos(x_2) - 1 = 0,<br />
\\ \vdots<br />
\\ cos(x_n) - 1 = 0.<br />
\end{matrix}\right. \qquad(**)<br />
</math><br />
<br />
Данная система выбрана так как для нее известно решение, а также известно начальное приближение <math> x_0 = (0.87, 0.87, ..., 0.87) </math>, при котором метод Ньютона успешно сходится.<br />
<br />
Количество ядер процессоров, на которых производились вычисления, изменялось от 16 до 128 по степеням 2. Эксперименты проводились на суперкомпьютере Ломоносов в разделе test. Исследование проводилось на узлах, обладающих следующими характеристиками:<br />
<br />
*Количество ядер: 8 ядер архитектуры x86<br />
*Количество памяти: 12Гб<br />
*Количество GPU: 0<br />
<br />
На рис. 3 показаны результаты измерений времени решения системы <math>(**)</math> в зависимости от размера системы и количества процессоров. Можно утверждать, что увеличение числа процессоров дает выигрыш во времени, однако, из-за особенностей реализации параллельного решения СЛАУ, при большом количестве узлов и большом размере системы будет осуществляться пересылка между узлами больших объемов данных и возникающие при этом задержки нивелируют выигрыш от увеличения числа процессоров. Поэтому масштабируемость исследуемой реализации весьма ограничена.<br />
<br />
[[file:Снимок экрана 2016-11-15 в 22.36.33.png|thumb|center|700px|Рис.3 Время решения системы уравнений в зависимости от числа процессоров и размера задачи]]<br />
<br />
Использовался компилятор g++, входящий в состав gcc version 4.4.7 20120313 (Red Hat 4.4.7-4). При компиляции указывался единственный параметр -Wall, использовалась библиотека Intel MPI 5.0.1. Ссылка на программную реализацию метода Ньютона для решения систем нелинейных уравнений: https://github.com/ArtyLebedev/newton <ref name="Программная реализация метода Ньютона">https://github.com/ArtyLebedev/newton</ref><br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
<br />
=== Выводы для классов архитектур ===<br />
<br />
=== Существующие реализации алгоритма ===<br />
*Последовательные реализации<br />
**ALIAS C++. Язык реализации - C++. Распространяется бесплатно, исходные коды и примеры использования можно скачать на сайте<ref name="ALIAS">https://www-sop.inria.fr/coprin/logiciels/ALIAS/ALIAS-C++/ALIAS-C++.html</ref>.<br />
**Numerical Recipes. Язык реализации - C++. Исходный код можно найти в секции 9.7 книги Numerical recipes(third edition)<ref name="RECIPES">http://numerical.recipes</ref>. Бесплатно доступны<ref name="RECIPES_old">http://numerical.recipes/oldverswitcher.html</ref> предыдущие издания для языков C, Fortran77, Fortran90.<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD. <br />
**Numerical Mathematics- NewtonLib. Язык реализации - C, Fortran. Исходные коды доступны<ref name="LIB_book">http://elib.zib.de/pub/elib/codelib/NewtonLib/index.html</ref> в качестве приложения к книге<ref name="LIB">https://www.amazon.com/Newton-Methods-Nonlinear-Problems-Computational/dp/364223898X/</ref> Peter Deuflhards "Newton Methods for Nonlinear Problems -- Affine Invariance and Adaptive Algorithms".<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
<br />
*Параллельные реализации<br />
**Sundials. Язык реализации - C, также есть интерфейс для использования в Fortran-программах. Распространяется<ref name="SUNDIALS_seq">http://computation.llnl.gov/projects/sundials/kinsol</ref> по лицензии BSD.<br />
**PETSc. Язык реализации - C, есть интерфейс для Java и Python. Распространяется<ref name = PETSC>https://www.mcs.anl.gov/petsc/</ref> по лицензии BSD. <br />
**Построение параллельной реализации метода Ньютона для решения задач оптимизации, описываются в работе V. A. Garanzha, A. I. Golikov, Yu. G. Evtushenko, and M. Kh. Nguen "Parallel Implementation of Newton’s Method for Solving Large-Scale Linear Programs" <ref name="IMPL_OPT">http://www.ccas.ru/personal/evtush/p/CMMP1303.pdf</ref>.<br />
**Построение параллельной реализации метода Ньютона, а также результаты некоторых экспериментов, описываются в работе Renato N. Elias Alvaro L. G. A. Coutinho Marcos A. D. Martins Rubens M. Sydenstricker "PARALLEL INEXACT NEWTON-TYPE METHODS FOR THE SUPG/PSPG SOLUTION OF STEADY INCOMPRESSIBLE 3D NAVIER-STOKES EQUATIONS IN PC CLUSTERS" <ref name="SUPG">http://www.nacad.ufrj.br/~rnelias/papers/CIL14-020.pdf</ref>.<br />
**Описание использования и результатов экспериментов с параллельной реализацией метода Ньютона в задаче фильтрации вязкой сжимаемой многофазной многокомпонентной смеси в пористой среде описано в работе К.Ю.Богачева "Эффективное решение задачи фильтрации вязкой сжимаемой многофазной многокомпонентной смеси на параллельных ЭВМ"<ref name="BOGACHEV">http://academy.hpc-russia.ru/files/reservoirdynamics_bogachev.pdf</ref>.<br />
<br />
== Литература ==<br />
<br />
<references \></div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:SKirill&diff=18912Обсуждение участника:SKirill2016-11-22T07:49:29Z<p>VadimVV: </p>
<hr />
<div><br />
<br />
== Статья [https://algowiki-project.org/ru/%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B8:_%D0%A8%D0%BE%D1%85%D0%B8%D0%BD_%D0%9A%D0%B8%D1%80%D0%B8%D0%BB%D0%BB,_%D0%9B%D0%B5%D0%B1%D0%B5%D0%B4%D0%B5%D0%B2_%D0%90%D1%80%D1%82%D0%B5%D0%BC/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9 Метод Ньютона решения систем нелинейных уравнений] ==<br />
<br />
=== Замечания ===</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:N_Zakharov/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%9D%D1%8C%D1%8E%D1%82%D0%BE%D0%BD%D0%B0_%D0%B4%D0%BB%D1%8F_%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC_%D0%BD%D0%B5%D0%BB%D0%B8%D0%BD%D0%B5%D0%B9%D0%BD%D1%8B%D1%85_%D1%83%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B9&diff=18911Участник:N Zakharov/Метод Ньютона для решения систем нелинейных уравнений2016-11-22T07:48:43Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
Основные авторы описания: [[Участник:Александр Чернышев|Александр Чернышев]], [[U:N Zakharov|Никита Захаров]]. Вклад во все разделы равнозначный.<br />
<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
'''Метод Ньютона, алгоритм Ньютона''' — это итерационный численный метод нахождения корня (нуля) заданной функции. Метод был впервые предложен английским физиком, математиком и астрономом Исааком Ньютоном (1643—1727). Поиск решения осуществляется путём построения последовательных приближений и основан на принципах простой итерации. Метод обладает квадратичной сходимостью. Модификацией метода является метод хорд и касательных. Также метод Ньютона может быть использован для решения задач оптимизации, в которых требуется определить ноль первой производной либо градиента в случае многомерного пространства.<br />
<br />
==== Историческая справка ====<br />
Метод был описан Исааком Ньютоном в рукописи «Об анализе уравнениями бесконечных рядов» (лат. «De analysi per aequationes numero terminorum infinitas»), адресованной в 1669 году Барроу, и в работе «Метод флюксий и бесконечные ряды» (лат. «De metodis fluxionum et serierum infinitarum») или «Аналитическая геометрия» (лат. «Geometria analytica») в собраниях трудов Ньютона, которая была написана в 1671 году. В своих работах Ньютон вводит такие понятия, как разложение функции в ряд, бесконечно малые и флюксии (производные в нынешнем понимании). Указанные работы были изданы значительно позднее: первая вышла в свет в 1711 году благодаря Уильяму Джонсону, вторая была издана Джоном Кользоном в 1736 году уже после смерти создателя. Однако описание метода существенно отличалось от его нынешнего изложения: Ньютон применял свой метод исключительно к полиномам. Он вычислял не последовательные приближения <math>x_{n}</math>, а последовательность полиномов и в результате получал приближённое решение <math>x</math>.<br />
<br />
Впервые метод был опубликован в трактате «Алгебра» Джона Валлиса в 1685 году, по просьбе которого он был кратко описан самим Ньютоном. В 1690 году Джозеф Рафсон опубликовал упрощённое описание в работе «Общий анализ уравнений» (лат. «Analysis aequationum universalis»). Рафсон рассматривал метод Ньютона как чисто алгебраический и ограничил его применение полиномами, однако при этом он описал метод на основе последовательных приближений <math>x_{n}</math> вместо более трудной для понимания последовательности полиномов, использованной Ньютоном. Наконец, в 1740 году метод Ньютона был описан Томасом Симпсоном как итеративный метод первого порядка решения нелинейных уравнений. В той же публикации Симпсон обобщил метод на случай системы из двух уравнений и отметил, что метод Ньютона также может быть применён для решения задач оптимизации путём нахождения нуля производной или градиента.<br />
<br />
В 1879 году Артур Кэли в работе «Проблема комплексных чисел Ньютона — Фурье» (англ. «The Newton-Fourier imaginary problem») был первым, кто отметил трудности в обобщении метода Ньютона на случай мнимых корней полиномов степени выше второй и комплексных начальных приближений. Эта работа открыла путь к изучению теории фракталов.<br />
<br />
=== Математическое описание алгоритма ===<br />
В этой статье мы сразу сформулируем задачу для многомерного случая и не будем останавливаться на методе Ньютона для одномерных задач <ref>Тыртышников Е. Е. Методы численного анализа — М., Академия, 2007. - 320 c.</ref> <ref>Бахвалов Н. С., Жидков Н. П., Кобельков. Г. М. — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с.</ref>.<br />
<br />
Пусть необходимо найти решение системы: <br /> <br /><br />
<math><br />
\left\{\begin{matrix}f_1(x_1, ..., x_n) = 0<br />
\\ ...<br />
\\ f_n(x_1, ..., x_n) = 0,<br />
<br />
\end{matrix}\right.<br />
</math><br />
<br /> <br /><br />
где <math>f = (f_1, ..., f_n) : \mathbb{R}^n \rightarrow \mathbb{R}^n</math> - непрерывно дифференцируемое отображение в окрестности решения.<br />
<br /> <br /><br />
<br />
При начальном приближении <math>x_0</math> и сделанных предположениях <math>(k+1)</math>-я итерация метода будет выглядеть следующим образом:<br />
<math> <br />
x_{k+1} = x_k - [f'(x_k)]^{-1}f(x_k)<br />
</math><br />
<br />
Основная сложность на каждой итерации заключается в решении системы уравнений. Один из самых эффективных методов их решения базируется на идеях Крылова, а именно проекционных итерационных методах. <br />
Они основываются на тех же принципах, что и методы, которые используются для задач поиска собственных значений. На самом деле, многие методы Крылова напрямую вытекают из алгоритмов Арнольди<ref>Daniel F. Gill, NEWTON-KRYLOV METHODS FOR THE SOLUTION OF THE k-EIGENVALUE PROBLEM IN MULTIGROUP NEUTRONICS CALCULATIONS, 2009 </ref>. Приближенное решение можно найти по формуле <math>\tilde{x} = x_0 + V_my</math>, а после подставления условий Петрова-Гарелкина оно принимает вид <math>\tilde{x} = x_0 + V (W^TAV)^{-1}W^Tr_0</math>, где <math>r_0 = b - Ax_0</math>, <math>V = \{v_1, v_2, ..., v_m\}</math> - матрица, чьи столбцы являются ортонормированным базисом пространства, а матрица <math>W</math> находится из соотношения <math>W^HV = I</math>. <br><br />
<math>V^T_mAV_m = H_m</math> <br><br />
<math>G(y) = \left \| b-Ax \right \| = \left \| b-A(x_0+V_my) \right \| = \left \| r_0 -AV_my \right \|</math><ref> https://en.wikipedia.org/wiki/Generalized_minimal_residual_method#Solving_the_least_squares_problem</ref><br />
<br>Решение задачи минимизации<br><br />
Наша цель состоит в нахождении вектора <math>y_n</math>, минимизирующего задачу <math> \| \tilde{H}_n y_n - \beta e_1 \|, \, </math><br />
где <math>\tilde{H}_n</math> матрица размера (n+1) на n.<br />
<br><br />
В результате применения QR-декомпозиции к матрице <math>\tilde{H}_n</math> находим ортогональную матрицу <math>\Omega_n</math> размера (n+1) на (n+1)<br />
и верхнюю треугольную матрицу <math>\tilde{R}_n</math> размера (n+1) на n.<br />
<br>При этом в матрице <math>\tilde{R}_n</math> последняя строка будет нулевой, то есть ее можно представить в следующем виде<br />
<br />
<br><math> \tilde{R}_n = \begin{bmatrix} R_n \\ 0 \end{bmatrix}. </math><br />
<br />
<br>Перепишем нашу исходную задачу <math> \| \tilde{H}_n y_n - \beta e_1 \| = \| \Omega_n (\tilde{H}_n y_n - \beta e_1) \| = \| \tilde{R}_n y_n - \beta \Omega_n e_1 \|. </math><br />
<br />
<br>Положим <math>\beta\Omega_ne_1=\tilde{g}_n</math>,<br />
<br />
<br>где <math> \tilde{g}_n = \begin{bmatrix} g_n \\ \gamma_n \end{bmatrix} </math><br />
<br />
<br>Вектор <math>y_n</math> находится из следующего соотношения <math> y_n = R_n^{-1} g_n. </math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Описание алгоритма Арнольди<br><br />
В результате мы получаем матрицу <math>H^{{m+1}\times{m}}</math><br><br />
Пусть <math>r_0</math> - произвольный входной вектор <br />
<math>v_1</math> = <math>\frac{r_0}{\|r_0\|}</math><br />
<br />
'''Для''' <math>m = 1,2,...,k </math><br />
<math>\omega_m</math> = <math>Av_k</math><br />
'''Для''' <math>j = 1,2, ..,m </math><br />
<math>h_{jm}=(\omega_m, v_j)</math><br />
<math>\omega_m=\omega_m-h_{jm}v_j</math><br />
'''Конец'''<br />
<math>h_{m+1,m}=\|\omega_m\|</math><br />
<math>v_{m+1}=\omega_m/h_{m+1,m}</math><br />
'''Конец'''<br />
<br />
Не будем подробно останавливаться на описании решения задачи оптимизации.<br><br />
Однако, важно отметить, что расчет матриц <math>\Omega_m, R_m</math> и вектора <math>g_m</math> можно упростить, используя формулы перехода от m к m+1<ref> https://en.wikipedia.org/wiki/Generalized_minimal_residual_method#Solving_the_least_squares_problem</ref><br />
.<br><br />
Модифицируя алгоритм Арнольди соответствующим образом, мы можем ощутимо снизить вычислительные затраты, необходимые нахождения QR-разложения матрицы <math>H_m</math> и вектора <math>g_m</math>.<br><br />
<br />
В результате, основной интерес для нас представляет решение СЛАУ <math>R_my_m=g_m</math>. Так как матрица <math>R_m</math> является верхнетреугольной, то вычисление вектора <math>y_m</math> совпадает с обратным ходом метода Гаусса.<br />
<br />
=== Макроструктура алгоритма ===<br />
В данном алгоритме можно выделить 2 базовых макрооперации:<br />
<br />
* Умножение матрицы на вектор<br />
<br />
* Скалярное произведение векторов<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
<source><br />
Выбор начального приближения x_k<br />
Вычисление вектора F(x_k)<br />
WHILE (условие остановки не выполнено)<br />
Вычисление якобиана J(x_k) = F'(x_k)<br />
Решение линейной системы уравнений алгоритмом GMRES J(x_k)s_k = -F(x_k)<br />
x_k = x_k + s_k<br />
Вычисление вектора F(x_k)<br />
</source><br />
<br />
=== Последовательная сложность алгоритма ===<br />
<br />
Будем оценивать сложность одной итерации метода Ньютона.<br><br />
Для начала оценим сложность решения СЛАУ. <br><br />
Будем считать, что наша исходная матрица была порядка n.<br />
Предположим, что мы нашли решение СЛАУ за <math>k</math> итераций. В результате выполнения алгоритма Арнольди мы получаем матрицу <math>H_k</math> размера <math>(k+1)k</math> и матрицу <math>Q_k</math> размера <math>nk</math>. QR-факторизация матрицы <math>H_k</math> будет стоить нам порядка <math>O(k^3)</math> арифметических операций. Сложность алгоритма Арнольди при этом составляет <math>O(k^2n)</math> арифметических операций плюс сложность <math>k</math> раз умножения матрицы на вектор - <math>O(kn^2)</math>.<br><br />
При решении задачи минимизации нам необходимо:<br><br />
1) Решить СЛАУ с верхнетреугольной матрицей порядка <math>k</math> для нахождения <math>y_k</math> - сложность <math>O(k^2)</math> арифметических операций.<br><br />
2) Умножить матрицу <math>Q_k</math> на найденный вектор - сложность <math>O(kn)</math> арифметических операций.<br><br />
3) Сложить 2 вектора длины n - <math>O(n).</math><br />
Предположим, что сложность расчета одного элемента вектор-функции F и матрицы якобиана J будет <math>O(1)</math>.<br />
<br />
Тогда сложность расчета вектор-функции будет <math>O(n)</math>, матрицы якобиана - <math>O(n^2)</math>.<br />
<br />
Сложность оставшихся операций из алгоритма Ньютона (сложение векторов + подсчет нормы) составляет <math>O(2n).</math><br><br />
В результате мы получаем, что на одну итерацию метода Ньютона нужно <math>O(kn^2)</math> операций.<br><br />
Отметим, что данная оценка справедлива при небольших <math>k</math> относительно <math>n</math>.<br />
<br />
=== Информационный граф ===<br />
[[Файл:Grafnewton.PNG|thumb|600px|center| Рис. 1. Информационный граф]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
Вообще говоря, метод Ньютона является последовательным методом, но на определенных шагах, таких как решение СЛАУ, приближенное вычисление Якобиана, можно применить идеи параллельных вычислений.<br />
<br />
Основным ресурсом параллелизма в представленном алгоритме являются: умножение матрицы на вектор, скалярное произведение и элементарные операции над векторами.<br />
<br />
Пусть нам будет дана матрица порядка ''n''. Будем предполагать, что доступно p процессоров.<br><br />
<br />
Как и ранее будем предполагать, что сложность расчета одного элемента вектор-функции F и матрицы якобиана J будет <math>O(1)</math>.<br />
<br />
При получении оценки последовательной сложности алгоритма было показано, что для одной итерации метода Ньютона необходимо выполнить:<br />
<br />
На итерациях Арнольди:<br />
<math>k</math> операций умножения матрицы на вектор - <math>O(\frac{kn^2}{p})</math><br />
<math>k^2</math> операций расчета нормы, умножения вектора на число, сложения векторов - <math>O(\frac{k^2n}{p})</math><br />
QR-факторизация матрицы - <math>O(k^3)</math><br />
На решении задачи минимизации:<br />
Обратный ход метода Гаусса - <math>O(k^2)</math><br />
Умножение матрицы на вектор - <math>O(\frac{kn}{p})</math><br />
Сложение векторов - <math>O(\frac{n}{p})</math><br />
На оставшиеся операции одной итерации метода Ньютона:<br />
Сложение векторов - <math>O(\frac{n}{p})</math><br />
Вычисление вектор-функции F в точке - <math>O(\frac{n}{p})</math><br />
Вычисление матрицы якобиана J в точке - <math>O(\frac{n^2}{p})</math><br />
Расчет нормы - <math>O(\frac{n}{p})</math><br />
<br />
В результате, при небольших значениях параметра <math>k</math> получим суммарную оценку <math>O(\frac{kn^2}{p})</math>.<br />
Выполнение данного ограничения можно достичь следующими способами:<br />
<br />
1) Увеличение скорости сходимости решения СЛАУ при использовании предобуславливания<br />
<br />
2) Используя модификацию данного алгоритма - GMRES(m)<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
'''Входные данные''':<br><math>n^2 + n</math> функций от <math>n</math> переменных<br><math>n</math> вещественных чисел<br><br />
'''Выходные данные''':<br><math>n</math> вещественных чисел<br><br />
<br />
=== Свойства алгоритма ===<br />
Так как во входных данных присутствует не только начальное приближение, но и функции, о свойствах которых, вообще говоря, ничего не известно, оценивать эффективность алгоритма сложно. Из классических результатов известно, что при выпуклых липшицевых функциях и удачном начальном приближении метод имеет квадратичную скорость сходимости, что выделяет его среди прочих итерационных методов. Но несмотря на это, он имеет и недостатки, которые особенно заметны при численных экспериментах.<br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
<br />
В качестве модельной задачи рассмотрим один из примеров<ref>http://www.mcs.anl.gov/petsc/petsc-3.5/src/snes/examples/tutorials/ex30.c.html</ref><br />
, поставляемых вместе с модулем SNES пакета PETSc.<br />
<br />
Тестирование алгоритма проводилось на суперкомпьютере "Ломоносов"[http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета].<br />
<br />
Версия MPI - impi/4.1.0<br />
<br />
Реализация BLAS - mkl/11.2.0<br />
<br />
Запуски проводились на regulal4<br />
<br />
Модель используемого CPU - Intel Xeon X5570 2.93GHz<br />
<br />
Версия PETSc - 3.7.4<br />
<br />
Флаги компиляции: -fPIC -Wall -Wwrite-strings -Wno-strict-aliasing -Wno-unknown-pragmas -fvisibility=hidden -g3<br />
<br />
Кроме того, использовались следующие параметры:<br />
<br />
1) Pестарт алгоритма GMRES через 300 итераций<br />
<br />
2) Максимальное число итераций для нахождения решения СЛАУ - 1500<br />
<br />
3) Максимальное число итераций метода Ньютона на данном этапе решения - 20<br />
<br />
Набор начальных параметров:<br />
<br />
* Число процессоров [1 2 4 8 16 32 48 64 80 96 112 128];<br />
* Порядок матрицы [82 122 162 202 242].<br />
<br />
[[Файл:NewtonFlops.jpg|thumb|center|700px|Рисунок 1. Изменение производительности в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonAcc.jpg|thumb|center|700px|Рисунок 2. Изменение ускорения в зависимости от числа процессоров и размера матрицы.]]<br />
[[Файл:NewtonEff.jpg|thumb|center|700px|Рисунок 3. Изменение эффективности в зависимости от числа процессоров и размера матрицы]]<br />
<br />
Как видно из приведенных графиков, эффективность распараллеливания алгоритма довольно быстро убывает при увеличении числа процессоров.<br />
<br />
При фиксированном числе процессоров наблюдается рост ускорения при увеличении вычислительной сложности задачи<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
Из существующих реализаций важно отметить пакет PETSc и его компонент SNES, позволяющий решать системы нелинейных уравнений методом Ньютона<br><br />
https://www.mcs.anl.gov/petsc/<br />
<br />
== Литература ==<br />
<br />
<references \></div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:%D0%90%D0%BB%D0%B5%D0%BA%D1%81%D0%B0%D0%BD%D0%B4%D1%80_%D0%A7%D0%B5%D1%80%D0%BD%D1%8B%D1%88%D0%B5%D0%B2&diff=18910Обсуждение участника:Александр Чернышев2016-11-22T07:48:34Z<p>VadimVV: </p>
<hr />
<div>== Статья [[Метод_Ньютона_для_решения_систем_нелинейных_уравнений]] ==<br />
<br />
=== Замечания ===</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Anlesnichiy/%D0%A0%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B8_%D0%9A%D0%BE%D1%88%D0%B8_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B_%D0%9E%D0%94%D0%A3_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%A0%D1%83%D0%BD%D0%B3%D0%B5-%D0%9A%D1%83%D1%82%D1%82%D1%8B_4_%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B0&diff=18909Участник:Anlesnichiy/Решение задачи Коши для системы ОДУ методом Рунге-Кутты 4 порядка2016-11-22T07:47:45Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
<br />
{{algorithm<br />
| name = Решение задачи Коши для системы ОДУ методом Рунге-Кутты 4 порядка<br />
| serial_complexity = <math> 4mn </math><br />
| pf_height = <math> O(n) </math><br />
| pf_width = <math> O(m) </math><br />
| input_data = <math> m + 3 </math><br />
| output_data = <math> (m+1)n </math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Anlesnichiy|А.А. Лесничий]](1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9), [[Участник:Алимов Дамир Алиевич|Д.А. Алимов]](1.1,1.7,1.10,2.4,2.7)<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
Метод Рунге-Кутты четвертого порядка — наиболее распространенный метод из семейства методов Метод Рунге-Кутты численных алгоритмов решения обыкновенных дифференциальных уравнений и их систем. Данные итеративные методы явного и неявного приближённого вычисления были разработаны около 1900 года немецкими математиками К. Рунге и М. В. Куттой.<br />
<br />
Формально, методом Рунге-Кутты является модифицированный и исправленный метод Эйлера, они представляют собой схемы второго порядка точности. Существуют стандартные схемы третьего порядка, не получившие широкого распространения.<br><br />
Основная идея алгоритмов Рунге - Кутты состоит в замене правой части дифференциального уравнения, зависящей от искомой неизвестной функции, некоторым приближением. Если задачу Коши переписать в интегральном виде<br />
:<math><br />
y(x) = y_{0} + \int\limits_{x_{0}}^{x}f(t,y)dt,<br />
</math><br />
то, применяя различные численные формулы для расчёта интеграла в правой части уравнения, можно получить методы Рунге - Кутты различных порядков.<br />
<br />
Общий вид формул методов Рунге - Кутты с шагом сетки <math>h_{n}</math>:<br />
:<math><br />
y = f(t,y),\,\,\, y(t_{0}) = y_{0},<br />
</math><br />
:<math><br />
y_{n+1} = y_{n} + h_{n+1}\sum\limits_{i=1}^{s}b_{i}K_{n}^{i},<br />
</math><br />
где<br />
:<math><br />
K_{n}^{i} = f(t_{n} + c_{i}h_{n+1}, y_{n} + h_{n+1}\sum\limits_{j=1}^{i-1}a_{ij}K_{n}^{j}).<br />
</math><br />
Конкретный метод Рунге - Кутты определяется набором коэффициентов <math>b_{i}, c_{j}, a_{ij}</math>, которые должны удовлетворять определённым соотношениям.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== Метод Рунге-Кутты 4-го порядка для задачи Коши для ДУ первого порядка ====<br />
Рассмотрим задачу Коши, где правая часть удовлетворяет условиям теорем существования и единственности решения.<br />
:<math><br />
y' = f(x,y),\ a \leq x \leq b;\ y(a) = y^0<br />
</math><br />
Зададим равномерную сетку<br />
:<math><br />
x_i = a + ih,\ i = 1,\dots, n,\ h = \frac{b-a}{n}<br />
</math><br />
<br />
Введём обозначения <math>y(x_i) = y_i</math>. Получим вычислительную формулу:<br />
<br />
:<math><br />
\begin{cases}<br />
k_1 = hf(x_i,y_i)\\<br />
k_2 = hf(x_i + h/2,y_i + k_1/2)\\<br />
k_3 = hf(x_i + h/2,y_i + k_2/2)\\<br />
k_4 = hf(x_i + h,y_i + k_3)\\<br />
y_{i+1} = y_i + [ k_1 + 2k_2 + 2k_3 + k_4 ]/6 \\<br />
\end{cases}<br />
</math><br />
<br />
==== Метод Рунге-Кутты 4-го порядка для задачи Коши для системы ДУ первого порядка ====<br />
Численное решение задачи Коши для систем ОДУ 1-го порядка методами Рунге-Кутты ищется по тем же формулам, что и для ОДУ первого порядка. Например, решение методом Рунге-Кутты 4-го порядка можно найти, если положить:<br />
:<math><br />
y_i \rightarrow \bar y_i<br />
</math><br />
:<math><br />
f(x_i,y_i) \rightarrow \bar f(x_i,\bar y_i)<br />
</math><br />
:<math><br />
k_l \rightarrow \bar k_l<br />
</math><br />
:<math><br />
\bar k_l = <br />
\begin{pmatrix}<br />
k^i_{l,1}\\<br />
\vdots\\<br />
k^i_{l,m}\\<br />
\end{pmatrix}<br />
</math><br />
где m – размерность системы, <math> l = 1, \dots, 4 </math>.<br />
В результате получим<br />
:<math><br />
\begin{cases}<br />
\bar k_1 = h\bar f(x_i,\bar y_i)\\<br />
\bar k_2 = h\bar f(x_i + h/2,\bar y_i + \bar k_1/2)\\<br />
\bar k_3 = h\bar f(x_i + h/2,\bar y_i + \bar k_2/2)\\<br />
\bar k_4 = h\bar f(x_i + h,\bar y_i + \bar k_3)\\<br />
\bar y_{i+1} = \bar y_i + [ \bar k_1 + 2\bar k_2 + 2\bar k_3 + \bar k_4 ]/6 \\<br />
\end{cases}<br />
</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Вычислительным яром алгоритма можно считать вычисление значение вектора <math> \bar y_i </math>, то есть вычислительным ядром является цикл по i. Иными словами сам алгоритм совпадает со своим ядром и был описан в пункте [[#Метод Рунге-Кутты для задачи Коши для системы ДУ первого порядка|1.2.2]].<br />
<br />
=== Макроструктура алгоритма ===<br />
Макроструктуру алгоритма составляют вычисления коэффициентов <math>k_{j},\,j=1,..,4</math> при нахождении значений искомой функции в каждом узле.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Приведем возможную реализацию последовательного алгоритма на языке Matlab (правая часть ОДУ задана в виде функции func(x,y))<br />
<source lang="matlab" line="1"><br />
function [x, y] = RungeKutta4(y0, a, b, n)<br />
h = (b-a)/n;<br />
x = a : h : b<br />
<br />
y(:, 1) = y0;<br />
for i = 2 : n<br />
k1 = h*func(x(i), y(:, i))<br />
k2 = h*func(x(i) + h/2, y(:, i) + k1/2)<br />
k3 = h*func(x(i) + h/2, y(:, i) + k2/2)<br />
k4 = h*func(x(i) + h, y(:, i) + k3)<br />
y(:, i+1) = y(:, i) + (k1 + 2*k2 + 2*k3 + k4)/6 <br />
end<br />
end<br />
</source><br />
<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Каждый шаг цикла алгоритма состоит из 4 обращений к функции <math> \bar f </math>, 11 умножений и 10 сложений. Так как обращение к функции <math> \bar f </math> является наиболее сложной операцией, то сложность линейного выполнения алгоритма можно определить как <math> 4mn </math>.<br />
<br />
=== Информационный граф ===<br />
Опишем граф алгоритма аналитически и графически.<br><br />
Пусть задана система из <math> m </math> одномерных дифференциальных уравнений. Граф имеет трёхмерную структуру, которая представляет собой связанные между собой ограниченные плоскости (уровни). На каждом i-ом уровне вычисляется i-ое приближение вектора решения системы. Всего таких уровней – n, где n – количество узлов, в которых вычисляется приближённое решение системы дифференциальных уравнений. Выходные данные i-го уровня являются входными данными для i+1-го уровня.<br><br />
Вершины информационного графа делятся на два типа:<br />
<ul><br />
<li>Первому типу вершин соответствует вычисление одной координаты функции, стоящей в правой части дифференциального уравнения, в точках, подаваемых на вход вершине. Результатом выполнения операции является одна координата вектора <math>k_{l},\,\,\,l = 1,2,3,4</math>. На каждом уровне таких вершин будет <math>4m</math>, а их общее количество – <math>4mn</math>.</li><br />
<li>Второму типу вершин соответствует операция <math>a + bc</math>. Эти вершины также можно разделить на две группы. В вершинах первой группы вычисляются выражения, результат которых будет подаваться в качестве аргументов для вершин первого типа, при этом входными данными для этих вершин будут результаты срабатывания m вершин первого типа, расположенных на одном уровне. На каждом уровне вершин первой группы будет <math>3m</math>, а их общее количество – <math>3mn</math>. Входными данными для вершин второй группы будет результат срабатывания одной вершины первого типа и одной такой же вершины второго типа. Результатом срабатывания данной вершины на i-ом уровне будут m координат вектора i-го приближения решения системы. На каждом уровне таких вершин будет <math>4m</math>, а их общее количество – <math>4mn</math>. </li><br />
</ul><br />
<br />
Приведём графическую иллюстрацию информационного графа (рис. 1). Для того чтобы сильно не загромождать граф, рассмотрим случай системы из двух дифференциальных уравнений. Красным цветом обозначены вершины первого типа, зелёным - вершины второго типа. Входные данные задачи подаются на все вершины первого типа, а также на <math>m</math> левых вершин второй группы второго типа и на все вершины первой группы второго типа на первом уровне подаются начальные значения искомой функции (см. пункт [[#HРесурс параллелизма алгоритма|1.8]]). Выходными данными будут результаты срабатывания <math>m</math> правых вершин второй группы второго типа на каждом уровне. Результаты срабатывания остальных вершин являются промежуточными данными алгоритма.<br />
[[Файл:Graf_2_rk.jpg|500px|thumb|center|Рисунок 1. Информационный граф для системы из двух дифференциальных уравнений.]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
Поскольку в описанной выше вычислительной схеме наиболее трудоемкой является операция расчета правых частей ОДУ при вычислении <math>k_i ( i = 1, \dots, 4) </math>, то основное внимание следует уделить распараллеливанию этой операции.<br />
Здесь будет применяться подход декомпозиции уравнений системы ОДУ на подсистемы.<br />
Поэтому для инициализации рассмотрим следующую схему декомпозиции данных по имеющимся процессорным элементам с локальной памятью: на каждый <math>\mu</math>- ПЭ (процессорный элемент) (<math>\mu = 0, \dots, p-1 </math>) распределяется <math> n/p </math> дифференциальных уравнений и вектор <math> \bar y_0 </math>. Далее расчеты производятся по следующей схеме:<br />
# на каждом ПЭ одновременно вычисляются <math> n/p </math> соответствующих компонент вектора <math> \bar k_1 </math> по формуле <math> [ \bar k_1 ]_{\mu} = h[ \bar f(x_i, \bar y_i) ]_{\mu} </math><br />
# для обеспечения второго расчетного этапа необходимо провести сборку вектора <math> \bar k_1 </math> целиком на каждом ПЭ. Затем независимо выполняется вычисление компонент вектора <math> \bar k_2 </math> по формуле <math> [ \bar k_2 ]_{\mu} = h[ \bar f(x_i + h/2,\bar y_i + \bar k_1/2)]_{\mu} </math>;<br />
# проводится сборка вектора <math> \bar k_2 </math> на каждом ПЭ, вычисляются компоненты вектора <math> \bar k_3:\ [ \bar k_3 ]_{\mu} = h [\bar f(x_i + h/2,\bar y_i + \bar k_2/2)]_{\mu} </math>;<br />
# проводится сборка вектора <math> \bar k_3 </math> на каждом ПЭ, вычисляются компоненты вектора <math> \bar k_4:\ [ \bar k_4 ]_{\mu} = h [\bar f(x_i + h,\bar y_i + \bar k_3)]_{\mu} </math>;<br />
# рассчитываются с идеальным параллелизмом компоненты вектора <math> \bar y_{i+1}:\ [\bar y_{i+1}]_{\mu} = [\bar y_{i}]_{\mu} + ([ \bar k_1 ]_{\mu} + 2[ \bar k_2 ]_{\mu} + 2[ \bar k_3 ]_{\mu} + [ \bar k_4 ]_{\mu})/6\ </math> и производится сборка вектора <math> \bar y_{i+1} </math> на каждом ПЭ. Если необходимо продолжить вычислительный процесс, то полагается <math> i = i + 1 </math> и осуществляется переход на п. 1<br />
<br />
Заметим, что данный алгоритм обладает конечным параллелизмом, но не массовым, так как циклы алгоритма являются информационно зависимыми.<br />
<br />
На каждом ярусе в данном алгоритме каждый ПЭ производит четыре операции вычисления правых частей ОДУ, шестнадцать операций сложения векторов и умножения вектора на число, а так же р-1 пересылку данных между другими ПЭ, что довольно сильно замедляет выполнение алгоритма и является накладными расходами при распараллеливании алгоритма.<br />
При этом количество итераций цикла равно длине вектора <math> x </math>, то есть <math> n </math>.<br />
Из вышеуказанного следует, что при классификации по высоте [[глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] алгоритм имеет сложность <math> O(n) </math>, а при классификации по ширине ЯПФ – O(m) (при условии, что <math> p = m </math>).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
Входными данными алгоритма являются:<br />
# вектор <math> y^0 </math> размерности <math> m </math>;<br />
# границы временного интервала <math> a </math> и <math> b </math>;<br />
# частота дискретизации <math> n </math>;<br />
<br />
Всего размер входных данных: <math> m + 3 </math><br />
<br />
Выходными данными являются<br />
# <math> n </math> векторов <math> \bar y_i </math> размерности <math> m </math>;<br />
# вектор <math> \bar x </math> размерности <math> n </math><br />
<br />
Всего размер выходных данных: <math> (m+1)n </math><br />
<br />
=== Свойства алгоритма ===<br />
Перечислим основные свойства алгоритма:<br />
<ul><br />
<li>Точность. Как следует из названия, у метода Рунге-Кутты 4-го порядка ошибка на конечно интервале интегрирования имеет порядок <math>\mathcal{O}(h^{4})</math>, где <math>h</math> – расстояние между узлами, в которых вычисляется искомая функция.</li><br />
<li>Устойчивость. Метод имеет интервал абсолютной устойчивости <math>(-2.78; 0)</math>. (Определение понятия интервала абсолютной устойчивости можно найти в учебном пособии <ref name="uch">А. Ф. Заусаев. Разностные методы решения обыкновенных дифференциальных уравнений.// Учебное пособие. Самарский гос. техн. ун-т, 2010 - 100 с. </ref>.)</li><br />
<li>Отношение числа расчётов функции, стоящей в правой части задачи Коши, при последовательном алгоритме к числу расчётов при параллельном равняется <math>p</math>, где <math>p</math> число, используемых процессорных элементов. То есть при увеличении числа используемых процессорных элементов скорость расчётов увеличивается, но стоит учитывать, что число пересылок данных между процессами при этом резко увеличивается также и, в свою очередь, замедляет работу программы.</li><br />
<li>Если известен класс функций, которые могут стоять в правой части задачи Коши, то алгоритм можно эффективно модифицировать, чтобы избавиться от лишних пересылок данных.</li><br />
<li>Количество операций алгоритма равно <math> n(m-1) </math> при этом совокупность входных и выходных параметров равняется <math> mn + n + m +4 </math>. Это означает, что [[глоссарий#Вычислительная мощность|вычислительная мощность]] алгоритма стремиться к 1 при увеличении размерности задачи. </li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
===== Анализ на основе теста Apex-Map =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации метода Рунге - Кутты согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Исследовалась собственная параллельная реализация алгоритма, написанная на языке C++ с использованием стандарта MPI. Сборка программы производилась при помощи компилятора компании Intel версии 15.0 (без указания ключей компиляции) с использованием библиотеки OpenMPI версии 1.8.4 . Расчеты проводились на узлах из [http://parallel.ru/cluster/lomonosov.html группы] T-Blade2 с процессорами Intel Xeon 5570 Nehalem 2.93GHz, при этом для каждого MPI - процесса выделялось одно ядро.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [4 : 100] с шагом по степеням двойки до 64, далее с шагом 10;<br />
* размерность системы [4 : 7000].<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 0.0000032%;<br />
* максимальная эффективность реализации 0.0024%.<br />
<br />
Перечислим некоторые особенности тестируемой параллельной реализации:<br />
<br />
* для тестирования использовалась псевдослучайная правая часть системы, состоящая из суперпозиций случайно выбранных элементарных функций;<br />
* начальные значения искомой функции задавались случайным образом;<br />
* так как в общем случае правая часть системы неизвестна, то не представляется возможным измерять производительность в гигафлопсах; в качестве показателя производительности использовалось количество точек, в которых была вычислена функция.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности параллельной реализации метода Рунге-Кутты в зависимости от изменяемых параметров запуска.<br />
<br />
<br />
[[Файл:Perf_rk.jpg|500px|thumb|center|Рисунок 2. Изменение производительности параллельного алгоритма в зависимости от размерности системы и числа процессоров.]]<br />
[[Файл:Eff_rk_fin.jpg|500px|thumb|center|Рисунок 3. Эффективность работы параллельного алгоритма в зависимости от размерности системы и числа процессоров.]]<br />
<br />
Построим оценки масштабируемости протестированной параллельной реализации метода Рунге - Кутты 4-го порядка: <br />
<br />
* По числу процессов -0.000000768. При увеличении числа процессов эффективность на рассмотренной области изменений параметров запуска уменьшается, однако уменьшение достаточно медленное, что связано с крайне низкой максимальной эффективностью работы параллельной реализации алгоритма. Уменьшение эффективности при увеличении числа процессов для систем с небольшой размерностью объясняется тем, что при количестве процессов превышающем размерность системы часть из них не будет участвовать в вычислениях. Для систем с большой размерностью увеличение процессов негативно сказывается на эффективности, так как увеличивается количество пересылок между ними, что существенно затормаживает работу. <br />
<br />
* По размеру задачи 0.000000763. При увеличении размерности системы при фиксированном количестве процессов эффективность на рассмотренной области изменений параметров запуска увеличивается, причём увеличение наблюдается на почти всей рассматриваемой области. Это объясняется тем, что при фиксированном количестве процессов увеличение размерности системы приводит к увеличению вычислительной нагрузки на каждый процессор, вследствие чего увеличивается время вычислительной работы относительно времени простоя. С увеличением числа процессов скорость возрастания эффективности при увеличении размерности задачи уменьшается, так как количество пересылок становится достаточно большим. <br />
<br />
* По двум направлениям 0.00000000174. При одновременном увеличении количества процессов и размерности системы эффективность увеличивается. Скорость возрастания эффективности крайне низкая, что объясняется высокими накладными расходами.<br />
<br />
[https://github.com/AlimovDamir/Runge-Kutta Исследованная параллельная реализация на языке C++]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
Метод 4-го порядка является самым часто используемым из всех схем Рунге - Кутты, поэтому существует множество его программных последовательных реализаций как коммерческих, так и бесплатных. Одной из самых известных программных реализаций является ode45 в среде MATLAB, у которой имеется большое количество возможностей для настройки численного расчёта, что делает её достаточно удобной для использования. Также метод Рунге - Кутты 4-го порядка реализован в параллельной библиотеке PETSc, которая является свободно распространяемой. Несмотря на то, что в этой библиотеки большинство методов реализовано с использованием параллельных алгоритмов, метод Рунге - Кутта в ней реализован последовательным. Довольно трудно найти параллельную реализацию метода в случае рассмотрения общей задачи Коши, но существует множество научных работ, в которых описываются параллельные реализации метода Рунге - Кутты для конкретных классов систем, например, в работе А.В. Старченко<ref name="Star">А. В. Старченко, В. Н. Берцун. Методы параллельных вычислений// Учебник. – Томск: Изд-во Том. ун-та, 2013. – 223 с</ref> описывается параллельная реализация для линейных систем.<br />
<br />
== Литература ==<br />
<br />
<references \></div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Anlesnichiy&diff=18908Обсуждение участника:Anlesnichiy2016-11-22T07:47:29Z<p>VadimVV: </p>
<hr />
<div>== Статья [[Решение_задачи_Коши_для_системы_ОДУ_методом_Рунге-Кутты_4_порядка]] ==<br />
<br />
=== Замечания ===</div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Anlesnichiy/%D0%A0%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%B4%D0%B0%D1%87%D0%B8_%D0%9A%D0%BE%D1%88%D0%B8_%D0%B4%D0%BB%D1%8F_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D1%8B_%D0%9E%D0%94%D0%A3_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_%D0%A0%D1%83%D0%BD%D0%B3%D0%B5-%D0%9A%D1%83%D1%82%D1%82%D1%8B_4_%D0%BF%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%B0&diff=18907Участник:Anlesnichiy/Решение задачи Коши для системы ОДУ методом Рунге-Кутты 4 порядка2016-11-22T07:47:21Z<p>VadimVV: /* Масштабируемость реализации алгоритма */</p>
<hr />
<div>{{Assignment}}<br />
{{algorithm<br />
| name = Решение задачи Коши для системы ОДУ методом Рунге-Кутты 4 порядка<br />
| serial_complexity = <math> 4mn </math><br />
| pf_height = <math> O(n) </math><br />
| pf_width = <math> O(m) </math><br />
| input_data = <math> m + 3 </math><br />
| output_data = <math> (m+1)n </math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Anlesnichiy|А.А. Лесничий]](1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9), [[Участник:Алимов Дамир Алиевич|Д.А. Алимов]](1.1,1.7,1.10,2.4,2.7)<br />
== Свойства и структура алгоритма ==<br />
<br />
=== Общее описание алгоритма ===<br />
Метод Рунге-Кутты четвертого порядка — наиболее распространенный метод из семейства методов Метод Рунге-Кутты численных алгоритмов решения обыкновенных дифференциальных уравнений и их систем. Данные итеративные методы явного и неявного приближённого вычисления были разработаны около 1900 года немецкими математиками К. Рунге и М. В. Куттой.<br />
<br />
Формально, методом Рунге-Кутты является модифицированный и исправленный метод Эйлера, они представляют собой схемы второго порядка точности. Существуют стандартные схемы третьего порядка, не получившие широкого распространения.<br><br />
Основная идея алгоритмов Рунге - Кутты состоит в замене правой части дифференциального уравнения, зависящей от искомой неизвестной функции, некоторым приближением. Если задачу Коши переписать в интегральном виде<br />
:<math><br />
y(x) = y_{0} + \int\limits_{x_{0}}^{x}f(t,y)dt,<br />
</math><br />
то, применяя различные численные формулы для расчёта интеграла в правой части уравнения, можно получить методы Рунге - Кутты различных порядков.<br />
<br />
Общий вид формул методов Рунге - Кутты с шагом сетки <math>h_{n}</math>:<br />
:<math><br />
y = f(t,y),\,\,\, y(t_{0}) = y_{0},<br />
</math><br />
:<math><br />
y_{n+1} = y_{n} + h_{n+1}\sum\limits_{i=1}^{s}b_{i}K_{n}^{i},<br />
</math><br />
где<br />
:<math><br />
K_{n}^{i} = f(t_{n} + c_{i}h_{n+1}, y_{n} + h_{n+1}\sum\limits_{j=1}^{i-1}a_{ij}K_{n}^{j}).<br />
</math><br />
Конкретный метод Рунге - Кутты определяется набором коэффициентов <math>b_{i}, c_{j}, a_{ij}</math>, которые должны удовлетворять определённым соотношениям.<br />
<br />
=== Математическое описание алгоритма ===<br />
<br />
==== Метод Рунге-Кутты 4-го порядка для задачи Коши для ДУ первого порядка ====<br />
Рассмотрим задачу Коши, где правая часть удовлетворяет условиям теорем существования и единственности решения.<br />
:<math><br />
y' = f(x,y),\ a \leq x \leq b;\ y(a) = y^0<br />
</math><br />
Зададим равномерную сетку<br />
:<math><br />
x_i = a + ih,\ i = 1,\dots, n,\ h = \frac{b-a}{n}<br />
</math><br />
<br />
Введём обозначения <math>y(x_i) = y_i</math>. Получим вычислительную формулу:<br />
<br />
:<math><br />
\begin{cases}<br />
k_1 = hf(x_i,y_i)\\<br />
k_2 = hf(x_i + h/2,y_i + k_1/2)\\<br />
k_3 = hf(x_i + h/2,y_i + k_2/2)\\<br />
k_4 = hf(x_i + h,y_i + k_3)\\<br />
y_{i+1} = y_i + [ k_1 + 2k_2 + 2k_3 + k_4 ]/6 \\<br />
\end{cases}<br />
</math><br />
<br />
==== Метод Рунге-Кутты 4-го порядка для задачи Коши для системы ДУ первого порядка ====<br />
Численное решение задачи Коши для систем ОДУ 1-го порядка методами Рунге-Кутты ищется по тем же формулам, что и для ОДУ первого порядка. Например, решение методом Рунге-Кутты 4-го порядка можно найти, если положить:<br />
:<math><br />
y_i \rightarrow \bar y_i<br />
</math><br />
:<math><br />
f(x_i,y_i) \rightarrow \bar f(x_i,\bar y_i)<br />
</math><br />
:<math><br />
k_l \rightarrow \bar k_l<br />
</math><br />
:<math><br />
\bar k_l = <br />
\begin{pmatrix}<br />
k^i_{l,1}\\<br />
\vdots\\<br />
k^i_{l,m}\\<br />
\end{pmatrix}<br />
</math><br />
где m – размерность системы, <math> l = 1, \dots, 4 </math>.<br />
В результате получим<br />
:<math><br />
\begin{cases}<br />
\bar k_1 = h\bar f(x_i,\bar y_i)\\<br />
\bar k_2 = h\bar f(x_i + h/2,\bar y_i + \bar k_1/2)\\<br />
\bar k_3 = h\bar f(x_i + h/2,\bar y_i + \bar k_2/2)\\<br />
\bar k_4 = h\bar f(x_i + h,\bar y_i + \bar k_3)\\<br />
\bar y_{i+1} = \bar y_i + [ \bar k_1 + 2\bar k_2 + 2\bar k_3 + \bar k_4 ]/6 \\<br />
\end{cases}<br />
</math><br />
<br />
=== Вычислительное ядро алгоритма ===<br />
Вычислительным яром алгоритма можно считать вычисление значение вектора <math> \bar y_i </math>, то есть вычислительным ядром является цикл по i. Иными словами сам алгоритм совпадает со своим ядром и был описан в пункте [[#Метод Рунге-Кутты для задачи Коши для системы ДУ первого порядка|1.2.2]].<br />
<br />
=== Макроструктура алгоритма ===<br />
Макроструктуру алгоритма составляют вычисления коэффициентов <math>k_{j},\,j=1,..,4</math> при нахождении значений искомой функции в каждом узле.<br />
<br />
=== Схема реализации последовательного алгоритма ===<br />
Приведем возможную реализацию последовательного алгоритма на языке Matlab (правая часть ОДУ задана в виде функции func(x,y))<br />
<source lang="matlab" line="1"><br />
function [x, y] = RungeKutta4(y0, a, b, n)<br />
h = (b-a)/n;<br />
x = a : h : b<br />
<br />
y(:, 1) = y0;<br />
for i = 2 : n<br />
k1 = h*func(x(i), y(:, i))<br />
k2 = h*func(x(i) + h/2, y(:, i) + k1/2)<br />
k3 = h*func(x(i) + h/2, y(:, i) + k2/2)<br />
k4 = h*func(x(i) + h, y(:, i) + k3)<br />
y(:, i+1) = y(:, i) + (k1 + 2*k2 + 2*k3 + k4)/6 <br />
end<br />
end<br />
</source><br />
<br />
<br />
=== Последовательная сложность алгоритма ===<br />
Каждый шаг цикла алгоритма состоит из 4 обращений к функции <math> \bar f </math>, 11 умножений и 10 сложений. Так как обращение к функции <math> \bar f </math> является наиболее сложной операцией, то сложность линейного выполнения алгоритма можно определить как <math> 4mn </math>.<br />
<br />
=== Информационный граф ===<br />
Опишем граф алгоритма аналитически и графически.<br><br />
Пусть задана система из <math> m </math> одномерных дифференциальных уравнений. Граф имеет трёхмерную структуру, которая представляет собой связанные между собой ограниченные плоскости (уровни). На каждом i-ом уровне вычисляется i-ое приближение вектора решения системы. Всего таких уровней – n, где n – количество узлов, в которых вычисляется приближённое решение системы дифференциальных уравнений. Выходные данные i-го уровня являются входными данными для i+1-го уровня.<br><br />
Вершины информационного графа делятся на два типа:<br />
<ul><br />
<li>Первому типу вершин соответствует вычисление одной координаты функции, стоящей в правой части дифференциального уравнения, в точках, подаваемых на вход вершине. Результатом выполнения операции является одна координата вектора <math>k_{l},\,\,\,l = 1,2,3,4</math>. На каждом уровне таких вершин будет <math>4m</math>, а их общее количество – <math>4mn</math>.</li><br />
<li>Второму типу вершин соответствует операция <math>a + bc</math>. Эти вершины также можно разделить на две группы. В вершинах первой группы вычисляются выражения, результат которых будет подаваться в качестве аргументов для вершин первого типа, при этом входными данными для этих вершин будут результаты срабатывания m вершин первого типа, расположенных на одном уровне. На каждом уровне вершин первой группы будет <math>3m</math>, а их общее количество – <math>3mn</math>. Входными данными для вершин второй группы будет результат срабатывания одной вершины первого типа и одной такой же вершины второго типа. Результатом срабатывания данной вершины на i-ом уровне будут m координат вектора i-го приближения решения системы. На каждом уровне таких вершин будет <math>4m</math>, а их общее количество – <math>4mn</math>. </li><br />
</ul><br />
<br />
Приведём графическую иллюстрацию информационного графа (рис. 1). Для того чтобы сильно не загромождать граф, рассмотрим случай системы из двух дифференциальных уравнений. Красным цветом обозначены вершины первого типа, зелёным - вершины второго типа. Входные данные задачи подаются на все вершины первого типа, а также на <math>m</math> левых вершин второй группы второго типа и на все вершины первой группы второго типа на первом уровне подаются начальные значения искомой функции (см. пункт [[#HРесурс параллелизма алгоритма|1.8]]). Выходными данными будут результаты срабатывания <math>m</math> правых вершин второй группы второго типа на каждом уровне. Результаты срабатывания остальных вершин являются промежуточными данными алгоритма.<br />
[[Файл:Graf_2_rk.jpg|500px|thumb|center|Рисунок 1. Информационный граф для системы из двух дифференциальных уравнений.]]<br />
<br />
=== Ресурс параллелизма алгоритма ===<br />
Поскольку в описанной выше вычислительной схеме наиболее трудоемкой является операция расчета правых частей ОДУ при вычислении <math>k_i ( i = 1, \dots, 4) </math>, то основное внимание следует уделить распараллеливанию этой операции.<br />
Здесь будет применяться подход декомпозиции уравнений системы ОДУ на подсистемы.<br />
Поэтому для инициализации рассмотрим следующую схему декомпозиции данных по имеющимся процессорным элементам с локальной памятью: на каждый <math>\mu</math>- ПЭ (процессорный элемент) (<math>\mu = 0, \dots, p-1 </math>) распределяется <math> n/p </math> дифференциальных уравнений и вектор <math> \bar y_0 </math>. Далее расчеты производятся по следующей схеме:<br />
# на каждом ПЭ одновременно вычисляются <math> n/p </math> соответствующих компонент вектора <math> \bar k_1 </math> по формуле <math> [ \bar k_1 ]_{\mu} = h[ \bar f(x_i, \bar y_i) ]_{\mu} </math><br />
# для обеспечения второго расчетного этапа необходимо провести сборку вектора <math> \bar k_1 </math> целиком на каждом ПЭ. Затем независимо выполняется вычисление компонент вектора <math> \bar k_2 </math> по формуле <math> [ \bar k_2 ]_{\mu} = h[ \bar f(x_i + h/2,\bar y_i + \bar k_1/2)]_{\mu} </math>;<br />
# проводится сборка вектора <math> \bar k_2 </math> на каждом ПЭ, вычисляются компоненты вектора <math> \bar k_3:\ [ \bar k_3 ]_{\mu} = h [\bar f(x_i + h/2,\bar y_i + \bar k_2/2)]_{\mu} </math>;<br />
# проводится сборка вектора <math> \bar k_3 </math> на каждом ПЭ, вычисляются компоненты вектора <math> \bar k_4:\ [ \bar k_4 ]_{\mu} = h [\bar f(x_i + h,\bar y_i + \bar k_3)]_{\mu} </math>;<br />
# рассчитываются с идеальным параллелизмом компоненты вектора <math> \bar y_{i+1}:\ [\bar y_{i+1}]_{\mu} = [\bar y_{i}]_{\mu} + ([ \bar k_1 ]_{\mu} + 2[ \bar k_2 ]_{\mu} + 2[ \bar k_3 ]_{\mu} + [ \bar k_4 ]_{\mu})/6\ </math> и производится сборка вектора <math> \bar y_{i+1} </math> на каждом ПЭ. Если необходимо продолжить вычислительный процесс, то полагается <math> i = i + 1 </math> и осуществляется переход на п. 1<br />
<br />
Заметим, что данный алгоритм обладает конечным параллелизмом, но не массовым, так как циклы алгоритма являются информационно зависимыми.<br />
<br />
На каждом ярусе в данном алгоритме каждый ПЭ производит четыре операции вычисления правых частей ОДУ, шестнадцать операций сложения векторов и умножения вектора на число, а так же р-1 пересылку данных между другими ПЭ, что довольно сильно замедляет выполнение алгоритма и является накладными расходами при распараллеливании алгоритма.<br />
При этом количество итераций цикла равно длине вектора <math> x </math>, то есть <math> n </math>.<br />
Из вышеуказанного следует, что при классификации по высоте [[глоссарий#Ярусно-параллельная форма графа алгоритма|ЯПФ]] алгоритм имеет сложность <math> O(n) </math>, а при классификации по ширине ЯПФ – O(m) (при условии, что <math> p = m </math>).<br />
<br />
=== Входные и выходные данные алгоритма ===<br />
Входными данными алгоритма являются:<br />
# вектор <math> y^0 </math> размерности <math> m </math>;<br />
# границы временного интервала <math> a </math> и <math> b </math>;<br />
# частота дискретизации <math> n </math>;<br />
<br />
Всего размер входных данных: <math> m + 3 </math><br />
<br />
Выходными данными являются<br />
# <math> n </math> векторов <math> \bar y_i </math> размерности <math> m </math>;<br />
# вектор <math> \bar x </math> размерности <math> n </math><br />
<br />
Всего размер выходных данных: <math> (m+1)n </math><br />
<br />
=== Свойства алгоритма ===<br />
Перечислим основные свойства алгоритма:<br />
<ul><br />
<li>Точность. Как следует из названия, у метода Рунге-Кутты 4-го порядка ошибка на конечно интервале интегрирования имеет порядок <math>\mathcal{O}(h^{4})</math>, где <math>h</math> – расстояние между узлами, в которых вычисляется искомая функция.</li><br />
<li>Устойчивость. Метод имеет интервал абсолютной устойчивости <math>(-2.78; 0)</math>. (Определение понятия интервала абсолютной устойчивости можно найти в учебном пособии <ref name="uch">А. Ф. Заусаев. Разностные методы решения обыкновенных дифференциальных уравнений.// Учебное пособие. Самарский гос. техн. ун-т, 2010 - 100 с. </ref>.)</li><br />
<li>Отношение числа расчётов функции, стоящей в правой части задачи Коши, при последовательном алгоритме к числу расчётов при параллельном равняется <math>p</math>, где <math>p</math> число, используемых процессорных элементов. То есть при увеличении числа используемых процессорных элементов скорость расчётов увеличивается, но стоит учитывать, что число пересылок данных между процессами при этом резко увеличивается также и, в свою очередь, замедляет работу программы.</li><br />
<li>Если известен класс функций, которые могут стоять в правой части задачи Коши, то алгоритм можно эффективно модифицировать, чтобы избавиться от лишних пересылок данных.</li><br />
<li>Количество операций алгоритма равно <math> n(m-1) </math> при этом совокупность входных и выходных параметров равняется <math> mn + n + m +4 </math>. Это означает, что [[глоссарий#Вычислительная мощность|вычислительная мощность]] алгоритма стремиться к 1 при увеличении размерности задачи. </li><br />
</ul><br />
<br />
== Программная реализация алгоритма ==<br />
<br />
=== Особенности реализации последовательного алгоритма ===<br />
=== Локальность данных и вычислений ===<br />
==== Локальность реализации алгоритма ====<br />
===== Структура обращений в память и качественная оценка локальности =====<br />
===== Количественная оценка локальности =====<br />
===== Анализ на основе теста Apex-Map =====<br />
=== Возможные способы и особенности параллельной реализации алгоритма ===<br />
=== Масштабируемость алгоритма и его реализации ===<br />
==== Масштабируемость алгоритма ====<br />
<br />
==== Масштабируемость реализации алгоритма ====<br />
Проведём исследование масштабируемости параллельной реализации метода Рунге - Кутты согласно [[Scalability methodology|методике]]. Исследование проводилось на суперкомпьютере "Ломоносов"<ref name="Lom">Воеводин Вл., Жуматий С., Соболев С., Антонов А., Брызгалов П., Никитенко Д., Стефанов К., Воеводин Вад. Практика суперкомпьютера «Ломоносов» // Открытые системы, 2012, N 7, С. 36-39.</ref> [http://parallel.ru/cluster Суперкомпьютерного комплекса Московского университета]. Исследовалась собственная параллельная реализация алгоритма, написанная на языке C++ с использованием стандарта MPI. Сборка программы производилась при помощи компилятора компании Intel версии 15.0 (без указания ключей компиляции) с использованием библиотеки OpenMPI версии 1.8.4 . Расчеты проводились на узлах из [http://parallel.ru/cluster/lomonosov.html группы] T-Blade2 с процессорами Intel Xeon 5570 Nehalem 2.93GHz, при этом для каждого MPI - процесса выделялось одно ядро.<br />
<br />
Набор и границы значений изменяемых [[Глоссарий#Параметры запуска|параметров запуска]] реализации алгоритма: <br />
<br />
* число процессоров [4 : 100] с шагом по степеням двойки до 64, далее с шагом 10;<br />
* размерность системы [4 : 7000].<br />
<br />
В результате проведённых экспериментов был получен следующий диапазон [[Глоссарий#Эффективность реализации|эффективности реализации]] алгоритма:<br />
<br />
* минимальная эффективность реализации 0.0000032%;<br />
* максимальная эффективность реализации 0.0024%.<br />
<br />
Перечислим некоторые особенности тестируемой параллельной реализации:<br />
<br />
* для тестирования использовалась псевдослучайная правая часть системы, состоящая из суперпозиций случайно выбранных элементарных функций;<br />
* начальные значения искомой функции задавались случайным образом;<br />
* так как в общем случае правая часть системы неизвестна, то не представляется возможным измерять производительность в гигафлопсах; в качестве показателя производительности использовалось количество точек, в которых была вычислена функция.<br />
<br />
На следующих рисунках приведены графики [[Глоссарий#Производительность|производительности]] и эффективности параллельной реализации метода Рунге-Кутты в зависимости от изменяемых параметров запуска.<br />
<br />
<br />
[[Файл:Perf_rk.jpg|500px|thumb|center|Рисунок 2. Изменение производительности параллельного алгоритма в зависимости от размерности системы и числа процессоров.]]<br />
[[Файл:Eff_rk_fin.jpg|500px|thumb|center|Рисунок 3. Эффективность работы параллельного алгоритма в зависимости от размерности системы и числа процессоров.]]<br />
<br />
Построим оценки масштабируемости протестированной параллельной реализации метода Рунге - Кутты 4-го порядка: <br />
<br />
* По числу процессов -0.000000768. При увеличении числа процессов эффективность на рассмотренной области изменений параметров запуска уменьшается, однако уменьшение достаточно медленное, что связано с крайне низкой максимальной эффективностью работы параллельной реализации алгоритма. Уменьшение эффективности при увеличении числа процессов для систем с небольшой размерностью объясняется тем, что при количестве процессов превышающем размерность системы часть из них не будет участвовать в вычислениях. Для систем с большой размерностью увеличение процессов негативно сказывается на эффективности, так как увеличивается количество пересылок между ними, что существенно затормаживает работу. <br />
<br />
* По размеру задачи 0.000000763. При увеличении размерности системы при фиксированном количестве процессов эффективность на рассмотренной области изменений параметров запуска увеличивается, причём увеличение наблюдается на почти всей рассматриваемой области. Это объясняется тем, что при фиксированном количестве процессов увеличение размерности системы приводит к увеличению вычислительной нагрузки на каждый процессор, вследствие чего увеличивается время вычислительной работы относительно времени простоя. С увеличением числа процессов скорость возрастания эффективности при увеличении размерности задачи уменьшается, так как количество пересылок становится достаточно большим. <br />
<br />
* По двум направлениям 0.00000000174. При одновременном увеличении количества процессов и размерности системы эффективность увеличивается. Скорость возрастания эффективности крайне низкая, что объясняется высокими накладными расходами.<br />
<br />
[https://github.com/AlimovDamir/Runge-Kutta Исследованная параллельная реализация на языке C++]<br />
<br />
=== Динамические характеристики и эффективность реализации алгоритма ===<br />
=== Выводы для классов архитектур ===<br />
=== Существующие реализации алгоритма ===<br />
Метод 4-го порядка является самым часто используемым из всех схем Рунге - Кутты, поэтому существует множество его программных последовательных реализаций как коммерческих, так и бесплатных. Одной из самых известных программных реализаций является ode45 в среде MATLAB, у которой имеется большое количество возможностей для настройки численного расчёта, что делает её достаточно удобной для использования. Также метод Рунге - Кутты 4-го порядка реализован в параллельной библиотеке PETSc, которая является свободно распространяемой. Несмотря на то, что в этой библиотеки большинство методов реализовано с использованием параллельных алгоритмов, метод Рунге - Кутта в ней реализован последовательным. Довольно трудно найти параллельную реализацию метода в случае рассмотрения общей задачи Коши, но существует множество научных работ, в которых описываются параллельные реализации метода Рунге - Кутты для конкретных классов систем, например, в работе А.В. Старченко<ref name="Star">А. В. Старченко, В. Н. Берцун. Методы параллельных вычислений// Учебник. – Томск: Изд-во Том. ун-та, 2013. – 223 с</ref> описывается параллельная реализация для линейных систем.<br />
<br />
== Литература ==<br />
<br />
<references \></div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%A3%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA:Odbaev/%D0%9D%D0%B0%D1%85%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%81%D0%BE%D0%B1%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D1%8B%D1%85_%D1%87%D0%B8%D1%81%D0%B5%D0%BB_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B9_%D0%BC%D0%B0%D1%82%D1%80%D0%B8%D1%86%D1%8B_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%BC_QR_%D1%80%D0%B0%D0%B7%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D1%8F_(2)&diff=18906Участник:Odbaev/Нахождение собственных чисел квадратной матрицы методом QR разложения (2)2016-11-22T07:43:17Z<p>VadimVV: </p>
<hr />
<div>{{Assignment|VadimVV}}<br />
<br />
{{algorithm<br />
| name = Нахождение собственных чисел квадратной матрицы методом QR разложения<br />
| serial_complexity = <math>N * O(n^3)</math><br />
| pf_height = <math>N*(12n-15)</math><br />
| pf_width = <math>O(n^2)</math><br />
| input_data = <math>n^2</math><br />
| output_data = <math>n</math><br />
}}<br />
<br />
Основные авторы описания: [[Участник:Odbaev|О.Д.Баев]] (пп. 1.1 - 1.5 базовый, 1.9, 2.4, 2.7), [[Участник:AlexShevelev|А.С.Шевелев]] (пп. 1.2-1.5 хессенберг, 1.6 - 1.8, 1.10)<br />
<br />
= Свойства и структура алгоритма =<br />
<br />
== Общее описание алгоритма ==<br />
QR-алгоритм — это численный метод в линейной алгебре, предназначенный для решения полной проблемы собственных значений, то есть отыскания всех собственных чисел и собственных векторов матрицы. Был разработан в конце 1950-х годов независимо В.Н. Кублановской и Дж. Фрэнсисом.<br />
<br />
== Математическое описание алгоритма ==<br />
<br />
=== Базовый QR-алгоритм ===<br />
Пусть <math>A</math> — вещественная квадратная матрица, для которой мы хотим найти собственные числа и векторы. Положим <math>A_0 = A</math>. На ''k''-ом шаге (начиная с ''k'' = 0) вычислим QR-разложение <math>A_k = Q_k R_k</math>, где <math>Q_k</math> — ортогональная матрица (то есть <math>Q_k^{T} = Q_k^{-1}</math>), а <math>R_k</math> — верхняя треугольная матрица. Затем мы определяем <math>A_{k+1} = R_k Q_k</math>.<br />
<br />
Заметим, что<br />
: <math> A_{k+1} = R_k Q_k = Q_k^{-1} Q_k R_k Q_k = Q_k^{-1} A_k Q_k = Q_k^{T} A_k Q_k, </math><br />
то есть все матрицы <math>A_k</math> являются подобными и их собственные значения равны.<br />
<br />
Пусть все диагональные миноры матрицы <math>A</math> не вырождены. Тогда последовательность матриц <math>A_k</math> при <math>k \rightarrow \infty</math> сходится по форме к клеточному правому треугольному виду, соответствующему клеткам с одинаковыми по модулю собственными значениями. <ref> Бахвалов Н.С., Жидков Н.П., Кобельков. Г.М. "Численные методы" — 6-е изд. — М. : БИНОМ. Лаборатория знаний, 2008. — 636 с. </ref><br />
<br />
Для того, чтобы получить собственные векторы матрицы, нужно перемножить все матрицы <math>Q_k</math>.<br />
<br />
=== Приведение матрицы к форме Хессенберга ===<br />
Матрица <math>A</math> имеет форму Хессенберга<ref>[https://en.wikipedia.org/wiki/Hessenberg_matrix | Матрица Хессенберга]</ref>, если все элементы <math>a_{ij}</math> под первой поддиагональю равны нулю (<math>a_{ij} = 0, i > j + 1</math>).<br />
<br />
Любая матрица <math>A</math> может быть приведена к подобной матрице, имеющей форму Хессенберга. Подобная манипуляция позволяет значительно ускорить дальнейший процесс QR-разложения. Одним из способов приведения матрицы к форме Хессенберга является метод Хаусхолдера<ref>[[Метод Хаусхолдера (отражений) приведения матрицы к хессенберговой (двухдиагональной) форме|Метод Хаусхолдера (отражений) приведения матрицы к хессенберговой]]</ref>.<br />
<br />
Идея метода состоит в последовательном приведении исходной матрицы <math>A</math> к форме Хессенберга за счет умножения на матрицы отражений, каждая из которых может быть определена одним вектором и имеет вид <math>U = E - 2ww^*</math>, где <math>w</math> - вектор, удовлетворяющий равенству <math>w^*w = 1</math>.<br />
Цель домножения исходной матрицы на матрицу отражения на <math>i</math>-ом шаге заключается в обнулении поддиагональных элементов <math>i</math>-го столбца. Таким образом за <math>n-1</math> итерацию исходная матрица будет приведена к форме Хессенберга.<br />
<br />
При этом матрица отражений на каждом из шагов имеет не стандартный вид, описанный ранее, а <math>U=E-\frac{1}{\gamma}vv^*</math>, где <math>v</math> находится для левых матриц отражения через координаты текущего <math>i</math>-го столбца при помощи операции скалярного произведения.<br />
<br />
=== QR-алгоритм со сдвигами ===<br />
Скорость сходимости QR-алгоритма зависит от того, насколько близки друг к другу собственные числа матрицы <math>A</math> - чем ближе они друг к другу, тем медленнее алгоритм сходится. Поэтому для ускорения сходимости используют так называемые сдвиги.<br />
<br />
Суть метода заключается в следующем:<br />
Пусть на <math>k</math>-й итерации есть матрица <math>A_k</math>, тогда переход к матрице <math>A_{k+1}</math> происходит следующим образом:<br />
* Выбирается число <math>v_k</math> и выполняется QR-разложение: <math>A_k - v_kE = Q_kR_k</math>.<br />
* <math>A_{k+1} = R_kQ_k + v_kE</math><br />
При этом матрицы <math>A_k</math> и <math>A_{k+1}</math> подобны, поэтому набор собственных чисел не изменяется.<br />
<br />
== Вычислительное ядро алгоритма ==<br />
=== Базовый QR-алгоритм ===<br />
Основными вычислительными ядрами алгоритма являются:<br />
* QR-разложение матрицы <math>A_k = Q_k R_k</math><br />
* Умножение плотных матриц <math>A_{k+1} = R_k Q_k</math><br />
<br />
Существуют различные алгоритмы вычисления QR-разложения. Можно выделить такие, как метод Гивенса и метод Хаусхолдера.<br />
<br />
Подробное описание вычислительных ядер содержится в соответствующих статьях, указанных в пункте 1.4 (Макроструктура алгоритма).<br />
<br />
=== QR-алгоритм с приведением к форме Хессенберга и сдвигами ===<br />
Основными вычислительными ядрами данного варианта алгоритма являются:<br />
* Приведение исходной матрицы <math>A</math> к матрице в форме Хессенберга. Данная операция производится методом Хаусхолдера.<br />
* Выполнение QR-разложения матрицы <math>A_k - v_kE = Q_kR_k</math> модифицированным методом Гивенса.<br />
* Перемножение двух плотных матриц <math>R_kQ_k</math>.<br />
<br />
== Макроструктура алгоритма ==<br />
=== Базовый QR-алгоритм ===<br />
QR-алгоритм на каждой итерации использует следующие макрооперации:<br />
# QR-разложение<br />
#* [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)|Метод Гивенса (вращений)]]<br />
#* [[Метод_Хаусхолдера_(отражений)_QR-разложения_квадратной_матрицы,_вещественный_вариант|Метод Хаусхолдера (отражений)]]<br />
# [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Умножение матриц]]<br />
<br />
=== QR-алгоритм с приведением к форме Хессенберга и сдвигами ===<br />
В данном варианте макроструктура алгоритма выглядит следующим образом:<br />
# Приведение исходной матрицы к матрице в форме Хессенберга.<br />
# Итеративное выполнение QR-разложения методом Гивенса<ref name="QRGivens">[[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]</ref> и перемножения матриц.<br />
<br />
== Схема реализации последовательного алгоритма ==<br />
=== Базовый QR-алгоритм ===<br />
QR-алгоритм является итерационным.<br />
<br />
На каждой итерации <math style="vertical-align:0%"> k </math>:<br />
# Строится QR-разложение матрицы <math> A_k = Q_k R_k </math><br />
# Получается матрица <math> A_{k+1} = R_k Q_k </math>, используемая на следующей итерации<br />
<br />
Алгоритм выполняется до сходимости матрицы <math> A_k</math> к треугольному виду.<br />
<br />
<br />
Псевдокод алгоритма:<br />
<br />
'''algorithm''' QR '''is'''<br />
'''input:''' Matrix ''A''<br />
'''output:''' Eigenvalues of the matrix ''A''<br />
<br />
'''repeat'''<br />
''Q'', ''R'' &larr; QR_factorization(''A'')<br />
''A'' &larr; ''R''&times;''Q''<br />
'''until''' convergence<br />
<br />
'''return''' diag(A)<br />
<br />
=== QR-алгоритм с приведением к форме Хессенберга и сдвигами ===<br />
Как уже было описано ранее, данный алгоритм состоит из следуюших этапов:<br />
* Приведение исходной матрицы <math>A</math> к матрице в форме Хессенберга <math>H</math>.<br />
* Выполнение <math>k</math> итераций (<math>A_0 = H</math>):<br />
** Выбор коэффициента <math>v_k</math>.<br />
** QR-разложение <math>A_k - v_kE = Q_kR_k</math>.<br />
** <math>A_{k+1} = R_kQ_k + v_kE</math>.<br />
<br />
Псевдокод алгоритма:<br />
<br />
'''algorithm''' QR '''is'''<br />
'''input:''' Matrix ''A''<br />
'''output:''' Eigenvalues of the matrix ''A''<br />
<br />
''H'' &larr; To_Hessenberg(''A'')<br />
''A &larr; H''<br />
'''repeat'''<br />
''v_k'' &larr; Get_coeff(''A'')<br />
''Q'', ''R'' &larr; QR_factorization(''A - v_k*E'')<br />
''A'' &larr; ''R''&times;''Q + v_k*E'' <br />
'''until''' convergence<br />
<br />
'''return''' diag(A)<br />
<br />
== Последовательная сложность алгоритма ==<br />
=== Базовый QR-алгоритм ===<br />
Пусть дана матрица <math> A \in \mathbb{R}^{n \times n}</math>. До момента приведения матрицы к треугольной форме произведено <math>N</math> итераций алгоритма.<br />
На каждой итерации алгоритма производится три действия: QR-разложение, перемножение матриц и проверка, является ли матрица треугольной.<br />
<br />
Распишем последовательную сложность каждого действия:<br />
* QR-разложение:<br />
** Методом Гивенса (вращений) - <math>2*n^3</math><ref name="QRGivens">[[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]</ref>.<br />
** Методом Хаусхолдера (отражений) - <math>\frac{4}{3}n^3</math><ref name="QRHouseholder">[[Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант|Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы]]</ref>.<br />
* Перемножение матриц - <math>n^3</math> операций<ref name="MatrixMultiplication">[[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]</ref>.<br />
* Проверка, имеет ли матрица треугольную форму - <math>\frac{n^2 - n}{2}</math> операций.<br />
Таким образом, на каждой итерации последовательный алгоритм выполняет <math>O(n^3)</math> операций. Для всего алгоритма потребуется выполнить <math>N * O(n^3)</math> операций.<br />
<br />
=== QR-алгоритм с приведением к форме Хессенберга и сдвигами ===<br />
Пусть как и для базового алгоритма дана матрица <math> A \in \mathbb{R}^{n \times n}</math>. Как уже было описано выше, в данном варианте алгоритма выполняются три основные операции: приведение исходной матрицы к матрице в форме Хессенберга, QR-разложение методом Гивенса и перемножение матриц.<br />
<br />
Распишем последовательную сложность каждой операции:<br />
* Приведение к матрице в форме Хессенберга методом Хаусхолдера потребует <math>O(n^3)</math> операций.<br />
* QR-разложение методом Гивенса матрицы в форме Хессенберга - <math>6n^2 + O(n)</math> операций <ref>[https://en.wikipedia.org/wiki/QR_algorithm | QR algorithm]</ref>.<br />
* Перемножение матриц потребует столько же операций, как и для базового алгоритма - <math>n^3</math> операций.<br />
* Вычисление сдвигов не является затратной операцией по сравнению с другими.<br />
<br />
Таким образом, полная последовательная сложность QR-алгоритма с матрицей в форме Хессенберга и сдвигами - <math>O(n^3) + N*(6n^2+O(n))</math> или <math>O(n^3) + N*O(n^2)</math><br />
<br />
== Информационный граф ==<br />
На Рисунке 1 изображен макрограф описываемого QR-алгоритма, представляющего собой последовательность итераций алгоритма. На рисунке 2 представлен граф, на котором более подробно отображено содержимое каждой итерации.<br />
Информационные графы [[Метод_Гивенса_(вращений)_QR-разложения_квадратной_матрицы_(вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|QR-разложения]] и [[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)#.D0.98.D0.BD.D1.84.D0.BE.D1.80.D0.BC.D0.B0.D1.86.D0.B8.D0.BE.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B3.D1.80.D0.B0.D1.84|матричного умножения]] представлены в соответствующих статьях.<br />
[[Файл:macro_shev_graph.png|thumb|center|700px|Рисунок 1. Макрограф базового QR-алгоритма.]]<br />
[[Файл:Screenshot - 15.10.2016 - 19-02-55.png|thumb|center|800px|Рисунок 2. Информационный граф i-й итерации QR-алгоритма.]]<br />
<br />
В случае QR-алгоритма, в котором применяется приведение исходной матрицы к форме Хессенберга и сдвиги, представленные графы будут немного изменены: на Рисунке 1 перед первой итерацией появится элемент с приведением матрицы, а на Рисунке 2 добавятся элементы, отображающие сдвиги.<br />
<br />
== Ресурс параллелизма алгоритма ==<br />
=== Базовый QR-алгоритм ===<br />
Базовый алгоритм является итерационным и выполняется последовательно. <br />
Возможность распараллеливания алгоритма предоставляется при реализации операций таких, как QR-разложение, перемножение матриц и проверка, имеет ли матрица треугольную форму.<br />
<br />
Рассчитаем параллельную сложность для каждой из указанных операций:<br />
* QR-разложение:<br />
** Методом Гивенса (вращений) - <math>11n - 16</math><ref name="QRGivens">[[Метод Гивенса (вращений) QR-разложения квадратной матрицы (вещественный вариант)|Метод Гивенса (вращений) QR-разложения квадратной матрицы]]</ref>.<br />
** Методом Хаусхолдера (отражений) - <math>O(n^2)</math><ref name="QRHouseholder">[[Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы, вещественный вариант|Метод Хаусхолдера (отражений) QR-разложения квадратной матрицы]]</ref>.<br />
* Перемножение матриц - <math>n</math><ref name="MatrixMultiplication">[[Перемножение_плотных_неособенных_матриц_(последовательный_вещественный_вариант)|Перемножение плотных неособенных матриц]]</ref>.<br />
* Проверка, имеет ли матрица треугольную форму - <math>1</math>.<br />
<br />
Результирующая параллельная сложность одной итерации <math>12n-15</math> (для метода вращений). А всего алгоритма из <math>N</math> итераций - <math>N*(12n-15)</math>.<br />
<br />
=== QR-алгоритм с приведением к форме Хессенберга и сдвигами ===<br />
Как и для базового алгоритма подсчитаем параллельную сложность для каждой операции, а в конце для всего алгоритма в целом:<br />
* Приведением исходной матрицы к форме Хессенберга - <math>O(n^2)</math>.<br />
* Отдельно каждая итерация - <math>O(n)</math>.<br />
<br />
Итоговая параллельная сложность алгоритма - <math>O(n^2) + N*O(n)</math>.<br />
<br />
== Входные и выходные данные алгоритма ==<br />
<br />
'''Входные данные''': плотная квадратная матрица <math>A</math> размера <math>n</math>.<br />
<br />
'''Объём входных данных''': <math>n^2</math>.<br />
<br />
'''Выходные данные''': <math>n</math> собственных чисел матрицы <math>A</math>.<br />
<br />
'''Объём выходных данных''': <math>n</math>.<br />
<br />
== Свойства алгоритма ==<br />
Описанный алгоритм обладает следующими свойствами:<br />
* Недетерминирован - не известно заранее число итераций алгоритма, которое необходимо произвести до момента схождения матрицы к полностью треугольной форме или с некоторым значением точности. <br />
* Скорость сходимости зависит от собственных чисел. Чем ближе собственные числа друг к другу, тем ниже скорость сходимости.<br />
* Может быть хорошо распараллелен, так как соотношение последовательной и параллельной сложности алгоритма является квадратичным.<br />
* Перемещение данных необходимых для выполнения алгоритма не затратно, так как вычислительная мощность <math> = \frac{N * O(n^3)}{n^2 + n}</math> (отношение числа операций к суммарному объему входных и выходных данных) линейна на каждой итерации.<br />
<br />
= Программная реализация алгоритма =<br />
== Особенности реализации последовательного алгоритма ==<br />
== Локальность данных и вычислений ==<br />
== Возможные способы и особенности параллельной реализации алгоритма ==<br />
== Масштабируемость алгоритма и его реализации ==<br />
<br />
Используется процедура [http://www.netlib.org/scalapack/explore-html/d3/d3a/pdhseqr_8f.html PDHSEQR] из библиотеки [http://www.netlib.org/scalapack/ ScaLAPACK]. <br><br />
Данный метод работает с матрицами в форме Хессенберга, поэтому сначала происходит приведение матрицы к данному виду с помощью процедуры [http://www.netlib.org/scalapack/explore-html/d2/d03/pdgehrd_8f.html PDGEHRD]<br />
<br />
Вычисления производились на суперкомпьютере [http://hpc.cmc.msu.ru/regatta Regatta].<br />
<br />
{| class="wikitable"<br />
|+ Время работы алгоритма (сек)<br />
|-<br />
|Процессы\Матрица||600||1200||1800||2400 <br />
|-<br />
|1||4.30549||25.2566||78.0507||177.054<br />
|-<br />
|4||3.63899||17.6559||51.2146||127.386<br />
|-<br />
|9||3.81662||9.19687||23.9181||53.7105<br />
|-<br />
|16||3.11883||7.26239||16.0958||32.3561<br />
|}<br />
<br />
[[Файл:baev_qr_execution.png|thumb|center|700px|Рисунок 3. Время выполнения QR-алгоритма.]]<br />
<br />
<br />
Эффективность распараллеливания <math>E = S/p</math>, где <math>S = T_1/T_p</math> - полученное ускорение работы программы, а <math>p</math> - число используемых процессов.<br />
<br />
{| class="wikitable"<br />
|+ Эффективность распараллеливания (%)<br />
|-<br />
|Процессы\Матрица||600||1200||1800||2400 <br />
|-<br />
|4||29.57||35.76||38.09||34.74<br />
|-<br />
|9||12.53||30.51||36.25||36.62<br />
|-<br />
|16||8.62||21.73||30.30||34.20<br />
|}<br />
<br />
[[Файл:baev_qr_efficiency.png|thumb|center|700px|Рисунок 4. Эффективность QR-алгоритма.]]<br />
<br />
Оценка масштабируемости<br />
* Минимальная эффективность: 8.62%<br />
* Максимальная эффективность: 38.09%<br />
* По числу процессов: при увеличении числа процессов эффективность в целом уменьшается<br />
* По размеру задачи: при увеличении размера задачи эффективность в целом увеличивается<br />
Уменьшение или увеличение эффективности происходит не интенсивно, существенные изменения заметны только при небольшом размере задачи.<br />
<br />
Библиотека [http://www.netlib.org/scalapack/ ScaLAPACK 2.0.0] была установлена самостоятельно.<br />
Использовался компилятор mpicxx с опциями -lscalapack -llapack и указанием пути до установленной библиотеки.<br />
<br />
[https://algowiki-project.org/ru/Файл:Odbaev_eigenvalues.docx Реализация алгоритма]<br />
<br />
== Динамические характеристики и эффективность реализации алгоритма ==<br />
== Выводы для классов архитектур ==<br />
== Существующие реализации алгоритма ==<br />
Полный алгоритм:<br />
* [http://netlib.org/lapack/ LAPACK]: функция [http://www.netlib.org/lapack/explore-html/d9/d28/dgeev_8f.html DGEEV] (последовательная реализация)<br />
* [http://www.netlib.org/scalapack/ ScaLAPACK]: функция [http://www.netlib.org/scalapack/explore-html/d3/d3a/pdhseqr_8f.html PDHSEQR] (параллельная реализация)<br />
* [http://alglib.sources.ru ALGLIB]: реализация [http://alglib.sources.ru/eigen/nonsymmetric/nonsymmetricevd.php алгоритма] для различных языков программирования<br />
* [http://www.numpy.org Python NumPy]: функции [https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eig.html linalg.eig], [https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.eigvals.html linalg.eigvals ]<br />
* [http://eigen.tuxfamily.org C++ Eigen]: [http://eigen.tuxfamily.org/dox/group__Eigenvalues__Module.html Eigenvalues модуль]<br />
<br />
QR-разложение:<br />
* [http://netlib.org/lapack/ LAPACK]: функция [http://www.netlib.org/lapack/explore-html/d3/d69/dgeqrf_8f.html DGEQRF] (последовательная реализация)<br />
* [http://www.netlib.org/scalapack/ ScaLAPACK]: функция [http://www.netlib.org/scalapack/explore-html/da/d85/pdgeqrf_8f.html PDGEQRF] (параллельная реализация)<br />
* [http://www.mathworks.com MATLAB]: функция [http://www.mathworks.com/help/matlab/ref/qr.html qr]<br />
* [http://www.wolfram.com/mathematica/ Mathematica]: функция [http://reference.wolfram.com/language/ref/QRDecomposition.html QRDecomposition]<br />
* [http://alglib.sources.ru ALGLIB]: реализация [http://alglib.sources.ru/matrixops/qr.php QR-разложения] для различных языков программирования<br />
* [http://www.numpy.org Python NumPy]: функция [https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.qr.html linalg.qr]<br />
* [http://eigen.tuxfamily.org C++ Eigen]: [http://eigen.tuxfamily.org/dox/group__QR__Module.html QR модуль]<br />
<br />
Умножение матриц:<br />
* [http://netlib.org/lapack/ LAPACK]: функция [http://www.netlib.org/lapack/explore-html/d7/d2b/dgemm_8f.html DGEMM] (последовательная реализация)<br />
* [http://www.netlib.org/scalapack/ ScaLAPACK]: функция [http://www.netlib.org/scalapack/explore-html/d6/da2/pdgemm___8c.html PDGEMM] (параллельная реализация)<br />
* [http://www.mathworks.com MATLAB]: функции [http://www.mathworks.com/help/matlab/ref/mtimes.html mtimes, *]<br />
* [http://www.wolfram.com/mathematica/ Mathematica]: функция [https://reference.wolfram.com/language/ref/Dot.html Dot]<br />
* [http://www.numpy.org Python NumPy]: функция [https://docs.scipy.org/doc/numpy/reference/generated/numpy.dot.html numpy.dot]<br />
<br />
= Литература =<br />
<references /></div>VadimVVhttps://algowiki-project.org/w/ru/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5_%D1%83%D1%87%D0%B0%D1%81%D1%82%D0%BD%D0%B8%D0%BA%D0%B0:Odbaev&diff=18905Обсуждение участника:Odbaev2016-11-22T07:43:07Z<p>VadimVV: </p>
<hr />
<div><br />
== Статья [[Нахождение_собственных_чисел_квадратной_матрицы_методом_QR_разложения_(2)]] ==<br />
<br />
=== По существу ===<br />
<br />
* В таком виде алгоритм не применяется из-за медленности. Где приведение к хессенбергу и сдвиги? Изменения затронут все разделы. --[[Участник:Frolov|Фролов А.В.]] ([[Обсуждение участника:Frolov|обсуждение]]) 12:48, 10 ноября 2016 (MSK)<br />
<br />
=== Замечания ===</div>VadimVV