§19. Обрамляющие функции MPI

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

Синтаксис функции инициализации MPI_Init значительно отличается в языках C и FORTRAN:

FORTRAN:

INTEGER IERR

MPI_INIT(IERR)

C:

int MPI_Init(int *argc, char ***argv)

В программах на C каждому процессу при инициализации передаются аргументы функции main, полученные из командной строки. В программах на языке FORTRAN параметр IERR является выходным и возвращает код ошибки.

Функция завершения MPI программ MPI_Finalize.

FORTRAN:

INTEGER IERR

MPI_FINALIZE(IERR)

C:

int MPI_Finalize(void)

Функция закрывает все MPI-процессы и ликвидирует все области связи.

Функция определения числа процессов в области связи MPI_Comm_size.

FORTRAN:

INTEGER COMM, SIZE, IERR

MPI_COMM_SIZE(COMM, SIZE, IERR)

C:

int MPI_Comm_size(MPI_Comm comm, int *size)

Входные параметры:

comm

-

коммуникатор.

Выходные параметры:

size

-

число процессов в области связи коммуникатора comm.

Функция возвращает количество процессов в области связи коммуникатора comm.

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

Функция определения номера процесса MPI_Comm_rank.

FORTRAN:

INTEGER COMM, RANK, IERR

MPI_COMM_RANK(COMM, RANK, IERR)

C:

int MPI_Comm_rank(MPI_Comm comm, int *rank)

Входные параметры:

comm

-

коммуникатор.

Выходные параметры:

rank

-

номер процесса, вызвавшего функцию.

Функция возвращает номер процесса, вызвавшего эту функцию. Номера процессов лежат в диапазоне 0..size-1 (значение size может быть определено с помощью предыдущей функции). Подпрограмма является локальной.

 

1    PROGRAM EXAMPLE

2    INCLUDE 'mpif.h'

3    INTEGER my_id, np, comm, ierr

4    CALL MPI_Init(ierr)

5    comm=MPI_COMM_WORLD

6    CALL MPI_Comm_Size(comm, np, ierr)

7    CALL MPI_Comm_Rank(comm, my_id, ierr)

8    Write(*,*) 'Hello, ',my_id,' processor

9   * of ',np, ' processors'

10   CALL MPI_Finalize(ierr)

11   STOP

12   END

Рис. 15. Пример использования обрамляющих функций MPI

 

1    PROGRAM EXAMPLE

2    INCLUDE 'mpif.h'

3    INTEGER my_id, np, comm,ierr

4    CALL MPI_Init(ierr)

5    comm=MPI_COMM_WORLD

6    CALL MPI_Comm_Size(comm, np, ierr)

7    CALL MPI_Comm_Rank(comm, my_id, ierr)

8    Write(*,*)  'Hello, ',my_id,' processor

9   * of ',np, 'processors'

10   j=0

11   do i=0,my_id

12    j=j+i

13   end do

14   Write(*,*) my_id,': j=',j

15   CALL MPI_Finalize(ierr)

16   STOP

17   END

Рис. 16. Пример использования идентификатора процесса

 

1    program EXAMPLE

2    include 'mpif.h'

3    integer my_id, np, tag, count, ierr, summa, namelen

4    integer status(MPI_STATUS_SIZE)

5    character p_name

6    call MPI_Init(ierr)

7    call MPI_Comm_rank(MPI_COMM_WORLD, my_id, ierr)

8    call MPI_Comm_size(MPI_COMM_WORLD, np, ierr)

9    call MPI_Get_processor_name(p_name, namelen)

10   print *, p_name , mpi_comm_world

11   call MPI_Finalize(ierr)

12   stop

13   end

Рис. 17. Пример для вывода имен машин кластера

 

На рис. 15 приведен пример программы, использующей обрамляющие функции MPI: строка 4 – инициализация библиотеки MPI, строка 6 – получение числа процессоров, на которых запущено данное параллельное приложение, 7 – получение идентификатора процесса, индивидуального для каждого процессора, 9 – завершение всех MPI-процессов.

При создании программ требуется, чтобы каждый процессор выполнял свою ветвь параллельного алгоритма, механизм реализации заключается в использовании ранга: в условии, в цикле и т.д. На рис. 16 приведен пример, демонстрирующий использование идентификатора процесса при вычислении суммы ряда, при этом на каждом узле кластера будет получена следующая сумма: .

На рис. 17 приведен пример программы, позволяющий получить имена машин, составляющих кластер.

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

Функция передачи сообщения MPI_Send.

FORTRAN:

INTEGER COUNT, DATATYPE, DEST, TAG, COMM, IERR

<type> BUF(*)

MPI_SEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERR)

C:

int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest,int tag, MPI_Comm comm)

Входные параметры:

buf

-

адрес начала расположения пересылаемых данных;

count

-

число пересылаемых элементов;

datatype

-

тип посылаемых элементов;

dest

-

номер процесса-получателя в группе, связанной с коммуникатором comm;

tag

-

идентификатор сообщения (аналог типа сообщения функций nread и nwrite PSE nCUBE2);

comm

-

коммуникатор области связи.

Функция выполняет посылку count элементов типа datatype сообщения с идентификатором tag процессу dest в области связи коммуникатора comm. Переменная buf - это, как правило, массив или скалярная переменная. В последнем случае значение count = 1.

Функция приема сообщения MPI_Recv.

FORTRAN:

<type> BUF(*)

INTEGER COUNT, DATATYPE, SOURCE, TAG, COMM

INTEGER STATUS(MPI_STATUS_SIZE), IERR

MPI_RECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS, IERR)

C:

int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)

Входные параметры:

count

-

максимальное число принимаемых элементов;

datatype

-

тип элементов принимаемого сообщения;

source

-

номер процесса-отправителя;

tag

-

идентификатор сообщения;

comm

-

коммуникатор области связи.

Выходные параметры:

buf

-

адрес начала расположения принимаемого сообщения;

status

-

атрибуты принятого сообщения.

Функция выполняет прием count элементов типа datatype сообщения с идентификатором tag от процесса source в области связи коммуникатора comm.

Ниже приведен ряд примеров для организации передачи данных с использованием пары коммуникационных функций: MPI_Send,MPI_Recv.

На рис. 18 приведен пример передачи переменной buf от нулевого процессора всем процессорам, в том числе и самому себе.

 

1    PROGRAM EXAMPLE

2    INCLUDE 'mpif.h'

3    INTEGER my_id, np, comm,ierr, status

4    REAL*8 Buf

5    CALL MPI_Init(ierr)

6    comm=MPI_COMM_WORLD

7    CALL MPI_Comm_Size(comm, np, ierr)

8    CALL MPI_Comm_Rank(comm, my_id, ierr)

9    IF (my_id.EQ.0) Then

10    Buf=1

11    DO I=0,NP-1

12     CALL MPI_Send(Buf,1,MPI_REAL8,I,1,comm,ierr)

13     END DO

14   End IF

15   CALL MPI_Recv(Buf,1, MPI_REAL8,0,1,comm,status,ierr)

16   Write(*,*) my_id,': Buf=',Buf

17   CALL MPI_Finalize(ierr)

18   STOP

19   END

Рис. 18. Пример пересылки переменной от нулевого процессора всем остальным процессорам

 

1    PROGRAM EXAMPLE

2    INCLUDE 'mpif.h'

3    INTEGER my_id, np, comm,ierr, status

4    INTEGER Buf(8)

5    CALL MPI_Init(ierr)

6    comm=MPI_COMM_WORLD

7    CALL MPI_Comm_Size(comm, np, ierr)

8    CALL MPI_Comm_Rank(comm, my_id, ierr)

9    CALL MPI_Send(MY_ID,1,MPI_INTEGER,0,1,comm,ierr)

10   IF (my_id.EQ.0) Then

11    do I=1,NP

12     CALL MPI_Recv(Buf(I),1, MPI_INTEGER,

13  *  i-1,1,comm,status,ierr)

14     Write(*,*) my_id,': Buf=',Buf(i)

15    END DO

16   End IF

17   CALL MPI_Finalize(ierr)

18   STOP

19   END

Рис. 19. Пример пересылки данных со всех процессоров на нулевой процессор

 

На рис. 19 приведен пример передачи переменной my_id со всех процессоров на нулевой процессор и формирование во время передачи данных на нулевом процессоре массива из присылаемых переменных.

В примере на рис. 20 переменные next и prev служат для задания топологии “кольцо”, передача данных осуществляется с использованием заданной топологии. В MPI существуют специальные функции для задания топологий. Далее при изучении функций задания топологий мы реализуем топологию кольцо, но уже с использованием функций MPI. На рис. 21 показано схематическое представление пересылок. Визуализация трассы исполнения параллельной программы приведена на рис. 22. Анализируя рисунок, можно сделать следующие пояснения: нулевой процессор после инициализации посылает данные 1 процессору и переходит в состояние ожидания данных от 3 процессора, инициализация параллельного приложения на 1 процессоре произошла намного позже, чем на 0 процессоре, время получения данных от 0 процессора меньше, чем время передачи сообщения 2 процессору и т.д.

 

1    PROGRAM EXAMPLE

2    INCLUDE 'mpif.h'

3    INTEGER my_id, np, comm,ierr, status

