| | | |


Источник: http://www.csa.ru/~il/mpi_tutor

1. Пример написания программы с использованием MPI

/*
* ex0.c -- пример 0
*
* Начало и завершение:
* MPI_Init, MPI_Finalize
* Определение количества задач в приложениии и своего порядкового номера:
* MPI_Comm_size, MPI_Comm_rank
*/

#include <mpi.h>
#include <stdio.h>

int main( int argc, char **argv )
{
int size, rank, i;

/* Инициализируем библиотеку */
MPI_Init( &argc, &argv );

/* Узнаем количество задач в запущенном приложении */
MPI_Comm_size( MPI_COMM_WORLD, &size );

/* ... и свой собственный номер: от 0 до (size-1) */
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* задача с номером 0 сообщает пользователю размер группы,
* к которой прикреплена область связи,
* к которой прикреплен описатель (коммуникатор) MPI_COMM_WORLD,
* т.е. число процессов в приложении!!
*/
if( rank==0 )
printf("Total processes count = %d\n", size );

/* Каждая задача выводит пользователю свой номер */
printf("Hello! My rank in MPI_COMM_WORLD = %d\n", rank );

/* Точка синхронизации, затем задача 0 печатает
* аргументы командной строки. В командной строке
* могут быть параметры, добавляемые загрузчиком MPIRUN.
*/
MPI_Barrier( MPI_COMM_WORLD );
if( rank == 0 )
for( puts("Command line of process 0:"), i=0; i<argc; i++ )
printf( "%d: \"%s\"\n", i, argv[i] );

/* Все задачи завершают выполнение */
MPI_Finalize();
return 0;
}

* ex1.c -- пример 1 *
* Простейшая приемопередача:
* MPI_Send, MPI_Recv
* Завершение по ошибке:
* MPI_Abort
*/

#include <mpi.h>
#include <stdio.h>
/* Идентификаторы сообщений */
#define tagFloatData 1
#define tagDoubleData 2

/* Этот макрос введен для удобства, */
/* он позволяет указывать длину массива в количестве ячеек */
#define ELEMS(x) ( sizeof(x) / sizeof(x[0]) )

int main( int argc, char **argv )
{
int size, rank, count;
float floatData[10];
double doubleData[20];
MPI_Status status;

/* Инициализируем библиотеку */
MPI_Init( &argc, &argv );

/* Узнаем количество задач в запущенном приложении */
MPI_Comm_size( MPI_COMM_WORLD, &size );

/* ... и свой собственный номер: от 0 до (size-1) */
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* пользователь должен запустить ровно две задачи, иначе ошибка */
if( size != 2 ) {

/* задача с номером 0 сообщает пользователю об ошибке */
if( rank==0 )
printf("Error: two processes required instead of %d, abort\n",
size );

/* Все задачи-абоненты области связи MPI_COMM_WORLD
* будут стоять, пока задача 0 не выведет сообщение.
*/
MPI_Barrier( MPI_COMM_WORLD );
/* Без точки синхронизации может оказаться, что одна из задач
* вызовет MPI_Abort раньше, чем успеет отработать printf()
* в задаче 0, MPI_Abort немедленно принудительно завершит
* все задачи и сообщение выведено не будет
*/
/* все задачи аварийно завершают работу */
MPI_Abort(
MPI_COMM_WORLD, /* Описатель области связи, на которую */
/* распространяется действие ошибки */
MPI_ERR_OTHER ); /* Целочисленный код ошибки */
return -1;
}

if( rank==0 ) {
/* Задача 0 что-то такое передает задаче 1 */

MPI_Send(
floatData, /* 1) адрес передаваемого массива */
5, /* 2) сколько: 5 ячеек, т.е. floatData[0]..floatData[4] */
MPI_FLOAT, /* 3) тип ячеек */
1, /* 4) кому: задаче 1 */
tagFloatData, /* 5) идентификатор сообщения */
MPI_COMM_WORLD ); /* 6) описатель области связи, через которую */
/* происходит передача */

/* и еще одна передача: данные другого типа */
MPI_Send( doubleData, 6, MPI_DOUBLE, 1, tagDoubleData, MPI_COMM_WORLD );

} else {
/* Задача 1 что-то такое принимает от задачи 0 */

/* дожидаемся сообщения и помещаем пришедшие данные в буфер */
MPI_Recv(
doubleData, /* 1) адрес массива, куда складывать принятое */
ELEMS( doubleData ), /* 2) фактическая длина приемного */
/* массива в числе ячеек */
MPI_DOUBLE, /* 3) сообщаем MPI, что пришедшее сообщение */
/* состоит из чисел типа 'double' */
0, /* 4) от кого: от задачи 0 */
tagDoubleData, /* 5) ожидаем сообщение с таким идентификатором */
MPI_COMM_WORLD, /* 6) описатель области связи, через которую */
/* ожидается приход сообщения */
&status ); /* 7) сюда будет записан статус завершения приема */

/* Вычисляем фактически принятое количество данных */
MPI_Get_count(
&status, /* статус завершения */
MPI_DOUBLE, /* сообщаем MPI, что пришедшее сообщение */
/* состоит из чисел типа 'double' */
&count ); /* сюда будет записан результат */

/* Выводим фактическую длину принятого на экран */
printf("Received %d elems\n", count );

/* Аналогично принимаем сообщение с данными типа float
* Обратите внимание: задача-приемник имеет возможность
* принимать сообщения не в том порядке, в котором они
* отправлялись, если эти сообщения имеют разные идентификаторы
*/
MPI_Recv( floatData, ELEMS( floatData ), MPI_FLOAT,
0, tagFloatData, MPI_COMM_WORLD, &status );
MPI_Get_count( &status, MPI_FLOAT, &count );
}

/* Обе задачи завершают выполнение */
MPI_Finalize();
return 0;
}

