1 1 Параллельное решение уравнения

  1. program diff
  2. include 'mpif.h'
  3. integer xmax,ymax
  4. parameter (xmax=4000)
  5. parameter (ymax=1500)
  6. real*8 f0(xmax,ymax), f1(xmax,ymax)
  7. real*8 df,gdf,ldf
  8. integer x,y,n,myid,np,ierr,ierr1,status(MPI_STATUS_SIZE)
  9. common myid,np
  10. double precision tstart, tstop, tdiff1, tdiff2
  11. c Инициализация MPI и определение процессорной конфигурации
  12. call MPI_INIT( ierr )
  13. call MPI_COMM_RANK( MPI_COMM_WORLD, myid, ierr )
  14. call MPI_COMM_SIZE( MPI_COMM_WORLD, np, ierr )
  15. if ( myid .eq. 0 ) write(*,*) 'Starting!'
  16. c Задаем начальные значения
  17. call initial(f0,xmax,ymax)
  18. call initial(f1,xmax,ymax)
  19. c Задаем граничные значения
  20. call boundary(f0,xmax,ymax)
  21. call boundary(f1,xmax,ymax)
  22. c Начинаем основной итерационный цикл
  23. df=0.0
  24. n=0
  25. if ( myid .eq. 0 ) write(*,*) 'Processing...'
  26. 1 continue
  27. tstart=MPI_WTIME();
  28. c При смене итераций меняем местами массивы
  29. if ( ((n/2)*2) .EQ. n ) then
  30. c Обрабатываем f0 для каждой четной итерации
  31. call iter(f0,f1,xmax,ymax,df)
  32. else
  33. c Обрабатываем f1 для каждой нечетной итерации
  34. call iter(f1,f0,xmax,ymax,df)
  35. endif
  36. ldf=df
  37. c Увеличиваем номер цикла на единицу
  38. n=n+1
  39. tstop=MPI_WTIME();
  40. tdiff1=tstop-tstart
  41. c Получаем разницу значений функции на слоях
  42. c с каждого узла кластера и находим максимальное значение
  43. c этой разницы для всей разностной сетки
  44. call MPI_REDUCE(df, gdf, 1, MPI_REAL8,
  45. x MPI_MAX, 0, MPI_COMM_WORLD, ierr1)
  46. c Сообщаем найденную максимальную разницу
  47. c всем узлам кластера
  48. if(myid .eq. 0) df=gdf
  49. CALL MPI_BCAST(df, 1, MPI_REAL8, 0, MPI_COMM_WORLD, ierr)
  50. tstart=MPI_WTIME();
  51. tdiff2=tstart-tstop
  52. c Выводим на экран разницу между итерациями
  53. if ( myid .eq. 0 )
  54. x write(*,*) 'D:',df,'R:',tdiff1,ierr1,'B:',tdiff2
  55. c Если разница больше заданной, то решение не найдено
  56. c и мы идем на следующий цикл
  57. if (df .GT. 0.01) goto 1
  58. c Печатаем количество итераций, потребовавшихся
  59. c для нахождения решения
  60. if ( myid .eq. 0 ) write(*,*) 'Iteration count:', n
  61. c Заканчиваем работу с MPI
  62. call MPI_FINALIZE(ierr)
  63. stop
  64. end
  65.  
  66. c Подпрограмма вычисления искомой функции
  67. subroutine iter(f0,f1,xmax,ymax,df)
  68. include 'mpif.h'
  69. integer xmax,ymax
  70. real*8 f0(xmax,ymax), f1(xmax,ymax)
  71. real*8 dt,dx,dy
  72. real*8 df,dff,df1
  73. integer x,y,n,myid,np,ierr,status(MPI_STATUS_SIZE)
  74. common myid,np
  75. double precision tstart, tstop, tdiff
  76. dt=0.01
  77. dx=0.5
  78. dy=0.5
  79. c Обмениваемся границами с соседом
  80. c tstart=MPI_WTIME();
  81. if ( myid .gt. 0 )
  82. x call MPI_SENDRECV(
  83. x f0(1,2), xmax, MPI_REAL8, myid-1, 1,
  84. x f0(1,1), xmax, MPI_REAL8, myid-1, 1,
  85. x MPI_COMM_WORLD, status, ierr)
  86. if ( myid .lt. np-1 )
  87. x call MPI_SENDRECV(
  88. x f0(1,ymax-1), xmax, MPI_REAL8, myid+1, 1,
  89. x f0(1,ymax), xmax, MPI_REAL8, myid+1, 1,
  90. x MPI_COMM_WORLD, status, ierr)
  91. c tstart=MPI_WTIME();
  92. c Вычисляем функцию в ячейках сетки
  93. df1=0.0
  94. do y=2,ymax-1
  95. do x=2,xmax-1
  96. dff=dt*(
  97. x (f0(x+1,y)-2*f0(x,y)+f0(x-1,y))/(dx*dx)
  98. x +
  99. x (f0(x,y+1)-2*f0(x,y)+f0(x,y-1))/(dy*dy)
  100. x )
  101. f1(x,y)=f0(x,y)+dff
  102. c Находим максимальную дельту
  103. if( df1 < abs(dff) ) df1=abs(dff)
  104. end do
  105. end do
  106. df=df1
  107. call MPI_BARRIER(MPI_COMM_WORLD, ierr)
  108. return
  109. end
  110.  
  111. c Подпрограмма задания начальных значений
  112. subroutine initial(f0,xmax,ymax)
  113. integer rank,xmax,ymax,x,y
  114. real*8 f0(xmax,ymax)
  115. common myid,np
  116. c Задаем начальные значения массива
  117. do x=1,xmax
  118. do y=1,ymax
  119. f0(x,y)=0.0
  120. end do
  121. end do
  122. return
  123. end
  124.  
  125. c Подпрограмма задания граничных условий
  126. subroutine boundary(f0,xmax,ymax)
  127. integer rank,xmax,ymax,x,y
  128. real*8 f0(xmax,ymax)
  129. common myid,np
  130. c Задаем граничные значения массива
  131. c на границах с X=1 и X=xmax
  132. do y=1,ymax
  133. f0(1,y)=0.0
  134. f0(xmax,y)=0.0
  135. end do
  136. c Если мы - первый процесс, то
  137. c задаем граничные условия массива
  138. c на границе с Y=1
  139. if ( myid .eq. 0 ) then
  140. do x=1,xmax
  141. f0(x,1)=sin((x*1.0)/(xmax/2))
  142. end do
  143. endif
  144. c Если мы - последний процесс, то
  145. c задаем граничные условия массива
  146. c на границе с Y=ymax
  147. if (myid .eq. np-1 ) f0(xmax/2,ymax)=-5.0
  148. return
  149. end
  150.  

