Міністерство Освіти, Науки, Молоді та Спорту України
Національний університет «Львівська політехніка»
Кафедра ЕОМ
КУРСОВА РОБОТА
з дисципліни:
«Паралельні та розподілені обчислення»
на тему:
«Паралельне виконання операцій множення матриць»
Львів – 2013
Завдання
Розробити програму перемноження двох матриць даних між 8-ми процесорною системою. Виконати докладний опис схеми пересилання даних між процесорами, виконати короткий опис кожного етапу пересилання, виконати розрахункові обчислення що до затримок при завантаженні, вивантаженні та множення матриць процесором. Матриця А(розмірністю n1*n2) на матрицю В (розмірністю n2*n3).
Вибір індивідуального завдання
Таблиця 1. Варіанти завдань до курсової роботи
Розміри матриць
Тип початкового завантаження даних
Співвідношення часових параметрів
n1
n2
n3
70
192
149
1
tU = 10tS = 3tP =1tZ =5tW
Примітки:
1 – завантаження початкових через спільну пам'ять ;
tU – час виконання однієї операції множення;
tS – час виконання однієї операції сумування;
tP – час виконання однієї операції пересилання даних між процесорами;
tZ – час виконання операції завантаження одних даних;
tW – час виконання операції вивантаження одних даних.
№ залікової книжки : 0909204
type = = (0+9+0+9+2+0+4)mod 3 +1=3 – спільна пам'ять.
А=(70 х 192)
B=(192 x 149)
Таблиця 2 - Порядкові номери
Згідно з таблицею декодування літер розмірностей:
АГДЦМУКО
А = 1B = 0001 1011
Г = 4A = 0100 1010
Д = D = 0000 1101
Ц = C9 = 1100 1001
М = 2B = 0010 1011
У = A4 = 1010 0100
К = 2F = 0010 1111
О = FA = 1111 1010
Відношення часових затримок при обрахунках:
tU = 10tS = 3tP =1tZ = 5tW
tU = tz
tP = 1/3tz
ts = 1/10 tz
tW = 1/5 tz
Таблиця 3 – матриця зв’язків
1
2
3
4
5
6
7
8
1
0
0
0
1
1
0
1
1
2
0
0
0
0
1
0
1
0
3
0
0
0
0
1
1
0
1
4
1
1
0
0
1
0
0
1
5
0
0
1
0
0
0
1
1
6
1
0
1
0
0
0
0
0
7
0
0
1
0
1
1
0
1
8
1
1
1
1
1
0
1
0
Таблиця 4 – Результат розбиття на підматриці
A B
A1 8
A2 8
A3 9
A4 9
A5 9
A6 9
A7 9
A8 9
B1
18
B2
18
B3
18
B4
19
B5
19
B6
19
B7
19
B8
19
Анотація
В даній курсовій роботі розроблено програму паралельного перемноження на 8 процесорах двох матриць з завантаженням даних з спільної пам’яті. Вхідні матриці мають розмірності 70*192 та 192*149. Виконаний докладний опис опрацювання даних, а також опис пересилання даних між процесорами.
Здійснений розрахунок часових характеристик роботи структури. Проведено також порівняння ефективності даного паралельного алгоритму перемноження матриць з послідовним алгоритмом.
Здійснена програмна реалізація даної структури. Програма написана на мові С++ з використання технології МРІ і має консольний інтерфейс.
Зміст
Вступ 7
1. Теоретична розділ 8
1.1 Матричні обчислення 8
1.2 Інтерфейс передачі повідомлень 8
2. Розробка граф-схеми виконання множення матриць 11
2.1 Орієнтований граф 8-ми процесорної системи 11
2.2 Пересилання даних по колу 11
2.3 Граф-схема виконання алгоритму множення двох матриць 13
3. Розробка функціональної схеми 14
4. Розрахунковий розділ 16
5. Розробка програми 18
Висновок. 20
СПИСОК ЛІТЕРАТУРИ. 21
Додаток. 22
Лістинг програми. 22
Вступ
Для розв’язання багатьох задач (прогноз погоди, задачі гідро- і газодинаміки, квантової хімії, астрономії, спектроскопії, біології, ядерної фізики) необхідна висока продуктивність та висока швидкість передачі інформації по каналах зв’язку, великі об’єми оперативної і постійної пам’яті, які не можуть забезпечити типові обчислювальні засоби. Одним з шляхів забезпечення таких вимог є організація паралельних обчислень і відповідних технічних засобів їх реалізації.
Причому, ефективність паралельної обробки залежить як від продуктивності комп’ютерів, так і від розмірів і структури пам’яті, пропускної здатності каналів зв’язку, використаних мов програмування, компіляторів, операційних систем, чисельних методів та інших математичних досліджень. Такий широкий обсяг параметрів вимагає проведення досліджень на різних рівнях: на рівні розпаралелення алгоритмів, створення спеціальних мов програмування, компіляторів, багатопроцесорних систем, неоднорідних систем, кластерів і систем, що розподілені на великих територіях.
Якщо розглядати сферу інформаційних технологій, то найкращим способом для вивчення паралельних обчислень будуть операції над матрицями. Аналізуючи матричні операції можна дослідити яким чином відбувається процес множення, виконати розпаралелення шляхом рівномірного розподілу елементів вхідних матриць та обчислень між процесорами, з врахуванням різних типів розділення вхідних даних.
1. Теоретична розділ
1.1 Матричні обчислення
Матриці та операції над ними широко використовуються при математичному моделюванні найрізноманітніших процесів, явищ і систем. Матричні обчислення складають основу багатьох наукових і інженерних розрахунків.
Враховуючи важливість ефективного виконання матричних обчислень багато стандартних бібліотек програм містять процедури для різних матричних операцій. Об'єм програмного забезпечення для операцій над матрицями постійно збільшується - розробляються нові оптимальні структури даних для зберігання матриць спеціального типу (трикутних, стрічкових, розріджених тощо), створюються різні високоефективні машинно-залежні реалізації алгоритмів, проводяться теоретичні дослідження для пошуку швидших методів матричних обчислень.
Будучи обчислювально трудомісткими, матричні обчислення є класичною областю застосування паралельних обчислень. З одного боку, використання високопродуктивних багатопроцесорних систем дозволяє істотно підвищити складність завдань, які розв’язуються. З іншого боку, через своє достатньо просте формулювання матричні операції надають прекрасну можливість для демонстрації багатьох прийомів і методів паралельного програмування.
1.2 Інтерфейс передачі повідомлень
При розробці паралельних програм виникають специфічні для даної моделі обчислень проблеми сугубо технічного характеру: забезпечення комунікацій між підзадачами, забезпечення надійності й ефективності цих комунікацій, дозвіл проблем зв'язаних із загальним доступом до поділюваних ресурсів та інше. Для рішення цих проблем можна реалізувати власні методи, а можна використовувати вже готові стандарти/специфікації/бібліотеки. MPI – «Інтерфейс передачі повідомлень» - це специфікація, що була розроблена в 1993-1994 роках групою MPI Forum (http://www.mpi-forum.org),і забезпечує реалізацію моделі обміну повідомленнями між процесами. Остання версія даної специфікації MPI-2. У моделі програмування MPI програма породжує кілька процесів, взаємодіючих між собою за допомогою звертання до підпрограм прийому і передачі повідомлень.
Звичайно, при ініціалізації MPI-програми створюється фіксований набір процесів, причому (що, утім, необов'язково) кожний з них виконується на своєму процесорі. У цих процесах можуть виконуватися різні програми, тому MPI-модель іноді називають MPMD-моделлю (Multiple Program, Multiple Data), на відміну від SPMD (Single Program…)моделі, де на кожному процесорі виконуються тільки однакові задачі. MPI підтримує двохточкові і глобальні, синхронні й асинхронні, блокуючі і типи комунікацій, що неблокують. Спеціальний механізм – комунікатор – ховає від програміста внутрішні комунікаційні структури. Структура комунікацій може змінюватися протягом часу життя процесу, але кількість задач повинна залишатися постійним (MPI-2 уже підтримує динамічна зміна числа задач).
Специфікація MPI забезпечує переносимість програм на рівні вихідних кодів і велику функціональність. Підтримується робота на гетерогенних кластерах і симетричних мультипроцесорних системах. Не підтримується, як уже відзначалося, запуск процесів під час виконання MPI-програми. У специфікації відсутні опису паралельного введення-висновку і налагодження програм – ці можливості можуть бути включені до складу конкретної реалізації MPI у виді додаткових пакетів і утиліт. Сумісність різних реалізацій не гарантується.
Важливою властивістю паралельної програми є детермінізм – програма повинна завжди давати той самий результат для того самого набору вхідних даних. Модель передачі повідомлень, загалом даною властивістю не володіє, оскільки не визначений порядок одержання повідомлень від двох процесів третім. Якщо ж один процес послідовно посилає кілька повідомлень іншому процесу, MPI гарантує, що одержувач одержить їхній саме в тім порядку, у якому вони були відправлені. Відповідальність за забезпечення детермінованого виконання програми лягає на програміста (з цього приводу див. приклад 3).
MPICH – MPI Chameleon – одна з реалізацій MPICH яка підтримує роботу на великому числі платформ із різними комунікаційними інтерфейсами, у т.ч. і TCP/IP.
Основні особливості MPICH v 1.2.2:
повна сумісність зі специфікацією MPI-1;
наявність інтерфейсу в стилі MPI-2 з функціями для мови C++ зі специфікації MPI-1;
наявність інтерфейсу з процедурами мови FORTRAN-77/90;
є реалізація для Windows NT (несумісна з UNIX-реалізацією);
підтримка великого числа архітектур, у т.ч. кластерів, SMP і т.д.;
часткова підтримка MPI-2;
часткова підтримка паралельного введення-висновку – ROMIO;
наявність засобів трасування і протоколювання (SLOG-based);
наявність засобів візуалізації продуктивності паралельних програм (upshot і jumpshot);
наявність у складі MPICH тестів продуктивності і перевірки функціонування системи.
Недоліки MPICH – неможливість запуску процесів під час роботи програми і відсутність засобів моніторингу за поточним станом системи.
2. Розробка граф-схеми виконання множення матриць
Для ефективної роботи паралельної системи важливу роль відіграє попереднє створення побудова графу зв’язків між процесорами та схеми пересилань даних.
2.1 Орієнтований граф 8-ми процесорної системи
Рис. 2.1. Орієнтований граф 8-ми процесорної системи
2.2 Пересилання даних по колу
Оскільки я використовую взаємодію із спільної памяті найефективніше завантаження буде виконуватись послідовним способом. Я використав пересилання даних по колу, оскільки це буде найефективніший спосіб обробки даних з оптимальними затримками.
Пересилання виконується таким способом :
Рис. 2.2 Кільце пересилання даних між процесорами
2.3 Граф-схема виконання алгоритму множення двох матриць
рис. 4. Граф-схема алгоритму множення матриць
Збір результатів виконання множення ( під матриць Сі ) відбувається в п’ятому процесорі і відповідно вивантаження результуючої матриці С також відбувається з п’ятого процесору.
3. Розробка функціональної схеми
Процесор1
Процесор2
Процесор3
Процесор4
Процесор5
Процесор6
Процесор7
Процесор 8
Завантажен
А1,В1.
Завантажен
А2,В2.
Завантажен
А3,В3.
Завантажен
А4,В4.
Завантажен
А5,В5.
Завантажен
А6,В6.
Завантажен
А7,В7.
Завантажен
А8,В8.
Множення
А1хВ1
Множення
А2хВ2
Множення
А3хВ3
Множення
А4хВ4
Множення
А5хВ5
Множення
А6хВ6
Множення
А7хВ7
Множення
А8хВ8
Перес.В1-P4,отрим.В8 з P8
Перес.В2-P5,отрим.В4
З P4
Перес.В3-P8,отрим.В6
З P6
Перес.В4-P2,отрим.В1
З P1
Перес.В5-P7,отрим.В2
З P2
Перес.В6-P3,отрим.В7
З P7
Перес.В7-P6,отрим.В5
З P5
Перес.В8-P1,отрим.В3
З P3
Множення
А1хВ8
Множення
А2хВ4
Множення
А3хВ6
Множення
А4хВ1
Множення
А5хВ2
Множення
А6хВ7
Множення
А7хВ5
Множення
А8хВ3
Перес.В8-P4,отрим.В3 з P8
Перес.В4-P5,отрим.В1
З P4
Перес.В6-P8,отрим.В7
З P6
Перес.В1-P2,отрим.В8
З P1
Перес.В2-P7,отрим.В4
З P2
Перес.В7-P3,отрим.В5
З P7
Перес.В5-P6,отрим.В2
З P5
Перес.В3-P1,отрим.В6
З P3
Множення
А1хВ3
Множення
А2хВ1
Множення
А3хВ7
Множення
А4хВ8
Множення
А5хВ4
Множення
А6хВ5
Множення
А7хВ2
Множення
А8хВ6
Перес.В3-P4,отрим.В6 з P8
Перес.В1-P5,отрим.В8
З P4
Перес.В7-P8,отрим.В5
З P6
Перес.В8-P2,отрим.В3
З P1
Перес.В4-P7,отрим.В1
З P2
Перес.В5-P3,отрим.В2
З P7
Перес.В2-P6,отрим.В4
З P5
Перес.В6-P1,отрим.В7
З P3
Множення
А1хВ6
Множення
А2хВ8
Множення
А3хВ5
Множення
А4хВ3
Множення
А5хВ1
Множення
А6хВ2
Множення
А7хВ4
Множення
А8хВ7
Перес.В6-P4,отрим.В7 з P8
Перес.В8-P5,отрим.В3
З P4
Перес.В5-P8,отрим.В2
З P6
Перес.В3-P2,отрим.В6
З P1
Перес.В1-P7,отрим.В8
З P2
Перес.В2-P3,отрим.В4
З P7
Перес.В4-P6,отрим.В1
З P5
Перес.В7-P1,отрим.В5
З P3
Множення
А1хВ7
Множення
А2хВ3
Множення
А3хВ2
Множення
А4хВ6
Множення
А5хВ8
Множення
А6хВ4
Множення
А7хВ1
Множення
А8хВ5
Перес.В7-P4,отрим.В5 з P8
Перес.В3-P5,отрим.В6
З P4
Перес.В2-P8,отрим.В4
З P6
Перес.В6-P2,отрим.В7
З P1
Перес.В8-P7,отрим.В3
З P2
Перес.В4-P3,отрим.В1
З P7
Перес.В1-P6,отрим.В8
З P5
Перес.В5-P1,отрим.В2
З P3
Множення
А1хВ5
Множення
А2хВ6
Множення
А3хВ4
Множення
А4хВ7
Множення
А5хВ3
Множення
А6хВ1
Множення
А7хВ8
Множення
А8хВ2
Перес.В5-P4,отрим.В2 з P8
Перес.В6-P5,отрим.В7
З P4
Перес.В4-P8,отрим.В1
З P6
Перес.В7-P2,отрим.В5
З P1
Перес.В3-P7,отрим.В6
З P2
Перес.В1-P3,отрим.В8
З P7
Перес.В8-P6,отрим.В3
З P5
Перес.В2-P1,отрим.В4
З P3
Множення
А1хВ2
Множення
А2хВ7
Множення
А3хВ1
Множення
А4хВ5
Множення
А5хВ6
Множення
А6хВ8
Множення
А7хВ3
Множення
А8хВ4
Перес.В2-P4,отрим.В4 з P8
Перес.В7-P5,отрим.В5
З P4
Перес.В1-P8,отрим.В8
З P6
Перес.В5-P2,отрим.В2
З P1
Перес.В6-P7,отрим.В7
З P2
Перес.В8-P3,отрим.В3
З P7
Перес.В3-P6,отрим.В6
З P5
Перес.В4-P1,отрим.В1
З P3
Множення
А1хВ4
Множення
А2хВ5
Множення
А3хВ8
Множення
А4хВ2
Множення
А5хВ7
Множення
А6хВ3
Множення
А7хВ6
Множення
А8хВ1
Перес. С1- Р4
Перес. С2- Р5
Перес. С3- Р8
Перес. С4- Р2
Вив. С5
Перес. С6- Р3
Перес. С7- Р6
Перес. С8- Р1
Перес. С8- Р4
Перес. С4- Р5
Перес. С6- Р8
Перес. С1- Р2
Вив. С2
Перес. С7- Р3
Перес. С3- Р1
Перес. С3- Р4
Перес. С1- Р5
Перес. С7- Р8
Перес. С8- Р2
Вив. С4
Перес. С6- Р1
Перес. С6- Р4
Перес. С8- Р5
Перес. С3- Р2
Вив. С1
Перес. С7- Р1
Перес. С7- Р4
Перес. С3- Р5
Перес. С6- Р2
Вив. С8
Перес. С6- Р5
Перес. С7- Р2
Вив. С3
Перес. С7- Р5
Вив. С6
Вив. С7
рис. 5. Функціональна схема
4. Розрахунковий розділ
Розміри матриць : A[70 * 192] та В[192 * 149]
Розміри підматриць:
A1 – A2 : [8* 192] A3 – A8 : [9* 192]
B1 – B3 : [192 * 18]; B4 – В8 : [192 * 19]
позначимо N1M = max(8, 9) = 9; N3M = max(18, 19) = 19.
Виразимо часові параметри :
ts – час виконання однієї операції сумування;
– час виконання однієї операції множення;
– час виконання однієї операції пересилання даних між процесорами;
– час виконання операції завантаження одних даних;
– час виконання операції вивантаження одних даних.
Час виконання виразу буде рівний
Оскільки за умовами в нас є спільна пам'ять, то звернення кожного процесору до пам'яті і завантаження даних буде виконуватися послідовно, тобто загальний час буде
Оскільки розміри під матриць Аі не є однаковими, так само як розміри під матриць Ві, тому час множення буде не однаковий і виникатимуть затримки під час обміну під матрицями Ві між процесорами. Тому при підрахунку загального часу виконання операцій множення та додавання будемо використовувати найбільші значення розмірів під матиць Аі та Ві.
Аналогічно максимальне значення другого виміру матриці Ві будемо використовувати для обчислення загального часу обміну даними між процесорами в тому числі і збору результату в першому процесорі.
Оскільки одночасно процесор може пересилати або приймати дані, тому час обміну даними між процесорами буде проводитися в два етапи.
Обчислюємо загальний час виконання множення матриць на даному паралельному алгоритмі:
Для обчислення ефективності даного алгоритму, потрібно визначити час виконання множення даних матриць на одному процесорі. В цьому випадку, час завантаження та вивантаження даних не зміниться, пересилання і збір проводити не потрібно, тому даний час не враховується. Час виконання операцій множення та сумування обчислюється відповідно N1 * N2 * N3 * tU та N1 * (N2-1) * N3 * tS.
Ефективність визначається як відношення часу виконання алгоритму на однопроцесорній системі, до часу потрібного для виконання на багатопроцесорної системі, помноженого на кількість процесорів в ній.
5. Розробка програми
Програма, що реалізує поставлену в курсовій роботі задачу, написана на мові програмування С++ із використанням технології MPI. Базується на алгоритмі, блок-схема якого наведена на рис. 6.
Рис. 4.2. Загальна граф-схема програми
Для обміну інформацією між процесами використовуються функції бібліотеки MPI 2.0 MPI_Send() та MPI_Recv(). Це парні функції, які призначені відповідно для відправки та прийому повідомлень.
Висновок.
Під час виконання курсового проекту я оволодів технологією написання програм для багатопроцесорних систем. Розробив восьми процесорну систему множення двох матриць в якій дані зчитуються з розподіленої пам`яті. Також було проведено деякі обчислення, які дають уявлення про характеристики даного паралельного алгоритму у порівняні з послідовним. В результаті послідовний алгоритм працює в 6,14 рази повільніший за паралельний. Ефективність при цьому рівна E = 76% .
СПИСОК ЛІТЕРАТУРИ.
Корнеев В.В. Параллельные вычислительные системы. М.: Нолидж, 1999
Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. – СПб: БХВ-Петербург, 2002.
Ортега Дж. Введение в параллельные и векторные методы решения линейных систем. М.:Мир, 1991.
Программирование на параллельных вычислительных системах: Пер с англ./Под ред. Р.Бэбба.М.:Мир, 1991.
Бройнль Т. Паралельне програмування: Початковий курс: Навчальний посібник. – К.:Вища школа.,1997.
Воеводин В.В. Математические основы параллельных вычислений.- М.: Изд-во МГУ, 1991.
Векторизация программ: теория, методы, реализация: Пер. с англ. и нем. /Под ред. Г.Д.Чинина. - М:. Мир, 1991.
Організація паралельних обчислень : Навчальний посібник з дисципліни “Паралельні та розподілені обчислення” для студентів базового напрямку 6.0915 "Комп'ютерна інженерія" / Укладачі: Є. Ваврук, О. Лашко – Львів: Національний університет “Львівська політехніка”, 2007, 70 с.
http://www.parallel.ru – Інформаційно-аналітичний центр з паралельних обчислень.
http://www.csa.ru – Інститут високопродуктивних обчислень і баз даних.
http://www.hpc.nw.ru – Високопродуктивні обчислення.
Додаток.
Лістинг програми.
#include <stdio.h>
#include "mpi.h"
#define NRA 70
#define NCA 192
#define NCB 149
#define MASTER 0
#define FROM_MASTER 1
#define FROM_WORKER 2
MPI_Status status;
double a[NRA][NCA],
b[NCA][NCB],
c[NRA][NCB];
int main(int argc, char **argv)
{
int numtasks,
taskid,
numworkers,
source,
dest,
nbytes,
mtype,
intsize,
dbsize,
rows,
averow, extra, offset,
i, j, k,
count;
intsize = sizeof(int);
dbsize = sizeof(double);
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &taskid);
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
numworkers = numtasks-1;
if (taskid == MASTER) {
printf("Number of worker tasks = %d\n",numworkers);
for (i=0; i<NRA; i++)
for (j=0; j<NCA; j++)
a[i][j]= i+j;
for (i=0; i<NCA; i++)
for (j=0; j<NCB; j++)
b[i][j]= i*j;
/* send matrix data to the worker tasks */
averow = NRA/numworkers;
extra = NRA%numworkers;
offset = 0;
mtype = FROM_MASTER;
for (dest=1; dest<=numworkers; dest++) {
rows = (dest <= extra) ? averow+1 : averow;
MPI_Send(&offset, 1, MPI_INT, dest, mtype, MPI_COMM_WORLD);
MPI_Send(&rows, 1, MPI_INT, dest, mtype, MPI_COMM_WORLD);
count = rows*NCA;
MPI_Send(&a[offset][0], count, MPI_DOUBLE, dest, mtype, MPI_COMM_WORLD);
count = NCA*NCB;
MPI_Send(&b, count, MPI_DOUBLE, dest, mtype, MPI_COMM_WORLD);
offset = offset + rows;
}
/* wait for results from all worker tasks */
mtype = FROM_WORKER;
for (i=1; i<=numworkers; i++) {
source = i;
MPI_Recv(&offset, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status);
MPI_Recv(&rows, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status);
count = rows*NCB;
MPI_Recv(&c[offset][0], count, MPI_DOUBLE, source, mtype, MPI_COMM_WORLD,
&status);
}
//#ifdef PRINT
printf("Here is the result matrix\n");
for (i=0; i<NRA; i++) {
printf("\n");
for (j=0; j<NCB; j++)
printf("%6.2f ", c[i][j]);
}
printf ("\n");
} /* end of master section */
/*---------------------------- worker (slave)----------------------------*/
if (taskid > MASTER) {
mtype = FROM_MASTER;
source = MASTER;
#ifdef PRINT
printf ("Master =%d, mtype=%d\n", source, mtype);
#endif
MPI_Recv(&offset, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status);
#ifdef PRINT
printf ("offset =%d\n", offset);
#endif
MPI_Recv(&rows, 1, MPI_INT, source, mtype, MPI_COMM_WORLD, &status);
#ifdef PRINT
printf ("row =%d\n", rows);
#endif
count = rows*NCA;
MPI_Recv(&a, count, MPI_DOUBLE, source, mtype, MPI_COMM_WORLD, &status);
#ifdef PRINT
printf ("a[0][0] =%e\n", a[0][0]);
#endif
count = NCA*NCB;
MPI_Recv(&b, count, MPI_DOUBLE, source, mtype, MPI_COMM_WORLD, &status);
#ifdef PRINT
printf ("b=\n");
#endif
for (k=0; k<NCB; k++)
for (i=0; i<rows; i++) {
c[i][k] = 0.0;
for (j=0; j<NCA; j++)
c[i][k] = c[i][k] + a[i][j] * b[j][k];
}
#ifdef PRINT
printf ("after computer\n");
#endif
MPI_Send(&offset, 1, MPI_INT, MASTER, FROM_WORKER, MPI_COMM_WORLD);
MPI_Send(&rows, 1, MPI_INT, MASTER, FROM_WORKER, MPI_COMM_WORLD);
MPI_Send(&c, rows*NCB, MPI_DOUBLE, MASTER, FROM_WORKER, MPI_COMM_WORLD);
#ifdef PRINT
printf ("after send\n");
#endif
}
MPI_Finalize();
}