Вы находитесь на странице: 1из 56

Нижегородский государственный университет им. Н.И.

Лобачевского
Факультет вычислительной математики и кибернетики

Образовательный комплекс
«Параллельные численные методы»

Лабораторная работа
Решение систем линейных алгебраических
уравнений с разреженной матрицей
итерационными методами в стационарной
задаче распределения тепла
____________________

Малова А.Ю., Козинов Е.А., Кустикова В.Д.

При поддержке компании Intel

Нижний Новгород
2011
Содержание
ВВЕДЕНИЕ ........................................................................................................ 3
1. МЕТОДИЧЕСКИЕ УКАЗАНИЯ ....................................................... 4
1.1. ЦЕЛИ И ЗАДАЧИ РАБОТЫ ........................................................................ 4
1.2. СТРУКТУРА РАБОТЫ ............................................................................... 5
1.3. ТЕСТОВАЯ ИНФРАСТРУКТУРА ............................................................... 5
1.4. РЕКОМЕНДАЦИИ ПО ПРОВЕДЕНИЮ ЗАНЯТИЙ ....................................... 6
2. ЗАДАЧА ВЫЧИСЛЕНИЯ РАСПРЕДЕЛЕНИЯ ТЕМПЕРАТУР
В ПЛАСТИНЕ ................................................................................................... 6
3. ВЫЧИСЛИТЕЛЬНАЯ СХЕМА ......................................................... 7
4. МЕТОД ВЕРХНЕЙ РЕЛАКСАЦИИ ................................................. 9
5. ФОРМАТЫ ХРАНЕНИЯ ЛЕНТОЧНЫХ МАТРИЦ ................... 12
6. ПОСЛЕДОВАТЕЛЬНАЯ РЕАЛИЗАЦИЯ ..................................... 18
6.1. СОЗДАНИЕ ПРОЕКТА............................................................................. 18
6.2. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ........................................................... 22
6.3. ПРОГРАММНАЯ РЕАЛИЗАЦИЯ МЕТОДА ВЕРХНЕЙ РЕЛАКСАЦИИ ......... 30
6.4. АНАЛИЗ СХОДИМОСТИ МЕТОДА .......................................................... 32
7. «НАИВНАЯ» ПАРАЛЛЕЛЬНАЯ РЕАЛИЗАЦИЯ С
ИСПОЛЬЗОВАНИЕМ INTEL® CILK PLUS ............................................ 34
7.1. СОЗДАНИЕ ПРОЕКТА............................................................................. 34
7.2. ПРОГРАММНАЯ РЕАЛИЗАЦИЯ .............................................................. 36
7.3. АНАЛИЗ МАСШТАБИРУЕМОСТИ........................................................... 37
8. ПАРАЛЛЕЛЬНАЯ РЕАЛИЗАЦИЯ КОНВЕЙЕРНОЙ СХЕМЫ
С ИСПОЛЬЗОВАНИЕМ БИБЛИОТЕКИ INTEL® TBB ........................ 39
8.1. СОЗДАНИЕ ПРОЕКТА............................................................................. 39
8.2. ОПИСАНИЕ КОНВЕЙЕРНОЙ СХЕМЫ РАСПАРАЛЛЕЛИВАНИЯ ............... 40
8.3. ПРОГРАММНАЯ РЕАЛИЗАЦИЯ .............................................................. 45
8.4. АНАЛИЗ МАСШТАБИРУЕМОСТИ........................................................... 53
9. КОНТРОЛЬНЫЕ ВОПРОСЫ ......................................................... 54
10. ДОПОЛНИТЕЛЬНЫЕ ЗАДАНИЯ.................................................. 55
11. ЛИТЕРАТУРА .................................................................................... 55
11.1. ИСПОЛЬЗОВАННЫЕ ИСТОЧНИКИ ИНФОРМАЦИИ ............................... 55
11.2. ДОПОЛНИТЕЛЬНАЯ ЛИТЕРАТУРА ....................................................... 56
11.3. РЕСУРСЫ СЕТИ ИНТЕРНЕТ ................................................................. 56

2
Введение

Но жизнь, к несчастью, коротка,


А путь до совершенства дальний.

И.В. Гёте, «Фауст»

П роблема решения системы линейных алгебраических уравнений


вида
размера , а
где – матрица размера , – известный вектор
– вектор размера , является центральной при
решении многих задач математической физики [1, 4]. Решением системы
указанного вида является вектор , при подстановке которого в систему
получается верное тождество. Система вида называется
совместной, если она имеет хотя бы одно решение.
Множество численных методов решения систем алгебраических уравнений
(СЛАУ) делятся на прямые (точные) и итерационные методы.
В данной лабораторной работе остановимся на итерационных методах
решения СЛАУ.
Итерационный метод строит последовательность приближений к точному
решению СЛАУ. Естественным образом возникает вопрос о сходимости
этой последовательности (или сходимости метода ). Исследование
сходимости некоторых итерационных методов можно найти, в частности, в
книгах [2, 3, 7]. В большинстве задач итерационные методы не сходятся за
конечное число итераций, поэтому вводят критерии остановки метода. Сам
факт сходимости метода не является достаточным, чтобы судить о его
практической применимости. Для этого, наряду со сходимостью, вводится
понятие скорости сходимости итерационного метода. Фактически
скорость сходимости определяется количеством приближений, которые
построены методом до тех пор, пока не выполнен критерий остановки.
В процессе вычислений неизбежно появление вычислительной
погрешности. В связи с этим говорят о вычислительной устойчивости
метода. Для фиксированного алгоритма вычислительная погрешность в
основном определяется машинной точностью . Метод называется
вычислительно устойчивым , если вычислительная погрешность
стремится к нулю при .
Проблемы сходимости и вычислительной устойчивости итерационных
методов – это основные вопросы, которые решаются при исследовании
качества итерационных методов.
Примерами итерационных методов являются методы Якоби, Гаусса-
Зейделя, верхней релаксации и ряд других [1, 2, 3].

3
Итерационные методы, как правило, применяют при решении задач
большой размерности, когда использование прямых методов невозможно
из-за ограниченности оперативной памяти. Системы уравнений,
возникающие во многих практических задачах, являются разреженными.
Недостатком большинства прямых методов является то, что в процессе их
использования большое количество нулевых элементов становятся
ненулевыми, и матрица теряет свойство разреженности и становится
плотно заполненной.
В настоящей работе рассматривается итерационный метод решения СЛАУ
– метод верхней релаксации – применительно к системе с блочной
пятидиагональной матрицей. Матрица указанного вида возникает в
процессе решения стационарной задачи распределения температур в
прямоугольной пластине с заданным температурным режимом на границах
пластины.

1. Методические указания

1.1. Цели и задачи работы

Цель данной лабораторной работы – продемонстрировать


применение итерационных методов решения СЛАУ с
разреженной матрицей на примере стационарной задачи
распределения температур в пластине прямоугольной формы
с заданн ым температурным режимом на границах пластин ы .
Данная цель предполагает решение следующих основных задач:
1. Изучение метода верхней релаксации для решения СЛАУ с матрицами
общего вида.
2. Разработка метода верхней релаксации для решения СЛАУ с матрицей
специального вида (блочная пятидиагональная матрица, у которой на
каждой диагонали в отдельности стоит одинаковое число).
3. Разработка инфраструктуры для проведения массовых экспериментов.
4. Разработка последовательной реализации метода верхней релаксации
для решения СЛАУ с блочной пятидиагональной матрицей.
5. Анализ сходимости разработанного метода верхней релаксации.
6. Разработка «очевидной» параллельной реализации, которая приводит к
модификации метода, с использованием технологии Intel® Cilk Plus [8,
9, 10].
7. Анализ масштабируемости «очевидной» параллельной реализации.

4
8. Разработка конвейерной схемы распараллеливания с использованием
библиотеки Intel® Threading Building Blocks [11].
9. Анализ масштабируемости модифицированной параллельной
реализации.

1.2. Структура работы

В работе рассматривается стационарная задача распределения температур


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

1.3. Тестовая инфраструктура

Вычислительные эксперименты проводились с использованием следующей


инфраструктуры (табл. 1).
Таблица 1. Тестовая инфраструктура

Процессор 2 четырехъядерных процессора Intel®


Xeon E5520 (2.27 GHz)
Память 16 Gb
Операционная система Microsoft Windows 7
Среда разработки Microsoft Visual Studio 2008
Компилятор, Intel® Parallel Studio XE 2011
профилировщик, отладчик

5
Библиотеки Intel® Math Kernel Library (в составе Intel®
Parallel Studio XE 2011);
Intel® Threading Building Blocks (в составе
Intel® Parallel Studio XE 2011)

1.4. Рекомендации по проведению занятий

Для проведения занятий предлагается следующая последовательность


действий:
1. Обсудить понятие ленточной матрицы и способы хранения ленточных
матриц.
2. Напомнить вводную информацию описательного характера о методах
решения СЛАУ с разреженными и ленточными матрицами.
3. Напомнить метод верхней релаксации для матриц общего вида.
4. Рассмотреть содержательную постановку стационарной задачи
распределения температур в прямоугольной пластине с заданным
температурным режимом на границах пластины, а также сведение
данной задачи к решению СЛАУ с пятидиагональной матрицей.
5. Рассмотреть метод верхней релаксации для решения СЛАУ
применительно к блочной пятидиагональной матрице.
6. Разработать тестовую инфраструктуру для сведения стационарной
задачи распределения температур к решению СЛАУ (функции
формирования матрицы системы, правой части).
7. Разработать последовательную реализацию метода верхней релаксации
для систем с блочной пятидиагональной матрицей.
8. Выполнить анализ сходимости метода.
9. Разработать «очевидную» параллельную реализацию и выполнить
анализ ее масштабируемости.
10. Разработать параллельную реализацию метода верхней релаксации на
основании конвейерной схемы. Выполнить анализ масштабируемости.

2. Задача вычисления распределения температур в


пластине

Пусть – длины сторон прямоугольной пластины, ( ) –


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

6
процесс распределения температур в пластине описывается
дифференциальным уравнением Пуассона:
( )
(1)
*( ) , - , -+
Для полного описания стационарного процесса необходимо задать
температурный режим на границе пластины:
( ) ( ) (2)
( ) ( )
( ) ( )
( ) ( )
Отметим, что в угловых точках пластины должны выполняться условия
согласованности граничных условий:
( ) ( )
( ) ( )
( ) ( )
( ) ( ).
Для определенности в качестве функций ( ) ( ) ( ) ( ) ( )
возьмем следующие функции:
( ) (3)
( )
( ) ( ) (4)
( )
( ) ( ) (5)
( )
( ) ( ) (6)
( )
( )= ( ) . (7)

3. Вычислительная схема

Введем сетку , где на области . Для


аппроксимации дифференциального уравнения (1) с граничными
условиями (2) будем использовать крестообразный шаблон.
Аппроксимируем дифференциальное уравнение разностной схемой (8).

7
( ) ( )

(8)

Граничные условия разрешены относительно неизвестных в граничных


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

. /
( ) ( )

. /
( ) ( )

. /

Аналогично можно рассмотреть случаи, когда и .


