Программирование для многопроцессорных систем в стандарте MPI - Шпаковский Г.И., Серикова Н.В.. Программирование для многопроцессорных систем в стандарте MPI -. Организация вычислений в многопроцессорных системах
Скачать 1.61 Mb.
|
Глава 8. РЕШЕНИЕ ДИФФЕРЕНЦИАЛЬНЫХ УРАВНЕНИЙ В ЧАСТНЫХ ПРОИЗВОДНЫХ Решение дифференциальных уравнений в частных производных является ядром многих приложений. Решение задачи на конечно- разностной вычислительной сетке методом Якоби, рассмотренное в этом параграфе, дает возможность продемонстрировать использова- ние «виртуальной топологии», которая делает более удобным разме- щение процессов на вычислительной среде, а также позволяет иссле- довать эффективность различных способов коммуникаций для реше- ния одной и той же задачи. Метод Якоби был выбран благодаря про- стоте вычислительной части [4, 21]. 8.1. ЗАДАЧА ПУАССОНА Задача Пуассона, то есть решение дифференциальных уравнений в частных производных, выражается следующими уравнениями: ∆ 2 u = f(x,y) – внутри области (8.1) u (x,y) = k(x,y) – на границе (8.2) Будем считать, что область решения является квадратной. Чтобы найти приближенное решение, определим квадратную сетку, содер- жащую точки (x i , y i ), задаваемые как 188 1 ,..., 0 , 1 + = + = n i n i x i , 1 ,..., 0 , 1 + = + = n j n j y j Таким образом, вдоль каждого края сетки имеется n+2 точки. Сле- дует найти аппроксимацию для u(x,y) в точках (x i , y j ) выбранной сет- ки. Обозначим через u i,j значения u в (x i , y j ), через h расстояние меж- ду точками, равное 1/(n+1). Тогда формула 8.1. для каждой из точек будет выглядеть следующим образом: Вычисляем значения u i,j в каждой точке сетки, которые замещают предыдущие значения, используя выражение Этот процесс называется итерациями Якоби и повторяется до по- лучения решения. Фрагмент программы для этого случая таков: integer i, j, n double precision, u (0:n+l, 0:n+l), unew(0:n+l, 0:n+l) do 10 j = l, n do 10 i = l, n unew (i, j) = 0.25*(u(i-l, j) + u(i, j+l) + u(i, j-l)+u(i+l, j)) – h * h * f(i, j) 10 continue 8.2. ПАРАЛЛЕЛЬНЫЕ АЛГОРИТМЫ ДЛЯ МЕТОДА ИТЕРАЦИЙ ЯКОБИ 8.2.1. Параллельный алгоритм для 1D композиции Чтобы распараллелить последовательный алгоритм, нужно парал- лелизовать цикл. Для этого необходимо распределить данные (в на- шем случае массивы u, unew, f) по процессам. Одна из простейших декомпозиций состоит в следующем: физическая область разделяет- ся на слои, каждый из них обрабатывается отдельным процессом. Эта декомпозиция может быть описана следующим образом: integer i, j, n, s, e double precision u(0:n+l, s:e), unew(0:n+l, s:e) do 10 j = s, e do 10 i = l, n unew(i, j) = 0.25*(u(i-l, j)+ u(i, j+l)+ u(i, j-l) + u(i+l,j)) – h * h * f(i,j) 10 continue 4 / ) f h u u u u ( u j , i 2 k j , 1 i k 1 j , i k 1 j , i k j , 1 i 1 k j , i − + + + = = − + − + ) 3 8 ( f h u 4 u u u u j , i 2 j , i j , 1 i 1 j , i 1 j , i j , 1 i = − + + + + − + − 189 Здесь s,e указывают значения номеров строк слоя, за которые ответственен данный процесс. При выполнении итераций каждый процесс использует не только элементы, принадлежащие его слою, но и элементы из смежных про- цессов. В этом случае организовать вычисления можно следующим образом. Расширим слой сетки для каждого процесса так, чтобы он содержал необходимые данные (рис. 8.1). Элементы среды, которые используются, чтобы сохранять данные из других процессов, называ- ются “теневыми”. Рис. 8.1. Область с теневыми точками для процесса 1 Вследствие этого размерность массивов изменится: double precision u ( 0:n+1, s-1: e+1). Следующая задача – решить, как назначать процессы разделенным областям массива. В качестве декомпозиции процессов для решения задачи выберем картезианскую топологию размерности 1. MPI имеет набор процедур для определения, исследования и манипулирования картезианскими топологиями. При одном измерении можно просто использовать номер в новом коммуникаторе плюс или минус 1, чтобы найти соседа и не использо- вать фунцию MPI_CART_CREATE, но даже в этом случае выбор может быть не наилучшим, поскольку соседи, определенные таким Процесс 2 Процесс 1 Процесс 0 190 образом, могут не быть соседями по аппаратуре. Если размерностей больше, чем одна, задача становится еще труднее. Каждый процесс посылает и получает сообщения от соседей. В де- композиции 1D это соседи, расположенные выше и ниже. Способ оп- ределения соседей заключается в следующем: копия верхней строки одного процесса является дном теневой строки процесса, который расположен выше. Следовательно, каждый процесс является как при- нимающим, так и посылающим данные. Фактически данные сдвига- ются от одного процесса к другому, и MPI имеет процедуру MPI_CART_SHIFT , которая может быть использована, чтобы найти соседей. Опишем процедуру MPE_DECOMP1D, которая определяет декомпозицию массивов и вызывается функцией call MPE_DECOMP1D(n, nprocs, myrank, s, e), где nprocs – число процессов в картезианской топологии, myrank – координаты процесса, n – размерность массива. MPE_Decomp1d вы- числяет значения s и e. Например следующим образом. Если n делится на nprocs без остатка, то s = 1 + myrank * (n/nprocs) e = s + (n/nprocs) – 1. Когда деление без остатка не получается, если floor(x) – целая часть х плюс 1, то наиболее простой выход: s = 1 + myrank * floor (n/nprocs) if (myrank .eq. nprocs – 1) then e = n else e = s + floor (n/nprocs) – 1 endif. Для каждого процесса мы должны получить теневые данные для строки с номером s–1 от процесса ниже и данные для строки с номе- ром e+1 от процесса выше. Ниже представлена процедура обмена данными, для которой определены следующие параметры: а – массив, nx – количество пересылаемых данных строки, s – номер первой строки массива в данном процессе, e - номер последней строки мас- сива данного процесса, nbrbottom – номер процесса, расположенного ниже данного , аnbrtop – номер процесса, расположенного выше дан- ного . subroutine EXCHG1(a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e double precision a(0:nx+l, s-l:e+l) 191 integer comm1d, nbrbottom, nbrtop, status (MPI_STATUS_SIZE), ierr call MPI_SEND( a(l,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(l,s-l), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm1d, status, ierr) call MPI_SEND( a(l,s),nx, MPI_DOUBLE_PRECISION,nbrbottom, 1,comm1d, ierr ) call MPI_RECV( a(l,e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, status, ierr ) return end В этой процедуре каждый процесс посылает данные процессу nbrtop, расположенному выше его, и затем принимает данные от про- цесса nbrbottom ниже его. Затем порядок меняется на обратный - данные посылаются процессу ниже и принимаются от процесса, выше данного. Опишем все части программы для решения задачи Пуассона. В основной части программы для назначения процессов разделенным областям массива будем использовать картезианскую топологию еди- ничной размерности. Используем процедуру MPI_CART_CREATE для создания декомпозиции процессов и процедуру MPE_Decomp1d, чтобы определить декомпозицию массива. Процедура ONEDINIT инициализирует элементы массивов a, f. Решение вычисляется попе- ременно в массивах а и b, так как в цикле имеется два обращения к EXCHNG1 и SWEEP1D. Итерации заканчиваются, когда разница между двумя смежными значениями аппроксимаций становится меньше, чем 10 -5 . Разность между двумя локальными частями а и b вычисляется процедурой DIFF. Процедура MPI_ALLREDUCE нуж- на, чтобы гарантировать, что во всех процессах достигнута точность вычислений. Программа печатает как результаты итераций, так и ко- нечный результат. Цикл DO с максимумом итераций maxit гаран- тирует, что программа закончится, даже если итерации не сходятся. Вычислительная часть программы представлена на рис. 8.2. ! определение нового коммуникатора картезианской топологии ! размерности 1 для декомпозиции процессов call MPI_CART_CREATE(MPI_COMM_WORLD,1,numprocs, .false., .true, comm1d, ierr ) ! определение позиции процесса в данном коммуникаторе call MPI_COMM_RANK( comm1d, myid, ierr ) ! определение номеров процессов для заполнения теневых точек call MPI_CART_SHIFT( comm1d, 0, 1, nbrbottom, nbrtop, ierr) ! определение декомпозиции исходного массива call MPE_DECOMP1D( ny, numprocs, myid, s, e ) 192 ! инициализация массивов f и a call ONEDINIT( а, b, f, nx, s, e ) ! Вычисление итераций Якоби , maxit – максимальное число итераций do 10 it=l, maxit ! определение теневых точек call EXCHNG1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) ! вычисляем одну итерацию Jacobi call SWEEP1D( a, f, nx, s, e, b ) ! повторяем, чтобы получить решение в а call EXCHNG1( b, nx, s, e, comm1d, nbrbottom, nbrtop ) call SWEEP1D( b, f, nx, s, e, a ) ! проверка точности вычислений diffw = DIFF( a, b, nx, s, e ) call MPI_ALLREDUCE( diffw, diffnorm, 1, MPI_DOUBLE_PRECISION,MPI_SUM, comm1d, ierr ) if (diffnorm .It. 1.Oe-5) goto 20 if (myid .eq. 0) print *, 2*it, ' Difference is ', diffnorm 10 continue if (myid .eq. 0) print *, 'Failed to converge' 20 continue if (myid .eq. 0) then print *, 'Converged after ', 2*it, ' Iterations' endif Рис. 8.2. Реализация итераций Якоби для решения задачи Пуассона 8.2.2. Параллельный алгоритм для 2D композиции Небольшая модификация программы преобразует ее из одномер- ной в двухмерную. Прежде всего следует определить декомпозицию двумерной среды, используя функцию MPI_CART_CREATE. isperiodic(l) = .false. isperiodic(2) = .false. reorder = .true. call MPI_CART_CREATE(MPI_COMM_WORLD, 2, dims, isperiodic, reorder, comm2d, ierr ) Для получения номеров левых nbrleft и правых nbrright процес- сов-соседей, так же как и верхних и нижних в предыдущем параграфе, воспользуемся функцией MPI_CART_SHIFT. call MPI_CART_SHIFT(comm2d, 0, 1, nbrleft, nbrright, ierr ) call MPI_CART_SHIFT(comm2d, 1, 1, nbrbottom, nbrtop, ierr ) 193 Процедура SWEEP длявычисления новых значений массива unew изменится следующим образом: integer i, j, n double precision u(sx-l:ex+l, sy-l:ey+l), unew(sx-l:ex+l, sy-l:ey+l) do 10 j=sy, ey do 10 i=sx, ex unew(i,j) = 0.25*(u(i-l,j) + ui(i,j+l) + u(i,j-l) + u(i+l,j)) – h * h * f(i,j) 10 continue Последняя процедура, которую нужно изменить, – процедура об- мена данными (EXCHNG1 в случае 1D композиции). Возникает про- блема: данные, посылаемые к верхним и нижним процессам, хранятся в непрерывной памяти, а данные для левого и правого процессов – нет. Если сообщения занимают непрерывную область памяти, то такие типы данных, как MPI_INTEGER и MPI_DOUBLE_PRECISION, используются при передаче сообщений. В других случаях необходи- мы наследуемые типы данных. Определим новый тип данных, описы- вающий группу элементов, адрес которых отличается на некоторую постоянную величину (страйд). call MPI_TYPE_VECTOR ( ey – sy + 3, 1, ex – sx + 3, MPI_DOUBLE_PRECISION, stridetype, ierr ) call MPI_TYPE_COMMIT( stridetype, ierr ) Аргументы MPI_TYPE_VECTOR описывают блок, который со- стоит из нескольких копий входного типа данных. Первый аргумент – это число блоков; второй – число элементов старого типа в каждом блоке (часто это один элемент); третий аргумент есть страйд (дис- танция в терминах входного типа данных между последовательными элементами); четвертый аргумент – это cтарый тип; пятый аргумент есть создаваемый наследуемый тип. После того как с помощью функции MPI_TYPE_VECTOR создан новый тип данных, он передается системе с помощью функции MPI_TYPE_COMMIT . Эта процедура получает новый тип данных и дает системе возможность выполнить любую желаемую возможную оптимизацию. Все сконструированные пользователем типы данных должны быть переданы до их использования в операциях обмена. Ко- гда тип данных больше не нужен, он должен быть освобожден проце- дурой MPI_TYPE_FREE. После определения новых типов данных программа для передачи строки отличается от программы для переда- 194 чи столбца только в аргументах типа данных. Конечная версия про- цедуры обмена EXCHNG2 представлена ниже. В ней определены следующие параметры: а – массив, nx– количество пересылаемых данных строки, sx – номер первой строки массива в данном процессе, ex – номер последней строки массива данного процесса, sy – номер первого столбца массива в данном процессе, ey – номер последнего столбца массива данного процесса, nbrbottom – номер процесса- соседа выше , nbrtop – номер процесса-соседа ниже, nbrleft – номер процеса-соседа слева, nbrright – номер процеса-соседа справа, stridetype – новый тип данных для элементов столбца. subroutine EXCHNG2 (a, sx, ex, sy, ey, comm2d, stridetype, nbrleft, nbrright, nbrtop, nbrbottom ) use mpi integer sx, ex, sy, ey, stridetype, nbrleft, nbrright, nbrtop, nbrbottom, comm2d double precision a(sx-l:ex+l, sy-l:ey+l) integer status (MPI_STATUS_SIZE), ierr, nx nx = ex – sx + I call MPI_SENDRECV( a(sx,ey), nx, MPI_DOUBLE_PRECISION, nbrtop,0, a(sx, sy-l), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm2d, status, ierr ) call MPI_SENDRECV( a(sx,sy), nx, MPI_DOUBLE_PRECISION, brbottom,1, a(sx,ey+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm2d, status, ierr ) call MPI_SENDRECV(a(ex,sy), 1, stridetype, nbrright, 0, a(sx-l,sy), 1, stridetype, nbrleft, 0,comm2d, status, ierr ) call MPI_SENDRECV( a(sx,sy), 1, stridetype, nbrieft, 1, a(ex+l,sy), 1, stridetype,nbrright, 1, comm2d, status, ierr ) return end 8.2.3. Способы межпроцессного обмена В процедуре EXCHG1 применен наиболее часто используемый вид обмена. Параллелизм зависит от величины буфера, обеспечивае- мого системой передачи сообщений. Если запустить задачу на систе- ме с малым объемом буферного пространства и с большим размером сообщений, то посылки не завершатся до тех пор, пока принимающие процессы не закончат прием. В процедуре EXCHG1 только один по- следний процесс не посылает сообщений на первом шаге, следова- тельно, он может получить сообщения от процесса выше, затем про- исходит прием в предыдущем процессе и так далее. Эта лестничная процедура посылок и приема представлена на рис. 8.3. 195 Рис. 8.3. Порядок посылок и соответствующих приемов во времени. Длина заштрихованной области указывает время, когда процесс ожидает возможности передать данные соседнему процессу. Приведем несколько вариантов реализации процедуры EXCHG1, которые могут быть более эффективными для различных MPI реали- заций. Упорядоченный send и reseive. Простейший путьскорректиро- вать зависимости из-за буферизации состоит в упорядочении посылок и приемов: если один из процессов посылает другому, то принимаю- щий процесс выполнит у себя сначала прием, а потом только начнет свою посылку, если она у него есть. Соответствующая программа представлена ниже. subroutine exchng1 a, nx, s, e, comm1d, nbrbottom, nbrtop) use mpi integer nx, s, e, comm1d, nbrbottom, nbrtop, rank, coord double precision a(0:nx+l, s-l:e+l) integer status (MPI_STATUS_SIZE), ierr call MPI_COMM_RANK( comm1d, rank, ierr ) call MPI_CART_COORDS( comm1d, rank, 1, coord, ierr ) if (mod( coord, 2 ) .eq. 0) then call MPI_SEND( a(l,e), nx, MPI_DOUBLE_PRECISION,nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(l, s-l), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm1d,status, ierr ) call MPI_SEND( a(l,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, 1, comm1d, ierr) send send send send send send send recv recv recv recv recv recv recv time P0 P1 P2 P3 P4 P5 P6 P7 196 call MPI_RECV( a(l, e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, status, ierr ) else call MPI_RECV ( a(l, s-1), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0,comm1d,status,ierr) call MPI_SEND( a(l, e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(l, e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, status, ierr ) call MPI_SEND( a(l, s), nx, MPI_DOUBLE_PRECISION, nbrbottom, 1,comm1d, ierr) endif return end В этой программе четные процессы посылают первыми, а нечет- ные процессы принимают первыми. Комбинированные send и receive. Спаривание посылки и приема эффективно, но может быть трудным в реализации при сложном взаимодействии процессов (например, на нерегулярной сетке). Аль- тернативой является использование процедуры MPI_SENDRECV. Эта процедура позволяет послать и принять данные, не заботясь о том, что может иметь место дедлок из-за нехватки буферного про- странства. В этом случае каждый процесс посылает данные процессу, расположенному выше, и принимает данные от процесса, располо- женного ниже, как это показано в следующей программе: subroutine exchng1 ( a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e, comm1d, nbrbottom, nbrtop double precision a(0:nx+l, s-l:e+l) integer status (MPI_STATUS_SIZE), ierr call MPI_SENDRECV( a(l,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, a(l,s-l), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm1d, status, ierr ) call MPI_SENDRECV( a(l,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, I, a(l,e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, status, ierr ) return end Буферизованные Sends. MPI позволяет программисту зарезерви- ровать буфер, в котором данные могут быть размещены до их достав- ки по назначению. Это снимает с программиста ответственность за 197 безопасное упорядочивание операций посылки и приема. Изменение в обменных процедурах будет простым – вызов MPI_SEND замещает- ся на вызов MPI_BSEND: subroutine exchng1 (a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e, integer coimn1d, nbrbottom, nbrtop double precision a(0:nx+l, s-l:e+l) integer status (MPI_STATUS_SIZE), ierr call MPI_BSEND( a(l,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(l, s-l), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm1d,status,ierr ) call MPI_BSEND( a(l,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, 1, comm1d, ierr ) call MPI_RECV( a(l,e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, commid, status, ierr ) return end В дополнение к изменению обменных процедур MPI требует, что- бы программист зарезервировал память, в которой сообщения могут быть размещены с помощью процедуры MPI_BUFFER_ATTACH. Этот буфер должен быть достаточно большим, чтобы хранить все со- общения, которые обязаны быть посланными перед тем, как будут выполнены соответствующие вызовы для приема. В нашем случае нужен буфер размером 2*nx значений двойной точности. Это можно сделать с помощью нижеприведенного отрезка программы (8 – число байтов в значениях двойной точности). double precision buffer(2*MAXNX+2*MPI_BSEND_OVERHEAD) call MPI_BUFFER_ATTACH( buffer, MAXNX*8+2*MPI_BSEND_OVERHEAD*8, ierr ) Заметим, что дополнительное пространство размещено в буфере. Для каждого сообщения, посланного при помощи MPI_BSEND, дол- жен быть зарезервирован дополнительный объем памяти размером MPI_BSEND_OVERHEAD байтов. Реализация MPI использует это внутри процедуры MPI_BSEND, чтобы управлять буферной областью и коммуникациями. Когда программа больше не нуждается в буфере, необходимо вызвать процедуру MPI_BUFFER_DETACH. 198 Имеется следующая особенность использования процедуры MPI_BSEND . Может показаться, что цикл, подобный следующему, способен посылать 100 байтов на каждой итерации: size = 100 + MPI_BSEND_OVERHEAD call MPI_BUFFER_ATTACH(buf, size, ierr ) do 10 i=l, n call MPI_BSEND(sbuf,100,MPI_BYTE,0, dest, МPI_COMM_WORLD, ierr ) . . . other work 10 continue call MPI_BUFFER_DETACH( buf, size, ierr ) Здесь проблема состоит в том, что сообщение, посланное в i-й ите- рации, может быть не доставлено, когда следующее обращение к MPI_BSEND имеет место на i+1-й итерации. Чтобы в этом случае правильно использовать MPI_BSEND, необходимо, чтобы либо бу- фер, описанный с помощью MPI_BUFFER_DETACH, был доста- точно велик, чтобы хранить все посланные данные, либо buffer attach и detach должны быть перемещены внутрь цикла. Неблокирующий обмен. Для большинства параллельных процес- соров обмен данными между процессами требует больше времени, чем внутри одиночного процесса. Чтобы избежать падения эф- фективности, во многих параллельных процессорах используется со- вмещение вычислений с одновременным обменом данными. В этом случае должны использоваться неблокирующие приемопередачи. Процедура MPI_ISEND начинает неблокирующую передачу. Процедура аналогична MPI_SEND за исключением того, что для MPI_ISEND буфер, содержащий сообщение, не должен модифициро- ваться до тех пор, пока сообщение не будет доставлено. Самый легкий путь проверки завершения операции состоит в использовании опера- ции MPI_TEST: call MPI_ISEND( buffer, count, datatype, dest, tag, cornm, request, ierr ) < do other work > 10 call MPI_TEST ( request, flag, status, ierr ) if (.not. flag) goto 10 Часто желательно подождать до окончания передачи. Тогда вместо того, чтобы писать цикл, как в предыдущем примере, можно исполь- зовать MPI_WAIT: call MPI_WAIT ( request, status, ierr ) 199 Когда неблокирующая операция завершена (то есть MPI_WAIT или MPI_TEST возвращают результат с flag=.true.), устанавливается значение MPI_REQUEST_NULL. Процедура MPI_IRECV начинает неблокирующую операцию приема. Точно так же как для MPI_ISEND, функция MPI_TEST мо- жет быть использована для проверки завершения операции приема, начатой MPI_IRECV, а функция MPI_WAIT может быть использо- вана для ожидания завершения такого приема. MPI обеспечивает спо- соб ожидания окончания всех или некоторой комбинации неблоки- рующих операций (функции MPI_WAITALL и MPI_WAITANY). Процедура EXCHNG1 с использованием неблокирующего обмена будет выглядеть следующим образом: subroutiтe exchng1 ( a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e, comm1d, nbrbottom, nbrtop double precision a(0:nx+l, s-l:e+l) integer status_array(MPI_STATUS_SIZE,4), ierr, req(4) call MPI_IRECV ( a(l, s-l), nx, MPI_DOUBLE_PRECISION, nbrbottom,0, comm1d, req(l), ierr ) call MPI_IRECV ( a(l, e+l), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, req(2), ierr ) call MPI_ISEND ( a(l, e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, comm1d, req(3), ierr ) call MPI_ISEND ( a(l, s), nx, MPI_DOUBLE_PRECISION, nbrbottom, comm1d, req(4), ierr ) MPI_WAITALL ( 4, req, status_array, ierr ) return end Этот подход позволяет выполнять в одно и то же время как пере- дачу, так и прием. При этом возможно двойное ускорение по сравне- нию с вариантом на рис. 8.4, хотя немногие существующие системы допускают это. Чтобы совмещать вычисления с обменом, необходимы некоторые изменения в программах. В частности, необходимо изме- нить процедуру SWEEP так, чтобы можно было совершать не- которую работу, пока ожидается приход данных. Синхронизируемые Sends. Что надо сделать, чтобы гарантиро- вать, что программа не зависит от буферизации? Общего ответа на этот вопрос нет, но во многих специальных случаях можно показать, что, если программа работает успешно без буферизации, она будет выполняться и с некоторым размером буферов. MPI обеспечивает 200 способ послать сообщение, при котором ответ не приходит до тех пор, пока приемник не начнет прием сообщения. Процедура называется MPI_SSEND . Программы, не требующие буферизации, называют «экономными». КОНТРОЛЬНЫЕ ВОПРОСЫ И ЗАДАНИЯ К ГЛАВЕ 8 Контрольные вопросы к 8.2 1. Почему для 1D декомпозиции процессов использование картезианской топо- логии предпочтительнее, чем использование стандартного коммуникатора MPI_COMM_WORLD? 2. Что такое “теневая ” точка? Зачем необходимо введение “теневых” точек для решения задачи Пуассона методом итераций Якоби? 3. Предложите способ декомпозиции массивов по процессам, который будет лучше учитывать балансировку нагрузки, чем способ в MPE_DECOMP1D. 4. Почему процедуры EXCHNG1, SWEEP1D вызываются дважды в реализации итераций Якоби для решения задачи Пуассона на рис. 8.4.? 5. В чем особенность метода итераций Якоби для 2D декомпозиции процессов? Контрольные вопросы к 8.3 1. Объясните, почему при выполнении обмена на рис. 8.9. не возникает дедлок? 2. В чем различие между обменами: упорядоченныv send/reseive и ком- бинированныv send/receive? 3. Как зависит размер буфера для буферизованного обмена от размерности вы- числений? 4. Какие изменения необходимы в программах, чтобы можно было совершать некоторую работу, пока ожидаются данные в случае неблокирующего обме- на? 5. Что такое «экономные» программы? Задания для самостоятельной работы 8.1. Напишите программу реализации метода итераций Якоби для одномер- ной декомпозиции. Используйте для топологии квадратную сетку произвольного размера maxn*maxn. Рассмотрите несколько вариантов обмена: 1) блокирующий обмен; 2) операции сдвига вверх и вниз, реализуемые с помощью MPI_SendRecv; 3) неблокирующий обмен. 8.2 . Напишите программу реализации метода итераций Якоби для двумерной декомпозиции. |