* ex2.c -- пример 2 *
* Прием сообщений неизвестной длины:
* MPI_Probe
*
* Прием сообщений от разных отправителей с разными идентификаторами
* ( с содержимым разных типов ) в произвольном порядке:
* MPI_ANY_SOURCE, MPI_ANY_TAG ( джокеры )
*/

#include <mpi.h>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Идентификаторы сообщений */
#define tagFloatData 1
#define tagLongData 2

/* Длина передаваемых сообщений может быть
* случайной от 1 до maxMessageElems
*/
#define maxMessageElems 100

int main( int argc, char **argv )
{
int size, rank, count, i, n, ok;
float *floatPtr;
int *longPtr;
char *typeName;
MPI_Status status;

/* Инициализация и сообщение об ошибке
* целиком перенесены из первого примера
*/
MPI_Init( &argc, &argv );
MPI_Comm_size( MPI_COMM_WORLD, &size );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* пользователь должен запустить ровно ТРИ задачи, иначе ошибка */
if( size != 3 ) {
if( rank==0 )
printf("Error: 3 processes required instead of %d\n", size );
MPI_Barrier( MPI_COMM_WORLD );
MPI_Abort( MPI_COMM_WORLD, MPI_ERR_OTHER );
return -1;
}

/* Каждая задача инициализирует генератор случайных чисел */
srand( ( rank + 1 ) * (unsigned )time(0) );

switch( rank ) {

case 0:
/* создаем сообщение случайной длины */
count = 1 + rand() % maxMessageElems;
floatPtr = malloc( count * sizeof(float) );
for( i=0; i<count; i++ )
floatPtr[i] = (float)i;

/* Посылаем сообщение в задачу 2 */
MPI_Send( floatPtr, count, MPI_FLOAT,
2, tagFloatData, MPI_COMM_WORLD );
printf("%d. Send %d float items to process 2\n",
rank, count );
break;

case 1:
/* создаем сообщение случайной длины */
count = 1 + rand() % maxMessageElems;
longPtr = malloc( count * sizeof(long) );
for( i=0; i<count; i++ )
longPtr[i] = i;

/* Посылаем сообщение в задачу 2 */
MPI_Send( longPtr, count, MPI_LONG,
2, tagLongData, MPI_COMM_WORLD );
printf("%d. Send %d long items to process 2\n",
rank, count );
break;

case 2:
/* задача 2 принимает сообщения неизвестной длины,
* используя MPI_Probe
*/
for( n=0; n<2; n++ ) /* Всего ожидаются два сообщения */
{
MPI_Probe(
MPI_ANY_SOURCE, /* Джокер: ждем от любой задачи */
MPI_ANY_TAG, /* Джокер: ждем с любым идентификатором */
MPI_COMM_WORLD,
&status );
/* MPI_Probe вернет управление, когда сообщение будет
* уже на приемной стороне в служебном буфере
*/

/* Проверяем идентификатор и размер пришедшего сообщения
*/
if( status.MPI_TAG == tagFloatData )
{
MPI_Get_count( &status, MPI_FLOAT, &count );

/* Принятое будет размещено в динамической памяти:
* заказываем в ней буфер соответствующей длины
*/
floatPtr = malloc( count * sizeof(float) );

MPI_Recv( floatPtr, count, MPI_FLOAT,
MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD,
&status );
/* MPI_Recv просто скопирует уже принятые данные
* из системного буфера в пользовательский
*/

/* Проверяем принятое */
for( ok=1, i=0; i<count; i++ )
if( floatPtr[i] != (float)i )
ok = 0;
typeName = "float";
}
else if( status.MPI_TAG == tagLongData )
{
/* Действия, аналогичные вышеописанным
*/
MPI_Get_count( &status, MPI_LONG, &count );
longPtr = malloc( count * sizeof(long) );

MPI_Recv( longPtr, count, MPI_LONG,
MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD,
&status );

for( ok=1, i=0; i<count; i++ )
if( longPtr[i] != i )
ok = 0;
typeName = "long";
}
/* Докладываем о завершении приема */
printf( "%d. %d %s items are received from %d : %s\n", rank,
count, typeName, status.MPI_SOURCE, ok ? "OK" : "FAILED" );

} /* for(n) */
break;

} /* switch(rank) */

/* Завершение работы */
MPI_Finalize();
return 0;
}

