МПП_Примеры2
.docПередача и прием сообщений процессами – это базовый коммуни-
кационный механизм MPI. Их использование иллюстрируем примерами:
#include "mpi.h"
main( int argc, char **argv)
{ char message[20];
int myrank;
MPI_Status status;
MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &myrank );
if (myrank == 0) /* код для процесса 0 */
{ strcpy(message,"Hello, there");
MPI_Send(message,strlen(message),MPI_CHAR,1,99,
MPI_COMM_WORLD);
}
else /* код для процесса 1 */
{ MPI_Recv(message, 20, MPI_CHAR, 0, 99, MPI_COMM_WORLD,
&status);
printf("received :%s:\n", message);
}
MPI_Finalize();
}
В этом примере процесс с номером 0 (myrank = 0) посылает со-
общение процессу с номером 1, используя операцию посылки
MPI_Send. Эта операция описывает буфер посылающего процесса, из
которого извлекаются посылаемые данные. В приведенном примере
посылающий буфер состоит из накопителя в памяти процесса 0, со-
держащего переменную message. Размещение, размер и тип буфера
посылающего процесса описываются первыми тремя параметрами
операции send. Посланное сообщение будет содержать 13 символов
этой переменной. Операция посылки также связывает с сообщением
его атрибуты. Атрибуты определяют номер процесса-получателя со-
общения и содержат различную информацию, которая может быть
использована операцией receive, чтобы выбрать определенное сооб-
щение среди других. Последние три параметра операции посылки
описывают атрибуты посланного сообщения. Процесс 1 (myrank = 1)
получает это сообщение, используя операцию приема MPI_Recv, и
данные сообщения записываются в буфер процесса-получателя. В
приведенном примере буфер получателя состоит из накопителя в па-
мяти процесса один, содержащего строку message. Первые три пара-
метра операции приема описывают размещение, размер и тип буфера
приема. Следующие три параметра необходимы для выбора входного
сообщения. Последний параметр необходим для возврата информации
о только что полученном сообщении.
Пример 4.2. Сбор 100 целых чисел с каждого процесса группы в кор-
невой процесс (рис. 4.2).
MPI_Comm comm;
int gsize,sendarray[100];
int root, *rbuf;
MPI_Comm_size( comm, &gsize);
rbuf = (int *)malloc(gsize*100*sizeof(int));
MPI_Gather(sendarray,100, MPI_INT, rbuf,100,MPI_INT,root, comm);
Пример 4.3 Предыдущий пример модифицирован – только корневой
процесс назначает память для буфера приема.
MPI_Comm comm;
int gsize,sendarray[100];
int root, myrank, *rbuf;
MPI_Comm_rank( comm, myrank);
if ( myrank == root)
{
MPI_Comm_size( comm, &gsize);
rbuf = (int *)malloc(gsize*100*sizeof(int));
}
MPI_Gather(sendarray,100,MPI_INT, rbuf,100, MPI_INT, root,comm);
Пример 4.5. Каждый процесс посылает 100 чисел int корневому про-
цессу, но каждое множество (100 элементов) размещается с некото-
рым шагом (stride) относительно конца размещения предыдущего
множества. Для этого нужно использовать MPI_GATHERV и аргу-
мент displs. Полагаем, что stride ≥ 100
MPI_Comm comm;
int gsize,sendarray[100], root, *rbuf, stride, *displs,i,*rcounts;
MPI_Comm_size( comm, &gsize);
rbuf = (int *)malloc(gsize*stride*sizeof(int));
displs = (int *)malloc(gsize*sizeof(int));
rcounts = (int *)malloc(gsize*sizeof(int));
for (i=0; i<gsize; ++i) {
displs[i] = i*stride;
rcounts[i] = 100;
}
MPI_Gatherv( sendarray, 100, MPI_INT, rbuf, rcounts, displs,
MPI_INT, root, comm);
Программа неверна, если stride < 100.
Пример 4.6. Со стороны процесса-получателя пример такой же, как и
4.5, но посылается 100 чисел типа int из 0-го столбца массива 100×150
чисел типа int
MPI_Comm comm;
int gsize,sendarray[100][150], root, *rbuf, stride, *displs,i,*rcounts;
MPI_Datatype stype;
MPI_Comm_size( comm, &gsize);
rbuf = (int *)malloc(gsize*stride*sizeof(int));
displs = (int *)malloc(gsize*sizeof(int));
rcounts = (int *)malloc(gsize*sizeof(int));
for (i=0; i<gsize; ++i) {
displs[i] = i*stride;
rcounts[i] = 100; }
/* Create datatype for 1 column of array */
MPI_Type_vector( 100, 1, 150, MPI_INT, &stype);
MPI_Type_commit( &stype );
MPI_Gatherv( sendarray, 1, stype, rbuf, rcounts, displs, MPI_INT, root, comm);
Пример 4.11. Обратен примеру 4.2, MPI_SCATTER рассылает 100
чисел из корневого процесса каждому процессу в группе (рис. 4.7).
MPI_Comm comm;
int gsize,*sendbuf;
int root, rbuf[100];
MPI_Comm_size( comm, &gsize);
sendbuf = (int *)malloc(gsize*100*sizeof(int));
MPI_Scatter( sendbuf, 100, MPI_INT, rbuf, 100, MPI_INT, root, comm);
Пример 4.14. Сбор 100 чисел типа int от каждого процесса в группе
для каждого процесса.
MPI_Comm comm;
int gsize,sendarray[100], *rbuf;
MPI_Comm_size( comm, &gsize);
rbuf = (int *)malloc(gsize*100*sizeof(int));
MPI_Allgather( sendarray, 100, MPI_INT, rbuf, 100, MPI_INT, comm);
После исполнения вызова каждый процесс содержит конкатена-
цию данных всей группы.
Редукция
Процедура вычисляет скалярное произведение двух
векторов, распределенных в группе процессов, и возвращает результат
в нулевой узел.
SUBROUTINE PAR_BLAS1(m, a, b, c, comm)
REAL a(m), b(m) ! локальная часть массива
REAL c ! результат (на узле ноль)
REAL sum
INTEGER m, comm, i, ierr
! локальная сумма
sum = 0.0
DO i = 1, m
sum = sum + a(i)*b(i)
END DO
! глобальная сумма
CALL MPI_REDUCE(sum, c, 1, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN
Пример 4.16 Процедура вычисляет произведение вектора на массив,
которые распределены в группе процессов, и возвращает результат в
нулевой узел.
SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
REAL a(m), b(m,n) ! локальная часть массива
REAL c(n) ! результат
REAL sum(n)
INTEGER n, comm, i, j, ierr
! локальная сумма
DO j= 1, n
sum(j) = 0.0
DO i = 1, m
sum(j) = sum(j) + a(i)*b(i,j)
END DO
END DO
! глобальная сумма
CALL MPI_REDUCE(sum, c, n, MPI_REAL, MPI_SUM, 0, comm, ierr)
RETURN
Пример 4.17. Каждый процесс имеет массив 30 чисел типа double.
Для каждой из 30 областей надо вычислить значение и номер процесса, содержащего наибольшее значение.
/* каждый процесс имеет массив из чисел двойной точности: ain[30]*/
double ain[30], aout[30];
int ind[30];
struct {
double val;
int rank;
} in[30], out[30];
int i, myrank, root;
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
for (i=0; i<30; ++i) {
in[i].val = ain[i];
in[i].rank = myrank;
}
MPI_Reduce(in,out,30,MPI_DOUBLE_INT,MPI_MAXLOC,root,comm );
/* в этой точке результат помещается на корневой процесс */
if (myrank == root) { /* читаются выходные номера */
for (i=0; i<30; ++i) {
aout[i] = out[i].val;
ind[i] = out[i].rank; /* номер обратно преобразуется в целое */
}
Пример 4.18. Каждый процесс имеет не пустой массив чисел. Требу-
ется найти минимальное глобальное число, номер процесса, храняще-
го его, его индекс в этом процессе.
#define LEN 1000
float val[LEN]; /* локальный массив значений */
int count; /* локальное количество значений */
int myrank, minrank, minindex;
float minval;
struct {
float value;
int index;
} in, out;
/* локальный minloc */
in.value = val[0];
in.index = 0;
for (i=1; i < count; i++)
if (in.value > val[i])
{ in.value = val[i];
in.index = i;
}
/* глобальный minloc */
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
in.index = myrank*LEN + in.index;
MPI_Reduce(in, out, 1, MPI_FLOAT_INT, MPI_MINLOC, root, comm );
/* в этой точке результат помещается на корневой процесс */
if (myrank == root)
{ minval = out.value;
minrank = out.index / LEN;
minindex = out.index % LEN;
}
Пример 4.19. Процедура вычисляет произведение вектора и массива,
которые распределены по всем процессам группы, и возвращает ответ
всем узлам.
SUBROUTINE PAR_BLAS2(m, n, a, b, c, comm)
! локальная часть массива
REAL a(m), b(m,n)
! результат
REAL c(n)
REAL sum(n)
INTEGER n, comm, i, j, ierr
! локальная сумма
DO j= 1, n
sum(j) = 0.0
DO i = 1, m
sum(j) = sum(j) + a(i)*b(i,j)
END DO
END DO
! глобальная сумма
CALL MPI_ALLREDUCE(sum,c,n,MPI_REAL,MPI_SUM,comm,ierr)
! возвращение результата всем узлам
RETURN