Opciones de programación Diseño específico de un lenguaje: Occam ? Transputer Extensión de lenguaje existente: Fortran M, CC++, Cilk++ www.netlib.org/fortran-m www-unix.mcs.anl.gov/dbpp/text/node51.html www.cilk.com Biblioteca de paso de mensajes sobre C, Fortran, C++ PVM: www.csm.ornl.gov/pvm MPI: www-unix.mcs.anl.gov/mpi HPC++: www.extreme.indiana.edu/hpc++/index.html Paralelización automática: El compilador extrae paralelismo del código secuencial ¿ Sistema Operativo ? OpenMP
(Gp:) 1979 Proyecto Europeo: Inmos SGS-Thomson ICL … (Gp:) Objetivo: µP de altas prestaciones, como elemento básico para construir máquinas paralelas (multicomputadores) (Gp:) 1992: habían vendido 500.000 unidades (µP+1MB => 180.000pts) (Gp:) Característica relevante: 4 enlaces de comunicación (canales)
(Gp:) T800 (Gp:) 0 (Gp:) 1 (Gp:) 2 (Gp:) 3
Ejecución eficiente de n procesos que envían/reciben mensajes por canales (Gp:) P1 (Gp:) P2 (Gp:) P3
Opciones (TRANSPUTER) (Gp:) cP3 ! dato (Gp:) cP2 ? dato
Creación de procesos ¿Cómo podría ser contarPar.c si no hay memoria común? (Gp:) 1 (Gp:) 3 (Gp:) 2 (Gp:) 2 (Gp:) 6 2 0 (Gp:) 0 6 7 (Gp:) 6 2 5 (Gp:) 2 1 7 (Gp:) + (Gp:) 8
(Gp:) esclavo1 (Gp:) esclavo2 (Gp:) esclavo3 (Gp:) esclavo4
(Gp:) maestro (Gp:) 6 2 0 (Gp:) 0 6 7 (Gp:) 6 2 5 (Gp:) 2 1 7 (Gp:) El vector lo tiene un proceso maestro
El maestro: reparte envía trabajo a los esclavos y recoge recibe resultados (Gp:) 6 2 0 (Gp:) 0 6 7 (Gp:) 6 2 5 (Gp:) 2 1 7
(Gp:) 1 (Gp:) 3 (Gp:) 2 (Gp:) 2
(Gp:) 2 tipos distintos de procesos
Creación de procesos (Gp:) ¿Cómo podría ejecutarse la aplicación? (Gp:) maestro (Gp:) esclavo1 (Gp:) esclavo2 (Gp:) esclavoN
(Gp:) Un proceso x núcleo (Gp:) M (Gp:) E (Gp:) E (Gp:) E
Dos ejecutables: maestro.exe y esclavo.exe (Gp:) maestro.c (Gp:) esclavo.c (Gp:) maestro.exe (Gp:) esclavo.exe
(Gp:) Multiple Program Multiple Data (Gp:) MPMD
(Gp:) maestro.exe (Gp:) esclavo.exe
Creación de procesos: estática vs dinámica ¿Arquitecturas distintas? ¿ SPMD ?
Creación de procesos (creación dinámica) MPMD: Multiple Program Multiple Data (Gp:) ————————— spawn (esclavo, 4, …) ————————— (Gp:) maestro.c (Gp:) ————————— contarEnTrozo (……) ————————— (Gp:) esclavo.c
(Gp:) maestro
(Gp:) esclavo
(Gp:) %pvm pvm>add PC2 ….. pvm>spawn maestro
M (Gp:) E (Gp:) E
Creación de procesos (creación estática) SPMD: Single Program Multiple Data programa fuente (Gp:) *.exe (Gp:) *.exe (Gp:) *.exe
(Gp:) M (Gp:) E
%mpirun np 9 sort
%mpirun p4pg fiConf sort (Gp:) ————————— if (miIdProceso == 0) maestro() else esclavo() ————————— (Gp:) sort.c
(Gp:) 0 (Gp:) 1 (Gp:) 8
Creación de procesos A veces, viene bien que el maestro también trabaje (Gp:) esclavo1 (Gp:) esclavo2 (Gp:) esclavo3 (Gp:) maestro0 (Gp:) 6 2 0 (Gp:) 0 6 7 (Gp:) 6 2 5 (Gp:) 2 1 7 (Gp:) 0 6 7 (Gp:) 6 2 5 (Gp:) 2 1 7 (Gp:) 1 (Gp:) 3 (Gp:) 2 (Gp:) 2
Modelo SPMD y creación de procesos estática
Rutinas genéricas de paso de mensajes Pi.enviar(Pj, &msj, …) ? Pj.recibir(Pi, &msj, …) (Gp:) ————————— enviar (esclavo, &rodaja[k], …) ————————— (Gp:) sortM (Gp:) ————————— recibir (maestro, &miRodaja, …) ————————— (Gp:) sortE
(Gp:) Máquina PID_Local
Varias semánticas: Bloqueante (Síncrona | Rendezvous) No bloqueante (Asíncrona: Capacidad de almacenar) Temporizada (Tiempo máximo de bloqueo) (Gp:) enviar( Pi, &msj, temporización, ) (Gp:) ? (Gp:) 0 (Gp:) t
Rutinas genéricas de paso de mensajes Bloqueante vs NoBloqueante (Gp:) ———- ———- enviar… ———- ———- (Gp:) ———- ———- recibir… ———- ———-
(Gp:) El primero se bloquea
(Gp:) Transferencia sin buffers adicionales
(Gp:) Se sabe que se ha recibido
(Gp:) ———- ———- enviar… ———- ———- (Gp:) ———- ———- recibir… ———- ———- (Gp:) Buffer
(Gp:) Se desacopla env | rec
(Gp:) Gestión de buffers
(Gp:) ¿ Dónde ?
(Gp:) ¿Se ha recibido? => msjACK
enviar( Pi, &msj, temporización, …)
Rutinas genéricas de paso de mensajes recibirDeCualquiera (&msj) | recibir ( -1, &msj) (Gp:) sortE1 (Gp:) sortEn (Gp:) sortM
(Gp:) ————————— quien = recibir (-1, &rodaja[k], …) ————————— (Gp:) sortM
Mensajes etiquetados P P P P P P P H (Gp:) enviar (S, &msj, tipoP) enviar (S, &msj, tipoH) (Gp:) recibir (-1, &msj, tipoH)
recibir (-1, &msj, -1 ) (Gp:) Cliente (Gp:) Servidor (Gp:) Cliente (Gp:) Cliente
Paso de mensajes entre grupos Broadcast (a todos) Multicast (a unos concretos) (Gp:) ————————— Para todo esclavo enviar (esclavo, &palabra, …) ————————— (Gp:) cuentaM
Problema: Número de ocurrencias de (un dato)* en array grande (Gp:) cuentaM (Gp:) cuentaE1 (Gp:) cuentaEn (Gp:) cuentaE2
(Gp:) Ala (Gp:) Ala (Gp:) Ala
(Gp:) ————————— bcast (esclavos, &palabra, …) ————————— (Gp:) cuentaM
¿Más eficiente? ¿Sincronismo?
(Gp:) sortM
(Gp:)
sortE1 (Gp:)
sortEn (Gp:) V
(Gp:) sortM
(Gp:)
sortE1 (Gp:)
sortEn (Gp:) V
Paso de mensajes entre grupos scatter ( esparcir ) y gather ( recoger ) Ej: Ordenación (Gp:) scatter (V, rodaja, grupo, emisor, ) (Gp:) sortM y sortE
(Gp:) gather (V, rodaja, grupo, receptor, ) (Gp:) sortM y sortE
Paso de mensajes entre grupos int V[N];
inic(V); i=0; for (e=1; e< =nProc; e++) { enviar (e, &V[i], longRodaja); i += longRodaja; } ordenar (V, longRodaja); i=0; for (e=1; e< =nProc; e++) { recibir(e, &V[i], longRodaja); i += longRodaja; } int R[longRodaja];
recibir (0, R, longRodaja); ordenar (V, longRodaja); enviar (0, R, longRodaja); int *V;
if (yo==0) { V = malloc(N); inic(V); } else V = malloc(longRodaja); scatter (V, V, grupo, 0); ordenar (V, longRodaja); gather (V, V, grupo, 0);
(Gp:) cuentaM
(Gp:)
cuentaE1 (Gp:) 2 (Gp:) 3 (Gp:)
cuentaE2 (Gp:) 5 (Gp:)
cuentaEn (Gp:) 1
Paso de mensajes entre grupos reduce (recibir de unos concretos y operar) Ej: Ocurrencias (Gp:) Total = veces; Para todo esclavo recibir (esclavo, &veces, …) Total += veces; (Gp:) cuentaM
(Gp:) reduce (+, &veces, grupo, receptor, …) (Gp:) cuentaM y cuentaE (Gp:) +
(Gp:) máximo, mínimo, *, …..
¿Más eficiente?
MPI (Introducción) MPI: Message Passing Interface 1994 MPI Forum [Nov/92] Ejecución de aplicaciones paralelas distribuidas en ordenadores heterogéneos (Gp:) maestro (Gp:) esclavo1 (Gp:) esclavo3 (Gp:) esclavo2 (Gp:) esclavo4
Cada uno con su dirIP Biblioteca mpi.h MPI_Send, MPI_Recv, ————- (Gp:) M (Gp:) E1 (Gp:) E2 (Gp:) E3 (Gp:) E4
Implementación MPICH LAM/MPI IBM, MPI-2 MPI-3
(Gp:) >= 0 => MPI_SUCCESS, significado concreto < 0 => un error ( … MPI_ERR_ARG …)
MPI (Procesos) (Gp:) int MPI_Comm_size( , int *numProcesos); (Gp:) int MPI_Comm_rank( , int *yo); (Gp:) int MPI_Finalize(); (Gp:) int MPI_Init( ……. ); /* Inicia MPI */
Creación estática de procesos (según implementación mpirun) (Gp:) 0 (Gp:) 1 (Gp:) 3 (Gp:) 2 (Gp:) 4 (Gp:) MPI_COMM_WORLD
2 (Gp:) 5
(Gp:) 2
MPI (Procesos) (Gp:) helloWorld paralelo (Gp:) #include mpi.h int main (int argc, char *argv[]) { int yo, numProcesos;
MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); MPI_Comm_rank (MPI_COMM_WORLD, &yo); if (yo == 0) printf (Creados %d procesosn, numProcesos); printf (Hola, soy el proceso %dn, yo); MPI_Finalize(); }
% mpirun np 3 helloWorld % mpirun p4pg helloWorld.txt helloWorld (Gp:) pc1 2 /usuarios/alumnosPropar/propar01/p1/helloWorld pc2 2 /usuarios/alumnosPropar/propar01/p1/helloWorld pc3 1 /usuarios/alumnosPropar/propar01/p1/holaMundo
MPI (Envío y Recepción Simple) Enviar y recibir arrays de datos simples (int, byte, …) Bloqueante
int vector[N]; ———- MPI_Send (vector, P2, …) ———- P1
int tabla[M]; ———- MPI_Recv (tabla, P1, …) ———- P2 int MPI_Send(void *buffer, int cuantos, MPI_Datatype tipo, int destino, int etiqueta, MPI_Comm grupo) (Gp:) MPI_INT, MPI_FLOAT,
(Gp:) MPI_COMM_WORLD (Gp:) 0..MPI_TAG_UB
MPI (Envío y Recepción Simple) Enviar y recibir arrays de datos simples (int, byte, …) Bloqueante int MPI_Recv(void *buffer, int cuantos, MPI_Datatype tipo, int remitente,int etiqueta,MPI_Comm grupo, MPI_Status *estado) (Gp:) estado.MPI_SOURCE estado.MPI_TAG
(Gp:) MPI_ANY_SOURCE
(Gp:) MPI_ANY_TAG
(Gp:) int MPI_Get_count( MPI_Status *estado, MPI_Datatype tipo, int *cuantos)
MPI (Envío y Recepción Simple) Ejemplo de uso: psendrec.c #include < stdio.h> #include < unistd.h>
#include mpi.h"
#define N 3 #define VECES 5
void esclavo(void) {…} void maestro(void) {…}
int main( int argc, char *argv[] ) { int yo; MPI_Init (&argc, &argv); MPI_Comm_rank (MPI_COMM_WORLD, &yo); if (yo == 0) maestro(); else esclavo(); MPI_Finalize(); return 0; }
MPI (Envío y Recepción Simple) Ejemplo de uso: psendrec.c void maestro (void) {
int i, j, vector[N];
for (i=0; i< VECES; i++) { printf ("M: envia => "); for (j=0; j< N; j++) { vector[j] = i*N+j; printf("%d ", vector[j]); } printf ("n"); MPI_Send (vector, N, MPI_INT, 1, 1, MPI_COMM_WORLD); } } (Gp:) esclavo
MPI (Envío y Recepción Simple) Ejemplo de uso: psendrec.c void esclavo(void) {
int i, j,tabla[N], n; MPI_Status estado;
sleep(2); for (i=0; i< VECES; i++) { MPI_Recv (tabla, N, MPI_INT, 0, 1, MPI_COMM_WORLD, &estado); MPI_Get_count (&estado, MPI_INT, &n); printf ("E: recibe => "); for (j=0; j< N; j++) printf("%d ", tabla[j]); printf (" de tid = %d eti = %d longi = %dn", estado.MPI_SOURCE, estado.MPI_TAG, n); } } (Gp:) maestro
MPI (Envío y Recepción No Tan Simple) int MPI_Iprobe(int origen, int etiqueta, MPI_Comm grupo, int *hayMsj, MPI_Status *estado) Enviar y recibir arrays de datos simples No Bloqueante Enviar y recibir datos no homogéneos Crear tipos => Algo tedioso (Gp:) Hacer otras cosas (Gp:) NO
(Gp:) int MPI_Recv(void *buffer, MPI_Status *estado) (Gp:) SI
MPI (Comunicación colectiva) int MPI_Bcast(void *buffer, int cuantos, MPI_Datatype tipo, int emisor, MPI_Comm grupo) (Gp:) cuenta.0 (Gp:) cuenta.1 (Gp:) cuenta.N (Gp:) cuenta.2 (Gp:) Ala (Gp:) Ala (Gp:) Ala
void maestro () { void esclavo () { char palabra[4]; char texto[4];
———- ———- MPI_Bcast ( MPI_Bcast ( palabra, 4, MPI_CHAR, texto, 4, MPI_CHAR, 0, MPI_COMM_WORLD); 0, MPI_COMM_WORLD); ———- ———- (Gp:) ? (Gp:) tag
(Gp:) Send+Recv
MPI (Comunicación colectiva) int MPI_Scatter( void *vOrg, int nOrg, MPI_Datatype tOrg, void *vDst, int nDst, MPI_Datatype tDst, int emisor, MPI_Comm grupo); (Gp:)
grupoE.1 (Gp:)
grupoE.9 (Gp:) grupoM.0
int MPI_Reduce(void *vOrg, void *vDst, int nOrg, MPI_Datatype tDatoOrg, int operacion, int receptor, MPI_Comm grupo); (Gp:) +
(Gp:) MPI_SUM, MPI_PROD, MPI_MIN, MPI_MAX, ….
(Gp:) sendCount
MPI (Comunicación colectiva: Ejemplo) Ejemplo de uso completo: cuentaPar2.c (modelo SPMD) #include < stdio.h>
#include mpi.h"
#define CARDINALIDAD 2000000 #define MAX_ENTERO 1000 #define NUM_BUSCADO 5
int main( int argc, char *argv[] ) {
int i, numVeces, numVecesTotal, yo; int longRodaja, numProcesos; int *vector;
MPI_Init (&argc, &argv); MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); MPI_Comm_rank (MPI_COMM_WORLD, &yo); ————————— MPI_Finalize(); }
MPI (Comunicación colectiva: Ejemplo) Ejemplo de uso completo: cuentaPar2.c // Pedir memoria vector e inicializarlo si maestro longRodaja = CARDINALIDAD / numProcesos; if (yo == 0) { vector = malloc (CARDINALIDAD * 4); for (i=0; i< CARDINALIDAD; i++) vector[i] = random() % MAX_ENTERO; } else vector = malloc (longRodaja * 4);
MPI_Scatter (vector, longRodaja, MPI_INT, vector, longRodaja, MPI_INT, 0, MPI_COMM_WORLD); // Todos buscan en su trozo numVeces = 0; for (i=0; i< longRodaja; i++) if (vector[i] == NUM_BUSCADO) numVeces++; MPI_Reduce (&numVeces, &numVecesTotal, 1, MPI_INT, MPI_SUM, 0 , MPI_COMM_WORLD); if (yo == 0) printf (Numero de veces => %dn, numVecesTotal);
MPI (Comunicación colectiva) Otras llamadas interesantes: int MPI_Gather(void *vOrg, int nOrg, MPI_Datatype tOrg, void *vDst, int nDst, MPI_Datatype tDst, int receptor, MPI_Comm grupo); int MPI_Barrier(MPI_Comm grupo); ———- MPI_Comm_size (MPI_COMM_WORLD, &numProcesos); ———- // Computar todos paso 1
MPI_Barrier (MPI_COMM_WORLD);
// Computar todos paso 2 (Gp:) 6
Evaluación de programas paralelos ¿Cuánto tardará mi programa paralelo TP? Medición experimental Estudio analítico (Gp:) Codificar, Depurar, Probar, …
(Gp:) Modelo imperfecto, aproximado
(Gp:) m (Gp:) e0 (Gp:) e9 (Gp:) cuentaVeces: V[1.000.000] 10 esclavos
(Gp:) TP = TCPU + TRED
(Gp:) Enviar rodajas TRED
(Gp:) Recibir veces TRED
(Gp:) Contar veces TCPU
(Gp:) veces = 0; for (i=0; i< N; i++) if (rodaja[i] == D) veces++;
(Gp:) N (Gp:) N
(Gp:) N * (#i * Tinst)
(Gp:) O (N)
| O (N2) | O (NlogN) | O(logN)
Evaluación de programas paralelos (Gp:) #m * (TL + #d * TD)
(Gp:) #d (Gp:) t (Gp:) TL
(Gp:) TP = TCPU + TRED (Gp:) N * (#i * Tinst)
(Gp:) Ejemplos (Gp:) T3D+PVM (Gp:) IBM PS2+MPI (Gp:) iacluster+MPI (Gp:) 40?s (Gp:) 35?s (Gp:) 3?s (Gp:) TL (Gp:) 64ns (Gp:) 230ns (Gp:) 63ns (Gp:) TD[8B] (Gp:) 0,6ns (Gp:) 4,2ns (Gp:) 11ns (Gp:) Tinst
(Gp:) 66666 (Gp:) 8333 (Gp:) 273 (Gp:) TL (Gp:) 107 (Gp:) 55 (Gp:) 6 (Gp:) TD (Gp:) 1 (Gp:) 1 (Gp:) 1 (Gp:) Tinst (Gp:) Normalizar
(Gp:) TP = TCPU + TRED
(Gp:) Solapar tiempos | ocultar latencias Comunicación asíncrona
(Gp:) #msj, tamaño(#d), topología red, tecnología, …
Evaluación de programas paralelos Ejemplo: cuentaVeces, V[1.000.000], 10 esclavos T1 = 1.000.000 * (#i * Tinst) T10 = TRED(10Rodajas) + TCPU(100.000) + TRED(10Resultados) (Gp:) T3D: TL(273) y TD(6) (Gp:) T10 = 10*(273+100.000*6) + 100.000*#i*1 + 10*(273+6) = 6.005.520 + 100.000#i
S10 = 1.000.000*#i / (6.005.520+100.000#i)
(Gp:) iacluster: TL(66.666) y TD(64) (Gp:) T10 = … 66.666 … 64 + …*1 + … 66.666+64 = 65.333.960 + 100.000#i
S10 = 1.000.000*#i / (65.333.960 +100.000#i)
(Gp:) #i (Gp:) S10 (Gp:) 50 (Gp:) 0,71 (Gp:) 100 (Gp:) 1,33 (Gp:) 500 (Gp:) 4,34
(Gp:) #i (Gp:) S10 (Gp:) 5 (Gp:) 0,8 (Gp:) 10 (Gp:) 1,4 (Gp:) 500 (Gp:) 8,9
Herramientas de depuración y monitorización Medición de tiempos de ejecución Depuración Monitorización (Gp:) #include < sys/time.h>
struct timeval t0, tf, tiempo;
/* Inicialización */
gettimeofday (&t0, NULL);
/* ejecucion del programa paralelo */
gettimeofday (&tf, NULL); timersub (&tf, &t0, &tiempo); printf (Tiempo => %ld:%ld seg:micron, tiempo.tv_sec, tiempo.tv_usec);
(Gp:) Evitar E/S
¿Efecto del optimizador de código? gcc O3
Herramientas de depuración y monitorización (Gp:) %mpirun dbg=ddd np 2 psendrec
Depurar más de un proceso, algo más complejo printf fflush(stdout) setbuf(stdout, NULL) (Gp:) depurar (ddd)
Herramientas de depuración y monitorización Monitorización (XPVM) para PVM
Herramientas de depuración y monitorización Monitorización (totalview) para MPI, openMP, www.etnus.com (Gp:) www.roguewave.com
Página anterior | Volver al principio del trabajo | Página siguiente |