Elementos básicos de cómputo paralelo Grandes paradigmas Máquinas de memoria compartida Todos los procesadores ven toda la memoria al mismo tiempo. La filosofía es básicamente dividir las tareas. EL estándar es OpenMP (Open Multi-Processing) Máquinas de memoria distribuida Cada procesador tiene su propia memoria. La filosofía es básicamente la de repartir los datos. Es necesario la comunicación explícita para pasar datos entre procesadores. EL estándar es MPI (Message Passing Interface). Procesamiento con GPUs Posible gracias al gran número de procesadores “tontos” en las tarjetas gráficas dedicadas. Aún no alcanzan una madurez los compiladores y demás herramientas de programación, pero hacia allá vamos. Limitante más grande es la RAM de las tarjetas y la necesidad de copiar memoria del CPU al GPU y viceversa. Ejemplo de código con OpenMP •Se compila el código activando las directivas de OpenMP, e.g. gfortran -openmp codigo.f -o ejecutable •El numero de hebras (CPUs) se define mediante una variable global. !$OMP PARALLEL private(NV) DO IP=1,NP DO NL=1,NLEVS !$OMP DO DO NV=0,NVEC LP(IP,NL,NV)=0 END DO !$OMP END DO NOWAIT END DO END DO DO NL=1,NLEVS !$OMP DO DO NV=0,NVEC UP(NEQ,NL,NV)=0. U(NEQ,NL,NV)=0. UP(NEQ1,NL,NV)=0. U(NEQ1,NL,NV)=0. END DO !$OMP END DO NOWAIT END DO DO NL=1,NLEVS NVMAX(NL)=0 ISTEP(NL)=0 END DO !$OMP END PARALLEL MPI: Message Passing Interface ■ ■ ■ ■ Es una librería de paso de mensajes. Disponible para FORTRAN77, C, Fortran90 y C++ y algunos otros lenguajes de programación. MPI sirve para comunicar entre procesos que tienen espacios de memoria distribuidos. Nos da herramientas de: Sincronización. Paso de datos entre procesadores. Documentación en: http:/www-unix.mcs.anl.gov/mpi/ El estándar completo http://www.mpi-forum.org Un buen tutorial https://computing.llnl.gov/tutorials/mpi/ Actualmente hay el estándar 3.1, pero la mayoría de lo que veremos aquí, es de MPI Standard 1.1. Es el standard de facto en clusters hoy en día. Como compilar y ejecutar en MPI En el programa necesario incluir un ‘header file’ con las definiciones de las librerías de MPI: include ‘mpif.h’ #include “mpi.h” (FORTRAN) (C) Se usan las instrucciones mpicc, mpicxx, mpif77, mpif90, como compiladores para C, C++, F77 y f90. mpif77 hello.f [opciones e.g. -O3 …] -o hello Para ejecutarlo se usa “mpiexec” mpiexec -np N ./hello Donde N (entero) es el numero de procesadores (ejemplo corriendo en 4 procesadores: mpiexec -np 4 ./hello) Ejemplo sencillo (fortran), hello.f90 program hello include 'mpif.h' integer :: err, rank,size call mpi_init(err) call mpi_comm_rank(mpi_comm_world, rank,err) call mpi_comm_size (mpi_comm_world, size,err) print*,"hellp world from processor", rank, " of" , size call mpi_finalize(err) end program hello Mismo ejemplo (en C), hello.c #include <stdio.h> #include "mpi.h" int main( int argc, char *argv[] ) { int rank; int size; MPI_Init( 0, 0 ); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); printf("Hello world from processor %d of %d\n", rank, size); MPI_Finalize(); return 0; } Instrucciones básicas de comunicación: send ■ Definición: En C: int MPI_Send(void* buf, int count, MPI_Datatype datatype,int dest, int tag, MPI_Comm comm) En fortran MPI_SEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM, IERROR) Instrucciones básicas de comunicación: recv ■ Definición ■ En C int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) ■ En fortran MPI_RECV(BUF, COUNT, DATATYPE, SOURCE, TAG, COMM, STATUS, IERROR) Ejemplo de uso en sendrcv.f90 Instrucción básica de sincronización ■ MPI_BARRIER Pone un semáforo, todos los procesadores asociados al comunicador esperan en un punto del programa, cuando todos los lleguen ahí se reanuda la ejecución. Una barrera está implícita en muchas de las rutinas, por ejemoplo en las de sincronización sintaxis: fortran CALL MPI_BARRIER(COMM, IERR) C mpi_barrier(comm) Instrucciones colectivas Las mas útiles son Broadcast: mpi_bcast Gather: mpi_gather Reduce: mpi_reduce mpi_reduce ■ Ejemplo: sum.f90 Operaciones Predefinidas MPI_MAX MPI_MIN MPI_SUM MPI_PROD MPI_LAND MPI_BAND MPI_LOR MPI_BOR MPI_LXOR MPI_BXOR MPI_MAXLOC MPI_MINLOC maximum minimum sum product logical and bit-wise and logical or bit-wise or logical xor bit-wise xor max value and location min value and location Filosofía para códigos eulerianos ■ Se parte el dominio y cada procesador resuelve un pedazo La comunicación se convierte en el paso de condiciones de frontera. Condiciones de frontera y “celdas fantasma” nx=nxtot/(num of proc en x) 0, 1… …nx,nx+1 0, 1… 0, 1… …nxtot,nxtot+1 0, 1… …nx,nx+1 En código (f90) … integer, parameter :: neq=4 integer, parameter :: nghost=1 integer, parameter :: nxtot=512,nytot=512 ! Numero de ecuaciones ! Celdas fantasma ! Tamaño de la malla !arreglo de procesadores en las direcciones x e y integer, parameter :: mpiNX=2, mpiNY=2 !tamaño de los arreglos para c/processador integer, parameter :: nx=nxtot/mpiNX, ny=nytot/mpiNY !limites de los arreglos integer, parameter :: nxmin=1-nghost, nxmax=nx+nghost integer, parameter :: nymin=1-nghost, nymax=ny+nghost !definimos los arreglos real :: u(neq,nxmin:nxmax,nymin:nymax) … Creando un arreglo cartesiano de procesadores !procs en X (columnas mpiNX) dims(1) = 4 !procs en y (filas, mpiNY) dims(2) = 3 !periodicidad en x? periods(1) = .true. Periodicidad en y? periods(2) = .false. !reordena los procesadores? reorder = .true. !dimensiones ndim = 2 call MPI_CART_CREATE( MPI_COMM_WORLD, ndim, dims, $ periods, reorder, comm2d, ierr ) Identificandose dentro del arreglo program main Include ‘mpif.h’ integer, parameter :: ndim=2 !Dimensiones integer, parameter :: mpiNX=2, mpiNY=2 !procs en x e y logical, parameter :: reorder=.true. integer, dimension(0:ndim-1) :: dims, coords logical, dimension(0:ndim-1):: period ! Variables adicionales integer :: rank, nps, ireq(2), err, comm2d, me, status(MPI_STATUS_SIZE) period(0) = .false. dims(0) = mpiNX ; period(1) = .false. ; dims(1) = mpiNY call mpi_init (err) call mpi_comm_rank (mpi_comm_world,rank,err) call mpi_comm_size (mpi_comm_world,nps,err) !inicializa mpi !obtiene su ID !obtiene el num total de procs if(rank == 0) then print * ,'*****************************************' print '(a,i3,a)',' * running with mpi with', nps , ' processors *' print * ,'*****************************************' end if !Crea arreglo de procesadores call mpi_cart_create(mpi_comm_world, ndim, dims, period, reorder, comm2d, err) call mpi_comm_rank(comm2d, rank, err) !actualiza su ID call mpi_cart_coords(comm2d, rank, ndim, coords, err) !obtiene coordenadas print '(a,i3,a,2i4)', 'processor ', rank,' ready w/coords',coords(0),coords(1) call mpi_finalize(err) end program main Para ubicar las coordenadas x,y,(z), en cada procesador (e.g. para poner condiciones iniciales.) ■ En fortran: dx=xphys/nxtot dy=xphys/nytot x=( float(i+coords(0)*nx) ) * dx y=( float(j+coords(1)*ny) ) * dy r=sqrt(x**2.+y**2.) Identificando a los “vecinos” MPI_CART_SHIFT(COMM, DIRECTION, DISP, RANK_SOURCE, RANK_DEST, IERROR) NTEGER COMM, DIRECTION, DISP, RANK_SOURCE, RANK_DEST, IERROR) ■ Agregar lo siguiente al programa anterior integer :: left, right, bottom, top call mpi_cart_shift(comm2d, 0, 1, left , right, ierr) call mpi_cart_shift(comm2d, 1, 1, bottom, top , ierr) print*,rank,left,right,top,bottom Pasando las c. de frontera ■ ■ Es conveniente usar la rutina mpi_sendrecv, una vez habiendo identificado los vecinos. definición: MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG, RECVBUF, RECVCOUNT, RECVTYPE, SOURCE, RECVTAG, COMM, STATUS, IERROR) Ejemplo c. de frontera !definimos arreglos para pasar las condiciones de frontera real, dimension(neq,nxmin:nxmax,1) :: bxsendt,bxrecvt,bxsendb,bxrecvb real, dimension(neq,1,nymin:nymax) :: bysendr,byrecvr,bysendl,byrecvl !cuantos elementos en cada frontera (suponiendo una sola celda fantasma) integer, parameter :: bxsize=neq*(nxmax-nxmin) integer, parameter :: bysize=neq*(nymax-nymin) !llenamos arreglos que se van a pasar bysendr(:,1,:)=u(:,nx-1, : ) bysendl(:,1,:)=u(:, 1 , : ) bxsendt(:,:,1)=u(:, : ,ny-1) bxsendb(:,:,1)=u(:, : , 1 ) !se mandan y reciben los datos call mpi_sendrecv(bysendr, bysize, byrecvl, bysize, call mpi_sendrecv(bxsendt, bxsize, bxrecvb, bxsize, call mpi_sendrecv(bysendl, bysize, byrecvr, bysize, call mpi_sendrecv(bxsendb, bxsize, bxrecvt, bxsize, !se reacomodan if (left .ne. -1) u(:, 0 , : ) if (right .ne. -1 u(:,nx+1, : ) if (bottom.ne. -1) u(:, : , 0 ) if (top .ne. -1) u(:, : ,ny+1) mpi_real, mpi_real, mpi_real, mpi_real, mpi_real, mpi_real, mpi_real, mpi_real, = = = = right , left , top , bottom, left , right , bottom, top , byrecvl(:,1,:) byrecvr(:,1,:) bxrecvb(:,:,1) bxrecvt(:,:,1) 0, 0, 0, 0, 0, 0, 0, 0, & comm2d, status , err) & comm2d, status , err) & comm2d, status , err) & comm2d, status , err)