* ex3.c -- пример 3 *
* Функции общего назначения для работы с типами:
* MPI_Type_commit, MPI_Type_free, MPI_Type_extent
* Создание пользовательских типов данных:
* MPI_Type_vector, MPI_Type_hvector
*
* 1) Быстрая передача решетки:
* передаются только те ячейки, у которых обе координаты нечетные
* x.x.x
* ..... x - выделятся и передаются
* x.x.x
* ..... . - игнорируются
* x.x.x
*
* 2) Передача с транспонированием: столбцы становятся строками
*
* Совет: перед тем, как запускать этот пример,
* переключите терминал/сессию в режим с максимальным количеством строк.
*/
#include <mpi.h>
#include <stdio.h>
/* количество строк и столбцов в исходной матрице
*/
#define SIZE 7

/* только для первого примера:
* количество строк и столбцов в итоговой матрице
*/
#define halfSize (SIZE+1)/2

int main( int argc, char **argv )
{
int a[ SIZE ][ SIZE ], /* исходная матрица - для обоих примеров */
b[ halfSize ][ halfSize ], /* итоговая матрица для первого примера */
c[ SIZE ][ SIZE ]; /* итоговая матрица для второго примера */
int rank, i, j;
MPI_Aint typeExtent;

MPI_Datatype /* описатели пользовательских типов */
oneSlice, twoSlice, /* для первого примера */
oneRow, rowByRow; /* для второго примера */

MPI_Status status; /* нужна только для приема, не используется */

/*-----------------*
* Инициализация *
*-----------------*/

MPI_Init( &argc, &argv );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* Нам нужен только один процесс: он будет и передатчиком, и приемником.
* Остальные процессы завершаются немедленно.
*/
if( rank != 0 )
goto done;

/* Генерируем исходную матрицу и печатаем ее
*/
for( i=0; i<SIZE; i++ )
for( j=0; j<SIZE; j++ )
a[i][j] = 10 * (i+1) + (j+1);

puts("Sended:");
for( i=0; i<SIZE; i++ ) {
for( j=0; j<SIZE; j++ )
printf("%4d", a[i][j] );
puts("");
}
puts("");

/*------------------------------------------------*
* Первый пример: выделение и пересылка решетки *
*------------------------------------------------*/

/* Создаем тип из элементов вида 'x.'
* #1. Количество элементов в новом типе = половине числа столбцов
* #2. Каждый элемент содержит одно целое число
* #3. Начала элементов отстоят друг от друга через две ячейки типа int
* => Новый тип накрывает РОВНО ОДНУ строку матрицы шаблоном 'x.x.x.x.'
*/
MPI_Type_vector( halfSize, 1, 2, MPI_INT, &oneSlice );

/* Создаем тип из элементов вида 'x.x.x.x.........'
* #1. Количество элементов в новом типе = половине числа строк
* #2. Каждый элемент содержит одну ячейку типа 'oneSlice'
* #3. Начала элементов отстоят друг от друга через две строки
* => Новый тип накрывает матрицу решеткой с нечетными координатами
*/
MPI_Type_hvector(halfSize, 1, SIZE*2*sizeof(int), oneSlice, &twoSlice );

/* Регистрируем тип 'twoSlice' для использования.
* Поскольку тип 'oneSlice' используется только
* для порождения других типов, его регистрировать не надо.
*/
MPI_Type_commit( &twoSlice );

/* На этом подготовительная работа, выполняемая
* РОВНО ОДИН РАЗ, завершена.
*/

/* Посылаем сами себе.
* Отправляем одну переменную типа 'twoSlice',
* расположенную по адресу матрицы 'a'.
* Принимаем поток целых чисел и размещаем его по адресу матрицы 'b'
*/
MPI_Sendrecv( a, 1, twoSlice, rank, 0,
b, halfSize*halfSize, MPI_INT, rank, 0,
MPI_COMM_WORLD, &status );

/* Печать принятой подматрицы
*/
puts("Received sub-matrix:");
for( i=0; i<halfSize; i++ ) {
for( j=0; j<halfSize; j++ )
printf("%4d", b[i][j] );
puts("");
}
puts("");

/* Печатаем размер типа при хранении, и размер типа при передаче
*/
MPI_Type_extent( oneSlice, &typeExtent );
/* MPI_Type_size ( oneSlice, &typeSize ); */
printf("\'oneSlice\' has extent = %3d\n", typeExtent );
MPI_Type_extent( twoSlice, &typeExtent );
/* MPI_Type_size ( twoSlice, &typeSize ); */
printf("\'twoSlice\' has extent = %3d\n", typeExtent );

/* Очистка описателей. MPI удалит соотвествующие данные
* из внутренних таблиц и сбросит описатели в MPI_TYPE_NULL
*/
MPI_Type_free( &oneSlice );
MPI_Type_free( &twoSlice );

/*------------------------------------------------*
* Второй пример: пересылка с транспонированием *
*------------------------------------------------*/

/* В оперативной памяти матрица хранится построчно: строка1,строка2,...
* Создаем тип-шаблон для быстрого выделения из матрицы одного столбца:
* #1. Число элементов = количеству строк, т.е. высоте столбца
* #2. В каждой строке нас интересует одна ячейка
* #3. Элементы расположены в памяти с шагом, равным длине строки
*/
MPI_Type_vector( SIZE, 1, SIZE, MPI_INT, &oneRow );

/* Получаем тип для постолбцового представления матрицы:
* #1. Число элементов = количеству столбцов
* #2. Каждый элемент содержит один столбец, т.е. ячейку типа 'oneRow'
* #3. Начала элементов (т.е. столбцов) отстоят друг от друга
* на расстоянии в одну ячейку типа int.
*/
MPI_Type_hvector( SIZE, 1, sizeof(int), oneRow, &rowByRow );

MPI_Type_commit( &rowByRow );
/* Тип зарегистрирован,
* подготовительная работа завершена.
*/

/* Передаем с одновременным транспонированием.
* Передается переменная типа 'rowByRow', находящаяся
* по адресу матрицы 'a'. Данные уходят столбец за столбцом.
* Принимается поток целых чисел, размещаемый по адресу матрицы 'b'.
* Данные размещаются непрерывно, то есть построчно!
*/
MPI_Sendrecv( a, 1, rowByRow, rank, 0,
c, SIZE*SIZE, MPI_INT, rank, 0,
MPI_COMM_WORLD, &status );

/* Печать принятой матрицы
*/
puts("Received transposed matrix:");
for( i=0; i<SIZE; i++ ) {
for( j=0; j<SIZE; j++ )
printf("%4d", c[i][j] );
puts("");
}
puts("");

/* Печатаем размер типа при хранении и размер типа при передаче
*/
MPI_Type_extent( oneRow, &typeExtent );
/* MPI_Type_size ( oneRow, &typeSize ); */
printf("\'oneRow\' has extent = %3d\n", typeExtent );
MPI_Type_extent( rowByRow, &typeExtent );
/* MPI_Type_size ( rowByRow, &typeSize ); */
printf("\'rowByRow\' has extent = %3d\n", typeExtent );

/* Очистка описателей.
*/
MPI_Type_free( &oneRow );
MPI_Type_free( &rowByRow );

/*---------------------*
* Завершение работы *
*---------------------*/

done:
MPI_Finalize();
return 0;
}

