Równoległy algorytm metody Jacobiego rozwiązywania zagadanienia brzegowego dla eliptycznych równań różniczkowych cząstkowych
Sformułowanie problemu
Dyskretyzacja zagadnienia
Rozwiązanie numeryczne metodą iteracji Jacobiego
Kod szeregowy iteracji Jacobiego !Main Loop DO WHILE(.NOT.converged) ! perform 4 point stencil DO j=1, n DO i=1, n B(i,j)=0.25*(A(i-1,j)+A(i+1,j)+A(i,j-1)+A(i,j+1)-& H*H*F(I,J)) END DO ! copy result back into array A A(i,j) = B(i,j) ... ! convergence test omitted
Przykładowy podział punktów siatki pomiędzy procesory dla 9 warstw i 3 procesorów
Projektowanie komunikacji Do obliczenia elementów tablicy u leżących na granicy podziału, procesor o 1 będzie potrzebował elementów u z pierwszego rzędu przypisanego procesorowi 2 oraz ostatniego rzędu przypisanego procesorowi 0.Podobnie, procesory 0 i 2 będą potrzebowały od procesora 1 elementów z odpowiednio pierwszego i ostatniego rzędu jemu przypisanych.
Kod iteracji Jacobiego dla warstwy punktów siatki przypisanej danemu procesorowi c Perform a Jacobi sweep for a 1-d decomposition. c Sweep from a into b subroutine sweep1d( a, f, nx, s, e, b ) integer nx, s, e double precision a(0:nx+1,s-1:e+1), f(0:nx+1,s-1:e+1), + b(0:nx+1,s-1:e+1) integer i, j double precision h h = 1.0d0 / dble(nx+1) do 10 j=s, e do 10 i=1, nx b(i,j) = 0.25 * (a(i-1,j)+a(i,j+1)+a(i,j-1) + a(i+1,j) - h * h * f(i,j) ) 10 continue return end
Wariant bardzo nieoptymalny (blokujące SEND i RECEIVE) subroutine exchng1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) include 'mpif.h' integer nx, s, e double precision a(0:nx+1,s-1:e+1) integer comm1d, nbrbottom, nbrtop integer status(MPI_STATUS_SIZE), ierr C call MPI_SEND( a(1,e), nx, MPI_DOUBLE_PRECISION, * nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(1,s-1), nx, MPI_DOUBLE_PRECISION, * nbrbottom, 0, comm1d, status, ierr ) call MPI_SEND( a(1,s), nx, MPI_DOUBLE_PRECISION, * nbrbottom, 1, comm1d, ierr ) call MPI_RECV( a(1,e+1), nx, MPI_DOUBLE_PRECISION, * nbrtop, 1, comm1d, status, ierr ) return end
Czas oczekiwania przez poszczególne procesory na dane w przypadku zastosowania wariantu 1
Rozpoczynanie albo od SEND albo od RECEIVE w zależności od rzędu procesora subroutine exchng1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e double precision a(0:nx+1,s-1:e+1) integer comm1d, nbrbottom, nbrtop, rank, coord 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(1,e), nx, MPI_DOUBLE_PRECISION, & nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(1,s-1), nx, MPI_DOUBLE_PRECISION, & nbrbottom, 0, comm1d, status, ierr ) call MPI_SEND( a(1,s), nx, MPI_DOUBLE_PRECISION, & nbrbottom, 1, comm1d, ierr ) call MPI_RECV( a(1,e+1), nx, MPI_DOUBLE_PRECISION, & nbrtop, 1, comm1d, status, ierr ) else endif return end
Wykorzystanie procedury MPI_Sendrecv subroutine exchng1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) include 'mpif.h' integer nx, s, e double precision a(0:nx+1,s-1:e+1) integer comm1d, nbrbottom, nbrtop integer status(MPI_STATUS_SIZE), ierr c call MPI_SENDRECV(a(1,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, & a(1,s-1), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, comm1d, status, ierr ) call MPI_SENDRECV(a(1,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, 1, & a(1,e+1), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, comm1d, status, ierr ) return end
Używanie buforowanego SEND subroutine exchng1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) use mpi integer nx, s, e double precision a(0:nx+1,s-1:e+1) integer comm1d, nbrbottom, nbrtop integer status(MPI_STATUS_SIZE), ierr call MPI_BSEND( a(1,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, comm1d, ierr ) call MPI_RECV( a(1,s-1), nx, MPI_DOUBLE_PRECISION, nbrbottom, & 0, comm1d, status, ierr ) call MPI_BSEND( a(1,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, & 1, comm1d, ierr ) call MPI_RECV( a(1,e+1), nx, MPI_DOUBLE_PRECISION, nbrtop, & 1, comm1d, status, ierr ) return end
Połączenie nieblokowanego SEND z WAITALL subroutine exchng1( a, nx, s, e, comm1d, nbrbottom, nbrtop ) include 'mpif.h' integer nx, s, e double precision a(0:nx+1,s-1:e+1) integer comm1d, nbrbottom, nbrtop integer status_array(MPI_STATUS_SIZE,4), ierr, req(4) C call MPI_IRECV (a(1,s-1), nx, MPI_DOUBLE_PRECISION, nbrbottom, 0, * comm1d, req(1), ierr ) call MPI_IRECV (a(1,e+1), nx, MPI_DOUBLE_PRECISION, nbrtop, 1, * comm1d, req(2), ierr ) call MPI_ISEND (a(1,e), nx, MPI_DOUBLE_PRECISION, nbrtop, 0, * comm1d, req(3), ierr ) call MPI_ISEND (a(1,s), nx, MPI_DOUBLE_PRECISION, nbrbottom, 1, * comm1d, req(4), ierr ) call MPI_WAITALL ( 4, req, status_array, ierr ) return end
Program główny: definiowanie topologii w celu podziału procesorów pomiędzy poszczególne warstwy siatki. Źródło programu
Porównanie czasów wykonania równoległego kodu iteracji Jacobiego dla zagadnienia Poissona dla różnych wariantów komunikacji P Blokujące send Uporządkowane send sendrecv Buforowane send Nieblokujące send 1 5.38 5.54 5.40 2 2.77 2.88 2.91 2.75 4 1.58 1.56 1.57 1.50 1.51 8 1.15 0.947 0.931 0.854 0.849 16 1.18 0.574 0.534 0.521 0.545 32 1.94 0.443 0.451 0.452 0.397 64 3.73 0.447 0.391 0.362