МІНІСТЕРСТВО ОСВІТИ І НАУКИ, МОЛОДІ ТА СПОРТУ УКРАЇНИ
НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ „ЛЬВІВСЬКА ПОЛІТЕХНІКА”
Кафедра ЕОМ
Курсова робота
з дисципліни: “ Паралельні та розподілені обчислення”
на тему:
«Паралельне виконання операцій множення матриць»
Завдання
Задаються дві матриці А (розмірністю n1*n2) та В (розмірністю n2*n3) :
n1=5Б , n2=3П, n3=2І – КІ-44
де nП, nБ та nІ номер букви в прізвищі, імені та по-батькові відповідно.
Жолубак Іван Михайлович КІ-44:
Завдання відповідно варіанту задане в таблиці 1.
Таблиця 1 Завдання відповідно варіанту
Розміри матриць
К-сть
процесорів (Р)
Тип початкового
завантаження даних
Співвідношення часових параметрів
N1
N2
N3
190
213
73
8
Завантаження з спільної пам’яті
tU=10tS=4tP=2tZ=10tW
Отже маємо матрицю А(120*110) та матрицю В(110*120)
Розробити та описати алгоритм множення матриць А*В на структурі, яка задається виразом:
3b-6b-2b-5b-10b-13b-14b-11b – КІ-44, де «nb»- номер букви П.І.Б студента.
Жолубак Іван Михайлович КІ-44:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Ж
О
Л
У
Б
А
К
І
В
А
Н
М
И
Х
А
Й
Л
О
В
И
Ч
3
1
4
2
5
8
6
7
Отримаємо – ЛАОБНИХМ
Для ЛАОБНИХМ отримаємо 212, 27, 250, 35, 134, 91, 127, 43. Даний набір чисел записуємо у стовпець і переводимо і двійкове 8-ми розрядне число. В отриманій двійковій матриці одиниці що розташовані на головній діагоналі замінюємо на 0.
212 - 11010100 01010100
027 - 00011011 00011011
250 - 11111010 11011010
035 - 00100011 00100011
134 - 10000110 10000110
091 - 01011011 01011011
127 - 01111111 01111101
043 - 00101011 00101010
Отримана матриця розміру 8*8 є матрицею зв’язків орієнтованого графу, вершини якого це процесори, а напрямлені дуги це напрямлені лінії зв’язку між процесорами. На основі цієї матриці будується відповідний їй граф що характеризує певну 8-ми процесорну систему (структуру) із направленими зв’язками між вершинами.
3.Для цієї структури визначити час виконання алгоритму, відсоток послідовної частини алгоритму та ефективність алгоритму для значень, співвідношення часових параметрів та тип початкового завантаження визначаються за правилами:
3.1. Тип початкового завантаження даних type:
, де Zi цифри номера залікової книжки, n=1..k, де k – кількість цифр номера залікової книжки.
КІ-41 … КІ-46: type=1(спільна пам’ять),type=2(через один процесор), type=3(розподілена пам’ять). 0809319
type=(0+8+0+9+3+1+9)=30 mod 3 + 1 = 1(з спільної пам’яті)
3.2. Співвідношення часових параметрів tU,tS,tP,tZ,tW:
tU – час виконання однієї операції множення;
tS - час виконання однієї операції сумування;
tP - час виконання однієї операції пересилання даних між процесорами;
tZ - час виконання операції завантаження одних даних;
tW - час виконання операції вивантаження одних даних.
tU=(Zk-3 +1)tS=(Zk-2 +1)tP=(Zk-1 +1)tZ=(Zk+1)tW, де Zi відповідна цифра номера залікової книжки, k = 7-кількість цифр в номері залікової книжки.
tU=(9 +1)tS=(3 +1)tP=(1 +1)tZ=(9+1)tW
tU=10tW; tS=tw; tP=5/2tw; tZ=5tw;
Анотація
В даній курсовій роботі було розроблено програму для множення двох матриць розміром 190*216 та 216*73 на восьми процесорах, з заданою структурою зв’язків. Завантаження даних в процесори відбувається через спільну пам’ять. Збір даних відбувається на одному процесорі. Також було розроблено функціональну схему і визначено основні характеристики розробленого паралельного алгоритму.
Програма в даній курсовій роботі написана на мові С++ з використанням бібліотек MPI та має консольний інтерфейс.
Зміст
Вступ…………………………………………………………………………………..6
1. Теоретичний розділ 8
2. Проектування граф-схеми виконання алгоритму 12
3. Розробка функціональної схеми 18
4. Розрахунковий розділ 22
5. Розробка програми виконання заданої функції (алгоритму) 29
Висновок
Список використаної літератури
Додатки
Вступ
На сьогоднішній день найбільш перспективні дві основні технології, які мають багато спільного: паралельні та розподілені обчислення.
У паралельних обчисленнях бере участь обладнання, що знаходиться, як правило, в одному фізичному місці, вони тісно поєднані між собою і всі параметри їх роботи відомі програмісту. У розподілених обчисленнях немає тісного постійного зв'язку між вузлами, вони розподілені по деякій території і параметри роботи цієї системи динамічні і не завжди відомі.
Існують різні методи і технології паралельних і розподілених обчислень, постійне зростання використання яких в сучасній практичній діяльності можна пояснити наступними обставинами:
• надзвичайно швидке зростання складності об'єктів моделювання (перехід від простих до складних ("великим") системам);
• перехід до вирішення завдань, вирішення яких вимагає досліджень, які необхідні для проведення ретельного аналізу складної поведінки ;
• необхідність управління складними промисловими і технологічними завданнями в режимі реального часу і в умовах невизначеності;
• зростання кількості завдань, для вирішення яких необхідно обробляти гігантські обсяги інформації.
Домінуюче положення при розробці паралельних програм для паралельних систем займає стандарт MPI (англ. Message Passing Interface). Програма, розроблена в моделі передачі повідомлень, може бути представлена інформаційним графом, вершинам якого відповідають паралельні гілки програми, а ребрам комунікаційні зв'язки між ними. Це можна використовувати для диспетчеризації завдань та їх обчислювальних потоків. Враховуючи гетерогенність обчислювальних ресурсів і середовищ передачі даних у кластері, можна здійснити розподіл обчислювальних потоків (гілок) з обчислювальних вузлів так, щоб мінімізувати накладні витрати на обмін даними між потоками і вирівняти обчислювальне навантаження між вузлами.
Теоретичний розділ
Завдання множення матриць є базовою макрооперацією для багатьох задач лінійної алгебри. Тому ефективний паралельний алгоритм множення матриць дозволяє значно збільшити розмірність подібних завдань без збільшення часу їх вирішення. Нехай дано дві матриці, і., результат яких необхідно обчислити. Елементи результуючої матриці визначаються за формулою
Час виконання послідовного алгоритму визначається так. Нехай - час, що витрачається на множення двох чисел, - час додавання двох чисел. Час, що витрачається на обчислення одного елемента результуючої матриці. В результуючій матриці міститься елементи, тому загальний час виконання множення матриць буде рівний:
Позначимо . Звідси випливає, що. . Розглянемо паралельний алгоритм множення матриць і оцінимо час його роботи. Нехай є процесорів, причому, , де - ціле. Розпаралелимо обчислення так, щоб кожен процесор обчислював елементів результуючої матриці. Тоді сумарний час виконання можна оцінити таким чином:
Позначимо . Порівнюючи і , отримаємо : . Маємо, що час, який витрачається на множення матриць за допомогою паралельного алгоритму, в раз менший, ніж при використанні послідовного.
Розглянемо один із способів розпаралелювання алгоритму. Пронумеруємо елементи результуючої матриці за наступним правилом (нумерація елементів, а також рядків і стовпців в даному випадку ведеться з нуля):
Обчислення розподілимо так, щоб перший процесор обчислював перших елементів результуючої матриці, другий – наступні й т.д. Таким чином, кінцева і ліва вихідна матриці виходять розподіленими горизонтальними смугами, а права вихідна матриця - вертикальними смугами.
Алгоритм перемноження матриці на матрицю на кільцевій структурі
Задано дві вихідні матриці A і B. Обчислюється добуток , де А - матриця , B - матриця . Матриця результатів C має розмір . Вихідні матриці попередньо розрізані на смуги, смуги записані в пам'ять. Матриця результатів повертається в нульовий процес.
Реалізація алгоритму виконується на кільці з процесорів. Матриці розрізані як наведено на рис. 1.1: матриця А розрізана на горизонтальних смуг, матриця B розрізана на вертикальних смуг, і матриця результату C розрізана на смуги. Передбачається, що в пам'ять кожного процесора завантажується і може зберігатися тільки одна смуга матриці А і одна смуга матриці B.
Рис. 1.1. Схема розбиття матриць на смуги.
Оскільки за умовою в процесорах знаходиться по одній смузі матриць, то смуги матриці B (або смуги матриці A) необхідно "прокрутити" по кільцю процесорів повз смуги матриці A (матриці B). Кожний зсув смуг уздовж кільця і відповідна операція множення наведена на рис. 1.2 у виді окремого кроку. На кожному з таких кроків обчислюється тільки частина смуги. Процес i обчислює на j-м кроці добуток i-й горизонтальної смуги матриці A j-ї вертикальної смуги матриці B, добуток отримується у підматриці (i,j) матриці C.
Обчислення відбувається в такій послідовності:
Головний процесор (processor 0) отримує дані (матриці А та В) з пам’яті, після чого відбувається обчислення щодо розподілу рядків матриці А та стовпців матриці В між процесорами.
На наступному етапі головний процесор починає послідовно (по кільцю) надсилати решті процесорів необхідні їм для обчислення дані. По закінченню етапу завантаження даних кожен з процесорів буде містити смугу матриці А і смугу матриці В.
Обчислювальний крок 1. Кожен процес обчислює одну підматрицю добутку. Вертикальні смуги матриці B зсуваються уздовж кільця процесорів.
Обчислювальний крок 2. Кожен процес обчислює одну підматрицю добутку. Вертикальні смуги матриці B зсуваються уздовж кільця процесорів і т.д.
Обчислювальний крок . Кожен процес обчислює одну підматрицю добутку. Вертикальні смуги матриці B зсуваються уздовж кільця процесорів.
Обчислювальний крок . Кожен процес обчислює одну підматрицю добутку. Вертикальні смуги матриці B зсуваються уздовж кільця процесорів.
Після виконання останнього обчислювального кроку відбувається вивантаження часткових результатів (підматриць ) до нульового процесора.
Якщо "прокручувати" вертикальні смуги матриці B, то матриця С буде розподілена горизонтальними смугами, а якщо "прокручувати" горизонтальні смуги матриці A, то матриця С буде розподілена вертикальними смугами.
Алгоритм характерний тим, що після кожного кроку обчислень здійснюється обмін даними. Нехай , , і час операцій, відповідно, множення, додавання і пересилання одного числа між сусідніми процесорами. Тоді загальний час виконання операцій множень дорівнює:
,
загальний час виконання операцій додавань:
,
загальний час виконання операцій пересилань даних по всіх процесорах:
.
Загальний час обчислень визначимо як:
.
Якщо час передачі даних великий в порівнянні з часом обчислень, або канали передачі повільні, то ефективність алгоритму буде не висока. Тут не враховується час початкового завантаження і вивантаження даних у пам'ять системи. У смугах матриць може бути різна кількість рядків, а різниця в кількості рядків між смугами - 1. При великих матрицях цим можна знехтувати.
Проектування граф-схеми виконання алгоритму
Алгоритм перемноження матриць на багатопроцесорній структурі складається з декількох етапів:
визначення зв’язків між процесорами, на яких будуть перемножуватись матриці;
розбиття матриць на частини і розсилання кожному процесору його підматриці;
обчислення результату;
збереження результату.
Рис.2.1. Граф-схема роботи алгоритму множення матриць
Кожен процесор по черзі звертається до спільної пам’яті і завантажує свою частину матриць А і В. Після того, як дані завантаженні відбувається процес обчислення. Обчислення складається з 8-ми циклів множення і додавання і 7 циклів обміну під матрицями В. Як тільки на кожному процесорі сформуються результуючі підматриці розпочинається процес вивантаження підматриці в пам'ять. Кожен процесор по черзі звертається до спільної пам’яті і вивантажує свою результуючу підматрицю.
На рис. 2.2. наведена граф-схема зв’язків між процесорами заданої структури. Дана граф-схема дозволяє визначити комунікації між процесорами. При розробці алгоритму множення матриць комунікації відіграють важливу роль. Від зв’язків між процесорами залежить алгоритм розсилання даних, збору і обміну підматрицею Ві при перемноженні матриць.
Рис.2.2. Граф 8-процесорної системи.
Наступним кроком є спосіб розбиття даних між процесорами. Від даного кроку залежить рівномірність навантаження кожного процесора і час простою при різному навантажені. На рис. 2.2 наведено схему розбиття матриць А і В між процесорами. Як видно з схеми навантаження буде рівномірне, так як кожен процесор отримає підматриці рівного розміру.
Рис.2.2. Розбиття матриці між процесорами.
Так як, завантаження відбувається з спільної пам’яті, то всі процесори звертаються до пам’яті послідовно і кожен завантажує для себе свої підматриці.
Вивантаження відбувається аналогічно до завантаження, кожен процесор послідовно звертається в пам'ять і зберігає свій результат.
В даній структурі зв’язків від процесорами можна виділити кільце, тому обмін підматрицею Ві при перемноженні матриць буде виконуватись по вибраному кільцю, яке зображене на рис.2.3:
Рис.2.3. Вибраний кільцевий маршрут передачі підматриць В при множенні
Передача відбувається за 2 етапи:
Етап 1: 7 -> 2, 4->0; 1->3; 6->5;
Етап 2: 5 -> 7, 2->4; 0->1; 3->6;
Де 1-8 – це номери процесорів, а -> вказує від якого до якого процесора відбувається пересилання підматриць B.
Розробка функціональної схеми
Функціональна схема відображає комунікації між процесорами в ході виконання обчислень, а також покрокове виконання алгоритму.
Рис. 3.1.Завантаженняданих з спільної пам’яті
Згідно з схемою рис. 3.1 дані кожен процесор послідовно звертається до спільної пам’яті і завантажує свої підматриці. Алгоритм є повністю послідовним і в той час як працює один процесор, на всіх інших відбувається простій.
Рис. 3.2.Перемноження матриць з обміном підматриці В
Відповідно до схеми рис. 3.2 відбувається перемноження матриці А на матрицю В на структурі з 8-ми процесорів. Розмір блоків відповідає часу потрібному для виконання вказаної операції. Процес перемноження складається з 8-ми рівнів перемноження і з 7-ми рівнів пересилання даних. Дана схема складається з блоків множення і блоків пересилання даних.
Рис. 3.3.Вивантаження результату в память.
За схемою рис. 3.3 процес вивантаження з послідовного вивантаження кожним процесором своєї підматриці в пам'ять. Поки працює один процесор всі інші простоюють.
Розрахунковий розділ
Для процесорних елементів визначимо такі розміри підматриць:
A0- A1 = n1*n2 = 23*216; B0 = n2*n3 = 216*8;
А2-А7 = n1*n2 = 24*216 B1-B7=n2*n3 = 216*9;
Де n1, n2, n3 – розміри підматриць.
р – кількість процесорів.
tU=(9 +1)tS=(3 +1)tP=(1 +1)tZ=(9+1)tW
tU=10tW; tS=tw; tP=5tw/2; tZ=5tw
Процес завантаження є послідовним і складається з восьми кроків. На кожному кроці кожен процесор завантажує свою підматрицю.
Тz1=(n1*n2+n2*n3)tz = (23*216+8*216)tz=6696tz = 33480tw
Тz2=(n1*n2+n2*n3)tz = (23*216+9*216)tz=6912tz = 34560tw
Тz3=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Тz4=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Тz5=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Тz6=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Тz7=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Тz8=(n1*n2+n2*n3)tz = (24*216+9*216)tz=7128tz = 35640tw
Z = Тz1+Тz2+Тz3+Тz4+Тz5+Тz6+Тz7+Тz8 = 33480 + 34560+6*35640 = 281880tw
Так як розбиття матриць не рівномірне, то загальний час множення і додавання визначається як найбільший на кожному кроці:
U = p*n1*n2*n3*tu = 8*24*216*9*tu = 373248tu = 3732480tw
S = p*n1*(n2-1)*n3*ts = 8*24*215*9*ts = 371520ts = 371520tw
Так як пересилання виконуються в два етапи, час пересилань буде рівним:
P = (p-1)*n2*n3*2*tp=7*216*9*2*tp= 27216tp = 68040tw
Процес вивантаження складається з 8-ми послідовних кроків вивантаження. Тому загальний час вивантаження буде рівний часові на кожному кроці:
Тw1=(n1*N3)tw = (23*73)tw=1679tw
Тw2=(n1*N3)tw = (23*73)tw=1679tw
Тw3=(n1*N3)tw = (24*73)tw=1752tw
Тw4=(n1*N3)tw = (24*73)tw=1752tw
Тw5=(n1*N3)tw = (24*73)tw=1752tw
Тw6=(n1*N3)tw = (24*73)tw=1752tw
Тw7=(n1*N3)tw = (24*73)tw=1752tw
Тw8=(n1*N3)tw = (24*73)tw=1752tw
W = Тw1+Тw2+Тw3+Тw4+Тw5+Тw6+Тw7+Тw8 = 2*1679+6*1752 = 13870tw
Загальний час алгоритму буде рівний:
T = Z+U+S+P+W = 281880 + 3732480 + 371520 + 68040 + 13870 = 4467790tw
Для порівняння обчислимо умовний час виконання послідовного алгоритму:
Час завантаження і час вивантаження будуть такими ж як і в паралельному алгоритмі без пересилань:
Zпос = (N1*N2+N2*N3)tz=(190*216 + 216* 73)tz = 56808tz = 284040tw
Wпос = (N1*N3)tw=(190*73)tw =.13870tw
Пересилань ніяких не буде, тому час пересилання Pпос = 0.
Час множення буде рівним:
Uпос = (N1*N2*N3) * tU = (190*216*73)*tu = 2995920tu = 29959200tw
Час додавання буде рівним:
Sпос = (N1*N2*N3) * tS = (190*215*73)*ts = 2982050ts=2982050tw
Tпос = Zпос + Uпос + Sпос + Wпос = (284040+13870+29959200+2982050) * tw = 33239160 * tw
Час виконання послідовного алгоритму перемноження однакових матриць на одному процесорі приблизно в 33239160/4467790 ≈ 7,44 раз більший, ніж паралельного алгоритму.
Для характеристики алгоритму визначимо коефіцієнт К, який характеризує відсоток послідовних обчислень. Послідовні обчислення характеризуються тим, що в конкретний момент часу працює тільки 1 процесор. В нашому випадку він буде рівний:
K = (Z + W) / T = (281880 + 13870) / 4467790 ≈ 0,066 = 6,6%.
Також порахуємо ефективність паралельного алгоритму. Ефективність визначається, як відношення часу виконання алгоритму на одно процесорній системі, до часу потрібного для виконання на багатопроцесорної системі, помноженого на кількість процесорів в ній.
E = TПОС / (T * p1) = 33239160/(4467790*8) ≈ 0,93.
Розробка програми виконання заданої функції (алгоритму)
Першим кроком розробки програми для виконання заданої функції є розробка блок схеми алгоритму.
Рис. 5.1.Блок-схема роботи програми.
Згідно з блок-схемою програма повинна завантажувати дані з спільної пам’яті. Після розсилання даних відбувається 8 циклів перемноження матриці. Після того як на кожному процесорі сформується результуючі підматриці всі дані послідовно вивантажуються в пам'ять.
Для реалізації даної програми використовується бібліотека MPI. Програма виконується згідно з розробленого алгоритму.
Всі пересилання виконуються функцією MPI_Send, яка має наступний синтаксис:
int MPI_Send(void *buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm)
Вхідні параметри
buf – початкова адреса буфера, який пересилається.
count – число елементів в буфері, який пересилається (невід'ємне ціле)
datatype – тип даних кожного елемента буфера (дескриптор)
dest – ранг процесора призначення (ціле)
tag – тег повідомлення (ціле)
comm – комунікатор (дескриптор)
Прийом даних відбувається за допомогою функції MPI_Recv:
int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source,
int tag,MPI_Comm comm, MPI_Status *status)
buf – початкова адреса буфера, який приймаєтсья.
count – число елементів в буфері, який приймається (невід'ємне ціле) datatype – тип даних кожного елемента буфера (дескриптор)
dest – ранг процесора відправника (ціле)
tag – тег повідомлення (ціле)
comm – комунікатор (дескриптор)
Дана програма написана на мові С++ в середовищі розробки MS Visual Studio 2010 і має консольний інтерфейс.
Результати роботи програми:
Рис. 5.1 Результати роботи програми.
Завантаження даних на процесори відбувається з файла. Файл був заздалегідь згенерований. Як видно з рис.5.1 програма працює коректно і видає результати множення.
Висновок
Після успішного виконання курсового проекту я оволодів технологією побудови багатопроцесорних системи. Розробив восьми процесорну систему множення двох матриць, при чому дані зчитуються з спільної пам’яті. Також було проведено деякі обчислення, які дають уявлення про характеристики даного паралельного алгоритму, та порівняно його з послідовним. В результаті проведеної роботи можна зробити такі висновки: для восьми процесорної системи та матриць з розмірами 290х216 216х73 умовний час виконання програми в інтервалах виконання операції виявився рівним Т =4467790 , а для послідовної структури Tpos =33239160 Tw що приблизно в 7,44 рази довше. Ефективність при цьому рівна E = 93% , що є досить хорошим показником, але не ідеальним.
Список літератури
Корнеев В.В. Параллельные вычислительные системы. М.: Нолидж, 1999
С. Немнюгин, О.Стесик Параллельное программирование для многопроцессорных вычислительных систем. – СПб: БХВ-Петербург, 2002.
Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. – СПб: БХВ-Петербург, 2002.
Ортега Дж. Введение в параллельные и векторные методы решения линейных систем. М.:Мир, 1991.
Программирование на параллельных вычислительных системах: Пер с англ./Под ред. Р.Бэбба.М.:Мир, 1991.
Бройнль Т. Паралельне програмування: Початковий курс: Навчальний посібник. – К.:Вища школа.,1997.
Воеводин В.В. Математические основы параллельных вычислений.- М.: Изд-во МГУ, 1991.
Векторизация программ: теория, методы, реализация: Пер. с англ. и нем. /Под ред. Г.Д.Чинина. - М:. Мир, 1991.
Організація паралельних обчислень : Навчальний посібник з дисципліни “Паралельні та розподілені обчислення” для студентів базового напрямку 6.0915 "Комп'ютерна інженерія" / Укладачі: Є. Ваврук, О. Лашко – Львів: Національний університет “Львівська політехніка”, 2007, 70 с.
1http://www.parallel.ru – Інформаційно-аналітичний центр з паралельних обчислень.
http://www.csa.ru – Інститут високопродуктивних обчислень і баз даних.
http://www.hpc.nw.ru – Високопродуктивні обчислення.
Додаток А
Лістинг програми:
// MPI.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <mpi.h>
using namespace std;
double ilndex;
const int pResInd[] = {0,23,46,70,94,118,142,166};
void Calc (double * pMatrixA, double * pMatrixB, double * pMatrixB1, double * pProcResult, double * pResult1, double ilndex);
int ProcNum = 8;
int ProcRank;
double* pProcRows;
const int N1 = 190;
const int N2 = 216;
const int N3 = 73;
int pRowsA[] = {23,23,24,24,24,24,24,24};
int pColsB[] = {8,9,9,9,9,9,9,9};
int pSendNumSubA[] = {23*216,23*216,24*216,24*216,24*216,24*216,24*216,24*216};
int pSendIndSubA[] = {0,23*216,2*23*216,2*23*216+24*216,2*23*216+2*24*216,2*23*216+3*24*216,2*23*216+4*24*216};
int pSendNumSubB[] = {216*8,216*9,216*9,216*9,216*9,216*9,216*9,216*9};
int pSendIndSubB[] = {0,216*8,216*8+216*9,216*8+2*216*9,216*8+3*216*9,216*8+4*216*9,216*8+5*216*9,216*8+6*216*9};
int pSendNumSubC[] = {23*73,23*73,24*73,24*73,24*73,24*73,24*73,24*73};
const int ProcNumber = 8;
void ReadMemory(double* &pProcRows, double* &pMatrixB1){
int temp;
pProcRows = new double[pSendNumSubA[ProcRank]];
pMatrixB1 = new double[pSendNumSubB[ProcRank]];
FILE *Ptr;
Ptr=fopen("mass.txt","r");
for(int i=0;i<N1*N2;i++)
{
fscanf(Ptr,"%d",&temp);
pProcRows[i] = (double)temp;
}
for(int i=0;i<N2*N3;i++)
{
fscanf(Ptr,"%d",&temp);
pMatrixB1[i] = (double)temp;
}
fclose(Ptr);
}
void WriteMemory(double* pProcResult, FILE* Ptr){
int temp;
Ptr=fopen("mass.txt","r");
for(int i=0;i<pSendNumSubC[ProcRank];i++)
{
temp = (int)pProcResult[i];
fprintf(Ptr,"%d",temp);
}
}
void Calc (double * pMatrixA, double * pMatrixB, double * pMatrixB1, double * pProcResult, double * pResult1, double iIndex);
int main(int argc, char* argv[])
{
double* pMatrixA; // Перший аргумент - початкова матриця
double* pMatrixB; // Другий аргумент - початковий вектор
double* pResult; // Результат множення матриці на вектор
double* pProcRows;
double* pProcResult;
double* pResult1=new double[N1*N3];
int *pSendNum = new int[ProcNumber];
int *pSendInd = new int[ProcNumber];
int RowNum;
pProcRows = new double[N1*N2*6];
pMatrixA = new double[N1*N2];
pMatrixB = new double[N2*N3];
pResult = new double[N1*N3];
pProcResult = new double[(N1/8)*N3];
double* pMatrixB1 = new double[N2*N3*6];
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &ProcNum);
MPI_Comm_rank(MPI_COMM_WORLD, &ProcRank);
for(int i=0; i<N2*N3; i++){
pMatrixB[i]=0;
}
//---------------------------------------------------------
Calc (pMatrixA, pMatrixB, pMatrixB1, pProcResult, pResult1, ilndex);
//-----------------------------------------------------------
MPI_Finalize();
return 0;
}
void Calc( double * pMatrixA, double * pMatrixB, double * pMatrixB1, double * pProcResult, double * pResult, int iIndex)
{
//Data Distribution
MPI_Status status;
if(ProcRank==0){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==1){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==2){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==3){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==4){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==5){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==6){
ReadMemory(pProcRows, pMatrixB1);
}
if(ProcRank==7){
ReadMemory(pProcRows, pMatrixB1);
}
//обнулення проміжних результатів
for(int i=0; i<N1/8; i++)
for(int j=0; j<N3+1; j++)
pProcResult[i*N3+j]=0;
//обнулення проміжних результатів
for(int i=0; i<N1/8; i++)
for(int j=0; j<N3+1; j++)
pProcResult[i*N3+j]=0;
//множення і обмін підматрицями В
if (ProcRank==0){
for(int i=0; i<pRowsA[0]; i++)
for(int j=0; j<pColsB[0]; j++)
for(int k=0; k<N2; k++)
pProcResult[i*N3+j+pResInd[0]]+=pProcRows[i*N2+k]*pMatrixB1[j*N2+k];
MPI_Send(pMatrixB1,pSendNumSubB[0],MPI_DOUBLE,1,0,MPI_COMM_WORLD);
MPI_Recv(pMatrixB1,pSendNumSubB[4],MPI_DOUBLE,4,0,MPI_COMM_WORLD,&status);
for(int i=0; i<pRowsA[0]; i++)
for(int j=0; j<pColsB[4]; j++)
for(int k=0; k<N2; k++)
pProcResult[i*N3+j+pResInd[4]]+=pProcRows[i*N2+k]*pMatrixB1[j*N2+k];
MPI_Send(pMatrixB1,pSendNumSubB[4],MPI_DOUBLE,1,0,MPI_COMM_WORLD);
MPI_Recv(pMatrixB1,pSendNumSubB[2],MPI_DOUBLE,4,0,MPI_COMM_WORLD,&status);
for(int i=0; i<pRowsA[0]; i++)
for(int j=0; j<pColsB[2]; j++)
for(int k=0; k<N2; k++)
pProcResult[i*N3+j+pResInd[2]]+=pProcRows[i*N2+k]*pMatrixB1[j*N2+k];
MPI_Send(pMatrixB1,pSendNumSubB[2],MPI_DOUBLE,1,0,MPI_COMM_WORLD);
MPI_Recv(pMatrixB1,pSendNumSubB[7],MPI_DOUBLE,4,0,MPI_COMM_WORLD,&status);
for(int i=0; i<pRowsA[0]; i++)
for(int j=0; j<pColsB[7]; j++)
for(int k=0; k<N2; k++)
pProcResult[i*N3+j+pResInd[7]]+=pProcRows[i*N2+k]*pMatrixB1[j*N2+k];
MPI_Send(pMatrixB1,pSendNumSubB[7],MPI_DOUBLE,1,0,MPI_COMM_WORLD);
MPI_Recv(pMatrixB1,pSendNumSubB[5],MPI_DOUBLE,4,0,MPI_COMM_WORLD,&status);
for(int i=0; i<pRowsA[0]; i++)
for(int j=0; j<pColsB[5]; j++)
for(int k=0; k<N2; k++)
pProcResult[i*N3+j+pResInd[5]]+=pProcRows[