Introducción a MPI


Multiplicación Matriz-Vector

El algorítmo de multiplicación matriz-vector utilizado aquí es el tradicionalmente basado en la ecuación:

Formula de integracion

La estrategia de paralelización aplicada consiste en lo siguiente:


program matvecmul
implicit none
include 'mpif.h'

integer N_MAX, M_MAX, MAX_PROC
parameter (N_MAX = 1000, M_MAX = 1000, MAX_PROC=64)
real a(N_MAX, M_MAX), b(M_MAX), c(M_MAX)
real av(M_MAX), bufa(M_MAX), bufb(M_MAX), cj
integer row, my_rank, MASTER, n_proc, ierr, status(MPI_STATUS_SIZE)
integer n, m, i, j, rowa, rowb, proc, res, n_rows, ne_proc, req(2)
logical flag

call MPI_INIT(ierr)
call MPI_COMM_SIZE(MPI_COMM_WORLD, n_proc, ierr)
call MPI_COMM_RANK(MPI_COMM_WORLD, my_rank, ierr)

! el procesador maestro solo va a realizar tareas de comunicacion y 
! concentracion de los resultados finales. Por lo tanto, se necesitan por lo
! menos de 2 procesadores para trabajar.

if(n_proc .lt. 2)then
   write(*, "(a48, a21)")"Se deben utilizar dos procesadores por lo menos.", &
      " Execucion Terminada."
   call MPI_ABORT(MPI_COMM_WORLD, 969)
   stop
elseif(n_proc .gt. MAX_PROC)then
   write(*, "(a41, a21)")"Se deben utilizar 64 procesadores maximo.", &
      " Execucion Terminada."
   call MPI_ABORT(MPI_COMM_WORLD, 334)
   stop
endif
data MASTER/0/
write(*, "(a11, i3, a4, i4, a10)")"Procesador ", my_rank, " de ", n_proc, &
   " activado."
n = 1000
m = 1000
if(my_rank .eq. MASTER)then

   ! procesador maestro genera vector 'b' y lo envia al resto de los
   ! procesadores. La generacion de los datos se realiza haciendo variar
   ! el primer indice de la matriz 'a' primero con objeto de trabajar
   ! este arreglo en la forma en que Fortran lo almacena en memoria

   do j = 1, m
      b(j)= j
      do i = 1, n
         a(i, j) = i
      end do
   end do
endif

call MPI_BCAST(b, m, MPI_REAL, MASTER, MPI_COMM_WORLD, ierr)

! determinar el numero de renglones para cada procesador. El procesador
! maestro solo realizara procesos de comunicacion y los procesadores
! 1 al nproc-1 efectuaran los calculos

ne_proc = n_proc - 1
n_rows = n / ne_proc
res = mod(n, ne_proc)

! debido a que ahora va a haber un procesador menos trabajando, la condicion
! de este 'if' se cambia de '.lt.' a '.le.'

if(my_rank .le. res)n_rows = n_rows + 1

if(my_rank .eq. MASTER)then
   proc = 1
   do row = 1, n
      
      ! hay que determinar cual de los dos buffers de comunicacion se encuentran
      ! libres para hacer la comunicacion

      do
         call MPI_TEST(req(1), flag, status(1), ierr)
         if(flag)then
            rowa = row
            do j = 1, m
               bufa(j) = a(rowa, j)
            end do

            ! enviar un renglon al procesador 'proc'. El envio se esta 
            ! etiquetando con el numero de renglon, el cual sera utilizado 
            ! para diferenciar los resultados al ser recibidos por el 
            ! procesador maestro

            call MPI_SEND(rowa, 1, MPI_INTEGER, proc, 0, MPI_COMM_WORLD, ierr)

            ! enviar un renglon de datos y continuar inmediatamente nuestro
            ! trabajo

            call MPI_ISEND(bufa, m, MPI_REAL, proc, rowa, MPI_COMM_WORLD, &
               req(1), ierr)
            exit 
         end if

         ! si el buffer 'a' no esta disponible, entonces verificar si el buffer
         ! 'b' lo esta. En caso contrario, seguir en el ciclo 'do ... enddo'
         ! hasta que se desocupe algun buffer.

         call MPI_TEST(req(2), flag, status(2), ierr)
         if(flag)then
            rowb = row
            do j = 1, m
               bufb(j) = a(rowb, j)
            end do
            call MPI_SEND(rowb, 1, MPI_INTEGER, proc, 0, MPI_COMM_WORLD, ierr)
            call MPI_ISEND(bufb, m, MPI_REAL, proc, rowb, MPI_COMM_WORLD, &
               req(2), ierr)
            exit 
         end if
      end do
      proc = proc + 1
      if(proc .eq. n_proc)proc = 1
   end do   
   do row = 1, n

      ! esta instruccion MPI_RECV obtiene resultados de cualquier procesador
      ! (MPI_ANY_SOURCE) pero correspondientes al renglon 'row'
      
      call MPI_RECV(cj, 1, MPI_REAL, MPI_ANY_SOURCE, row, MPI_COMM_WORLD, &
         status, ierr)
      c(row) = cj
   end do
   write(*, "(5(1p, e14.5))")(c(i), i = 1,n)
else
   do i = 1, n_rows

      ! recibir y procesar el renglon 'row' de parte del procesador
      ! maestro

      call MPI_RECV(row, 1, MPI_INTEGER, MASTER, 0, MPI_COMM_WORLD, &
         status, ierr)
      call MPI_RECV(av, m, MPI_REAL, MASTER, row, MPI_COMM_WORLD, &
         status, ierr)
      cj = 0.0
      do j = 1, m
         cj = cj + av(j) * b(j)
      end do

      ! enviar el elemento 'row' del producto punto al procesador maestro

      call MPI_SEND(cj, 1, MPI_REAL, MASTER, row, MPI_COMM_WORLD, ierr)
   end do
endif

call MPI_FINALIZE(ierr)

end program matvecmul

Compile este código y ejecutelo con arreglos de diferentes ordenes. Determine la relación entre el tiempo de proceso dedicado por el procesador maestro a generar los datos y a comunicarlos a los procesadores servidores y el tiempo de ejecución empleado por éstos.

Integracion Numerica (Instruccion de Envio sin Bloqueo Contenido Laplaciano 2-D (Jacobi)