* ex6.c -- пример 4 *
* Создание коммуникатора-дупликата, надежное разделение потоков сообщений:
* MPI_Comm_dup, MPI_Comm_free
*
* Измененный подход к обработке ошибок через
* MPI_Abort, MPI_Barrier
*/

#include <mpi.h>
#include <stdio.h>

#define tag1 1
#define tag2 2
#define tag3 1 /* сознательная "ошибка": идентификатор равен 'tag1' */

#define ELEMS(x) (sizeof( x )/sizeof( x[0] ))

int main( int argc, char **argv )
{
MPI_Comm myComm;
int rank, size;

MPI_Init( &argc, &argv );
MPI_Comm_size( MPI_COMM_WORLD, &size );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* Обработка ошибки "неверное количество запущенных задач" */
if( size != 2 && rank==0 ) {
printf( "ERROR: 2 processes required instead of %d, abort.\n", size );
MPI_Abort( MPI_COMM_WORLD, MPI_ERR_OTHER );
}
/* Барьер нужен, чтобы в случае ошибки, пока ветвь 0 рапортует
* о ней и вызывает MPI_Abort, остальные ветви не смогли приступить
* к выполнению содержательной части программы.
*/
MPI_Barrier( MPI_COMM_WORLD );

/** Общая часть закончена, начинается содержательная часть... **/

/* Создаем еще один коммуникатор - копию MPI_COMM_WORLD */
MPI_Comm_dup( MPI_COMM_WORLD, &myComm );

if( rank == 0 ) /* Ветвь 0 передает */
{
static char buf1[] = "Contents of first message";
static char buf2[] = "Contents of second message";
static char buf3[] = "Contents of third message";

/* Обратите внимание: хотя для сообщений выбран один и тот же
* идентификатор, описатели области связи разные
*/
MPI_Send( buf1, ELEMS(buf1), MPI_CHAR, 1, tag1, myComm );
MPI_Send( buf2, ELEMS(buf2), MPI_CHAR, 1, tag2, MPI_COMM_WORLD );
MPI_Send( buf3, ELEMS(buf3), MPI_CHAR, 1, tag3, MPI_COMM_WORLD );
}
else /* Ветвь 1 принимает */
{
char buf1[100], buf2[100], buf3[100];
MPI_Status st;

/* Вызов НЕ перехватит первое сообщение из-за того, что tag1=tag3 */
MPI_Recv( buf3, ELEMS(buf3), MPI_CHAR, 0, tag3, MPI_COMM_WORLD, &st );

/* Вызов НЕ перехватит первое сообщение джокером */
MPI_Recv( buf2, ELEMS(buf2), MPI_CHAR, 0, MPI_ANY_TAG,
MPI_COMM_WORLD, &st );

/* Первое сообщение будет успешно принято там, где надо */
MPI_Recv( buf1, ELEMS(buf1), MPI_CHAR, 0, tag1, myComm, &st );

/* Печатаем результаты приема */
printf("Received in buf1 = \'%s\'\n", buf1 );
printf("Received in buf2 = \'%s\'\n", buf2 );
printf("Received in buf3 = \'%s\'\n", buf3 );

} /* rank 1 */

/* Уведомляем MPI, что больше коммуникатором не пользуемся.
* После этого myComm будет сброшен в MPI_COMM_NULL (то есть в 0),
* а соответствующие ему данные будут помечены на удаление.
*/
MPI_Comm_free( &myComm );

MPI_Finalize();
}


