Вперед: 1.4.2.3. Совместное использование MPI и OpenMP
Назад: 1.4.2.1. Коммуникационный интерфейс PVM
К содержанию: Оглавление


1.4.2.2. Коммуникационный интерфейс MPI

MPI (Message Passing Interface) - это хорошо стандартизованный коммуникационный интерфейс для создания параллельных программ в модели передачи сообщений [14]. Этот механизм разрабатывался, в первую очередь, для многопроцессорных систем с распределенной памятью, однако, возможно его использованию и на системах с общей памятью. В этом случае в качестве коммуникационной среды выступает общая память. В MPI включены специальные драйверы, имитирующие передачу сообщений через общую память. Таким образом, MPI - это одна из наиболее универсальных технологий параллельного программирования, которую можно использовать на любых многопроцессорных системах.

MPI представляет собой библиотеку, добавляющую поддержку механизма передачи сообщений в стандартные языки программирования, такие как C/C++ и Фортран. Разрабатывался MPI специально созданным в 1992 г. консорциумом MPI Forum, в который вошли практически все разработчики многопроцессорных систем и программного обеспечения для них. Первая реализация MPI-1.1 вышла в 1995 г.

В 1997 г. вышла исправленная и доработанная версия MPI-1.2. Эта версия по сей день является базовой для MPI-1 [15]. Текущей версией является версия 2.0, первая реализация которой появилась в 1998 году. Эта версия открыла новое направление развития MPI-2.

Основное отличие MPI-2 от MPI-1 состоит в том, что добавлено динамическое управление процессами, односторонние операции, параллельные ввод/вывод, расширенный набор коллективных операций. Однако следует отметить, что до сих пор ни одна реализация MPI-2 не поддерживает стандарт 2.0 в полной мере. Все реализации в полной мере поддерживают стандарт 1.2 с некоторыми элементами стандарта 2.0.

На сегодняшний день имеется множество коммерческих и специализированных реализаций <заточенных> под высокоскоростное коммуникационное оборудование (Infiniband, Marynet, QsNet). Кроме того, успешно развиваются две свободно распространяемых версии - MPICH (Argonne National Laboratory) и LAM/MPI (Ohio Supercomputer Center). В зависимости от предпочтений, либо одна, либо другая версии включаются в различные дистрибутивы ОС Linux. В настоящее время большинство разработчиков сосредоточили свои усилия на проекте OpenMPI, в котором объединены оба стандарта MPI и поддерживается широкий спектр коммуникационного оборудования [16].

В MPI-1 имеется порядка 130 функций, тем не менее, большинство параллельных программ могут быть написаны с использованием всего шести базовых функций: MPI_INIT (), MPI_FINALIZE (), MPI_COMM_SIZE (), MPI_COMM_RANK (), MPI_SEND () и MPI_RECV (). Рассмотрим реализацию нашей программы вычисления числа π с использование указанных средств.

Программа pi_mpi.f

program pi_mpi
   include 'mpif.h'
   integer n, i
   double precision d, s, x, pi, temp
   integer myid, numprocs, ierr, status(3)
   integer sumtag, sizetag
   call MPI_INIT(ierr)
   call MPI_COMM_SIZE(MPI_COMM_WORLD,numprocs,ierr)
   call MPI_COMM_RANK(MPI_COMM_WORLD,myid,ierr)
   sizetag = 10
   sumtag = 17
   if (myid .eq. 0) then
    write(*,*) 'n?'
    read(*,*) n
    do i = 1, numprocs-1
     call MPI_SEND(n,1,MPI_INTEGER,i,sizetag,
   $     MPI_COMM_WORLD,ierr)
    enddo
   else
    call MPI_RECV(n,1,MPI_INTEGER,0,sizetag,
   $    MPI_COMM_WORLD,status,ierr)
   endif
   d = 1.0/n
   s = 0.0
   do i = myid+1, n, numprocs
     x = (i-0.5)*d
     s = s+4.0/(1.0+x*x)
   enddo
   pi = d*s
   if (myid .ne. 0) then
    call MPI_SEND(pi,1,MPI_DOUBLE_PRECISION,
   $    0,sumtag,MPI_COMM_WORLD,ierr)
   else
    do i = 1, numprocs-1
     call MPI_RECV(temp,1,MPI_DOUBLE_PRECISION,
   $     i,sumtag,MPI_COMM_WORLD,status,ierr)
     pi = pi+temp
    enddo
   endif
   if (myid .eq. 0) then
    write(*, 100) pi
 100  format(' pi = ', f20.15)
   endif
   call MPI_FINALIZE(ierr)
   end

