Uploaded by Alina Fedorova

Курсовой итог

advertisement
Министерство науки и высшего образования Российской Федерации
Федеральное государственное бюджетное образовательное учреждение высшего
образования
Рязанский государственный радиотехнический университет
Кафедра электронных вычислительных машин
К защите
Руководитель КП
_____________А.И. Ефимов
«__»____________2018 г.
ПОЯСНИТЕЛЬНАЯ ЗАПИСКА
К КУРСОВОМУ ПРОЕКТУ
по дисциплине
«ВЫЧИСЛИТЕЛЬНЫЕ СИСТЕМЫ»
Тема
«Параллельные вычисления в SMP и гетерогенных вычислительных системах с
использованием технологий OpenMP и CUDA (Open CL)»
Алгоритм для реализации:
«Параллельные методы матричного умножения. Алгоритм умножения матриц,
основанный на ленточном разделении данных»
Выполнил студент группы 8045М
Федорова А. А.
________________________
дата сдачи на проверку, подпись
Руководитель проекта
к.т.н., доцент Ефимов А.И.
_______
________________________
оценка
дата защиты, подпись
Рязань 2018 г.
Министерство науки и высшего образования Российской Федерации
Федеральное государственное бюджетное образовательное учреждение
высшего образования
Рязанский государственный радиотехнический университет
Кафедра ЭВМ
ЗАДАНИЕ
на курсовое проектирование
по дисциплине «Вычислительные системы»
Студенту Федоровой Алине Александровне, гр.8045М
1. Тема проекта: «Параллельные вычисления в SMP и гетерогенных вычислительных
системах с использованием технологий OpenMP и CUDA (OpenCL)»
2. Срок сдачи студентом законченного проекта 25 декабря 2018 г.
3. Руководитель проекта: Ефимов Алексей Игоревич, к.т.н., доцент кафедры ЭВМ РГРТУ
4. Исходные данные к проекту
1. Операционная система Windows XP/7/8/10
2. Язык программирования С/С++
3. Среда программирования MS Visual Studio C++
4. Технология для распараллеливания программ на С/С++ OpenMP
5. Технология параллельных вычислений CUDA или OpenCL
6. Индивидуальное задание: Параллельные методы матричного умножения. Алгоритм
умножения матриц, основанный на ленточном разделении данных
5. Содержание пояснительной записки
Задание
Содержание
Введение
1. Теоретический материал по индивидуальному заданию
2. Постановка задачи
3. Разработка алгоритмов и программ
4. Сравнительный анализ последовательной и параллельных реализаций
5. Руководство оператора.
Заключение.
Список использованных источников.
Приложение – листинги программ.
Задание выдано «25» сентября 2018 г. доцент каф. ЭВМ Ефимов А.И.
Оглавление
Введение....................................................................................................................................................... 4
1.
Теоретический материал по индивидуальному заданию ................................................................ 5
1.1
Матрицы. Умножение матриц ................................................................................................... 5
1.2
Умножение матриц при ленточной схеме разделения данных ............................................... 6
1.3
Описание используемых средств ............................................................................................... 9
1.3.1
Описание технологии OpenMP........................................................................................... 9
1.3.2
Описание технологии OpenCL ..........................................................................................10
2.
Постановка задачи.............................................................................................................................12
3.
Разработка алгоритмов и программ.................................................................................................13
3.1
Последовательная реализация..................................................................................................13
3.2
Параллельная реализация .........................................................................................................16
4.
Сравнительный анализ последовательной и параллельных реализаций ...................................20
5.
Руководство оператора .....................................................................................................................25
6.
Список использованных источников ..............................................................................................27
Приложение 1. Листинги программ ........................................................................................................29
Введение
Существует широкий круг вычислительных задач, требующих для
своего решения значительно больших вычислительных ресурсов, чем может
предоставить обычный персональный компьютер (ПК). Таким задачам
необходимо:

большее быстродействие

больший объем оперативной памяти

большое количество передаваемой информации
 обработка и хранение большого объема информации.