4    INTEGER next, prev

5    REAL*8 Buf

6    CALL MPI_Init(ierr)

7    comm=MPI_COMM_WORLD

8    CALL MPI_Comm_Size(comm, np, ierr)

9    CALL MPI_Comm_Rank(comm, my_id, ierr)

10   Write(*,*)'Hello, ',my_id,' processor

11  *  of ',np, 'processors'

12   next=my_id+1

13   prev=my_id-1

14   IF (my_id.EQ.0) Then

15    prev=np-1

16   End IF

17   IF (my_id.EQ.np-1) Then

18    next=0

19   End IF

20   IF (my_id.EQ.0) Then

21    Buf=1

22    CALL MPI_Send(Buf,1,MPI_REAL8,next,1,comm,ierr)

23    CALL MPI_Recv(Buf,1, MPI_REAL8,prev,1,

24  *  comm,status,ierr)

25   Else

26    CALL MPI_Recv(Buf,1, MPI_REAL8,prev,1,

27  *  comm,status,ierr)

28    Buf=Buf+1

29    CALL MPI_Send(Buf,1,MPI_REAL8,next,1,comm,ierr)

30    End IF

31   Write(*,*) my_id,': Buf=',Buf

32   CALL MPI_Finalize(ierr)

33   STOP

34   END

Рис. 20. Пример пересылки данных между процессорами с использованием топологии ”кольцо”

 

Рис. 21. Передача данных по “кольцу”

 

Более детально операции обмена сообщениями будут рассмотрены в следующем разделе, а в заключении этого раздела приведем функцию, которая не входит в очерченный минимум, но которая важна для разработки эффективных программ. Речь идет о функции получения отсчета времени - таймере. С одной стороны, такие функции имеются в составе всех операционных систем, но, с другой стороны, существует полнейший произвол в их реализации. Опыт работы с различными операционными системами показывает, что при переносе приложений с одной платформы на другую первое (а иногда и единственное), что приходится переделывать, это обращения к функциям учета времени. Поэтому разработчики MPI, добиваясь полной независимости приложений от операционной среды, ввели и свои функции отсчета времени.

 

Рис. 22. Визуализация трассы исполнения параллельной программы пересылки данных с использованием топологии ”кольцо”

 

Функция отсчета времени (таймер) MPI_Wtime.

FORTRAN:

DOUBLE PRECISION MPI_WTIME()

C:

double MPI_Wtime(void)

Функция возвращает астрономическое время в секундах, прошедшее с некоторого момента в прошлом (точки отсчета). Гарантируется, что эта точка отсчета не будет изменена в течение жизни процесса. Для хронометрирования участка программы вызов функции делается в начале и конце участка и определяется разница между показаниями таймера.

Функция MPI_Wtick, имеющая точно такой же синтаксис, возвращает разрешение таймера (минимальное значение кванта времени).

На рис. 23 приведен предыдущий пример, но уже с использованием функций для замера времени (строки 12, 33).

 

1    Program EXAMPLE

2    INCLUDE 'mpif.h'

3    PARAMETER (max=100)

4    INTEGER my_id, np, comm,ierr, status

5    INTEGER next, prev

6    REAL*8 Buf(max)

7    DOUBLE PRECISION start_time, end_time

8    CALL MPI_Init(ierr)

9    comm=MPI_COMM_WORLD

10   CALL MPI_Comm_Size(comm, np, ierr)

11   CALL MPI_Comm_Rank(comm, my_id, ierr)

12   start_time=MPI_Wtime()

13   next=my_id+1

14   prev=my_id-1

15   IF (my_id.EQ.0) Then

16    prev=np-1

17   End IF

18   IF (my_id.EQ.np-1) Then

19    next=0

20   End IF

21   IF (my_id.EQ.0) Then

22    Buf(1)=1

23    CALL MPI_Send(Buf,np,MPI_REAL8,next,1,comm,ierr)

24    CALL MPI_Recv(Buf,np, MPI_REAL8,prev,

25  *  1,comm,status,ierr)

26   Else

27    CALL MPI_Recv(Buf,np, MPI_REAL8,prev,

28  *  1,comm,status,ierr)

29    Buf(my_id+1)=Buf(prev+1)+1

30    CALL MPI_Send(Buf,np,MPI_REAL8,next,1,comm,ierr)

31   End IF

32   IF (my_id.EQ.0) Then

33    Write(*,*) my_id,': Buf=',(Buf(j),j=1,np)

34   End IF

35   end_time=MPI_Wtime()

36   Write(*,*) my_id,': Time when processors work',

37  *  end_time-start_time

38   CALL MPI_Finalize(ierr)

39   STOP

40   END

Рис. 23. Пример использования функций для замера времени