* ex7.c -- пример 5 *
* Создание под-коммуникаторов. Неявное разбиение группы на подгруппы:
* MPI_Comm_split
*
* Рекомендация:
* запустите этот пример несколько раз с разным числом ветвей N ( -np N ),
* например: N = 6,7,8
*/

#include <mpi.h>
#include <stdio.h>

#define tag1 1
#define tag2 2
#define tag3 1 /* сознательная "ошибка": идентификатор равен 'tag1' */

#define ELEMS(x) (sizeof( x )/sizeof( x[0] ))

int main( int argc, char **argv )
{
MPI_Comm subComm;
int rank, size, subCommIndex, subRank, subSize;

MPI_Init( &argc, &argv );
MPI_Comm_size( MPI_COMM_WORLD, &size );
MPI_Comm_rank( MPI_COMM_WORLD, &rank );

/* Распределяем по следующему правилу:
* каждые три ветви - в подгруппу,
* каждую четвертую - "в никуда"
*/
subCommIndex = rank % 4;
if( subCommIndex == 3 )
subCommIndex = MPI_UNDEFINED;
/* Желательная нумерация внутри подгрупп:
* обратная той, что имеется в MPI_COMM_WORLD
*/
subRank = size - rank;

/* Вызываем во ВСЕХ ветвях-абонентах MPI_COMM_WORLD,
* указанного первым аргументом функции
*/
MPI_Comm_split( MPI_COMM_WORLD, subCommIndex, rank, &subComm );
/* Каждая ветвь пишет, к чему она теперь относится
*/
printf("My rank in MPI_COMM_WORLD = %d. ", rank);
if( subComm == MPI_COMM_NULL )
printf("I\'m not attached to any sub-communicator\n");
else {
MPI_Comm_size( subComm, &subSize );
MPI_Comm_rank( subComm, &subRank );
printf("My local rank = %d, local size = %d\n", subRank, subSize );
}
MPI_Comm_free( &subComm );
MPI_Finalize();
}


| | | |