Если присутствует хотя бы одно из перечисленных требований, то
использование
высокопроизводительных
вычислений
необходимо
и
оправдано.
Технология OpenMP разрабатывалась как стандарт эффективного
программирования
на
Архитектура таких
симметричных
систем включала
мультипроцессорных
системах.
себя несколько однородных
процессоров и обую память, состоящая из независимых блоков.
Изначально
обработки
графические
изображений,
но
с
процессоры
создавались
только
для
развитием
архитектуры
графические
процессоры начали использоваться также и для параллельных вычислений.
Целью курсового проекта является реализация параллельных методом
матричного умножения на примере алгоритма, основанного на ленточном
разделении данных. В ходе работы необходимо не только произвести анализ
технологий параллельного программирования OpenMP и Open CL, но и
разработать программу для демонстрации.
В заключении будет сделан соответствующий вывод на основе
полученных
результатов
по
применению
программирования OpenCL и OpenCL.
технологий
параллельного
1.
Теоретический материал по индивидуальному заданию
1.1
Матрицы. Умножение матриц
Матрицей размера M x N называется прямоугольная таблица,
содержащая m*n чисел, состоящая из m строк и n столбцов.
Произведением матрицы A m*x на матрицу B n*k называется матрица
C m*k такая, что элемент матрицы C, стоящий в i-ой строке и j-ом столбце,
т.е. элемент C[i, j], равен сумме произведений элементов i-ой строки матрицы
A на соответствующие элементы j-ого столбца матрицы B.
Рассмотрим пример. Необходимо найти произведение двух матриц A и
B, если
1
1 2 0
A=(
) , B = (2)
3 1 −1
3
Так как A = A2 х 3 , а B = B3 х 1 , то в результате получим матрицу
размера C = C2 х 1 , т.е. матрицу вида
c11
C = (c )
12
Найдем элементы данной матрицы:
c11 = a11 ∗ b11 + a12 ∗ b21 + a13 ∗ b31 = 1 ∗ 1 + 2 ∗ 2 + 0 ∗ 3 = 5
c21 = a21 ∗ b11 + a22 ∗ b21 + a23 ∗ b31 = 3 ∗ 1 + 1 ∗ 2 + (−1) ∗ 3 = 2
Таким образом, получаем, что:
5
C = AB = ( )
2
Все вычисления можно было сделать в более компактном виде:
1 2
AB = (
3 1
1
1∗1+2∗2+0∗3
0
5
) ∗ B = (2) = (
)=( )
3 ∗ 1 + 1 ∗ 2 + (−1) ∗ 3
−1 2∗3
2
3 3∗1
1.2
Умножение матриц при ленточной схеме разделения данных
Классической областью применения распараллеливания является
алгебра. Решение больших систем линейных алгебраических уравнений,
действия с матрицами и векторами занимают много машинного времени и
при этом хорошо распараллеливаются.
Рассмотрим два параллельных алгоритма умножения матриц, в
которых матрицы A и B разбиваются на непрерывные последовательности
строк или столбцов (полосы).
Из определения операции матричного умножения следует, что
вычисление всех элементов матрицы С может быть выполнено независимо
друг от друга. Подход для организации параллельных вычислений состоит в
том, чтобы использовать в качестве базовой подзадачи процедуру
определения одного элемента результирующей матрицы С.
Каждому потоку выделяется то или иное подмножество строк или
столбцов матрицы. Умножение матрицы A размера m× n и матрицы B
размера n × l приводит к получению матрицы С размера m × l , каждый
элемент которой определяется в соответствии с выражением:
n 1
cij   aik  bkj ,0  i  m,0  j  l
k 0
Каждый
элемент
результирующей
матрицы
С
есть
скалярное
произведение соответствующих строки матрицы A и столбца матрицы B:


cij  ai , bTj , ai  ai 0 , ai1 ,, ain1 , bTj  b0 j , b1 j ,, bn 1 j 
T
Этот алгоритм предполагает выполнение m*n*l операций умножения и
столько же операций сложения элементов исходных матриц. При умножении
квадратных матриц размера n× n количество выполненных операций имеет
порядок n3
Рассмотрев предложенный подход, можно отметить, что достигнутый
уровень параллелизма является в большинстве случаев избыточным. Обычно
при проведении практических расчетов такое количество сформированных
подзадач превышает число имеющихся процессоров и делает неизбежным
этап укрупнения базовых задач.
Возможные способы организации параллельных вычислений состоят в
следующем.
Первый алгоритм. Алгоритм представляет собой итерационную
процедуру, количество итераций которой совпадает с числом подзадач. На
каждой итерации алгоритма каждая подзадача содержит по одной строке
матрицы А и одному столбцу матрицы В. При выполнении итерации
проводится скалярное умножение содержащихся в подзадачах строк и
столбцов,
что
приводит
к
получению
соответствующих
элементов
результирующей матрицы С. По завершении вычислений в конце каждой
итерации столбцы матрицы В должны быть переданы между подзадачами с
тем, чтобы в каждой подзадаче оказались новые столбцы матрицы В и могли
быть вычислены новые элементы матрицы C. При этом данная передача
столбцов между подзадачами должна быть организована таким образом,
чтобы
после
завершения
итераций
алгоритма
в
каждой
подзадаче
последовательно оказались все столбцы матрицы В.
Возможная
последовательности
простая
передач
схема
столбцов
организации
матрицы В между
необходимой
подзадачами
состоит в представлении топологии информационных связей подзадач в виде
кольцевой структуры. В этом случае на каждой итерации подзадача i, 0<=i<n,
будет передавать свой столбец матрицы В подзадаче с номером i+1 (в
соответствии с кольцевой структурой подзадача n-1 передает свои данные
подзадаче с номером 0 ) – см. рисунок 1. После выполнения всех итераций
алгоритма необходимое условие будет обеспечено – в каждой подзадаче
поочередно окажутся все столбцы матрицы В.
На рисунке
1 представлены
итерации
алгоритма
матричного
умножения для случая, когда матрицы состоят из четырех строк и четырех
столбцов ( n=4 ). В начале вычислений в каждой подзадаче i, 0<=i<n,
располагаются i -я строка матрицы A и i -й столбец матрицы B. В результате
их
перемножения
подзадача
получает
элемент cii результирующей
матрицы С. Далее подзадачи осуществляют обмен столбцами, в ходе
которого каждая подзадача передает свой столбец матрицы B следующей
подзадаче в соответствии с кольцевой структурой информационных
взаимодействий. Далее выполнение описанных действий повторяется до
завершения всех итераций параллельного алгоритма.
Рисунок 1. Общая схема передачи данных для первого параллельного
алгоритма матричного умножения при ленточной схеме разделения данных
Второй алгоритм. Отличие второго алгоритма состоит в том, что в
подзадачах располагаются не столбцы, а строки матрицы B. Как результат,
перемножение данных каждой подзадачи сводится не к скалярному
умножению имеющихся векторов, а к их поэлементному умножению. В
результате подобного умножения в каждой подзадаче получается строка
частичных результатов для матрицы C.
При рассмотренном способе разделения данных для выполнения
операции матричного умножения нужно обеспечить последовательное
получение в подзадачах всех строк матрицы B, поэлементное умножение
данных и суммирование вновь получаемых значений с ранее вычисленными
результатами. Организация необходимой последовательности передач строк
матрицы
B
между
подзадачами
также
может
быть
выполнена
с
использованием кольцевой структуры информационных связей (см. рисунок
2).
На рисунке
2 представлены
итерации
алгоритма
матричного
умножения для случая, когда матрицы состоят из четырех строк и четырех
столбцов ( n=4 ). В начале вычислений в каждой подзадаче i, 0<=i<n,
располагаются i -е строки матрицы A и матрицы B. В результате их
перемножения подзадача определяет i -ю строку частичных результатов
искомой матрицы C. Далее подзадачи осуществляют обмен строками, в ходе
которого каждая подзадача передает свою строку матрицы B следующей
подзадаче в соответствии с кольцевой структурой информационных
взаимодействий. Далее выполнение описанных действий повторяется до
завершения всех итераций параллельного алгоритма.
Рисунок 2. Общая схема передачи данных для второго параллельного
алгоритма матричного умножения при ленточной схеме разделения данных
1.3
Описание используемых средств
1.3.1 Описание технологии OpenMP
В последнее время активно развивается подход к разработке
параллельных программ, когда указания программиста по организации
параллельных вычислений добавляются в программу при помощи тех или
иных внеязыковых средств языка программирования – например, в виде
директив
или
комментариев,
которые
обрабатываются
специальным
препроцессором до начала компиляции программы. При этом исходный
текст программы остается неизменным, и по нему, в случае отсутствия
препроцессора,
компилятор
построит
исходный
последовательный
программный код. В противном случае директивы параллелизма будут
заменены на некоторый дополнительный программный код (как правило, в
виде обращений к процедурам какой-либо параллельной библиотеки).
Рассмотренный выше подход является основой технологии OpenMP,
наиболее широко применяемой в настоящее время для организации
параллельных вычислений на многопроцессорных системах с общей
памятью.
В результате такого подхода программа представляется в виде набора
последовательных (однопотоковых) и параллельных (многопотоковых)
участков программного кода.
1.3.2 Описание технологии OpenCL
Технология OpenCL задумывалась как кроссплатформенный аналог
технологии
DirectCompute
и
как
кроссаппаратный
аналог
CUDA.
Архитектура её вычислений очень похожа на внутреннюю архитектуру
видеокарт. Технология разрабатывается компанией Khronos Group. Эта
компания является консорциумом разработчиков, куда входят Microsoft,
Apple, AMD, NVidia, Google и ряд других производителей оборудования и
программного обеспечения, и занимается такими технологиями как OpenGL
и WebGL.
Все потоки на видеокарте делятся на группы (Work Group). В разные
промежутки времени размер групп может отличаться. Это связано с Hyper
Threading`ом и с функциями энергосбережения. Также их размер различается
на
устройствах
различных
производителей,
но,
например,
запуская
программу на видеокарте NVidia, можно быть уверенным, что максимальный
размер одной рабочей группы будет 48 потоков. Также можно определить
размер рабочей группы вручную, но он не должен превышать максимального
размера для выбранного устройства.
2.
Постановка задачи
Необходимо реализовать программу для умножения двух матриц,
используя алгоритм, основанный на ленточном разделении данных.
Программа должна иметь следующий функционал:
-
выбор размерности квадратной матрицы;
-
выбор алгоритма умножения матриц;
-
реализация и замер последовательного алгоритма;
-
реализация и замер параллельных алгоритмов.
В качестве языка разработки использовать язык программирования
Microsoft Visual C++.
В качестве технологий параллельных вычислений использовать
OpenMP и OpenCL.
Задачи курсовой работы:
1. Изучение директив OpenMP. Ознакомление со средствами для
организации параллельного выполнения программы, предоставляемыми
технологией OpenMP.
2. Изучение директив CUDA. Ознакомление со средствами для
организации параллельного выполнения программы, предоставляемыми
технологией CUDA.
3. Знакомство со средой разработки Microsoft Visual C++ входящих в
состав Visual Studio Community 2017.
4. Разработка последовательной и параллельной программ, которые
находят произведение двух матриц.
5. Выполнение замеров времени выполнения последовательной
программы и параллельной программы на двух различных процессорах,
сравнение полученных результатов, построение графиков и таблиц.
Разработка алгоритмов и программ
3.
3.1
Последовательная реализация
Ниже представлен алгоритм последовательной реализации программы.
Начало
Width
Ввод размера
матриц
i=0; i<Width; i++
j=0; j<Width; j++
FstMatrix [i][j] = rand()
SndMatrix [i][j] = rand()
Заполнение
матриц А и В
случайными
числами
j
i
double start = omp_get_wtime()
Начало отсчета
времени
i=0; i<Width; i++
1
1
j=0; j<Width; j++
ResNrmMatrix [i][j] = 0
Заполнение
матрицы С
нулями
k=0; k<Size; k++
ResNrmMatrix [i][j] =
ResNrmMatrix [i][j] +
FstMatrix [i][k] * SndMatrix
[k][j]
Умножение
матриц А и В
k
j
i
double f = omp_get_wtime()
Окончание
отсчета времени
double NormalTime = f - start;
TimeGridView->Rows>Add(NormalTime, 0, 0);
2
Вывод в
консоль
времени работы
2
i=0; i<Width; i++
j=0; j<Width; j++
j
i
Конец
Результаты для матрицы размера 5*5 представлены на рисунке 3.
Рисунок 3. Результат умножения двух матриц последовательным
алгоритмом
3.2
Параллельная реализация
При вычислении одной строки матрицы С необходимо, чтобы в каждой
подзадаче содержалась строка матрицы А и был обеспечен доступ ко всем
столбцам матрицы В.
Рисунок 4. Организация вычислений при выполнении параллельного
алгоритма умножения матриц, основанного на разделении матриц по
строкам
Таким образом, происходит умножение строк матрицы А на столбцы
матрицы B с использованием нескольких параллельных потоков. Каждый
поток выполняет вычисления над элементами горизонтальной полосы
матрицы A и элементами вертикальной полосы матрицы B, таким образом,
получает значения элементов прямоугольного блока результирующей
матрицы С.
Приведем общий алгоритм
Начало
Width
Ввод размера
матриц
i=0; i<Width; i++
j=0; j<Width; j++
FstMatrix [i][j] = rand()
SndMatrix [i][j] = rand()
j
i
double start = omp_get_wtime()
i=0; i<Width; i++
j=0; j<Width; j++
ResNrmMatrix [i][j] = 0
1
Заполнение
матриц А и В
случайными
числами
1
k=0; k<Size; k++
ResNrmMatrix [i][j] =
ResNrmMatrix [i][j] +
FstMatrix [i][k] * SndMatrix
[k][j]
Умножение
матриц А и В
k
j
i
double f = omp_get_wtime()
Окончание
отсчета времени
double NormalTime = f - start;
TimeGridView->Rows>Add(NormalTime, 0, 0);
2
Вывод в
консоль
времени работы
2
i=0; i<Width; i++
j=0; j<Width; j++
j
i
Конец
Результаты для матрицы размера 5*5 представлены на рисунке 4.
Рисунок 4. Результат умножения двух матриц последовательным
алгоритмом
4.
Сравнительный анализ последовательной и параллельных реализаций
Вычислительные
эксперименты
для
оценки
эффективности
параллельного алгоритма умножения матриц при ленточном разделении
данных проводились на двух компьютерах:
1.
AMD A10-7300 Radeon R6 1.90 GHz – четырехядерный процессор
(оперативная память – 8 ГБайт)
Результаты
вычислительных
экспериментов
для
параллельного
алгоритма умножения матриц при ленточной схеме разделения данных
приведены с таблице (время дано в секундах).
Параллельный
алгоритм
Процессор
Матрица
OpenMP
OpenCL
10 x 10
0,000009
0,001658
0,00035
50 x 50
0,000637
0,00179
0,00023
100 x100
0,00743
0,006463
0,00027
AMD A10250 x 250
0,166517
0,069645
0,00058
7300
500 x 500
1,57377
0,671901
0,00292
Radeon
1000 x 1000
11,7647
5,33398
0,00535
R6
2000 х 2000
116,578
54,301
0,01984
3000 х 3000
393,618
218,931
0,04696
5000 х 5000
2196,86
1214,17
0,13023
Таблица 1. Время в секундах, затраченное на выполнение
Послед.
алгоритм
последовательного и параллельного умножения матриц
В идеале, решение задачи на P процессорах должно выполняться в P
раз быстрее, чем на одном процессоре, или должно позволить решить задачу,
содержащую в P раз больше данных. На практике такое недостижимо, что
наглядно иллюстрируется законом Амдала (В случае, когда задача
разделяется на несколько частей, суммарное время её выполнения на
параллельной системе не может быть меньше времени выполнения самого
длинного фрагмента). Согласно этому закону, ускорение выполнения
программы за счёт распараллеливания её инструкций на множество
вычислителей ограничено временем, необходимым для выполнения её
последовательных инструкций).
Speedup parallel f , n  
1
1  f  
f
n
Speedup – относительное ускорение, f – часть кода, которая может быть
распараллелена, n – число параллельных процессоров.
Закон определяет теоретически возможную верхнюю границу, но на
практике дело обстоит еще хуже – часть ресурсов каждого процессора
уходит на обеспечение коллаборативной работы (Коллаборация, или
сотрудничество — процесс совместной деятельности в какой-либо сфере
двух и более людей или организаций для достижения общих целей, при
которой происходит обмен знаниями, обучение и достижение согласия
(консенсуса), а шины обладают конечной пропускной способностью.
На больших матрицах (250х250 и выше) скорость умножения матриц
возрастает в два – два с половиной раза (рисунок 5-6),.
Вывод: технологию OpenMP целесообразно использовать на матрицах
250 х 250 и выше.
Технология CUDA проигрывает последовательному алгоритму в
скорости умножения матриц только на самых малых (10х10) матрицах.
Начиная с матриц 50х50 (рисунок 7-8) технология CUDA дает прирост
в скорости умножения матриц в разы (сотни, тысячи раз).
Вывод: технологию CUDA целесообразно использовать на матрицах
любых размеров, кроме самых малых.
Ускорение OpenMP по отношению к последовательной
программе
3
2,5
2
1,5
1
0,5
0
10 x 10
50 x 50 100 x100 250 x 250 500 x 500 1000 x
1000
2000 х
2000
3000 х
3000
5000 х
5000
Рисунок 5. Ускорение параллельного умножения матриц (OpenMP) по
отношению к последовательному на первом компьютере
Время выполнения последовательной программы и
OpenMP
2500
2000
1500
1000
500
0
10 x 10
50 x 50
100 x100 250 x 250 500 x 500
1000 x
1000
2000 х
2000
3000 х
3000
5000 х
5000
Рисунок 6. Время, затраченное на выполнение последовательного и
параллельного умножения матриц (OpenMP)
Ускорение OpenCL по отношению к последовательной
программе
18000
16000
14000
12000
10000
8000
6000
4000
2000
0
10 x 10
50 x 50 100 x100 250 x 250 500 x 500 1000 x
1000
2000 х
2000
3000 х
3000
5000 х
5000
Рисунок 7. Ускорение параллельного умножения матриц (OpenCL) по
отношению к последовательному
Время выполнения последовательной программы и
OpenCL
2500
2000
1500
1000
500
0
10 x 10
50 x 50
100 x100 250 x 250 500 x 500
1000 x
1000
2000 х
2000
3000 х
3000
5000 х
5000
Рисунок 7. Время, затраченное на выполнение последовательного и
параллельного умножения матриц (OpenCL)
Проанализировав результаты экспериментов, можно сказать, что
выполнение параллельных вычислений обосновано только при наличии
большого объёма данных и возможности распараллелить алгоритм. При
небольших объёмах данных, выполнение без распараллеливания по времени
превосходит выполнение с распараллеливанием.
5.
Руководство оператора
Открыть файл MulMatrix.exe
Рисунок 9. Главное окно программы
На вкладке меню выбрать пункт «Создать»
Рисунок 10. Пункт меню «Создать»
В открывшемся окне ввести размерность матриц и нажать «ОК»
Рисунок 11. Создание матриц
Автоматически вернувшись на главную форму выбрать в пункте меню
необходимый метод умножения
Рисунок 12. Выбор метода
Нажать на кнопку «Умножить» для получения результата
Рисунок 13. Результат умножения
Ддя просмотра созданных матриц, необходимо воспользоваться
переключателями «Матрица 1» и «Матрица 2»
Рисунок 14. Просмотр матриц для умножения
Заключение
Применение
актуальным
параллельных
направлением
вычислительных
развития
систем
компьютерной
является
техники.
Это
обстоятельство вызвано не только физическим ограничением максимально
возможного быстродействия обычных последовательных вычислительных
систем, а и существованием задач для решения которых, возможностей
существующей вычислительной техники всегда оказывается мало. Знание
современных тенденций развития ЭВМ и аппаратных средств для
достижения параллелизма, умение разрабатывать модели, методы и
программы параллельного решения задач обработки данных, следует отнести
к важным квалификационным характеристикам современного программиста.
Целью данной курсовой работы является разработка программного
обеспечение по параллельным методам матричного умножения основанного
на ленточном разделении данных с использованием технологий OpenMP и
OpenCL, дальнейший анализ полученных данных.
При анализе данных для OpenMP и OpenCL, оказалось, что
распараллеливание для матриц малых размеров (10 х 10, 100 х 100
элементов) нецелесообразно и ведет к увеличению времени работы. Это
объясняется
затратой
ресурсов
на
инициализацию
дополнительных
процессов и передачу данных. Но распараллеливание матриц больших
размеров (3000 х 3000, 5000 х 5000 элементов) оправдывает себя. Время,
затраченное на выполнение, получается намного меньше даже вместе с
потраченным временем на создание новых процессов и передачу данных.
Использование
параллельной
конфигурации
программы
даёт
существенный прирост производительности при большом количестве
операций.
Список использованных источников
1.
Антонов А.С. Параллельное программирование с использованием
технологии OpenMP: [Учебное пособие] – Москва: Изд-во МГУ, 2009. – 77 с.
2.
Бахтин В. А. Технология параллельного программирования
OpenMP. : [Учебное пособие] – Москва, 2012.
3.
А. В. Боресков и др. Предисл.: В. А. Садовничий Параллельные
вычисления на GPU. Архитектура и программная модель CUDA: [Учебное
пособие] - Издательство Московского университета, 2012 Перепелт, 336 с.
4.
Лаборатория
Параллельных
Научно-исследовательского
информационных
вычислительного
технологий
центра
Московского
государственного университета имени М.В.Ломоносова
[Электронный
ресурс]: - режим доступа – URL: https://parallel.ru/, свободный.
5.
Хранилище документации
Майкрософт для
пользователей,
разработчиков и ИТ-специалистов [Электронный ресурс]:- режим доступа URL: https://docs.microsoft.com/ru-ru/, свободный.
6.
Информационный сервис для разработчиков [Электронный
ресурс]:- режим доступа - URL: https://msdn.microsoft.com/ru-ru/, свободный.
7.
Учебник по OpenMP [Электронный ресурс]:- режим доступа -
URL: https://pro-prof.com/archives/4335, свободный.
Приложение 1. Листинги программ
//Код добавления новых матриц
#include "AddMatrixForm.h"
namespace MatrixMul
{
System::Void AddMatrixForm::buttonOk_Click(System::Object ^ sender,
System::EventArgs ^ e)
{
*Width = (int)NumWidth->Value;
*Height = (int)NumWidth->Value;
Close();
}
System::Void AddMatrixForm::buttonCans_Click(System::Object ^ sender,
System::EventArgs ^ e)
{
Close();
}
System::Void AddMatrixForm::EQSizeCB_CheckedChanged(System::Object ^ sender,
System::EventArgs ^ e)
{
}
}
//Основные функции вычисления произведении я матриц последовательным
и параллельными методами
#include "MainForm.h"
#include <omp.h>
using namespace System;
using namespace System::Windows::Forms;
namespace MatrixMul
{
Void MainForm::SingleMulMatrixNrm(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResNrmMatrix = new int*[HFMtx];
double incr = 100.0 / HFMtx;
double stat = 0;
//time_t start = clock();
double start = omp_get_wtime();
for (int i = 0; i < HFMtx; i++)
{
ResNrmMatrix[i] = new int[WRMtx];
ProgressNrm->Value = (int)stat;
stat += incr;
for (int j = 0; j < WSMtx; j++)
{
ResNrmMatrix[i][j] = 0;
for (int k = 0; k < HRMtx; k++)
ResNrmMatrix[i][j] += FstMatrix[i][k] * SndMatrix[k][j];
}
}
ProgressNrm->Value = 100;
double f = omp_get_wtime();
NormalTime = f - start;
}
Void MainForm::SingleMulMatrixOMP(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOMPMatrix = new int*[HFMtx];
int **M1 = FstMatrix;
int **M2 = SndMatrix;
int **Res = ResOMPMatrix;
double incr = 100.0 / HFMtx;
double stat = 0;
//time_t start = clock();
double start = omp_get_wtime();
#ifdef _OPENMP
omp_set_num_threads(omp_get_max_threads());
#pragma omp parallel for shared(M1, M2, Res, stat) schedule(dynamic,3)
for (int i = 0; i < HFMtx; i++)
{
Res[i] = new int[WRMtx];
#pragma omp parallel for shared(M1, M2, Res)
for (int j = 0; j < WSMtx; j++)
{
Res[i][j] = 0;
for (int k = 0; k < HFMtx; k++)
Res[i][j] += M1[i][k] * M2[k][j];
}
//PrograssOMP->Value += (int)stat;
#pragma omp atomic
stat += incr;
}
#endif
PrograssOMP->Value = 100;
double f = omp_get_wtime();
OpenMPTime = f - start;
}
Void MainForm::SingleMulMatrixOCL(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOCLMatrix = new int*[HFMtx];
int **Mtrx2 = TransMatrix(SndMatrix, HSMtx, WSMtx);
double incr = 100.0 / (HRMtx);
double stat = 0;
//time_t start = clock();
//double start = omp_get_wtime();
cl_kernel kernel;
cl_mem inMatrix1Buf;
cl_mem inMatrix2Buf;
cl_mem outMatrixBuf;
double s1, s2, f1, f2;
try
{
cl_int sts;
const size_t globalSize[] = { (size_t)HRMtx };
outMatrixBuf = clCreateBuffer(context, CL_MEM_READ_WRITE,
sizeof(int)*WRMtx, NULL, &sts);
kernel = clCreateKernel(programm, "SingleMulMatrix", &sts);
//double start = omp_get_wtime();
for (int i = 0; i < HRMtx; i++)
{
//double s1 = omp_get_wtime();
stat += incr;
//ProgressOCL->Value = (int)stat;
ResOCLMatrix[i] = new int[WRMtx];
inMatrix1Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WFMtx, (void*)FstMatrix[i], &sts);
//double f1 = omp_get_wtime();
double start = omp_get_wtime();
for (int j = 0; j < WRMtx; j++)
{
//double s2 = omp_get_wtime();
inMatrix2Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WSMtx, (void*)Mtrx2[j], &sts);
int pos = j;
sts
=
clSetKernelArg(kernel,
0,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
1,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
2,
sizeof(cl_mem),
(void*)&inMatrix1Buf);
(void*)&inMatrix2Buf);
(void*)&outMatrixBuf);
sts = clSetKernelArg(kernel, 3, sizeof(int), (void*)&pos);
sts = clEnqueueNDRangeKernel(command, kernel, 1,
NULL, globalSize, NULL, 0, NULL, NULL);
//double f2 = omp_get_wtime();
//double time = s1 - f1;
}
double f = omp_get_wtime();
OpenCLTime = (f - start);
clEnqueueReadBuffer(command, outMatrixBuf, CL_TRUE, 0,
sizeof(int) * WRMtx, (void*)ResOCLMatrix[i], 0, NULL,
NULL);
}/*double f = omp_get_wtime();
OpenCLTime = (f - start);*/
}
finally
{
clReleaseKernel(kernel);
clReleaseMemObject(inMatrix1Buf);
clReleaseMemObject(inMatrix2Buf);
clReleaseMemObject(outMatrixBuf);
for (int i = 0; i < WSMtx; i++)
delete[] Mtrx2[i];
ProgressOCL->Value = 100;
}
}
Void MainForm::Rib1MulMatrixNrm(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResNrmMatrix = new int*[HFMtx];
int **Mtrx2 = TransMatrix(SndMatrix, HSMtx, WSMtx);
double incr = 100.0 / (HFMtx * HFMtx);
double stat = 0;
int shft = 0;
double start = omp_get_wtime();
//time_t start = clock();
for (int i = 0; shft < HFMtx; i++)
{
int pos = ((i + shft) < HFMtx) ? (i + shft) : (i + shft - HFMtx);
if (shft == 0) ResNrmMatrix[i] = new int[WRMtx];
ProgressNrm->Value = (int)stat;
stat += incr;
ResNrmMatrix[pos][i] = 0;
for (int k = 0; k < HRMtx; k++)
ResNrmMatrix[pos][i] += FstMatrix[pos][k] * Mtrx2[i][k];
if (i == HFMtx - 1) { i = -1; shft++; }
}
ProgressNrm->Value = 100;
double f = omp_get_wtime();
NormalTime = f - start;
}
Void MainForm::Rib1MulMatrixOMP(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOMPMatrix = new int*[HFMtx];
int **Res = ResOMPMatrix;
int **M1 = FstMatrix;
int **M2 = TransMatrix(SndMatrix, HSMtx, WSMtx);
int size = HFMtx;
double incr = 100.0 / HFMtx;
double stat = 0;
double start = omp_get_wtime();
//time_t start = clock();
#ifdef _OPENMP
omp_set_num_threads(omp_get_max_threads());
for (int shft = 0; shft < HFMtx; shft++)
{
#pragma omp parallel for shared(M1, M2, Res, shft, size) schedule(dynamic,3)
for (int i = 0; i < HFMtx; i++)
{
int pos = ((i + shft) < size) ? (i + shft) : (i + shft - size);
if (shft == 0) Res[i] = new int[size];
Res[pos][i] = 0;
#pragma omp parallel for shared(M1, M2, Res, pos)
for (int k = 0; k < size; k++)
Res[pos][i] += M1[pos][k] * M2[i][k];
}
stat += incr;
PrograssOMP->Value = (int)stat;
}
#endif
PrograssOMP->Value = 100;
double f = omp_get_wtime();
OpenMPTime = f - start;
}
Void MainForm::Rib1MulMatrixOCL(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOCLMatrix = new int*[HFMtx];
int **Mtrx2 = TransMatrix(SndMatrix, HSMtx, WSMtx);
double incr = 100.0 / (HFMtx * HFMtx);
double stat = 0;
int shft = 0;
//time_t start = clock();
double start = omp_get_wtime();
cl_kernel kernel;
cl_mem inMatrix1Buf;
cl_mem inMatrix2Buf;
cl_mem outMatrixBuf;
try
{
cl_int sts;
const size_t globalSize[] = { (size_t)HRMtx };
outMatrixBuf = clCreateBuffer(context, CL_MEM_READ_WRITE,
sizeof(int), NULL, &sts);
kernel = clCreateKernel(programm, "Ribbon1MulMatrix", &sts);
for (int shft = 0; shft < HFMtx; shft++)
{
for (int i = 0; i < HFMtx; i++)
{
int pos = ((i + shft) < HFMtx) ? (i + shft) : (i + shft HFMtx);
if (shft == 0) ResOCLMatrix[i] = new int[WRMtx];
ProgressOCL->Value = (int)stat;
stat += incr;
inMatrix1Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WFMtx, (void*)FstMatrix[pos], &sts);
inMatrix2Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WSMtx, (void*)Mtrx2[i], &sts);
sts
(void*)&inMatrix1Buf);
=
clSetKernelArg(kernel,
0,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
1,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
2,
sizeof(cl_mem),
(void*)&inMatrix2Buf);
(void*)&outMatrixBuf);
sts = clEnqueueNDRangeKernel(command, kernel, 1,
NULL, globalSize, NULL, 0, NULL, NULL);
clEnqueueReadBuffer(command,
outMatrixBuf,
CL_TRUE, 0,
sizeof(int), (void*)&ResOCLMatrix[pos][i], 0,
NULL, NULL);
}
}
}
finally
{
clReleaseKernel(kernel);
clReleaseMemObject(inMatrix1Buf);
clReleaseMemObject(inMatrix2Buf);
clReleaseMemObject(outMatrixBuf);
for (int i = 0; i < WSMtx; i++)
delete[] Mtrx2[i];
ProgressOCL->Value = 100;
double f = omp_get_wtime();
OpenCLTime = f - start;
}
ProgressOCL->Value = 100;
double f = omp_get_wtime();
OpenCLTime = f - start;
}
Void MainForm::Rib2MulMatrixNrm(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResNrmMatrix = new int*[HFMtx];
double incr = 100.0 / (HFMtx * HFMtx);
double stat = 0;
int shft = 0;
double start = omp_get_wtime();
//time_t start = clock();
for (int i = 0; shft < HFMtx; i++)
{
int pos = ((i + shft) < HFMtx) ? (i + shft) : (i + shft - HFMtx);
if (shft == 0) ResNrmMatrix[i] = new int[WRMtx];
ProgressNrm->Value = (int)stat;
stat += incr;
for (int k = 0; k < HRMtx; k++)
if (shft!= 0)ResNrmMatrix[pos][k] += FstMatrix[pos][k] *
SndMatrix[i][k];
else ResNrmMatrix[pos][k] = FstMatrix[pos][k] * SndMatrix[i][k];
if (i == HFMtx - 1) { i = -1; shft++; }
}
ProgressNrm->Value = 100;
double f = omp_get_wtime();
NormalTime = f - start;
}
Void MainForm::Rib2MulMatrixOMP(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOMPMatrix = new int*[HFMtx];
int **Res = ResOMPMatrix;
int **M1 = FstMatrix;
int **M2 = SndMatrix;
int size = HFMtx;
double incr = 100.0 / HFMtx;
double stat = 0;
double start = omp_get_wtime();
//time_t start = clock();
#ifdef _OPENMP
omp_set_num_threads(omp_get_max_threads());
for (int shft = 0; shft < HFMtx; shft++)
{
#pragma omp parallel for shared(M1, M2, Res, shft, size) schedule(dynamic,3)
for (int i = 0; i < HFMtx; i++)
{
int pos = ((i + shft) < size) ? (i + shft) : (i + shft - size);
if (shft == 0) Res[i] = new int[size];
#pragma omp parallel for shared(M1, M2, Res, pos)
for (int k = 0; k < size; k++)
Res[pos][k] = (shft != 0) ? (Res[pos][k] + M1[pos][k] *
M2[i][k]) : (M1[pos][k] * M2[i][k]);
}
stat += incr;
PrograssOMP->Value = (int)stat;
}
#endif
PrograssOMP->Value = 100;
double f = omp_get_wtime();
OpenMPTime = f - start;
}
Void MainForm::Rib2MulMatrixOCL(Object^ sender, MulEventArgs^ e)
{
HRMtx = HFMtx; WRMtx = WSMtx;
ResOCLMatrix = new int*[HFMtx];
double incr = 100.0 / (HFMtx * HFMtx);
double stat = 0;
int shft = 0;
double start = omp_get_wtime();
//time_t start = clock();
cl_kernel kernel;
cl_mem inMatrix1Buf;
cl_mem inMatrix2Buf;
cl_mem outMatrixBuf;
try
{
cl_int sts;
const size_t globalSize[] = { (size_t)HRMtx };
outMatrixBuf = clCreateBuffer(context, CL_MEM_READ_WRITE,
sizeof(int), NULL, &sts);
kernel = clCreateKernel(programm, "Ribbon2MulMatrix", &sts);
for (int shft = 0; shft < HFMtx; shft++)
{
for (int i = 0; i < HFMtx; i++)
{
int pos = ((i + shft) < HFMtx) ? (i + shft) : (i + shft HFMtx);
if (shft == 0) ResOCLMatrix[i] = new int[WRMtx];
ProgressOCL->Value = (int)stat;
stat += incr;
inMatrix1Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WFMtx, (void*)FstMatrix[pos], &sts);
inMatrix2Buf
=
clCreateBuffer(context,
CL_MEM_COPY_HOST_PTR | CL_MEM_READ_ONLY,
sizeof(int)*WSMtx, (void*)SndMatrix[i], &sts);
if (shft > 0) outMatrixBuf = clCreateBuffer(context,
CL_MEM_READ_WRITE,
sizeof(int)*WSMtx,
(void*)ResOCLMatrix[pos],
&sts);
sts
(void*)&inMatrix1Buf);
=
clSetKernelArg(kernel,
0,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
1,
sizeof(cl_mem),
sts
=
clSetKernelArg(kernel,
2,
sizeof(cl_mem),
(void*)&inMatrix2Buf);
(void*)&outMatrixBuf);
sts = clSetKernelArg(kernel, 3, sizeof(int), (void*)&shft);
sts = clEnqueueNDRangeKernel(command, kernel, 1,
NULL, globalSize, NULL, 0, NULL, NULL);
clEnqueueReadBuffer(command,
outMatrixBuf,
CL_TRUE, 0,
sizeof(int), (void*)&ResOCLMatrix[pos], 0, NULL,
NULL);
}
}
}
finally
{
clReleaseKernel(kernel);
clReleaseMemObject(inMatrix1Buf);
clReleaseMemObject(inMatrix2Buf);
clReleaseMemObject(outMatrixBuf);
ProgressOCL->Value = 100;
double f = omp_get_wtime();
OpenCLTime = f - start;
}
ProgressOCL->Value = 100;
double f = omp_get_wtime();
OpenCLTime = f - start;
}
}
Приложение 2. Результаты выполнения программы
Размернос
Последовательный
ть
метод
Последоват
OpenMp
Ленточный метод1
OpenCL
Последоват
OpenMp
Ленточный метод1
OpenCL
Последоват
ельная
ельная
ельная
реализация
реализация
реализация
10*10
0.0033
0.0591
0.0004
50*50
0.0209
0.0047
0.0020
100*100
0.0780
0.0318
0.003
500*500
6,3506
5,1879
5,0295
5.4974
4.8475
4.1060
OpenMP OpenCL
Download