Изменения, внесенные в нашу программу для распараллеливания, достаточно просты. Рассмотрим их последовательно.

Первое. Предположим, что мы готовим программу для расчетов на кластере, состоящем из 4х узлов. Поскольку массивы Фортрана хранятся в памяти по столбцам, то есть a(1,1),a(2,1),a(3,1)...a(xmax,1),a(1,2),a(2,2),a(3,2) и т.д., то нашу разностную сетку мы поделим между узлами, разрезав ее вдоль оси X. Другими словами, размерность X массивов останется прежней (4000), а размерность Y мы уменьшим в 4 раза (до 1500), что и было сделано в строке 5.

Второе. Добавим файл с описаниями MPI во все подпрограммы (в том числе и в основную программу), где используются методы MPI. Это сделано в строках 2 и 68.

Третье. Добавим описание некоторых переменных, которые будут использоваться в вызовах MPI - строчки 7,8,9,73,74,113,114,127,129.

Четвертое. В начале программы проинициализируем среду MPI, запросив номер текущего процесса и общее количество процессов - строки 12,13 и 14 соответственно.

Пятое. Все операторы печати, которые имеются в программе мы оставим работать только в корневом процессе. Это было сделано путем проверки равенства значения переменной myid нулю: строки 15, 25, 53 и 60.

Шестое. Поскольку максимальная разница между значениями функции в ячейках на разных временных слоях, необходимая нам для принятия решения о продолжении итераций, вычисляется отдельно на каждом узле кластера на своем собственном наборе данных, то в строке 44 мы запрашиваем все узлы и находим максимальное значение разницы по всей разностной сетке.

Седьмое. После этого в строке 49 мы рассылаем всем узлам значение этой глобальной разницы, тем самым, дав им возможность принять решение о продолжении вычислений.

Восьмое. И наконец в строке 62 мы завершаем работу среды MPI.

Изменение основной программы на этом закончено. Дополнению подверглась так же подпрограмма нахождения значений искомой функции (строка 67). Поскольку для вычислений этой функции на границах той области, которую обсчитывает узел кластера, необходимо знать значения ячеек из соседних областей. Возникает необходимость в граничном обмене.

Девятое. Для обеспечения граничного обмена непосредственно перед началом очередной итерации каждый процесс обменивается своими границами с соседними процессами справа и слева (строки 81-90). За исключением первого и последнего процесса - у них только один сосед, поэтому первый процесс обменивается границами с соседом справа (строки 81-85), а последний обменивается только с соседом слева (строки 86-90). Алгоритм такого обмена достаточно прост. Для каждого узла кластера первый и последний столбец разностной сетки, в данном случае f0(*,1) и f0(*,ymax), заполнены данными из соседних областей, а граница собственной области проходит по второму и предпоследнему столбцу - f0(*,2) и f0(*,ymax-1). Поэтому процесс обмена между узлами A (левый) и B (правый) происходит по следующей схеме:

  • f0А(*,ymax-1) → f0B(*,1)
  • f0А(*,ymax) ← f0B(*,2)

Для оптимизации обмена вместо последовательного вызова функций передачи (MPI_SEND) и приема (MPI_RECV) данных мы использовали совмещенную функцию MPI_SENDRECV.

На этом изменение подпрограммы вычисления функции завершено. Последнее, что нам осталось сделать, так это - модифицировать подпрограммы задания начальных и граничных условий с учетом параллелизма. Процедуру задания начальных значений initial мы менять не стали, поскольку в нашем варианте вся разностная сетка заполняется единообразно (нулями). В случае же граничных условий заполнение разностной сетки разнится в зависимости от узла на котором выполняется процедура.

Десятое. Верхняя и нижняя граница сетки f0(1,*) и f0(xmax,*) заполняется единообразно нулями и не зависит от номера процесса. Левая граница f0(*,1) заполняется только первым процессом, а правая f0(*,ymax) - только последним. Поэтому в подпрограмме boundary предусмотрена проверка номера процесса (строки 139 и 147) и три блока: первый (строки 132-135) выполняется всеми процессами, второй (строки 140-142) выполняется только первым процессом и третий (строка 147) выполняется только последним процессом.

Как видите, процесс распараллеливания программы требует в сущности минимальных действий.


Copyright © 1998-2011 Юрий Сбитнев