Сформируем вектор неизвестных ⃗ следующим образом: ⃗ (
) -
внутренние узлы сетки, которые перечислены в порядке, соответствующем
их расположению вдоль оси изменения координаты .
Тогда систему разностных уравнений можно записать в матричном виде
⃗ , где - это значения функции ( ) во внутренних узлах сетки
минус «довески» от граничных условий, а матрица – блочная
пятидиагональная матрица, причем каждая диагональ в отдельности
содержит одинаковое число. На Рис. 1 показан пример такой матрицы для
случая (где коэффициент . /).

8
Рис. 1. Пример матрицы для сетки размерности

4. Метод верхней релаксации

Cистема линейных уравнений ⃗ , полученная в стационарной задаче


теплопроводности, имеет симметричную отрицательно определенную

9
матрицу1. Матрица ( ) будет положительно определена. Тогда для
решения системы (9) применимы численные методы, которые работают
для случая симметричных положительно определенных матриц.
⃗ (9)

Один из них – метод верхней релаксации. Он относится к классу


стационарных одношаговых итерационных методов линейной алгебры [2].
Рассмотрим систему линейных уравнений (10) с симметричной,
положительно определенной матрицей размера
.
(10)

Представим матрицу в виде суммы трех матриц:

. (11)
Здесь – диагональная матрица размера , главная диагональ которой
совпадает с главной диагональю матрицы ; – нижне-треугольная
матрица размера , у которой элементы, стоящие под главной
диагональю, совпадают с элементами матрицы , а главная диагональ
является нулевой; – верхне-треугольная матрица размера , у
которой элементы, стоящие над главной диагональю, совпадают с
элементами матрицы , а главная диагональ также является нулевой.
Тогда канонический вид метода верхней релаксации можно представить в
виде (12):
( ) ( )
( )( ) ( ) (12)

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

1
Матрица отрицательно определена, если для всех скалярное произведение
( ) .

10
( ) ( ) ( )
( )( )
( ) ( ) ( ) ( ) ( )

( ) ( ) ( ) ( )
( )
Учитывая, что , получим:
( ) ( ) ( ) ( )
( ) (13)
Из (13) запишем явные формулы для нахождения компонент нового
вектора ( ) :

( ) ( ) ( )
∑ ( )
(14)
( )

Как видно из формулы (14), при подсчете -ой компоненты нового


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

( ) ( ) ( )
∑ (15)

Скорость сходимости метода верхней релаксации определяется выбором


параметра . Известно из [2], что при решении некоторых классов
разреженных систем уравнений, метод верхней релаксации требует ( )
итераций, а при некотором выборе итерационного параметра  метод
сходится за ( ) итераций. В общем случае нет аналитической формулы
для вычисления оптимального параметра , обеспечивающего
наилучшую скорость сходимости. Однако для рассматриваемой системы
линейных уравнений, построенной по разностной схеме (8), оптимальный
параметр метода верхней релаксации известен [2], и в случае
одинаковых шагов сетки и определяется выражением (16):

(16)
( )

11
Отметим, что минимальное и максимальное собственные числа матрицы
системы уравнений в этом случае составят:

. / . / (17)

. / . / (18)

5. Форматы хранения ленточных матриц

Как было отмечено в п. 3, матрица системы линейных уравнений,


построенная по разностной схеме (8), блочная пятидиагональная. Для ее
хранения удобно применить специальный формат, используемый для
ленточных и диагональных матриц.
Матрицу называют ленточной, если все ее ненулевые элементы
заключены внутри ленты, образованной между диагоналями,
параллельными главной. Если для матрицы справедливо: при
и при , то называется нижней шириной
ленты, – верхней шириной ленты . Величина
называется шириной ленты матрицы (Рис. 2).
Для хранения ленточных матриц существует несколько различных
форматов (см. [5], [6]), рассмотрим некоторые из них.

q – верхняя
ширина ленты
m – ширина
ленты
n – размер
матрицы
p – нижняя
ширина ленты главная
диагональ

Рис. 2. Понятие ленточной матрицы, ширины ленты


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

12
В ленточном строчн ом формате для хранения исходной матрицы
размерности с шириной ленты используется массив размера ,в
котором построчно хранятся ненулевые элементы матрицы . При этом
побочные диагонали доопределяются нулями до размерности : в начале
диагоналей для нижнего треугольника и в конце диагоналей для верхнего
треугольника (Рис. 3). Пример хранения матрицы в строчном ленточном
формате показан на Рис. 4.

0 0
0

хранение по
n
строкам

0
0 0

Рис. 3. Ленточный строчный фортам хранения матриц

Матрица Структура хранения:


( ) Matrix
1 0 2 0 1 0 2
3 4 5 0 3 4 5 0
6 7 8 9 6 7 8 9
10 11 0 12 10 11 0 12
0 13 14 0 13 14 0
15 16 15 16 0 0

Рис. 4. Пример хранения матрицы в ленточном строчном формате


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

13
исходной матрицы хранится в элементе массива , -, где
– ширина верхней ленты матрицы (Рис. 6).

0
0 0
хранение по
столбцам

0 0
0

Рис. 5. Ленточный столбцовый формат хранения матриц

Матрица Структура хранения:


( ) Matrix
1 0 2 0 0 2 0 9 12
3 4 5 0 0 0 5 8 0 14
6 7 8 9 1 4 7 11 13 16
10 11 0 12 3 6 10 0 16 0
0 13 14
15 16

Рис. 6. Пример хранения матрицы в ленточном столбцовом формате

Существует модификация ленточного формата, в которой элементы


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

14
ленточных матриц, то есть используется массив размера , где –
размерность исходной матрицы, – количество ненулевых диагоналей
(Рис. 7). Побочные диагонали доопределяются до общей размерности
нулями аналогично ленточному формату. При этом дополнительно
хранится массив целых чисел Index размера , в котором указывается
сдвиг каждой диагонали от главной – положительные индексы для
верхнего треугольника, отрицательные для нижнего треугольника. Так, для
матрицы, показанной на Рис. 7, массив Index будет содержать элементы
– i1, – i2, 0, i3, i4, i5. Пример хранения матрицы в диагональном строчном
формате показан на Рис. 8.

i3 i4 i5
i2

i1

n – размер
матрицы

Рис. 7. Ленточная матрица с неплотно расположенными


диагоналями

Матрица Структура хранения:


( )
1 2 Matrix 0 1 2
3 4 0 3 4 0
6 7 9 6 7 9
10 11 12 10 11 12
0 13 0 13 0
15 16 15 16 0

Index –1 0 2

Рис. 8. Пример хранения матрицы в диагональном строчном


формате

15
Профильный формат хранения матрицы используется, когда матрица
имеет широкую ленту с большим числом нулей внутри нее. В этом случае
матрица не имеет выраженной структуры, но ее ненулевые элементы
сосредоточены близко к главной диагонали. Рассмотрим использование
профильной схемы для симметричной матрицы.
Определим для каждой строки симметричной матрицы размера
величину сдвига первого ненулевого элемента от главной диагонали
( ), где ( ) – минимальный номер столбца строки , для
которого . Тогда оболочкой матрицы называется множество
элементов , для которых . То есть в -ой строке матрицы
оболочке принадлежат элементы с индексами столбцов от ( ) до ,
всего элементов (Рис. 9). Заметим, что диагональные элементы не
входят в оболочку. Профилем матрицы называется число элементов в
оболочке:
( ) ∑ .

Элементы 0
оболочки

Рис. 9. Профильный формат хранения матрицы. ( )


Для реализации хранения симметричной матрицы с помощью профильной
схемы используется два массива. Все элементы оболочки матрицы,
включая нули, упорядоченные по строкам, хранятся в массиве Matrix
длины ( ) . Диагональный элемент для данной строки
помещается в ее конец. Дополнительно хранится массив индексов Index
размера , в котором содержатся индексы диагональных элементов
матрицы в массиве Matrix. Таким образом, при элементы -ой
строки матрицы хранятся в массиве Matrix с позиции от Index[i –
1] + 1 до Index[i](Рис. 10). Здесь и далее будем предполагать, что
массивы индексируются с нуля.

16
Матрица 1 2
2 3 4
5 6 7 8
4 6 9
7 10
8 11 12
Структура хранения:

Matrix 1 2 3 5 4 6 9 7 0 10 8 0 11 12

Index 0 2 3 6 9 13

Рис. 10. Пример хранения симметричной матрицы в профильном


формате
Существует модификация профильной схемы хранения для
несимметричной матрицы с симметричным портретом (т.е. в случае, когда
ненулевые элементы расположены симметрично относительно главной
диагонали). В этом случае для хранения матрицы используется четыре
массива. Массив di размера содержит диагональные элементы. Массивы
au и al размера ( ) содержат внедиагональные элементы верхнего
треугольника по столбцам и элементы нижнего треугольника по строкам
соответственно. Вспомогательный массив Index размера содержит
индексы начала строк в массивах au и al.
В работе используем диагональную строчную схему для хранения
симметричной матрицы ( ). Так как одна из основных операций в
реализации метода верхней релаксации – умножение строки матрицы на
вектор неизвестных , то для быстрого выполнения операции будем
хранить как верхний, так и нижний треугольник. Тогда дополнительный
массив Index будет содержать элементы . Также
дополним вектор неизвестных нулевыми элементами в начале и в конце, по
размеру блока матрицы :
( )
где ̅̅̅̅̅̅̅̅̅̅.
Пример структуры хранения матрицы в выбранном формате см. на Рис. 11.

17
Матрица Структура хранения:
( )
Matrix
50 -16 -9 0 0 50 -16 -9
-16 50 -16 -9 0 -16 50 -16 -9
-16 50 -9 0 -16 50 0 -9
-9 50 -16 -9 0 50 -16 0
-9 -16 50 -16 -9 -16 50 -16 0
-9 -16 50 -9 -16 50 0 0
о Index -3 -1 0 1 3

Рис. 11. Диагональная строчная схема хранения для реализации


задачи, поставленной в лабораторной работе

6. Последовательная реализация

6.1. Создание проекта

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


Прежде всего, создадим новое Решение (Solution), в которое включим
первый Проект (Project) данной лабораторной работы. Для этого
последовательно выполните следующие шаги:
 Запустите приложение Microsoft Visual Studio 2008.
 В меню File выполните команду New→Project….
 В диалоговом окне New Project в типах проекта выберите Win32, в
шаблонах Win32 Console Application, в поле Solution введите
ВandOverRelaxation, в поле Name – 01_BandOR_seqv, в поле
Location укажите путь к папке с лабораторными работами курса –
c:\ParallelCalculus\. Нажмите OK.
 В диалоговом окне Win32 Application Wizard нажмите Next (или
выберите Application Settings в дереве слева) и установите флаг
Empty Project. Нажмите Finish.
 В окне Solution Explorer в папке Source Files выполните команду
контекстного меню Add→New Item…. В дереве категорий слева
выберите Code, в шаблонах справа – C++ File (.cpp), в поле Name
введите имя файла main. Нажмите Add.

18
 В окне Solution Explorer в папке Header Files выполните команду
контекстного меню Add→New Item…. В дереве категорий слева
выберите Code, в шаблонах справа – Header File (.h), в поле Name
введите имя файла BandOverRelax. Нажмите Add. В данном файле
будут находиться прототипы функций, необходимых для реализации
метода верхней релаксации.
 Аналогично добавьте в проект следующие файлы:
o BandOverRelax.cpp, который будет в дальнейшем содержать
реализацию функций, необходимых для метода верхней
релаксации.
o PoissonDecision.h, в котором будут находиться прототипы
функций, задающих правую часть и граничные условия
дифференциального уравнения, а также функции решения
дифференциального уравнения.
o PoissonDecision.cpp, который будет содержать реализации
функций, объявленных в заголовочном файле PoissonDecision.h.
o Utilities.h, в котором будут находиться прототипы
вспомогательных функций.
o Utilities.cpp, который будет содержать реализации
вспомогательных функций.
Для проверки корректности решения, полученного с помощью метода
верхней релаксации, будем использовать функции решения СЛАУ из
библиотеки MKL.
 Чтобы собрать 32-битное приложение с вызовом необходимых
функций MKL, нужно прописать пути до заголовочных и .lib файлов
библиотеки. Для этого в меню Tools выполните команду Options. В
появившемся диалоговом окне выберите вкладку Projects and
Solutions→VC++ Directories. В выпадающем меню выберите сначала
Include Files и добавьте новую запись с путем к заголовочным файлам
библиотеки MKL (к примеру, C:\Program Files
(x86)\Intel\ComposerXE-2011\mkl\include), затем выберите Library
Files и добавьте путь к библиотечным файлам для платформы ia-32 (к
примеру, C:\Program Files (x86)\Intel\ComposerXE-
2011\mkl\lib\ia32). Для сборки 64-битного приложения необходимо
указать путь к статическим библиотекам для 64-битной платформы (к
примеру, C:\Program Files (x86)\Intel\ComposerXE-
2011\mkl\lib\intel64).
 Далее нужно выбрать конкретные статические библиотеки, в которых
находятся реализации используемых функций. Для 32-битного
приложения это файлы mkl_core.lib, mkl_intel_c.lib,

19
mkl_sequential.lib; для 64-битного: mkl_core.lib, mkl_sequential.lib,
mkl_intel_lp64.lib, mkl_blas95_lp64.lib. Для их подключения во
вкладке Linker→Input→Additional Dependencies в свойствах проекта
Configuration Properties необходимо вписать требуемый набор
библиотек.
Теперь создадим в файле main.cpp заготовку функции main(). Через
параметры командной строки передадим размер сетки . В качестве
необязательных параметров можно задать точность метода и имя файла
для сохранения результатов работы метода. По умолчанию точность
метода задается константой EPSILON.

//точность метода верхней релаксации


#define EPSILON 0.00001

int main(int argc, char* argv[])


{
//параметры сетки
int n, m;
//число шагов, выполненное методом верхней релаксации
int StepCount;
//размерность системы уравнений
int size;
//переменные для хранения времени работы вычислительных
//функций
double time, MKLtime;
//заданная точность метода верхней релаксации как
//критерий останова
double Accuracy = EPSILON;
//достигнутая точность метода верхней релаксации
double ORAccuracy;
//разница между точным решением и решением, найденным с
//помощью метода верхней релаксации (по норме)
double ExcAccuracy;
//решение СЛАУ, найденное методом верхней релаксации
double* Decision;
//точное решение СЛАУ, найденного с помощью MKL
double *DecisionMKL;
//переменные для сохранения результатов в файл
char* FileName = "sparseOR_res.csv"; FILE* file;

//1. Считывание параметров с командной строки


if ((argc > 2) && (argc < 6))
{
n = atoi(argv[1]);
m = atoi(argv[2]);
if (argc >= 4)
{
Accuracy = atof(argv[3]);

20
if (argc == 5)
FileName = argv[4];
}
}
else
{
printf("Invalid input parameters\n");
return 1;
}
if ((n < 0) || (m < 0) || (Accuracy < 0))
{
printf("Incorrect arguments of main\n");
return 1;
}

size = (n - 1)*(m - 1);

//2. Выделение памяти для массивов:


// - Decision размера size
// - DecisionMKL размера size

//3. Вызов функции решения СЛАУ методом верхней


//релаксации, время ее работы сохраним в переменную time
printf("OverRelaxation:\ntime = %.15f\n", time);
printf("Accuracy = %.15f, stepCount = %d\n",
ORAccuracy, StepCount);

//4. Проверка решения:


// 4.1. Нахождение точного решения СЛАУ с помощью MKL
// 4.2. Сравнение решений Decision и DecisionMKL
MKLtime = СomputeDecisionMKL(n, m, DecisionMKL);
ExcAccuracy = СompareDecisions(Decision,
DecisionMKL, size);
printf("MKL:\ntime = %.15f\n", MKLtime);
printf("OR and MKL comparison = %.15f\n", ExcAccuracy);

//5. Запись в файл результатов


file = fopen(FileName, "a+");
if (file)
{
fprintf(file, "%d;%d;%.15f;%.15f;%.15f;%d;%.15f\n",
n, m, Accuracy, ORAccuracy, ExcAccuracy,
StepCount, time);
}
fclose(file);

//6. Освобождение памяти для массивов Decision,


// DecisionMKL

return 0;

21
}
В текст данного кода вместо комментариев «2. Выделение памяти для
массивов Decision размера size, DecisionMKL размера size» и «6.
Освобождение памяти для массивов Decision, DecisionMKL»
необходимо вставить соответственно функции выделения и освобождения
памяти. Предоставляем читателю выполнить это самостоятельно. На месте
комментария «3. Вызов функции решения СЛАУ методом верхней
релаксации» в дальнейшем поместим вызов функции с нашей реализацией
метода.

6.2. Вспомогательные функции

В данном разделе рассмотрим функции, необходимые для перехода от


решения дифференциального уравнения (1) к решению СЛАУ. Это
функции, задающие правую часть дифференциального уравнения и
граничные условия, а также функции формирования матрицы и вектора
правой части для СЛАУ по исходной задаче. Размеры пластины зададим в
файле PoissonDecision.h константами LEFT_BOUND и RIGHT_BOUND.
#define LEFT_BOUND 10.0
#define RIGHT_BOUND 10.0
В файле PoissonDecision.h поместим прототипы, а в файле
PoissonDecision.cpp – реализацию функций, задающих граничные условия
и правую часть дифференциального уравнения:
1. Функция f() вычисления правой части дифференциального уравнения
(1) в соответствии с формулой (3).
//Функция вычисления правой части ДУ в ЧП
// Uxx + Uyy = -f(x,y)
double f(double x, double y)
{
return 10*sin(M_PI*x/LEFT_BOUND)*sin(M_PI*y/RIGHT_BOUND);
}
2. Функция mu1() вычисления значения граничного условия на левой
стороне прямоугольника G - ( ) по формуле (4).
//Функция вычисления значения ГУ на левой стороне
прямоугольника
double mu1(double y)
{
return y*(RIGHT_BOUND - y)*cos(M_PI*(RIGHT_BOUND - y)/
RIGHT_BOUND)*cos(M_PI*y/RIGHT_BOUND);
}
3. Функция mu2() вычисления значения граничного условия на правой
стороне прямоугольника G - ( ) по формуле (5).

22
//Функция вычисления значения ГУ на правой стороне
//прямоугольника
double mu2(double y)
{
return -y*(RIGHT_BOUND - y)*sin(M_PI*(RIGHT_BOUND - y)/
RIGHT_BOUND);
}
4. Функция mu3() вычисления значения граничного условия на нижней
стороне прямоугольника G - ( ) по формуле (6).
//Функция вычисления значения ГУ на нижней стороне
//прямоугольника
double mu3(double x)
{
return x*(LEFT_BOUND - x)*cos(M_PI*(LEFT_BOUND - x)/
LEFT_BOUND)*cos(M_PI*x/ LEFT_BOUND);
}
5. Функция mu4() вычисления значения граничного условия на верхней
стороне прямоугольника G - ( ) по формуле (7).
//Функция вычисления значения ГУ на верхней стороне
//прямоугольника
double mu4(double x)
{
return -x*(LEFT_BOUND - x)*sin(M_PI*(LEFT_BOUND - x)/
LEFT_BOUND);
}
В этих функциях используются значения стандартных математических
функций () ( ) в некоторых точках и константа π. Для их
вычисления необходимо подключить заголовочный файл math.h и
объявить макрос _USE_MATH_DEFINES.
#define _USE_MATH_DEFINES
#include "math.h"
В файле Utilities.h поместим прототипы, а в файле Utilities.cpp –
реализацию функций, формирующих матрицу и вектор правой части
СЛАУ по разностной схеме в формате, выбранном нами в п. 5:
1. Функция CreateDUMatrix(), реализующая составление матрицы
– системы линейных уравнений по исходной задаче согласно
разностной схеме (8) для сетки ( ). Как было отмечено в п. 5, для
хранения получаемой пятидиагональной матрицы используется
диагональный строчный формат.
//инициализация матрицы для сетки (n, m)
void CreateDUMatrix(int n, int m, double** Matrix,
int** Index);

23
В массиве Matrix размера ( ) ( ) будут храниться
ненулевые элементы матрицы (– ), в массиве Index длины 5 – смещение
побочных диагоналей относительно главной. Ненулевые элементы в
матрице могут принимать только три значения: . /, и ,в
коде программы обозначим их как A, hsqr и ksqr соответственно.
Матрица (– ) блочно-диагональная, построчно ее можно разделить на
блоков порядка . На главной диагонали матрицы расположены
числа . /, на соседних с ней диагоналях – числа , за
исключением строк, соответствующих концу блока. На диагоналях,
отстоящих от главной на ширину блока, расположены числа (Рис. 11).
void CreateDUMatrix(int n, int m, double** Matrix,
int** Index)
{
//вспомогательные переменные:
//размер матрицы, ширина ленты
int size = (n - 1)*(m - 1), bandWidth = 5;
//элементы матрицы
double hsqr = (double)n*n/LEFT_BOUND/ LEFT_BOUND; // 1/h
double ksqr = (double)m*m/RIGHT_BOUND/RIGHT_BOUND;// 1/k
double A = 2*(hsqr + ksqr);

//1. Выделение памяти


InitializeVector(Matrix, size*bandWidth);
InitializeVector(Index, bandWidth);

//2. Инициализация массива Index


(*Index)[0] = -n + 1; (*Index)[1] = -1; (*Index)[2] = 0;
(*Index)[3] = 1; (*Index)[4] = n - 1;

//3. Инициализация матрицы (-А) по разностной схеме


for (int i = 0; i < size; i++)
{
if (i >= n - 1) (*Matrix)[i*bandWidth] = -ksqr;
else (*Matrix)[i*bandWidth] = 0.0;
if (i % (n - 1) != 0)
(*Matrix)[i*bandWidth + 1] = -hsqr;
else (*Matrix)[i*bandWidth + 1] = 0.0;
(*Matrix)[i*bandWidth + 2] = A;
if ((i + 1) % (n - 1) != 0)
(*Matrix)[i*bandWidth + 3] = -hsqr;
else (*Matrix)[i*bandWidth + 3] = 0.0;
if (i < (n - 1)*(m - 2))
(*Matrix)[i*bandWidth + 4] = -ksqr;
else (*Matrix)[i*bandWidth + 4] = 0.0;
}
}

