comm — комунікатор.
Обмін із синхронізацією
Синхронізація за допомогою "бар'єра" є найпростішою формою синхронізації колективних обмінів. Вона не вимагає пересилання даних. Підпрограма MPI_Barrier блокуючий виконання кожного процесу з комунікатора comm доти, поки всі процеси не викликають цю підпрограму: int MPI_Barrier(MPI_Comm comm)
Розподіл і збір даних
Розподіл і збір даних виконуються за допомогою підпрограм MPI_Scatter і MPI_Gather відповідно. Список аргументів в обох підпрограм однаковий, але діють вони по-різному.
Схеми передачі даних для операцій збору і розподілу даних приведені на малюнках.
Повний список підпрограм розподілу і збору даний приведений у таблиці:
Підпрограма | Короткий опис |
MPI_Allgather | Збирає дані від усіх процесів і пересилає їх усім процесам |
MPI_Allgatherv | Збирає дані від усіх процесів і пересилає їх усім процесам ("векторний" варіант підпрограмиMPI_Allgather) |
MPI_Allreduce | Збирає дані від усіх процесів, виконує операцію приведення, і результат розподіляє всім процесам |
MPI_Alltoall | Пересилає дані від усіх процесів усім процесам |
MPI_Alltoallv | Пересилає дані від усіх процесів усім процесам ("векторний" варіант підпрограми MPI_Alltoall) |
MPI_Gather | Збирає дані від групи процесів |
MPI_Gatherv | Збирає дані від групи процесів ("векторний" варіант підпрограми MPI Gather) |
MPI_Reduce | Виконує операцію приведення, тобто обчислення єдинного значення по масиву вихідних даних |
MPI_Reduce_scatter | Збір значень з наступним розподілом результата операції приведення |
MPI_Scan | Виконання операції сканування (часткова редукція) для даних від групи процесів |
MPI_Scatter | Розподіляє дані від одного процесу всім іншим процесам у групі |
MPI_Scatterv | Пересилає буфер вроздріб усім процесам у групі ("векторний" варіант підпрограми MPI_Scatter) |
При широкомовному розсиланні всім процесам передається той самий набір даних, а при розподілі передаються його частини. Виконує розподіл даних підпрограмою MPI_Scatter, що пересилає дані від одного процесу всім іншим процесам у групі так, як це показано на малюнку.
int MPI_Scatter(void *sendbuf, int sendcount, MPI_Datatype sendtype, void *rcvbuf, int rcvcount, MPI_Datatype rcvtype, int root, MPI_Comm comm)
Її вхідні параметри (параметри підрограми MPI_Gather такі ж):
sendbuf – адреса буфера передачі;
sendcount – кількість елементів, що пересилаються кожному процесу (але не сумарна кількість елементів, що пересилаються,);
sendtype – тип переданих даних;
rcvcount – кількість елементів у буфері прийому;
rcvtype – тип прийнятих даних;
root – ранг передавального процесу;
comm – комунікатор.
Вихідний параметр rcvbuf – адреса буфера прийому. Працює ця підпрограма в такий спосіб. Процес з рангом root ("головний процес") розподіляє вміст буфера передачі sendbuf серед усіх процесів. Уміст буфера передачі розбивається на кілька фрагментів, кожний з який містить sendcount елементів. Перший фрагмент передається процесу 0, другий процесу 1 і т.д. Аргументи send мають значення тільки на стороні процесу root.
При зборці (MPI_Gather) кожен процес у комунікаторі comm пересилає вміст буфера передачі sendbuf процесу з рангом root. Процес root "склеює" отримані дані в буфері прийому. Порядок склейки визначається рангами процесів, тобто в результуючому наборі після даних від процесу 0 випливають дані від процесу 1, потім дані від процесу 2 і т.д. Аргументи rcvbuf, rcvcount і rcvtype відіграють роль тільки на стороні головного процесу. Аргумент rcvcount указує кількість елементів даних, отриманих від кожного процесу (але не їхня сумарна кількість). При виклику підпрограм MPI_scatter і MPI_Gather з різних процесів варто використовувати загальний головний процес.
Операції приведення і сканування
Операції приведення і сканування відносяться до категорії глобальних обчислень. У глобальній операції приведення до даних від усіх процесів із заданого комунікатора застосовується операція MPI_Reduce (див рис).
Аргументом операції приведення є масив даних — по одному елементі від кожного процесу. Результат такої операції — єдине значення (тому вона і називається операцією приведення).
У підпрограмах глобальних обчислень функція, передана в підпрограму, може бути: визначеною функцією MPI, наприклад MPI_SUM, користувальницькою функцією, а також оброблювачем для користувальницької функції, що створюється підпрограмою MPI_Op_create.
Три версії операції приведення повертають результат:
одному процесу;
усім процесам;
розподіляють вектор результатів між усіма процесами.
Операція приведення, результат якої передається одному процесу, виконується при виклику підпрограми MPI_Reduce:
int MPI_Reduce(void *buf, void *result, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
Вхідні параметри підпрограми MPI_Reduce:
buf — адреса буфера передачі;
count — кількість елементів у буфері передачі;
datatype — тип даних у буфері передачі;
ор — операція приведення;
root — ранг головного процесу;
comm — комунікатор.
Підпрограма MPI_Reduce застосовує операцію приведення до операндам з buf, а результат кожної операції міститься в буфер результату result. MPI_Reduce повинна викликатися всіма процесами в комунікаторі comm, a аргументи count, datatype і op у цих викликах повинні збігатися. Функція приведення (ор) не повертає код помилки, тому при виникненні аварійної ситуації або завершується робота всієї програми, або помилка мовчазно ігнорується. І те й інше в однаковій мірі небажано.
У MPI мається 12 визначених операцій приведення (див. табл.).
Операція | Опис |
MPI_MAX | Визначення максимальних значень елементів одномірних масивів цілого чи речовинного типу |
MPI_MIN | Визначення мінімальних значень елементів одномірних масивів цілого чи речовинного типу |
MPI_SUM | Обчислення суми елементів одномірних масивів цілого, речовинного чи комплексного типу |
MPI_PROD | Обчислення заелементного добутку одномірних масивів цілого, речовинного чи комплексного типу |
MPI_LAND | Логічне "И" |
MPI_BAND | Бітове "И" |
MPI_LOR | Логічне "ЧИ" |
MPI_BOR | Бітове "ЧИ" |
MPI_LXOR | Логічне "ЧИ", що виключає |
MPI_BXOR | Бітове "ЧИ", що виключає |
MPI_MAXLOC | Максимальні значення елементів одномірних масивів і їхні індекси |
MPI_MINLOC | Мінімальні значення елементів одномірних масивів і їхні індекси |
Розглянемо приклад 3:
===== Example2.cpp =====
#include <mpi.h>
#include <stdio.h>
#include <math.h>
int main(int argc, char *argv[])
{
int n, myid, numprocs, i;
double PI25DT = 3.141592653589793238462643;
double mypi, pi, h, sum, x;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
while (1) {
if (myid == 0) {
printf("Enter the number of intervals: (0 quits) ");
scanf("%d",&n);
}
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
if (n == 0)
break;
else {
h = 1.0 / (double) n;
sum = 0.0;
for (i = myid + 1; i <= n; i += numprocs) { //розподіл навантаження
x = h * ((double)i - 0.5);
sum += (4.0 / (1.0 + x*x));
}
mypi = h * sum;
MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
//зборка результату
if (myid == 0)
printf("pi is approximately %.16f, Error is %.16f\n",
pi, fabs(pi - PI25DT));
}
MPI_Finalize();
return 0;
}
===== Example2.cpp =====
Ця програма обчислює число π методом підсумовування ряду. Спочатку один із процесів (0) запитує число інтервалів, що потім поширює іншим процедурою MPI_Bcast. Помітьте, що процедура MPI_Bcast для процесу 0 є передавальної, а для всіх інших – приймаючої. Кінцеві результати обчислень здаються процесу 0 для підсумовування: процедура
MPI_Reduce(&mypi,&pi,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD)
збирає з усіх процесів перемінну mypi, підсумовує (MPI_SUM), і зберігає результат у змінної pi процесу 0.
Вивід приклада: Example3 output (np = 6)
Process 5 on apc-pc.
Process 3 on apc-pc.
Process 0 on apc-pc.
Enter the number of intervals: (0 quits) Process 1 on apc-pc.
Process 2 on apc-pc.
Process 4 on apc-pc.
15
pi is approximately 3.1419630237914191, Error is 0.0003703702016260
wall clock time = 0.031237
Enter the number of intervals: (0 quits) 2
pi is approximately 3.1623529411764704, Error is 0.0207602875866773
wall clock time = 0.000943
Enter the number of intervals: (0 quits) 0
Завдання 1: поясніть вивід;)
Приклад 4 показує створення комплексної системи керування процесами на прикладі розподіленого дешифратора паролів. Використовується структура master-slave (головн-підлеглий).
===== Example4.cpp =====
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TAG_READY 99
#define TAG_RESULT 98
int do_decrypt_pass(char* incoming_pass_str, char * result_pass_str, int length)
{
if (length % 2 == 0) return 1;
else return 0;
}
int main(int argc, char* argv[])
{
int k,x;
char in_line[256],acc_name[256],acc_pass[256];
int myrank, size;
MPI_Status status;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&size);
MPI_Comm_rank(MPI_COMM_WORLD,&myrank);
if (myrank == 0) // kind'a Master Process
{
puts("Initializing"); fflush(stdout);
FILE* in_file = fopen("pass.txt","r");
for (x=1;x<size;x++) MPI_Recv (&k, 1, MPI_INT, x, TAG_READY, MPI_COMM_WORLD, &status);
char* p;
puts ("Feeding"); fflush(stdout);
sprintf(in_line,"apc::1234");
if (p = strtok(in_line,"::")) sprintf(acc_name,"%s",p); else return 0;
if (p = strtok(NULL,"::")) sprintf(acc_pass,"%s",p); else return 0;
int acc_name_len = strlen(acc_name)+1, acc_pass_len = strlen(acc_pass)+1;
MPI_Bcast(&acc_name_len, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(&acc_pass_len, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(&acc_name, acc_name_len, MPI_CHAR, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(&acc_pass, acc_pass_len, MPI_CHAR, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);
for (x=1;x<size;x++)
{
MPI_Probe(MPI_ANY_SOURCE, TAG_RESULT, MPI_COMM_WORLD, &status);
int src = status.MPI_SOURCE; int res;
MPI_Recv(&res, 1, MPI_INT, src, TAG_RESULT, MPI_COMM_WORLD, &status);
printf("Proc %d returned %d\n",src,res);fflush(stdout);
}
}
else
{
MPI_Ssend(&myrank, 1, MPI_INT, 0, TAG_READY, MPI_COMM_WORLD);
int acc_name_len, acc_pass_len;
MPI_Bcast(&acc_name_len, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);
MPI_Bcast(&acc_pass_len, 1, MPI_INT, 0, MPI_COMM_WORLD); MPI_Barrier(MPI_COMM_WORLD);