Міністерство Освіти, Науки, Молоді та Спорту України
Національний університет «Львівська політехніка»
Кафедра ЕОМ
КУРСОВА РОБОТА
з дисципліни:
«Паралельні та розподілені обчислення»
на тему:
«Паралельне виконання операцій множення матриць»
Завдання
Розробити схему та описати процедуру перемноження матриці А (розмірністю N1*N2) на матрицю В (розмірністю N2*N3) на структурі з восьми процесорів. Для цієї структури визначити час виконання алгоритму, відсоток послідовної частини алгоритму та ефективність алгоритму.
N1 = 40
N2 = 192
N3 = 159
Таблиця 1. Часові параметри
Співвідношення часових параметрів
Пояснення
tu=9tz
час виконання однієї операції множення
tS=9/10tZ
час виконання однієї операції сумування
tP =9/10tZ
час виконання однієї операції пересилання даних між процесорами
tz
час виконання операції завантаження одних даних
tW=tZ
час виконання операції вивантаження одних даних
Таблиця 2. Кодування букв
К
Р
Ф
Д
Г
Е
Й
С
47
93
233
13
74
171
146
82
2F
5D
E9
D
4A
AB
92
52
Таблиця 3. Матриця суміжності
1
2
3
4
5
6
7
8
1
0
0
1
0
1
1
1
1
2
0
0
0
1
1
1
0
1
3
1
1
0
0
1
0
0
1
4
0
0
0
0
1
1
0
1
5
0
1
0
0
0
0
1
0
6
1
0
1
0
1
0
1
1
7
1
0
0
1
0
0
0
0
8
0
1
0
1
0
0
1
0
Type = (i)mod3 + 1
Z= 0909488.
Type =(0+9+0+9+4+8+8)mod3+1 = 3 Розподілена пам'ять.
Анотація
В даній курсовій роботі розроблено алгоритм паралельного перемноження матриць на структурі з восьми процесорів. Завантаження даних відбувається з спільної пам’яті. Вхідні матриці мають розмірності А(40*192) та В(192*159).
Робота складається з розрахунку часових характеристик алгоритму, розробки функціональної схеми алгоритму та програмної реалізації. Відповідно до часових затрат паралельного алгоритму визначено його ефективність відносно послідовного.
Програмно, алгоритм реалізований на C++ з консольним інтерфейсом, та з використанням MPI.
Зміст
Вступ. 6
1. Теоретичний розділ. 7
2. Розробка блок - схеми виконання алгоритму. 9
3. Розробка функціональної схеми. 12
4. Розрахунковий розділ 13
5. Розробка програми. 16
СПИСОК ЛІТЕРАТУРИ. 19
Висновок 20
Додаток. 21
Лістинг програми. 21
Вступ.
Паралельні обчислювальні системи - комп'ютерні системи, що реалізовують тим або іншим способом паралельну обробку даних на багатьох обчислювальних вузлах для підвищення загальної швидкості розрахунку. Ідея розпаралелення обчислень базується на тому, що більшість завдань можуть бути розділені на набір менших завдань, які можуть бути вирішені одночасно. Зазвичай паралельні обчислення вимагають координації дій.
Паралельні алгоритми досить важливі з огляду на постійне вдосконалення багатопроцесорних систем і збільшення числа ядер у сучасних процесорах. Зазвичай простіше сконструювати комп'ютер з одним швидким процесором, ніж з багатьма повільними з тією ж продуктивністю. Однак збільшення продуктивності за рахунок вдосконалення одного процесора має фізичні обмеження, такі як досягнення максимальної щільності елементів та тепловиділення. Зазначені обмеження можна подолати лише шляхом переходу до багатопроцесорної архітектури, що виявляється ефективним навіть у малих обчислювальних системах. Складність послідовних алгоритмів виявляється в обсязі використаної пам'яті та часу (число тактів процесора), необхідних для виконання алгоритму.
Розподілені обчислення - спосіб розв'язання трудомістких обчислювальних завдань з використанням двох і більше комп'ютерів, об'єднаних в мережу. Розподілені обчислення є окремим випадком паралельних обчислень, тобто одночасного розв'язання різних частин одного обчислювального завдання декількома процесорами одного або кількох комп'ютерів. Тому необхідно, щоб завдання, що розв'язується було сегментоване — розділене на підзадачі, що можуть обчислюватися паралельно. При цьому для розподілених обчислень доводиться також враховувати можливу відмінність в обчислювальних ресурсах, які будуть доступні для розрахунку різних підзадач. Проте, не кожне завдання можна «розпаралелити» і прискорити його розв'язання за допомогою розподілених обчислень.
Теоретичний розділ.
Паралельна система складається з певної кількості процесорів та модулів пам’яті. В даному випадку це структура з 8 процесорів та розподілена пам'ять.
Множення матриці на матрицю або матриці на вектор є базовими мікроопераціями різних задач. Для їх реалізації використовують різні алгоритми та різні структури. Для вирішення цієї задачі використовується алгоритм, при якому матриця А розбивається на 8 горизонтальних смуг, а матриця В – на 8 вертикальних, в такому разі матриця результату буде складатись з 8 горизонтальних смуг (рис.1.1)
Рис. 1.1 Розбиття матриць
Кожний процесор зчитує з пам’яті відповідну підматрицю А та підматрицю В. Після того як процесор помножив під матрицю А на підматрицю В, він обмінюється з іншим процесором підматрицею В. Підматриця А завжди знаходиться у відповідному процесорі, а підматриці В рухаються по всіх процесорах. Отже кожен процесор повинен помножити відповідну підматрицю А на всі підматриці В. В результаті всіх множень у пам’яті буде результуюча матриця. Однак обмін підматрицями В між процесорами відбувається не в довільному порядку. Схема обміну відображена у графі (рис. 1.2).
Рис. 1.2 Граф обміну даними між процесорами.
Оскільки в нас розподілена пам'ять то завантаження вхідних та вивантаження вихідних даних буде виконуватись паралельно.
Розробка блок - схеми виконання алгоритму.
На рис. 2.1 наведена граф-схема виконання алгоритму множення двох матриць на кільцевій структурі з розподіленою пам’яттю в загальному випадку.
Рис. 2.1. Граф-схема виконання алгоритму множення двох матриць на кільцевій структурі з розподіленою пам’яттю.
Для матриць А[N1,N2] та B[N2,N3] і Р процесорів розміри підматриць Аі та Ві визначаються так.
Для А та для В обчислюємо
де С1А та С1В – ціла частина від ділення, С2А та С2В - залишки.
Перші (Р - С2А) підматриці матриці А матимуть кількість рядків С1А, решту – (С1А + 1) рядків. Для матриці В,
відповідно, (Р - С2В), С1В, (С1В + 1) стовпців.
На рисунку 2.2 наведено граф-схему покрокового виконання алгоритму.
На початку роботи кожен процесор паралельно з іншими зчитує дані із своєї розподіленої пам’яті.
Для кінцевого визначення результату необхідно виконати кількість циклів множення рівну кількості процесорів в системі. Тобто, кожен процесор в кільці зчитує з наступного по ходу нову підматрицю та її розміри. Після
завершення останнього циклу множення, яке проходить паралельно на кожному процесорі, починається заключна послідовна фаза обчислення добутку, а саме вивантаження часткових результатів(підматрицьСі).
Рис. 2.2. Граф-схема покрокового виконання алгоритму множення матриць на кільцевій структурі.
Оскільки стоїть задача розпаралелити виконання перемноження матриць, то доцільно використовувати обмін даними по кільцевій структурі, що забезпечує роботу з мінімальними затримками. Граф пересилання даних між процесорами на кільцевій структурі зображений на рис. 2.3.
Рис. 2.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-P6,отрим.В7 з P7
Перес.В2-P4,отрим.В8
З P8
Перес.В3-P8,отрим.В6
З P6
Перес.В4-P5,отрим.В2
З P2
Перес.В5-P7,отрим.В4
З P4
Перес.В6-P3,отрим.В1
З P1
Перес.В7-P1,отрим.В5
З P5
Перес.В8-P2,отрим.В3
З P3
Множення
А1хВ7
Множення
А2хВ8
Множення
А3хВ6
Множення
А4хВ2
Множення
А5хВ4
Множення
А6хВ1
Множення
А7хВ5
Множення
А8хВ3
Перес.В7-P6,отрим.В5 з P7
Перес.В8-P4,отрим.В3
З P8
Перес.В6-P8,отрим.В1
З P6
Перес.В2-P5,отрим.В8
З P2
Перес.В4-P7,отрим.В2
З P4
Перес.В1-P3,отрим.В7
З P1
Перес.В5-P1,отрим.В4
З P5
Перес.В3-P2,отрим.В6
З P3
Множення
А1хВ5
Множення
А2хВ3
Множення
А3хВ1
Множення
А4хВ8
Множення
А5хВ2
Множення
А6хВ7
Множення
А7хВ4
Множення
А8хВ6
Перес.В5-P6,отрим.В4 з P7
Перес.В3-P4,отрим.В6
З P8
Перес.В1-P8,отрим.В7
З P6
Перес.В8-P5,отрим.В3
З P2
Перес.В2-P7,отрим.В8
З P4
Перес.В7-P3,отрим.В5
З P1
Перес.В4-P1,отрим.В2
З P5
Перес.В6-P2,отрим.В1
З P3
Множення
А1хВ4
Множення
А2хВ6
Множення
А3хВ7
Множення
А4хВ3
Множення
А5хВ8
Множення
А6хВ5
Множення
А7хВ2
Множення
А8хВ1
Перес.В4-P6,отрим.В2 з P7
Перес.В6-P4,отрим.В1
З P8
Перес.В7-P8,отрим.В5
З P6
Перес.В3-P5,отрим.В6
З P2
Перес.В8-P7,отрим.В3
З P4
Перес.В5-P3,отрим.В4
З P1
Перес.В2-P1,отрим.В8
З P5
Перес.В1-P2,отрим.В7
З P3
Множення
А1хВ2
Множення
А2хВ1
Множення
А3хВ5
Множення
А4хВ6
Множення
А5хВ3
Множення
А6хВ4
Множення
А7хВ8
Множення
А8хВ7
Перес.В2-P6,отрим.В8 з P7
Перес.В1-P4,отрим.В7
З P8
Перес.В5-P8,отрим.В4
З P6
Перес.В6-P5,отрим.В1
З P2
Перес.В3-P7,отрим.В6
З P4
Перес.В4-P3,отрим.В2
З P1
Перес.В8-P1,отрим.В3
З P5
Перес.В7-P2,отрим.В5
З P3
Множення
А1хВ8
Множення
А2хВ7
Множення
А3хВ4
Множення
А4хВ1
Множення
А5хВ6
Множення
А6хВ2
Множення
А7хВ3
Множення
А8хВ5
Перес.В8-P6,отрим.В3 з P7
Перес.В7-P4,отрим.В5
З P8
Перес.В4-P8,отрим.В2
З P6
Перес.В1-P5,отрим.В7
З P2
Перес.В6-P7,отрим.В1
З P4
Перес.В2-P3,отрим.В8
З P1
Перес.В3-P1,отрим.В6
З P5
Перес.В5-P2,отрим.В4
З P3
Множення
А1хВ3
Множення
А2хВ5
Множення
А3хВ2
Множення
А4хВ7
Множення
А5хВ1
Множення
А6хВ8
Множення
А7хВ6
Множення
А8хВ4
Перес.В3-P6,отрим.В6 з P7
Перес.В5-P4,отрим.В4
З P8
Перес.В2-P8,отрим.В8
З P6
Перес.В7-P5,отрим.В5
З P2
Перес.В1-P7,отрим.В7
З P4
Перес.В8-P3,отрим.В3
З P1
Перес.В6-P1,отрим.В1
З P5
Перес.В4-P2,отрим.В2
З P3
Множення
А1хВ6
Множення
А2хВ4
Множення
А3хВ8
Множення
А4хВ5
Множення
А5хВ7
Множення
А6хВ3
Множення
А7хВ1
Множення
А8хВ2
Вив.С1
Перес. С2- Р4
Перес. С3- Р8
Перес. С4- Р5
Перес. С5- Р7
Перес. С6- Р3
Перес. С7- Р1
Перес. С8- Р2
Вив.С7
Перес. С8- Р4
Перес. С6- Р8
Перес. С2- Р5
Перес. С4- Р7
Перес. С5- Р1
Перес. С3- Р2
Вив.С5
Перес. С3- Р4
Перес. С8- Р5
Перес. С2- Р7
Перес. С4- Р1
Перес. С6- Р2
Вив.С4
Перес. С6- Р4
Перес. С3- Р5
Перес. С8- Р7
Перес. С2- Р1
Вив.С2
Перес. С6- Р5
Перес. С3- Р7
Перес. С8- Р1
Вив.С8
Перес. С6- Р7
Перес. С3- Р1
Вив.С3
Перес. С6- Р1
Вив.С6
Рис 3.1 Схема планування обчислень.
Розрахунковий розділ
Для процесорних елементів визначимо такі розміри підматриць:
A1-A8[5,192], B1[192, 19], B2-B8[192, 20].
Для визначення точних характеристик системи врахуємо співвідношення часових параметрів (згідно з завданням). Насамперед, зведемо часи виконання різних операцій до спільного знаменника, тобто визначимо базову операцію для знаходження часів виконання інших операцій.
Виразимо інші операції через найменший час Tz:
tu=9tz , tS=9/10tZ , tP =9/5tZ , tW=tZ.
Оскільки в нашому випадку матриці завантажуються з розподіленої памяті то кількість результуючих послідовних операцій завантаження буде рівна сумі елементів найбільших під матриць А та В.
Nzav = (n1*N2+N2*n3) = (5*192+192*20) =4800
Де n1- кількість рядків найбільшої з підматриць А, n3 – кількість стовпців найбільшої з підматриць В.
Отже в даному випадку загальний час завантаження:
Тzav = (n1*N2+N2*n3)*Tz = 4800Tz.
Для послідовного алгоритму час завантаження рівний:
Тzp = (N1*N2+N2*N3)*Tz = 38208 Tz.
Після операції завантаження даних в процесори запускається цикл обробки даних, який має 8 ітерацій, які включають в себе як множення підматриць, так і пересилання даних між процесорами (підматриці ).
Для послідовного алгоритму загальна кількість операцій множення та додавання та час на їх виконання:
Загальна кількість операцій множення та додавання на кільцевій структурі при кожній ітерації рівна:
,
.
(n1- кількість рядків найбільшої з підматриць А, n3 – кількість стовбців найбільшої з під матриць В)
Час виконання:
Сумарна кількість операцій множення та додавання при опрацюванні даних на кільцевій структурі:
, .
, .
Час їх виконання:
Кожний процесор може в один момент часу або тільки приймати або тільки передавати інформацію. Тому передача інформації буде відбуватися за два такти.
Тобто, кількість операцій обміну на кожній ітерації рівна подвійній кількості елементів найбільшої з підматриць :
.
Тоді загальна кількість операцій обміну буде рівна кількості таких операцій на семи ітераціях:
.
вивантаження включає в себе сім послідовних передач матриць результату то загальний час збору буде рівний:
Tzb = ((7*n1*N3))*Tp=7*5*159 *Tp = 5565Tp= 10017Tz.
Вивантаження даних відбувається послідовно кількість даних рівна величині результуючої матриці.
.
Для визначення повного часу необхідно визначити час всіх його складових, де операції виконуються послідовно (завантаження, вивантаження) та паралельно (обчислення, обмін даними).
Тоді загальна кількість операцій при виконанні паралельного алгоритму на кільцевій структурі:
Для порівняння обчислимо умовний час виконання послідовного алгоритму, який включає в себе час на завантаження та вивантаження даних та час множення та додавання при послідовному алгоритмі :
Порівнюючи остаточні результати бачимо, що перемноження матриць на одному процесорі приблизно в рази повільніше за множення на восьми процесорній системі на основі кільцевої топології.
Ефективність визначається як відношення часу виконання операцій на однопроцесорній системі, до часу потрібного на виконання для багатопроцесорної на кількість процесорів в ній.
Розробка програми.
Лістинг програми наведений у додатку А. Дана програма написана на мові C++. Програма має консольний інтерфейс.
Рис. 5.1. Вікно, після виконання програми.
Завдяки тому, що при написанні коду програми були використані засоби технології MPI, дана програма не просто імітує роботу паралельної системи, а дійсно є цілком працездатною і може бути запущеною на паралельній системі,
яка досліджується у цій курсовій роботі.
Загальна блок-схема роботи програми вказана на рисунку 5.2.
Рис. 5.2. Блок – схема роботи програми.
Розглянема синтаксис операцій обміну повідомлень які використовувалися в даній курсовій роботі:
MPI_Send
int MPI_Send(void *buf, int count, MPI_Datatype type, int dest, int tag, MPI_Comm comm), де
buf - адреса буфера пам'яті, в якому розташовуються дані повідомлення, що відправляється;
count - кількість елементів даних в повідомленні;
type - тип елементів даних повідомлення, що пересилається;
dest - ранг процесу, якому відправляється повідомлення;
tag - значення-тег, що використовується для ідентифікації повідомлення;
comm - комунікатор, в рамках якого виконується передача даних.
MPI_Recv
int MPI_Recv(void *buf, int count, MPI_Datatype type, int source,
int tag, MPI_Comm comm, MPI_Status *status), де
buf, count, type - буфер пам'яті для прийому повідомлення, призначення кожного окремого параметра відповідає опису в MPI_Send.
source - ранг процесу, від якого повинен бути виконаний прийом повідомлення.
tag - тег повідомлення, яке повинне бути прийняте для процесу.
comm - комунікатор, в рамках якого виконується передача даних.
status - вказівник на структуру даних з інформацією про результат виконання операції прийому даних.
MPI_Scaterv
int MPI_Scatterv(void* sendbuf, int *sendcounts, int *displs, MPI_Datatype sendtype, void* recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
sendbuf – адрес початку буфера передачі;
sendcounts – цілочисельний масив (розмір якого рівний кількості процесорів в групі) який вміщує кількість елементів які будуть посилатися кожному процесорові
displs – цілочисельний масив (розмір якого рівний кількості процесорів в групі) визначає зміщення від початку sendbuf для даних які посилаються кожному процесорові;
sendtype – тип даних що передається
recvbuf – адрес початку прийому
recvcount – кількість елементів що приймається
recvtype – тип елементів що приймається
root – номер процесора відправника
comm – комунікатор
СПИСОК ЛІТЕРАТУРИ.
Корнеев В.В. Параллельные вычислительные системы. М.: Нолидж, 1999
Воеводин В.В., Воеводин Вл.В. Параллельные вычисления. – СПб: БХВ-Петербург, 2002.
Ортега Дж. Введение в параллельные и векторные методы решения линейных систем. М.:Мир, 1991.
Программирование на параллельных вычислительных системах: Пер с англ./Под ред. Р.Бэбба.М.:Мир, 1991.
Бройнль Т. Паралельне програмування: Початковий курс: Навчальний посібник. – К.:Вища школа.,1997.
Воеводин В.В. Математические основы параллельных вычислений.- М.: Изд-во МГУ, 1991.
Векторизация программ: теория, методы, реализация: Пер. с англ. и нем. /Под ред. Г.Д.Чинина. - М:. Мир, 1991.
Організація паралельних обчислень : Навчальний посібник з дисципліни “Паралельні та розподілені обчислення” для студентів базового напрямку 6.0915 "Комп'ютерна інженерія" / Укладачі: Є. Ваврук, О. Лашко – Львів: Національний університет “Львівська політехніка”, 2007, 70 с.
http://www.parallel.ru – Інформаційно-аналітичний центр з паралельних обчислень.
http://www.csa.ru – Інститут високопродуктивних обчислень і баз даних.
http://www.hpc.nw.ru – Високопродуктивні обчислення.
Висновок
Під час виконання курсового проекту я оволодів технологією написання програм для багатопроцесорних систем. Розробив восьми процесорну систему множення двох матриць в якій дані зчитуються з розподіленої пам`яті. Також було проведено деякі обчислення, які дають уявлення про характеристики даного паралельного алгоритму у порівняні з послідовним. В результаті проведеної роботи можна зробити такі висновки: для восьми процесорної системи та матриць з розмірами 40х192 192х159 умовний час виконання послідовної програми в 7,26 рази довше. Ефективність при цьому рівна E = 92% .
Додаток.
Лістинг програми.
#include <stdio.h>
#include "mpi.h"
#define NRA 40
#define NCA 192
#define NCB 159
#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;
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;
}
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 */
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();
}