24
2. Функция CreateDUVector() отвечает за инициализацию вектора
правых частей системы линейных уравнений по исходной задаче
согласно разностной схеме (8) для сетки ( ).
//инициализация вектора для сетки (n, m)
void CreateDUVector(int n, int m, double** Vector)
Как видно из разностной схемы, каждая компонента вектора включает
значение функции f в узле сетки, а также «довески» от граничных
функций. Каждая первая компонента в блоке включает слагаемое от
функции mu1, а каждая последняя – от функции mu2. При этом все
компоненты первого блока включают значения функции mu3, а все
компоненты последнего – функции mu4.
void CreateDUVector(int n, int m, double** Vector)
{
//вспомогательные переменные
double h = LEFT_BOUND/(double)n;
double k = RIGHT_BOUND/(double)m;
double hsqr = (double)n*n/LEFT_BOUND/LEFT_BOUND;
double ksqr = (double)m*m/RIGHT_BOUND/RIGHT_BOUND;

//1. Выделение памяти


InitializeVector(Vector, (n - 1)*(m - 1));

//2. Инициализация правой части СЛАУ по разностной схеме


for(int j = 0; j < m - 1; j++)
{
for(int i = 0; i< n - 1; i++)
(*Vector)[j*(n- 1) + i] = f((double)(i + 1)*h,
(double)(j + 1)*k);
(*Vector)[j*(n - 1)] +=
hsqr*mu1((double)(j + 1)*k);
(*Vector)[j*(n - 1) + n - 2] +=
hsqr*mu2((double)(j + 1)*k);
}
for (int i =0; i < n - 1; i++)
{
(*Vector)[i] += ksqr*mu3((double)(i + 1)*h);
(*Vector)[(m - 2)*(n - 1) + i] +=
ksqr*mu4((double)(i + 1)*h);
}
}
Поскольку в функциях CreateDUVector() и CreateDUMatrix()
вызываются функции, задающие граничные условия mu1(), mu2(),
mu3(), mu()4, и правую часть дифференциального уравнения f(), в
файле Utilities.h необходимо подключить заголовочный файл
PoissonDecision.h.

25
#include "PoissonDecision.h"
Для проверки корректности полученных результатов используем точное
решение СЛАУ с помощью функций dpbtrf() и dpbtrs()
библиотеки MKL LAPACK [13]. Для этого в файле Utilities.h поместим
прототипы оберток для вызова функций решателя MKL, а также прототип
функции сравнения решений. В файле Utilities.cpp разместим реализации
указанных функций.
3. Функция CreateMKLMatrix() реализует инициализацию матрицы
(– ) системы линейных уравнений согласно разностной схеме (8) для
сетки ( ) в формате, используемом в библиотеке MKL. Для этого
инициализируем нижний треугольник матрицы в массиве размера
( ) ( ) , т.к. ширина полуленты составляет . Элементы
матрицы (– ) запишем по строкам.
//инициализация матрицы для MKL
void CreateMKLMatrix(int n, int m, double** Matrix)
{
//вспомогательные переменные
int size = (n - 1) * (m - 1);
double hsqr = (double)n*n/LEFT_BOUND/LEFT_BOUND; // 1/h
double ksqr = (double)m*m/RIGHT_BOUND/RIGHT_BOUND;// 1/k
double A = 2*(hsqr + ksqr);

//1. Выделение памяти


InitializeVector(Matrix, size * n);
memset((*Matrix), 0, sizeof(double)*size*n);

//2. Инициализация матрицы (-А) по разностной схеме


for(int i = 0; i < size; i++)
{
if(i >= n - 1)
(*Matrix)[i * n] = -ksqr;
if(i % (n - 1)!= 0)
(*Matrix)[i * n + n - 2] = -hsqr;
(*Matrix)[i * n + n - 1] = A;
}
}
4. Функция ComputeDecisionMKL() содержит нахождение точного
решения СЛАУ с использованием методов dpbtrf() и dpbtrs()
библиотеки MKL. Для использования функций MKL необходимо
подключить заголовочный файл mkl_lapack.h. Функция
ComputeDecisionMKL() возвращает время вычислений с
использованием библиотеки.
#include "mkl_lapack.h"
//нахождение точного решения СЛАУ с помощью MKL
double СomputeDecisionMKL(int n, int m, double* Decision);

26
В теле функции предварительно вызовем функции, формирующие матрицу
и вектор правой части системы, описанные в п. 3, 2. Затем факторизуем
матрицу с помощью функции dpbtrf()и найдем решение системы,
используя решатель dpbtrs().
double СomputeDecisionMKL(int n, int m, double* Decision)
{
//матрица и вектор СЛАУ
double *Matrix, *Vector;
int size = (n - 1) * (m - 1);
//вспомогательные переменные для функций MKL
//рассмотрим матрицу как верхнетреугольную
char uplo = 'U';
//число верхних диагоналей
int kd = n - 1;
//первая размерность матрицы
int ldab = n;
//выходной параметр, код ошибки
int info = 0;
//число правых частей
int nrhs = 1;
//время работы
double time;

//1. Инициализация матрицы и вектора правой части


CreateMKLMatrix(n, m, &Matrix);
CreateDUVector(n, m, &Vector);

//2. Решение системы


clock_t start = clock();
dpbtrf(&uplo, &size, &kd, Matrix, &ldab, &info);
dpbtrs(&uplo, &size, &kd, &nrhs, Matrix, &ldab,
Vector, &size, &info);
time = (double)(clock() - start) / CLOCKS_PER_SEC;

//3. Сохранение результата


memcpy(Decision, Vector, sizeof(double)*size);

//4. Освобождение памяти


FreeVector(&Matrix);
FreeVector(&Vector);

return time;
}
5. Функция CompareDecisions() позволяет найти норму разницы
между решением, найденным методом верхней релаксации, и точным
решением, найденным MKL. Используем для этого определение нормы
вектора как ‖ ‖ | |. Функция принимает на вход указатель на
массив ORResult, содержащий решение, найденное методом верхней

27
релаксации, указатель на массив MKLDecision, содержащий точное
решение системы, а также размер массивов. Функция возвращает
норму невязки между указанными решениями.
//сравнение решений
double СompareDecisions(double* ORResult,
double* MKLDecision, int size)
{
double max = 0.0, temp;
for (int i = 0; i < size; i++)
{
temp = fabs(MKLDecision[i] - ORResult[i]);
if (temp > max)
max = temp;
}
return max;
}
И наконец, для вычисления приближенного решения дифференциального
уравнения методом верхней релаксации на сетке ( ) и проведения
массовых экспериментов, реализуем функцию ComputeDecision() в
файле PoissonDecision.cpp. Прототип функции объявим в файле
PoissonDecision.h.
//вычисление приближенного решения ДУ на сетке (n, m)
//найденное решение хранится в векторе Decision
//функция возвращает время работы метода
double СomputeDecision(int n, int m, double* Decision,
double Accuracy, double &ORAccuracy,
int &StepCount);
На вход функция принимает размеры сетки n и m, параметр останова
метода – необходимую точность Accuracy. Выход функции – полученное
решение Decision, достигнутая точность метода ORAccuracy (как
норма невязки приближений) и выполненное число шагов StepCount.
Функция возвращает время вычислений данным методом. В теле функции
инициализируем систему уравнений с помощью функций
CreateDUMatrix(), CreateDUVector(), затем определим начальное
приближение и параметр метода верхней релаксации. После этого вызовем
функцию вычисления приближенного решения системы, описанную в
следующем пункте. Для этого также необходимо подключить
заголовочный файл BandOverRelax.h. Для использования функций
формирования матрицы и вектора, выделения и освобождения памяти
необходимо подключить заголовочный файл Utilities.h.
#include "BandOverRelax.h"
#include "Utilities.h"

double СomputeDecision(int n, int m, double* Decision,


double Accuracy, double &ORAccuracy,

28
int &StepCount)
{
//Вспомогательные переменные:
//матрица, вектор, решение
double* Matrix, *Vector, *Result;
int* Index;
//размер системы, размер расширенного вектора, размер
ленты
int size = (n - 1)*(m - 1);
int ResSize = size + 2*(n - 1);
int bandWidth = 5;
//переменные для замера времени
clock_t start, finish;
double time;
//параметры метода верхней релаксации
double WParam;
double step = (n/LEFT_BOUND > m/RIGHT_BOUND) ?
(double) LEFT_BOUND /n : (double) RIGHT_BOUND /m;

//1. Инициализация СЛАУ


CreateDUMatrix(n, m, &Matrix, &Index);
CreateDUVector(n, m, &Vector);

//2. Инициализация метода


InitializeVector(&Result, ResSize);
GetFirstApproximation(&Result, ResSize);
WParam = GetWParam(step);

//3. Вычисление приближенного решения методом верхней


// релаксации
start = clock();
ORAccuracy = BandOverRelaxation(Matrix, Vector, &Result,
Index, size, bandWidth,
WParam, Accuracy,
StepCount);
finish = clock();
time = (double)(finish - start)/CLOCKS_PER_SEC;
// сохранение решения
memcpy(Decision, Result + n - 1, sizeof(double)*size);

//4. Освобождение памяти


FreeVector(&Matrix);
FreeVector(&Index);
FreeVector(&Vector);
FreeVector(&Result);

return time;
}

29
6.3. Программная реализация метода верхней релаксации

Теперь перейдем к реализации метода верхней релаксации применительно


к блочной пятидиагональной матрице. Прототипы соответствующих
функций поместим в файл BandOverRelax.h, а реализацию – в файл
BandOverRelax.cpp.
При разработке функции нахождения приближенного решения системы
нам понадобятся вспомогательные функции для задания начального
приближения и параметра метода . Назовем их
GetFirstApproximation() и GetWParam() соответственно.
//задание нулевого приближения для метода ВР
void GetFirstApproximation(double** Result, int size)
{
for(int i = 0; i < size; i++)
Result[i] = 0.0;
}

//задание параметра метода ВР в зависимости от шага сетки


double GetWParam(double Step)
{
return 2 / (1 + 2*sin(M_PI*Step/2));
}
Реализацию метода верхней релаксации поместим в функцию
BandOverRelaxation(). На вход она принимает указатели на массивы
Matrix и Index, содержащие матрицу в диагональном формате, вектор
правой части системы Vector, размер системы size и число ненулевых
диагоналей матрицы bandWidth, параметр метода WParam, заданную
точность метода Accuracy. Выходом функции является число шагов
StepCount, выполненное методом для достижения заданной точности, и
полученное приближенное решение Result. Функция возвращает
достигнутую точность метода.
//метод верхней релаксации для ленточной матрицы
//функция возвращает достигнутую точность решения СЛУ
double BandOverRelaxation(double* Matrix, double* Vector,
double** Result, int* Index,
int size, int bandWidth,
double WParam, double Accuracy,
int &StepCount);
В теле функции в цикле будем вычислять новое приближенное решение по
обобщенной формуле (15). Для единообразного вычисления скалярного
произведения строки матрицы на вектор-приближение, массив Result
расширим нулями в его начале и конце на ширину полуленты . При
этом будем считать, что необходимое приближение найдено, если на
данной итерации достигнутая точность CurrError не превышает

30
заданной точности Accuracy, или было выполнено максимально
допустимое число шагов N_MAX. Этот параметр задан константой в файле
BandOverRelax.h.
#define N_MAX 50000 //максимально допустимое число шагов