Любая MPI-программа должна начинаться с вызова функции инициализации MPI_Init. В результате выполнения этой функции создается группа процессов, в которую помещаются все процессы, запущенные командой mpirun, и создается область связи (группа взаимодействующих процессов), описываемая предопределенным коммуни-катором (специальным объектом, описывающим область связи) MPI_COMM_WORLD. Процессы в группе упорядочены и пронумерованы от 0 до numprocs-1, где numprocs равно числу процессов в области связи. Число процессов в области связи можно определить с помощью процедуры MPI_COMM_SIZE (), а номер каждого процесса можно опросить процедурой MPI_COMM_RANK ().

В нашей программе нулевой процесс требует ввести число интервалов разбиения и затем рассылает эту переменную с помощью процедуры MPI_SEND () всем остальным процессам. В MPI одна и та же процедура служит для передачи различных типов данных. Тип передаваемых данных указывается третьим аргументом. Первый аргумент указывает адрес начала расположения передаваемых данных, а второй указывает их количество. Естественно, при посылке данных необходимо указывать адресата, т.е. процесс, которому они предназначены. Адресат указывается 4-м параметром. Пятый параметр указывает идентификатор сообщения.

Процессы-получатели для получения сообщения использует процедуру MPI_RECV (). Параметры этой процедуры почти полностью совпадают с параметрами MPI_SEND ().

Второй пакет коммуникационных операций выполняет пересылку частичных сумм от каждого процесса нулевому процессу для вычисления полной суммы.

Программа выглядит довольно громоздко, потому что в ней сознательно использовался минимальный набор MPI функций. Для выполнения таких операций как рассылка данных или сборка частичных сумм в библиотеке MPI имеются специальные коллективные операции. Программа с использованием таких операций будет гораздо нагляднее и компактней. Приведем пример такой более компактной программы на языке Си.

Программа pi_mpi.c

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

double f( double a )
{
  return (4.0 / (1.0 + a*a));
}
int main( int argc, char *argv[])
{
 int done = 0, n, myid, numprocs, i;
 double PI25DT = 3.141592653589793238462643;
 double mypi, pi, h, sum, x;
 double startwtime, endwtime;

 MPI_Init(&argc,&argv);
 MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
 MPI_Comm_rank(MPI_COMM_WORLD,&myid);
  
  if (myid == 0)
   {
   n = atoi(argv[1]);
   startwtime = MPI_Wtime();
   }
   MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
    h  = 1.0 / (double) n;
    sum = 0.0;
    for (i = myid + 1; i <= n; i += numprocs)
    {
    x = h * ((double)i - 0.5);
    sum += f(x);
    }
    mypi = h * sum;
    MPI_Reduce(&mypi,&pi,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
   if (myid == 0)
   {
    printf("pi is %.16f, Error is %.16f\n",pi, fabs(pi-PI25DT));
   endwtime = MPI_Wtime();
  printf("wall clock time = %f\n", endwtime-startwtime);	    
   }	
  MPI_Finalize();

  return 0;
}

Процедура MPI_Bcast () рассылает данные, указанные первым параметром, от процесса указанного 4-м параметром всем остальным процессам. Процедура MPI_Reduce (..., MPI_SUM, ...) выполняет глобальное суммирование частичных сумм.



Вперед: 1.4.2.3. Совместное использование MPI и OpenMP
Назад: 1.4.2.1. Коммуникационный интерфейс PVM
К содержанию: Оглавление