double BandOverRelaxation(double* Matrix, double* Vector,


double** Result, int* Index,
int size, int bandWidth,
double WParam, double Accuracy,
int &StepCount)
{
//вспомогательные переменные
double CurrError;//достигнутая точность на итерации
double sum, TempError;
int ii;
int index = Index[bandWidth - 1];
int bandHalf = (bandWidth - 1)/2;
StepCount = 0;

do
{
CurrError = -1.0;
for(int i = index; i < size + index; i++)
{
ii = i - index;
TempError = (*Result)[i];
sum = 0.0;
for (int j = 0; j < bandWidth; j++)
sum += Matrix[ii*bandWidth + j] *
(*Result)[i + Index[j]];
(*Result)[i] = (Vector[ii] - sum) * WParam /
Matrix[ii*bandWidth + bandHalf] + (*Result)[i];
TempError = fabs((*Result)[i] - TempError);
if (TempError > CurrError) CurrError = TempError;
}
StepCount++;
}
while ((CurrError > Accuracy)&&(StepCount < N_MAX));
return CurrError;
}
После того, как выполнена программная реализация метода верхней
релаксации, соберем проект и проверим правильность работы программы.
Для этого в файле main.cpp подключим необходимые заголовочные
файлы, вставим вместо комментариев вызовы необходимых функций.
Теперь скомпилируем проект, вызвав команду Build→Rebuild
01_BandOR_seqv. Убедиться в правильности работы программы можно,
например, используя дифференциальное уравнение с известным решением.

31
6.4. Анализ сходимости метода

Для анализа сходимости реализованного метода верхней релаксации


рассмотрим систему уравнений, полученных по дифференциальному
уравнению с заданным известным решением и граничными условиями.
Рассмотрим изменение температуры на пластине с длинами сторон
. Пусть решение уравнения теплопроводности – функция
( ) . Тогда уравнение (1) принимает вид:
( ) ( ) (19)
Граничные условия (2) будут удовлетворять следующим уравнениям:
( ) ( ) (20)
( ) ( ) ( )
( ) ( )
( ) ( ) ( )
Заменим реализацию функций, соответствующих заданию функций
в коде программы. Дополнительно в файле
PoissonDecision.cpp реализуем функцию, возвращающую значение
функции-решения в некоторой точке:
//точное решение ДУ
double FuncU(double x, double y)
{
return x*x*y + y*y*x;
}
Также в файле Utilities.cpp реализуем функцию CheckDecision(),
позволяющую найти норму невязки решения, полученного некоторым
методом, с заранее известным решением:
//проверка точности решения
double CheckDecision(double* Decision, int n, int m)
{
double h = LEFT_BOUND /(double)n;
double k = RIGHT_BOUND/(double)m;
double err = 0, temp;

for (int j = 0; j < m - 1; j++)


for (int i = 0; i < n - 1; i++)
{
temp = fabs(Decision[j*(n - 1) + i] –
FuncU((double)(i + 1)*h, (double)(j + 1)*k));
if (temp > err)
err = temp;
}
return err;

32
}
Проведем ряд экспериментов с различными размерами сеток и исследуем
порядок сходимости метода и число итераций в зависимости от заданной
точности решения. Результаты экспериментов приведены в Таблице 2.
Как видно из таблицы, наблюдается достаточно быстрая сходимость к
решению. Частично такой эффект объясняется простым видом функции
правой части и граничных условий. Норма разницы с точным решением на
всех рассмотренных примерах, кроме
имеет порядок . Для указанных тестовых параметров – . Также
можно заметить, что для выбранных функций при фиксированном размере
сетки уменьшение на порядок требуемой точности незначительно
сказывается на числе итераций метода.
Таблица 2. Сходимость метода верхней релаксации
Число Достигнутая Норма разницы с
шагов точность точным решением
10 0,001 20 0,000467 0,000383
50 0,001 101 0,000723 0,000380
100 0,001 201 0,000703 0,000455
500 0,001 1001 0,000461 0,000671
1000 0,001 2001 0,000380 0,000844
10 0,0001 22 0,000078 0,000107
50 0,0001 104 0,000075 0,000268
100 0,0001 204 0,000084 0,000360
500 0,0001 1004 0,000080 0,000535
1000 0,0001 2004 0,000085 0,000624
10 0,00001 26 0,0000099 0,000013
50 0,00001 115 0,0000095 0,000058
100 0,00001 219 0,0000098 0,000131
500 0,00001 1016 0,0000097 0,000393
1000 0,00001 2036 0,0000099 0,000382

При решении реальных задач вид функций в правой части уравнения, а


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

33
вычислительной погрешности, а слишком крупное – из-за грубой
аппроксимации.

7. «Наивная» параллельная реализация с


использованием Intel® Cilk Plus

7.1. Создание проекта

Перед непосредственной реализацией метода верхней релаксации с


использованием технологии Intel® Cilk Plus создадим в рамках решения
BandOverRelaxation новый проект с названием 02_BandOR_cilk. Для
этого повторите все действия, описанные в п. 6.
После получения пустых файлов main.cpp, PoissonDecision.h,
PoissonDecision.cpp, BandOverRelax.h, BandOverRelax.cpp, Utilities.h,
Utilities.cpp, скопируем в них код из соответствующих файлов проекта
01_BandOR_seq.
Теперь настроим в свойствах проекта возможность использования
технологии Intel® Cilk Plus. Для этого в свойствах проекта Configuration
Properties во вкладке С\С++→Language в поле Disable Intel Cilk Plus
Keywords For Serial Semantics должно стоять ―No‖.
Внесем изменения в функцию main(), учитывающие многопоточность
программы. Для этого введем дополнительный параметр – число потоков,
которое считаем с командной строки. Затем это число передадим в
качестве параметра в функцию ComputeDecision(). Приведем
фрагмент функции main(), изменения в коде которой выделены
полужирным начертанием.
int main(int argc, char* argv[])
{
//объявление переменных
//число потоков
int NumThreads;
//1. Считывание параметров с командной строки
if ((argc > 2) && (argc < 7))
{
n = atoi(argv[1]);
m = atoi(argv[2]);
NumThreads = atoi(argv[3]);
if (argc >= 5)
{
Accuracy = atof(argv[4]);
if (argc == 6)
FileName = argv[6];
}

34
}
else
{
printf("Invalid input parameters\n");
return 1;
}

//2. Выделение памяти для массивов Decision, DecisionMKL

//3. Вызов функции решения СЛАУ методом верхней


//релаксации
time = СomputeDecision(n, m, Decision, NumThreads,
Accuracy, ORAccuracy, StepCount);
// печать результатов
//4. Проверка решения

//5. Запись в файл результатов


file = fopen(FileName, "a+");
if (file)
{
fprintf(file, "%d;%d;%.15f;%.15f;%.15f;%d;%.15f;%d\n",
n, m, Accuracy, ORAccuracy, ExcAccuracy,
StepCount, time, NumThreads);
}
fclose(file);

//6. Освобождение памяти для массивов Decision,


// DecisionMKL
return 0;
}
Внесем изменения в прототип функции ComputeDecision(), добавив в
нее параметр числа потоков NumThreads:
double СomputeDecision(int n, int m, double *Decision,
int NumThreads, double Accuracy,
double& ORAccuracy, int& StepCount);
Для того чтобы установить число потоков, равное NumThreads, добавим
в тело функции ComputeDecision() перед вызовом функции
BandOverRelaxation() следующие строки:
//установить число потоков
char nt[3];
itoa(NumThreads, nt, 10);
__cilkrts_set_param("nworkers", nt);
Для использования функции __cilkrts_set_param() в файле
PoissonDecision.h необходимо подключить соответствующий
заголовочный файл:
#include "cilk/cilk_api.h"

35
7.2. Программная реализация

Итерации метода верхней релаксации зависимы по данным, вычисление


следующего приближения решения зависит от предыдущего. Поэтому
распараллелим вычисления внутри итерации метода. В процессе
вычислений будут получены «смешанные» приближения, элементы
которых подсчитаны не строго по формуле (14), а с перемешанными
старыми и новыми компонентами.
Внесем простые изменения в код функции BandOverRelaxation(),
которой дадим название BandOverRelaxationCilk(). Сначала
распараллелим цикл по элементам вектора с помощью конструкции
cilk_for. При этом переменные ii, TempError, sum, принимающие
различные значения для каждой итерации цикла, необходимо объявить
локально. Теперь каждый поток будет вычислять последовательность
элементов текущего приближения, согласно планировщику Cilk Plus, и
использовать разделяемую переменную currError, хранящую
достигнутую точность решения.
Однако такая организация взаимодействия потоков приводит к большим
накладным расходам на синхронизацию. Объявим переменную
currError как преобразователь (reducer) для операции нахождения
максимума. Это позволит безопасно использовать разделяемую
переменную, сократить расходы на синхронизацию, и использовать ресурс
параллелизма при нахождении ее значения.
//достигнутая точность
cilk::reducer_max<double> CurrError;
Для инициализации значения точности в начале итерации используем
функцию set_value(), для получения значения – get_value().
Непосредственно операцию редукции выполним с помощью функции
cilk::max_of().
Для использования выбранных функций добавим подключение
необходимых заголовочных файлов Intel® Cilk Plus в файле
BandOverRelax.h:
#include "cilk/cilk.h"
#include "cilk/reducer_max.h"
Приведем фрагмент кода функции BandOverRelaxationCilk(), где
изменения выделены полужирным начертанием.
double BandOverRelaxationCilk(...)
{
cilk::reducer_max<double> CurrError;
...
StepCount = 0;
do

36
{
CurrError.set_value(-1.0);
cilk_for (int i = index; i < size + index; i++)
{
int ii = i - index;
double TempError = Result[i];
double sum = 0.0;
for (int j = 0; j < bandWidth; j++)
sum += Matrix[ii*bandWidth + j] *
Result[i + Index[j]];
Result[i] = (Vector[ii] - sum) * WParam /
Matrix[ii*bandWidth + bandHalf] + Result[i];
TempError = fabs(Result[i] - TempError);
CurrError = cilk::max_of(TempError, CurrError);
}
StepCount++;
}
while ((CurrError.get_value() > Accuracy)&&
(StepCount < N_MAX));

return CurrError.get_value();
}
Теперь скомпилируем проект, выполнив команду Build→Rebuild
02_bandOR_cilk, зададим параметры запуска приложения и убедимся в
корректности результатов.

7.3. Анализ масштабируемости

Для анализа эффективности параллельной схемы проведем эксперимент


для функций, заданных по формулам (3) – (7) с точностью . Число
итераций, выполненных методом при работе в несколько потоков,
приведено в Таблице 3.
Таблица 3. Число итераций метода верхней релаксации
Последова Параллельная версия с использованием Intel® Cilk Plus
Размер
тельная
сетки 1 поток 2 потока 4 потока 6 потоков 8 потоков
версия
100 2917 2917 2917 2995 3114 3136
200 5409 5409 5409 5657 6049 6172
300 7731 7731 7732 8243 8844 9023
400 9944 9944 9945 10611 11766 12126
500 12076 12076 12076 12993 14366 14896
600 14145 14145 14145 15237 17219 18120
700 16160 16160 16160 17573 19713 20836
800 18129 18129 18131 19808 22536 23848

37
900 20059 20059 20060 21738 25065 26494
1000 21953 21953 22031 23862 27662 29155

Как видно из Таблицы 3, число выполненных итераций растет с числом


потоков. Это связано с тем, что параллельная схема не учитывает строгую
последовательность приближений метода, а строит «промежуточные»
приближения.
В Таблице 4 приведены результаты работы многопоточной Сilk-версии.
Время работы (в секундах) обозначено T, ускорение относительно работы в
один поток – S.
Таблица 4. Результаты работы многопоточной Сilk-версии.

1 поток 2 потока 4 потока 6 потоков 8 потоков


Размер
системы T T S T S T S T S
100 0,68 0,53 1,27 0,32 2,12 0,33 2,04 0,34 2,01
200 5,03 2,75 1,83 1,68 2,99 1,47 3,41 1,31 3,85
300 16,23 8,61 1,89 4,98 3,26 4,15 3,91 3,46 4,69
400 38,03 20,31 1,87 11,09 3,43 9,08 4,19 7,49 5,08
500 73,03 38,45 1,90 21,65 3,37 17,21 4,24 14,54 5,02
600 123,74 63,88 1,94 36,31 3,41 30,20 4,10 29,28 4,23
700 192,64 98,88 1,95 57,14 3,37 47,92 4,02 48,70 3,96
800 283,27 145,97 1,94 83,73 3,38 71,78 3,95 74,52 3,80
900 397,73 205,74 1,93 115,16 3,45 101,53 3,92 105,10 3,78
1000 538,16 278,87 1,93 155,83 3,45 137,41 3,92 143,02 3,76
График ускорения для некоторых размеров системы приведен на Рис. 12.
Как видно из Таблицы 4 и Рис. 12, максимальное ускорение, равное 5, было
получено на 8 потоках при . При больших размерах сетки
ускорение снижается до 4. Причины такого эффекта – рост числа итераций
при увеличении числа потоков, а также рост накладных расходов и
расходов на синхронизацию потоков.
В целом подход показывает неплохие результаты ускорения, однако
асимптотика его работы свидетельствует о необходимости модификации
параллельной версии чтобы, в первую очередь, сократить лишние
вычисления.

38
6

4
Ускорение

n = 200
3 n = 400
n = 600
2
n = 800
1 n = 1000

Рис. 12. График ускорения многопоточной cilk-версии

8. Параллельная реализация конвейерной схемы с


использованием библиотеки Intel® TBB

8.1. Создание проекта

Как показал анализ наивной параллельной реализации, необходимо


построить схему параллельных вычислений, которая будет находить то же
решение, что и последовательная версия. Для этого реализуем
конвейерную схему метода верхней релаксации с использованием
механизма задач библиотеки Intel® TBB [11].
Прежде всего, создадим в рамках решения BandOverRelaxation новый
проект с названием 03_BandOR_tbb. Для этого повторим действия,
описанные в п. 6. После получения пустых файлов main.cpp,
PoissonDecision.h, PoissonDecision.cpp, BandOverRelax.h,
BandOverRelax.cpp, Utilities.h, Utilities.cpp, скопируем в них код из
соответствующих файлов проекта 02_BandOR_cilk. Кроме того, создадим
файлы TaskImplementation.h и TaskImplementation.cpp, в которых
поместим соответственно объявление и реализацию классов-задач,
необходимых для работы конвейерной схемы.
Чтобы подключить библиотеку TBB к проекту, требуется изменить
настройки проекта:

39
1. Указать путь к заголовочным файлам библиотеки: Configuration
Properties→C/C++→General→Additional Include Directories.
2. Указать путь к .lib файлам библиотеки: Configuration
Properties→Linker→General→Additional Library Directories.
3. Указать библиотеку tbb.lib в Configuration
Properties→Linker→Input→Additional Dependencies, с которой
должен собираться проект.
Для того чтобы установить нужное число потоков, в теле функции
ComputeDecision() перед вызовом функции
BandOverRelaxation() заменим установку числа потоков Cilk Plus на
инициализацию объекта класса tbb::task_scheduler_init. Этот
класс предназначен для создания потоков и внутренних структур,
необходимых планировщику потоков TBB. Для распараллеливания
вычислений при помощи TBB необходимо иметь хотя бы один активный
(инициализированный) экземпляр этого класса. Функции
BandOverRelaxation() дадим название
BandOverRelaxationTBB().
double СomputeDecision(...)
{
...
//инициализация метода
...
WParam = GetWParam(step);

//установить число потоков


tbb::task_scheduler_init init(NumThreads);

start = clock();
ORAccuracy = BandOverRelaxationTBB(Matrix, Vector,
&Result, Index, size, bandWidth, WParam, Accuracy,
StepCount, n, NumThreads);
finish = clock();
...
}
В файле PoissonDecision.h не забудем заменить подключаемый
заголовочный файл:
#include "tbb/task_scheduler_init.h"

8.2. Описание конвейерной схемы распараллеливания

Исследуем зависимость вычислений между соседними приближениями


метода верхней релаксации. Как видно из формулы (14), для вычисления
очередного элемента ( ) необходимы элементы приближения ( ) с

40
1й поток 1й поток 1й поток

номерами, большими . Следовательно, для каждого узла сетки его


верхний и правый соседи в шаблоне «крест» будут взяты из предыдущего
приближения, а левый и нижний – из текущего. Число узлов, которые
необходимо вычислить на каждом слое, равно для сетки ( ).
Следовательно, для вычисления элемента ( ) должны быть вычислены
( )
элементы предыдущего приближения . На этом
соотношении построим конвейерную схему метода. Если для нескольких
( ) ( ) ( )
приближений выполнено, что на предыдущем
приближении число вычисленных элементов не менее, чем на
элемент больше, то далее нахождение приближений можно выполнять
параллельно и синхронно с разницей в элемент.
Организуем параллельные вычисления по схеме «мастер – рабочий».
«Мастер» будет координировать вычисление приближений, распределять
нагрузку между «рабочими» и выполнять проверку критерия останова
метода. «Рабочие» будут непосредственно находить приближенные
решения. Работу конвейерной схемы организуем итерационно, при этом
каждую итерацию можно разбить на три стадии:
 «Мастер» инициализирует данные для текущего этапа вычислений.
 «Мастер», распределив нагрузку между «рабочими», ожидает их
завершения. «Рабочие» производят вычисления некоторой порции
элементов, каждый на своем приближении.
 «Мастер» проверяет критерий останова метода.
В программной реализации количество «рабочих» и число физических
потоков приложения может не совпадать. Для лучшей балансировки
нагрузки на каждый физический поток должна приходиться нагрущка
нескольких «рабочих». Пример загрузки потоков при организации
конвейерной схемы для двух физических потоков и четырех «рабочих»
представлен на Рис. 13. Текущие вычисления на каждом этапе обозначены
голубым цветом, функционал, который должен быть выполнен «мастером»
(обозначен «М») после запуска рабочих, – серым цветом. Около каждого
«рабочего» (обозначены «Р») изображено его текущее приближение.
Зеленым цветом выделены уже найденные элементы, желтым –
вычисляемые на данном шаге работы.
Рассмотрим подробнее работу конвейерной схемы. Пусть каждый
«рабочий» имеет указатель на текущее вычисляемое им приближение и
предыдущее приближение. Для первого «рабочего» предыдущим будет
приближение, вычисляемое последним «рабочим». Также для каждого

41
М
М М
Р1

Р2

2й поток 2й поток 2й поток

Р3

Р4

а) инициализация b) вычисления c) проверка точности

Рис. 13. Организация работы конвейерной схемы при двух


физических потоках и четырех потоков-рабочих
«рабочего» будем хранить число уже подсчитанных элементов и число
элементов, которое ему необходимо найти за некоторый шаг метода.
Обозначим порцию из элементов через Chunk. Размер системы
кратен Chunk, и равен ( ) ( ). Следовательно, вычисление
вектора-приближения можно проводить поэтапно, вычисляя за один раз
число элементов, кратное Chunk. Обозначим максимальное число порций
из Chunk элементов, которое можно найти за один раз, Portion.
Последний номер порции, , обозначим maxChunk.
Пусть имеется numWorkers «рабочих». Их найденные приближения
хранятся в массиве WorkerResults (размера numWorkers*ResSize,
где ResSize – размер вектора-приближения). Уже подсчитанное число
порций из Chunk элементов хранятся в массиве PrevPos (размера
numWorkers), максимальный номер порции, элементы которой
необходимо найти на данном этапе – в массиве CurrPos (размера
numWorkers). Тогда конвейерную схему метода можно представить
следующей процедурой:
 Инициализировать приближения WorkerResults начальным
приближением. Для каждого «рабочего» i положить CurrPos[i] =
0, PrevPos[i] = 0 . Число шагов метода установить равным нулю.
 Пока не будет достигнута заданная точность:
1. Определить номер текущей (ведущей) итерации метода верхней
релаксации currIter. Это приближение с наименьшим номером,
которое не было досчитано до конца.

42
2. Для «рабочего» с номером k, определяющего приближение
currIter, найти границы элементов, которые будут вычислены
на данном этапе. Нижней границей станет верхняя граница
предыдущего этапа вычислений CurrPos[k]. Теперь «рабочий»
будет находить элементы либо до CurrPos[k] + Portion,
либо до последней порции (если она меньше):
PrevPos[k] = CurrPos[k];
CurrPos[k] = min(CurrPos[k] + Portion, maxChunk);
3. Для всех остальных «рабочих», кроме предшествующего k-ому,
также определить границы вычисления элементов. Эти «рабочие»
вычисляют приближения с номерами больше текущего. Тогда для
«рабочего» i нижней границей будет также номер первого
ненайденного элемента CurrPos[i]. Верхняя граница
определяется следующим образом: PrevPos[j]-1, где j –
«рабочий», вычисляющий предыдущее приближение, либо
CurrPos[i] + Portion, если «рабочим» j было вычислено
достаточно элементов, иначе – нуль.
PrevPos[i] = CurrPos[i];
CurrPos[i] = max(min(CurrPos[i] + Portion,
PrevPos[j] - 1), 0);
4. Для «рабочего» с номером l, непосредственно предшествующего
«рабочему» с ведущим приближением currIter, определить
номера вычисляемых элементов следующим образом:
a. Если приближение «рабочего» l найдено не полностью, то он
вычисляет приближение с номером больше ведущего (и
равным currIter + numWorkers - 1). Тогда границы
вычислений определяются аналогично п. 3, за исключением
случая CurrPos[l] + Portion:
PrevPos[l] = CurrPos[l];
CurrPos[l] = max(PrevPos[j] – 1, 0);
b. Иначе «рабочий» l завершил вычисление приближения с
номером currIter - 1, и может перейти к нахождению
приближения с номером currIter + numWorkers. В этом
случае для него нижняя граница вычислений устанавливается
равной нулю, а верхняя – PrevPos[j] - 1 (j – «рабочий»,
вычисляющий предыдущее приближение для данного) или
Portion, если было вычислено достаточно элементов.
PrevPos[l] = 0;
CurrPos[l] = min(Portion, PrevPos[j] - 1);

43
5. Запустить параллельно вычисление элементов для каждого
«рабочего» i с позиции Chunk*PrevPos[i] до
Chunk*CurrPos[i] – 1, ожидать завершения вычислений.
6. Если текущее приближение было вычислено полностью, проверить
критерий останова: если необходимая точность решения была
достигнута, запомнить результат и завершить работу.
7. Инициализировать новый этап вычислений, перейти на шаг 1.
В данной схеме при завершении вычисления некоторого приближения k
ведущим приближением становится приближение с номером k + 1. Со
следующего этапа вычислений «рабочий», нашедший приближение k,
будет находить приближение с номером k + numWorkers. При этом
элементы k-го приближения, необходимые для вычисления ведущего
приближения k + 1, не удаляются в силу синхронности вычислений на
нескольких потоках с разницей Chunk.
Заметим, что схема накладывает ограничения на соотношение следующих
параметров: размера единичной порции Portion, размера сетки и числа
потоков-рабочих. Корректность работы конвейерной схемы гарантирована
при выполнении условия (21):
numWorkers*Portion < n – 1 - Portion (21)

Однако для некоторых сеток можно подобрать такое соотношение


параметров, что конвейерная схема будет работать при выполнении более
мягкого условия (22):
numWorkers*Portion < n – 1 (22)

Такой случай проиллюстрирован на Рис. 14. В качестве параметров метода


выбраны , Portion = 3, numWorkers = 3. На каждом
рисунке серым цветом выделены уже найденные элементы вектора,
голубым – вычисляемые элементы на данном этапе. Указатели границ
вычислений указаны стрелками слева от вектора. Номера вычисляемых
приближений указаны над соответствующим вектором, ведущее
приближение выделено скобками и полужирным начертанием.
На Рис. 14, e видно, что поток, вычислявший первое приближение,
приступил к вычислению четвертого приближения, при этом его последние
Chunk элементов используются для получения ведущего приближения 2.
Аналогичная ситуация показана на Рис. 14, f.

44
(1) 2 3 (1) 2 3 (1) 2 3

a) b) c)

(1) 2 3 1, 4 (2) 3 4 2, 5 (3)

5
4

2
1

d) e) f)

Рис. 14. Работа конвейерной схемы метода верхней релаксации на


примере m = 11, Portion = 3, numWorkers = 3

8.3. Программная реализация

Реализуем представленную конвейерную схему метода верхней


релаксации с помощью механизма задач библиотеки Intel® TBB. При
разработке программной реализации будем считать, что функционал
«мастера» и «рабочего» реализуется в виде отдельных классов задач.
В файле TaskImplementation.h объявим классы TBB-задач,
предварительно подключив необходимые заголовочные файлы:
#include "tbb\task.h"
#include "Utilities.h"
Задачу-мастера объявим в классе OverRelaxMaster. Поля класса
объявим открытыми и статическими, поскольку их значения будут общими
для всех экземпляров класса. Для организации вычислений нам
понадобятся: указатель на полученное решение, заданная точность метода,
точность, достигнутая в ходе работы, размер системы, размер вектора-
решения, число выполненных итераций, максимально возможное число

45
итераций, размер единичной порции вычислений и число созданных задач-
рабочих.
//класс tbb - задача-мастер
//организация параллельных вычислений, запуск и остановка
метода
class OverRelaxMaster : public tbb::task
{
public:
static double* tDecision; //полученное решение
static double tORAccuracy; //достигнутая точность
static double tAccuracy; //заданная точность решения
static int tSize; //размер системы
static int tResSize; //размер вектора-решения
static int NumSteps; //число выполненных итераций
static int MaxNumSteps; //максимальное число итераций
static int numWorkers; //число задач-"рабочих"
static int Chunk; //размер единичной порции вычислений

public:
tbb::task* execute();
};
Задачу-рабочего объявим в классе OverRelaxWorker. Часть полей,
связанных с параметрами матрицы и метода, также объявим статическими.
В этом классе нам понадобятся следующие поля: указатель на текущее
(вычисляемое) приближение, указатель на предыдущее приближение,
матрица, размер системы, ширина ленты, индексы смещения диагоналей
матрицы, вектор правой части, параметр верхней релаксации, начальная и
конечная границы порции вычислений, полученная на данном
приближении точность.
//класс tbb - задача-рабочий
//выполнение вычислений на некоторой итерации метода
class OverRelaxWorker : public tbb::task
{
public:
double* PrevResult; //приближение на предыдущей итерации
double* CurrResult; //приближение на данной итерации
static double* tMatrix; //матрица
static int* tIndex; //индекс смещения диагоналей
static double* tVector; //вектор правой части
static double tWParam; //параметр метода
static int tSize; //размер системы
static int tBandWidth; //ширина ленты
int start; //начальная позиция порции вычислений
int finish; //конечная позиция порции вычислений
double CurrError;//точность решения на данном приближении

tbb::task* execute();

46
};
Метод execute() класса OverRelaxWorker, выполняемый при
запуске задачи, будет состоять в вычислении элементов текущего
приближения CurrResult с индексами от start до finish – 1 (с
учетом смещения элементов в векторе). Реализацию метода поместим в
файле TaskImplementation.cpp.
tbb::task* OverRelaxWorker::execute()
{
int ii, j;
int index = tIndex[tBandWidth - 1];
int bandHalf = (tBandWidth - 1)/2;
double sum, TempError;

//вычисление элементов нового приближения [start, finish)


for (int i = index + start; i < index + finish; i++)
{
ii = i - index;
sum = 0.0;
for (j = 0; j < bandHalf; j++)
sum += tMatrix[ii*tBandWidth + j] *
CurrResult[i + tIndex[j]];
for (j = bandHalf; j < tBandWidth; j++)
sum += tMatrix[ii*tBandWidth + j] *
PrevResult[i + tIndex[j]];
CurrResult[i] = (tVector[ii] - sum) * tWParam /
tMatrix[ii*tBandWidth + bandHalf] + PrevResult[i];
TempError = fabs(CurrResult[i] - PrevResult[i]);
CurrError = max(CurrError, TempError);
}
return NULL;
}
Метод execute() класса OverRelaxMaster будет состоять в
организации вычислении по схеме, описанной в п. 8.2. В теле метода
объявим массив Workers задач класса OverRelaxWorker, которые будем
вызывать как подзадачи для OverRelaxMaster. Приближения,
вычисляемые задачами-рабочими, будем хранить в массиве
workerResults размера numWorkers, границы очередной порции
вычислений для задач-рабочих – в массивах currPos и prevPos.
Конвейерную схему реализуем в бесконечном цикле с прерыванием при
достижении критерия останова. После определения границ вычислений для
задач Workers, их выполнение будет запущено из списка tasks и
распределено по физическим потокам планировщиком TBB. Чтобы
сэкономить время на синхронизацию подзадач, после проверки критерия
останова задачи Workers будут пересозданы. Для этого понадобится
вспомогательный массив currErrorBuff, хранящий текущую точность,

47
достигнутую каждым «рабочим». Порцию вычислений для запуска метода
portion зададим константой в файле TaskImplementation.h
#define FIRST_PORTION 5
Непосредственно реализацию метода execute() поместим в файле
TaskImplementation.cpp.
#ifndef min
#define min(a, b) ((a)<(b))?(a):(b)
#endif
#ifndef max
#define max(a, b) ((a)>(b))?(a):(b)
#endif

tbb::task* OverRelaxMaster::execute()
{
//потоки-"рабочие"
OverRelaxWorker **Workers;
//список порожденных задач
tbb::task_list tasks;
//приближения, найденные потоками-"рабочими"
double** workerResults;
//текущая точность метода
double CurrError = -1.0;
//число вычисленных элементов за предыдущий этап
int *prevPos;
//число вычисленных элементов за текущий этап
int *currPos;
//текущая ошибка, достигнутая потоками-"рабочими"
double *currErrorBuff;
//начальная порция вычислений
int portion = FIRST_PORTION;
//номер текущей итерации
int currIter;
//номер максимальной порции вычислений (число слоев)
int maxPortion = tSize / Chunk;
//вспомогательные переменные
int i;
int refCount;

//1. Инициализация памяти для работы:


// 1.1 - вспомогательные массивы
InitializeVector(&currPos, numWorkers);
InitializeVector(&prevPos, numWorkers);
InitializeVector(&currErrorBuff, numWorkers);

// 1.2 - потоки-"рабочие" и массив найденных ими


приближений
Workers = new OverRelaxWorker* [numWorkers];
workerResults = new double* [numWorkers];

48
for(i = 0; i < numWorkers; i++)
{
Workers[i] = new(tbb::task::allocate_child())
OverRelaxWorker;
InitializeVector(&(workerResults[i]), tResSize);
memset(workerResults[i], 0, sizeof(double) * tResSize);
}

// 1.3 - Инициализация приближений для потоков-"рабочих"


for(i = 0; i < numWorkers; i++)
{
currPos[i] = 0;
prevPos[i] = 0;
currErrorBuff[i] = -1.0;
Workers[i]->CurrResult = workerResults[i];
Workers[i]->PrevResult =
workerResults[(i+ numWorkers - 1) % numWorkers];
}

//2. Запуск работы метода верхней релаксации


NumSteps = 0;
while(true)
{
//2.1 - Определение номера текущего приближения
currIter = NumSteps % numWorkers;
//2.2 - Определение границ новой порции вычислений
// для текущего приближения
prevPos[currIter] = currPos[currIter];
currPos[currIter] = min(currPos[currIter] + portion,
maxPortion);
Workers [currIter]->start = prevPos[currIter] * Chunk;
Workers [currIter]->finish = currPos[currIter] * Chunk;

//Запись новой задачи в список задач


refCount = 1;
tasks.push_back(*(Workers[currIter]));
refCount ++;

//2.3 - Определение границ новой порции вычислений


// для следующих приближений,
// помещение "рабочих" в список задач
for(i = 1; i < numWorkers - 1; i++)
{
prevPos[(currIter + i) % numWorkers] =
currPos[(currIter + i) % numWorkers];
//Если на предыдущем приближении найдено
//меньше 1 порции (слоя), то текущая позиция - 0.
//Иначе - следующая порция
currPos[(currIter + i) % numWorkers] = max(
min(

49
currPos[(currIter + i) % numWorkers] + portion,
prevPos[(currIter + i - 1) % numWorkers] - 1
), 0);
Workers [(currIter + i) % numWorkers]->start =
prevPos[(currIter + i) % numWorkers] * Chunk;
Workers [(currIter + i) % numWorkers]->finish =
currPos[(currIter + i) % numWorkers] * Chunk;
tasks.push_back(*(Workers[(currIter + i) %
numWorkers]));
refCount ++;
}

//2.4 - Определение границ новой порции вычислений


// для последнего рабочего (предыдущего
// относительно текущего приближения)
// а) если не подсчитали весь вектор-приближение
if(currPos[(currIter + i) % numWorkers] != maxPortion)
{
prevPos[(currIter + i) % numWorkers] =
currPos[(currIter + i) % numWorkers];
currPos[(currIter + i) % numWorkers] =
max(prevPos[(currIter + i - 1) % numWorkers] - 1, 0);
}
// b) если подсчитали весь вектор-приближение
else
{
prevPos[(currIter + i) % numWorkers] = 0;
currPos[(currIter + i) % numWorkers] = min(portion,
prevPos[(currIter + i - 1) % numWorkers] - 1);
Workers [(currIter + i) % numWorkers]->CurrError =
-1.0;
}
Workers [(currIter + i) % numWorkers]->start =
prevPos[(currIter + i) % numWorkers] * Chunk;
Workers [(currIter + i) % numWorkers]->finish =
currPos[(currIter + i) % numWorkers] * Chunk;
tasks.push_back(*(Workers[(currIter + i) %
numWorkers]));
refCount ++;

//2.5 - Помещение новых задач в пул, их выполнение


set_ref_count(refCount);
spawn_and_wait_for_all(tasks);

//2.6 - Если текущее приближение вычислено полностью,


// проверим критерий останова
if(currPos[currIter] == maxPortion)
{
CurrError = Workers[currIter]->CurrError;
Workers[currIter]->CurrError = -1.0;

50
currErrorBuff[currIter] = -1.0;
//если решение найдено, запомним его в tDecision
if ((CurrError < tAccuracy) ||
(NumSteps > MaxNumSteps))
{
tORAccuracy = CurrError;
this->tDecision = Workers[currIter]->CurrResult;
NumSteps++;
return NULL;
}
NumSteps++;
}

//2.7 - Определение новых задач


// а) запомним текущую ошибку каждого приближения
for(i = 0; i < numWorkers; i++)
currErrorBuff[i] = Workers[i]->CurrError;
// b) выделим новые задачи
delete [] Workers;
Workers = new OverRelaxWorker * [numWorkers];
for(i = 0; i < numWorkers; i++)
Workers[i] = new(tbb::task::allocate_child())
OverRelaxWorker;
// с) инициализируем приближения для задач
for(i = 0; i < numWorkers; i++)
{
Workers[i]->CurrError = currErrorBuff[i];
Workers[i]->CurrResult = workerResults[i];
Workers[i]->PrevResult =
workerResults[(i+ numWorkers - 1) % numWorkers];
}
}

//3. Освобождение памяти


FreeVector(&currPos);
FreeVector(&prevPos);
FreeVector(&currErrorBuff);

for (i = 0; i < numWorkers; i++)


if (workerResults[i] != tDecision)
FreeVector(&workerResults[i]);
delete [] workerResults;

return NULL;
}
Теперь поместим вызов задачи-мастера в функцию
BandOverRelaxation(), которую переименуем в
BandOverRelaxationTBB(). Перед этой функцией в файле

51
BandOverRelaxation.cpp необходимо объявить статические поля классов
OverRelaxMaster и OverRelaxWorker.
//статические поля класса OverRelaxMaster
double* OverRelaxMaster::tDecision; //полученное решение
double OverRelaxMaster::tORAccuracy; //достигнутая точность
double OverRelaxMaster::tAccuracy; //заданная точность
//решения
int OverRelaxMaster::tSize; //размер вектора
int OverRelaxMaster::tResSize; //размер вектора
int OverRelaxMaster::NumSteps; //выполнено итераций
int OverRelaxMaster::MaxNumSteps; //максимальное число
//итераций
int OverRelaxMaster::numWorkers; //число рабочих
int OverRelaxMaster::Chunk; //размер порции

//статические поля класса OverRelaxWorker


double* OverRelaxWorker::tMatrix; //матрица
double* OverRelaxWorker::tVector; //вектор правой части
double OverRelaxWorker::tWParam; //параметр метода
int* OverRelaxWorker::tIndex; //индекс смещения диагоналей
int OverRelaxWorker::tSize; //размер вектора
int OverRelaxWorker::tBandWidth; //ширина ленты
В самой функции BandOverRelaxationTBB()поместим
инициализацию статических полей и запуск задачи типа
OverRelaxMaster как корневой. Число задач-рабочих можно задать в
зависимости от числа физических потоков с помощью константы
THREADS_PER_WORKER, объявленной в файле TaskImplementation.h:
#define THREADS_PER_WORKER 2
Теперь реализация функции BandOverRelaxationTBB() примет
следующий вид:
double BandOverRelaxationTBB(double* Matrix,
double* Vector,
double** Result,
int* Index, int size,
int bandWidth,
double WParam,
double Accuracy,
int &StepCount, int n,
int NumThreads)
{
//статические поля класса OverRelaxMaster
OverRelaxMaster::tAccuracy = Accuracy;
OverRelaxMaster::tSize = size;
OverRelaxMaster::tResSize =
size + 2*Index[bandWidth - 1];
OverRelaxMaster::MaxNumSteps = N_MAX;

52
OverRelaxMaster::tORAccuracy = -1.0;
OverRelaxMaster::NumSteps = 0;
OverRelaxMaster::Chunk = n - 1;
OverRelaxMaster::numWorkers =
THREADS_PER_WORKER*NumThreads;

//статические поля класса OverRelaxWorker


OverRelaxWorker::tMatrix = Matrix;
OverRelaxWorker::tVector = Vector;
OverRelaxWorker::tWParam = WParam;
OverRelaxWorker::tIndex = Index;
OverRelaxWorker::tSize = size;
OverRelaxWorker::tBandWidth = bandWidth;

//запуск метода вехней релаксации


OverRelaxMaster& FirstIter =
*new (tbb::task::allocate_root()) OverRelaxMaster();
tbb::task::spawn_root_and_wait(FirstIter);
(*Result) = OverRelaxMaster::tDecision;
StepCount = OverRelaxMaster::NumSteps;

return OverRelaxMaster::tORAccuracy;
}
Чтобы убедиться в корректности реализации и провести эксперименты, ,
зададим параметры запуска приложения и скомпилируем проект, выполнив
команду Build→Rebuild 03_bandOR_tbb.

8.4. Анализ масштабируемости

Для анализа эффективности параллельной схемы проведем эксперимент


для функций, заданных по формулам (3) – (7) с точностью .
Параметры конвейерной схемы были заданы в зависимости от констант,
описанных в п. 8.3. Результаты работы многопоточной TBB-версии
приведены в Таблице 5.
Таблица 5. Результаты работы многопоточной TBB-версии

1 поток 2 потока 4 потока 6 потоков 8 потоков


Размер
системы T T S T S T S T S
100 0,67 0,35 1,90 0,21 3,20 0,17 3,94 0,14 4,65
200 4,98 2,57 1,94 1,45 3,43 1,08 4,59 0,90 5,52
300 16,03 8,25 1,94 4,60 3,48 3,34 4,80 2,71 5,91
400 37,87 19,61 1,93 10,50 3,61 7,46 5,08 6,01 6,31
500 73,47 36,98 1,99 19,75 3,72 13,91 5,28 11,17 6,58
600 123,42 62,83 1,96 32,96 3,74 23,10 5,34 18,53 6,66

53
700 190,60 96,76 1,97 51,26 3,72 35,63 5,35 28,56 6,67
800 280,10 141,79 1,98 74,73 3,75 51,91 5,40 41,88 6,69
900 390,15 199,02 1,96 104,32 3,74 72,27 5,40 58,73 6,64
1000 529,07 265,81 1,99 141,18 3,75 97,46 5,43 80,50 6,57
График ускорения для некоторых размеров системы приведен на Рис. 15.

5
Ускорение

n = 200
4 n = 400
3 n = 600
n = 800
2
n = 1000
1

Рис. 15. Ускорение многопоточной TBB-версии


Как видно из Таблицы 5 и Рис. 15, максимальное ускорение, равное 6,7,
было получено на 8 потоках при . При размерах сетки
ускорение превосходит 6. Рост ускорения в зависимости от числа
потоков и размеров сетки свидетельствует о хорошей масштабируемости
предложенной конвейерной схемы. Дополнительный ресурс ускорения
можно получить, подобрав параметры схемы под конкретную задачу.

9. Контрольные вопросы

1. Приведите вывод системы линейных уравнений, полученной из


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

54
3. Обоснуйте сходимость метода верхней релаксации. Покажите
зависимость скорости сходимости метода от выбора его параметров.
4. Какие форматы хранения ленточных матриц вы знаете? В каком случае
используется каждый из них?
5. Почему отличается число итераций метода верхней релаксации при
многопоточной работе параллельной реализации с использованием
Intel® Cilk Plus?
6. Обоснуйте необходимые ограничения, накладываемые на параметры
конвейерной схемы распараллеливания. Какие изменения нужно
внести в схему, чтобы смягчить их?

10. Дополнительные задания

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


используется при решении СЛАУ в данной лабораторной работе,
является отрицательно определенной.
2. Покажите, что если матрица является отрицательно определенной, то
матрица (– ) является положительно определенной.
3. Покажите, что для рассмотренной матрицы, которая входит в СЛАУ,
собственные числа вычисляются по формулам (18) и (19).
Предполагается, что
4. Реализуйте метод Якоби применительно к блочной пятидиагональной
матрице, рассмотренной в данной лабораторной работе. Подумайте над
возможной схемой распараллеливания.
5. Реализуйте метод Зейделя применительно к блочной пятидиагональной
матрице, рассмотренной в данной лабораторной работе. Подумайте над
возможной схемой распараллеливания.
6. Проведите эксперимент, найдя оптимальные значения параметров
конвейерной схемы с использованием Intel® TBB для тестовых
размеров сеток.

11. Литература

11.1. Использованные источники информации

1. Тихонов А.Н., Самарский А.А. Уравнения математической физики. –


М.: Наука, 1977.
2. Самарский А.А., Гулин А.В. Численные методы. – М.: Наука, 1989.

55
3. Вержбицкий В.М. Численные методы. – М.: Высшая школа, 2001.
4. Байков В.А., Жибер А.В. Уравнения математической физики. –
Москва-Ижевск: Институт компьютерных исследований, 2003.
5. Голуб Дж., Ван Лоун Ч. Матричные вычисления. – М.: Мир, 1999.
6. Писсанецки С. Технология разреженных матриц. — М.: Мир,1988.

11.2. Дополнительная литература

7. Амосов А.А., Дубинский Ю.А., Копченова Н.В. Вычислительные


методы для инженеров. – М.: Высшая школа, 1994.

11.3. Ресурсы сети Интернет

8. Руководство по использованию Intel® Cilk Plus


[http://software.intel.com/sites/products/evaluation-guides/docs/cilk-plus-
evaluation-guide.pdf].
9. Спецификация синтаксических конструкций Intel® Cilk Plus
[http://software.intel.com/sites/products/cilk-
plus/cilk_plus_language_specification.pdf].
10. Документация к Intel® Cilk Plus
[http://software.intel.com/sites/products/documentation/hpc/composerxe/en
-us/cpp/mac/index.htm#cref_cls/common/cilk_bk_using_cilk.htm].
11. Страница библиотеки TBB на сайте корпорации Intel
[http://software.intel.com/en-us/articles/intel-tbb/].
12. Документация к Intel® Math Kernel Library [http://software.intel.com/en-
us/articles/intel-math-kernel-library-documentation/].
13. Справочник функций Intel® Math Kernel Library
[http://software.intel.com/sites/products/documentation/hpc/mkl/mklman/m
klman.pdf].
14. Страница учебного курса «Параллельные численные методы»
[http://www.hpcc.unn.ru/?doc=491].

56

Оценить