Paradigma Shared Address Space Algoritmos paralelos Glen Rodrguez

  • Slides: 56
Download presentation
Paradigma “Shared Address Space” Algoritmos paralelos Glen Rodríguez

Paradigma “Shared Address Space” Algoritmos paralelos Glen Rodríguez

Plataformas de memoria compartida n n n En MPI: division de tareas y comunicación

Plataformas de memoria compartida n n n En MPI: division de tareas y comunicación es explícita. En memoria compartida es semiimplícita. Además la comunicación es a través de la memoria común. Tecnologías: Open. MP, Parallel toolbox de Matlab

Hilos n n n Un hilo es un flujo de control individual en un

Hilos n n n Un hilo es un flujo de control individual en un programa. Ej. Mult. matrices. for (row = 0; row < n; row++) for (column = 0; column < n; column++) c[row][column] = create_thread(dot_product(get_row(a, row), get_col(b, col)));

Hilos o procesos acceden a memoria compartida Cada hilo puede tener variables privadas (stack)

Hilos o procesos acceden a memoria compartida Cada hilo puede tener variables privadas (stack)

Por qué hilos? n n Portabilidad de software Memoria tiene menos latencia (ts=α) que

Por qué hilos? n n Portabilidad de software Memoria tiene menos latencia (ts=α) que las redes. Además solo 1 hilo puede usar la red en un momento dado. Scheduling y balance de carga semiautomático. Facilidad de programar.

API de hilos de Posix n n n n POSIX API : estándar IEEE

API de hilos de Posix n n n n POSIX API : estándar IEEE 1003. 1 c-1995. Llamado Pthreads. #include <pthread. h> int pthread_create ( pthread_t *thread_handle, const pthread_attr_t *attribute, void * (*thread_function)(void *), void *arg);

Ejemplo 1 #include <pthread. h> 2 #include <stdlib. h> 3 4 #define MAX_THREADS 512

Ejemplo 1 #include <pthread. h> 2 #include <stdlib. h> 3 4 #define MAX_THREADS 512 5 void *compute_pi (void *); 6 7 int total_hits, total_misses, hits[MAX_THREADS], 8 sample_points, sample_points_per_thread, num_threads; 9 10 main() { 11 int i; 12 pthread_t p_threads[MAX_THREADS]; 13 pthread_attr_t attr; 14 double computed_pi; 15 double time_start, time_end; 16 struct timeval tv; 17 struct timezone tz; 18 19 pthread_attr_init (&attr); 20 pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); 21 printf("Enter number of sample points: "); 22 scanf("%d", &sample_points); 23 printf("Enter number of threads: "); 24 scanf("%d", &num_threads);

Ejemplo 26 27 28 29 30 31 32 33 34 35 36 37 38

Ejemplo 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 } gettimeofday(&tv, &tz); time_start = (double)tv. tv_sec + (double)tv. tv_usec / 1000000. 0; total_hits = 0; sample_points_per_thread = sample_points / num_threads; for (i=0; i< num_threads; i++) { hits[i] = i; pthread_create(&p_threads[i], &attr, compute_pi, (void *) &hits[i]); } for (i=0; i< num_threads; i++) { pthread_join(p_threads[i], NULL); total_hits += hits[i]; } computed_pi = 4. 0*(double) total_hits / ((double)(sample_points)); gettimeofday(&tv, &tz); time_end = (double)tv. tv_sec + (double)tv. tv_usec / 1000000. 0; printf("Computed PI = %lfn", computed_pi); printf(" %lfn", time_end - time_start);

Ejemplo 51 void *compute_pi (void *s) { 52 int seed, i, *hit_pointer; 53 double

Ejemplo 51 void *compute_pi (void *s) { 52 int seed, i, *hit_pointer; 53 double rand_no_x, rand_no_y; 54 int local_hits; 55 56 hit_pointer = (int *) s; 57 seed = *hit_pointer; 58 local_hits = 0; 59 for (i = 0; i < sample_points_per_thread; i++) { 60 rand_no_x =(double)(rand_r(&seed))/(double)((2<<14)-1); 61 rand_no_y =(double)(rand_r(&seed))/(double)((2<<14)-1); 62 if (((rand_no_x - 0. 5) * (rand_no_x - 0. 5) + 63 (rand_no_y - 0. 5) * (rand_no_y - 0. 5)) < 0. 25) 64 local_hits ++; 65 seed *= i; 66 } 67 *hit_pointer = local_hits; 68 pthread_exit(0); 69 }

Primitivas de sincronización en Pthreads n n n Exclusión mutua para variables compartidas Ej.

Primitivas de sincronización en Pthreads n n n Exclusión mutua para variables compartidas Ej. : best_cost es compartida (=100 al inicio), my_cost es local (50 y 75) 1 /* each thread tries to update variable best_cost as follows */ 2 if (my_cost < best_cost) 3 best_cost = my_cost;

Mutex-locks n n El if y la asignación no son independientes: deberían ser una

Mutex-locks n n El if y la asignación no son independientes: deberían ser una unidad. Además son una zona crítica (pedazo del programa donde solo 1 hilo debe correr a la vez). Como soportar zona crítica y ops. atómicas: mutex-locks (locks de exclusión mutua)

Controlando atributos de Hilos y de Sincronización n Atributos de hilo: ¡ ¡ n

Controlando atributos de Hilos y de Sincronización n Atributos de hilo: ¡ ¡ n n Tipo de scheduling Tamaño de stack Atributos de mutez: tipo de mutex. Separo atributos del programa: más fácil programar.

Cancelando hilos n n n Si un hilo ya no es necesario: int pthread_cancel

Cancelando hilos n n n Si un hilo ya no es necesario: int pthread_cancel (pthread_t thread); Un hilo se puede autocancelar, o puede cancelar a otros.

Constructos compuestos de sincronización n Locks read-write ¡ ¡ n Muchos hilos pueden leer

Constructos compuestos de sincronización n Locks read-write ¡ ¡ n Muchos hilos pueden leer una data a la vez, pero sólo 1 puede escribir en ella. Y debería hacerlo sin reads a la vez. Una vez que hay en cola un pedido de lock de escritura, ya no se aceptan más lecturas. Barreras: similar a MPI

Diseñando programas asíncronos n n n Alt. 1: no importa el orden de los

Diseñando programas asíncronos n n n Alt. 1: no importa el orden de los hilos Alt. 2: sí importa, en este caso hay que programar explícitamente el orden con locks, mutexes, barreras, joins etc, Setear los atributos, datos, mutexes, etc. al inicio de la vida del hilo. Si hay hilo productor y consumidor, asegurarse que se produzca (y se ponga en memoria compartida) antes de que se consuma. Si se puede, definir y usar sincronizaciones de grupo y replicación de data.

Open. MP n n n n ¿Qué es Open. MP? Regiones Paralelas Bloques de

Open. MP n n n n ¿Qué es Open. MP? Regiones Paralelas Bloques de construcción para trabajo en paralelo Alcance de los datos para proteger datos Sincronización Explícita Cláusulas de Planificación Otros bloques de construcción y cláusulas útiles

¿Qué es Open. MP*? n n Directivas del compilador para programación multihilos Es fácil

¿Qué es Open. MP*? n n Directivas del compilador para programación multihilos Es fácil crear hilos en Fortran y C/C++ Soporta el modelo de paralelismo de datos Paralelismo incremental Combina código serial y paralelo en un solo código fuente

¿Qué es Open. MP*? C$OMP FLUSH #pragma omp critical CALL OMP_SET_NUM_THREADS(10) C$OMP THREADPRIVATE(/ABC/) call

¿Qué es Open. MP*? C$OMP FLUSH #pragma omp critical CALL OMP_SET_NUM_THREADS(10) C$OMP THREADPRIVATE(/ABC/) call omp_test_lock(jlok) C$OMP parallel do shared(a, b, c) C$OMP MASTER call OMP_INIT_LOCK (ilok) http: //www. openmp. org C$OMP ATOMIC C$OMP SINGLE PRIVATE(X) OMP_SCHEDULE La especificación actualsetenv es Open. MP 2. 5“dynamic” C$OMP PARALLEL DO ORDERED PRIVATE (A, B, C) 250 Páginas C$OMP PARALLEL REDUCTION (+: A, B) (C/C++ y Fortran) #pragma omp parallel for private(A, B) C$OMP PARALLEL COPYIN(/blk/) Nthrds = OMP_GET_NUM_PROCS() C$OMP ORDERED C$OMP SECTIONS !$OMP BARRIER C$OMP DO lastprivate(XX) omp_set_lock(lck)

Arquitectura Open. MP* n n n Modelo fork-join Bloques de construcción para trabajo en

Arquitectura Open. MP* n n n Modelo fork-join Bloques de construcción para trabajo en paralelo Bloques de construcción para el ambiente de datos Bloques de construcción para sincronización API (Application Program Interface) extensiva para afinar el control

Modelo de programación Paralelismo fork-join: • El hilo maestro se divide en un equipo

Modelo de programación Paralelismo fork-join: • El hilo maestro se divide en un equipo de hilos como sea necesario • El Paralelismo se añade incrementalmente: el programa secuencial se convierte en un programa paralelo Hilo maestro Regiones paralelas

Sintaxis del Pragma Open. MP* n La mayoría de los bloques de construcción en

Sintaxis del Pragma Open. MP* n La mayoría de los bloques de construcción en Open. MP* son directivas de compilación o pragmas. n En C y C++, los pragmas toman la siguiente forma: #pragma omp construct [clause]…]

Regiones Paralelas n n Define una región paralela sobre un bloque de código estructurado

Regiones Paralelas n n Define una región paralela sobre un bloque de código estructurado Los hilos se crean como ‘parallel’ Los hilos se bloquean al final de la región Los datos se comparten entre hilos al menos que se especifique otra cosa #pragma omp parallel Hilo 1 Hilo 2 Hilo 3 C/C++ : #pragma omp parallel { bloque }

¿Cuántos hilos? n Establecer una variable de ambiente para el número de hilos. Ejemplo

¿Cuántos hilos? n Establecer una variable de ambiente para el número de hilos. Ejemplo 4 hilos: export OMP_NUM_THREADS=4 n No hay un default estándar en esta variable ¡ En muchos sistemas: # de hilos = # de procesadores

Ej. 1: Hello World Modificar el código serial de “Hello, Worlds” para ejecutarse paralelamente

Ej. 1: Hello World Modificar el código serial de “Hello, Worlds” para ejecutarse paralelamente usando Open. MP* intnmain() { saludo(); } int saludo() { int i; for(i=0; i<10; i++) { printf(“Hola mundo desde el hilo principaln”); sleep(1) } }

Bloques de construcción de trabajo en paralelo #pragma omp parallel #pragma omp for (i=0;

Bloques de construcción de trabajo en paralelo #pragma omp parallel #pragma omp for (i=0; i<N; i++){ Do_Work(i); } n n n Divide las iteraciones del ciclo en hilos Debe estar en la región paralela Debe preceder el ciclo

Bloques de construcción de trabajo en paralelo #pragma omp parallel #pragma omp for(i =

Bloques de construcción de trabajo en paralelo #pragma omp parallel #pragma omp for(i = 0; i < 12; i++) c[i] = a[i] + b[i] n n Los hilos se asignan a un conjunto de iteraciones independientes Los hilos deben de esperar al final del bloque de construcción de trabajo en paralelo #pragma omp parallel #pragma omp for i=0 i=4 i=8 i=1 i=5 i=9 i=2 i=6 i = 10 i=3 i=7 i = 11 Barrera implícita

Combinando pragmas n Ambos segmentos de código son equivalentes #pragma omp parallel { #pragma

Combinando pragmas n Ambos segmentos de código son equivalentes #pragma omp parallel { #pragma omp for (i=0; i< MAX; i++) { res[i] = huge(); } } #pragma omp parallel for (i=0; i< MAX; i++) { res[i] = huge(); }

Ambiente de datos n Open. MP usa un modelo de programación de memoria compartida

Ambiente de datos n Open. MP usa un modelo de programación de memoria compartida n La mayoría de las variables por default son compartidas. n Las variables globales son compartidas entre hilo

Ambiente de datos n Pero, no todo es compartido. . . ¡ Las variables

Ambiente de datos n Pero, no todo es compartido. . . ¡ Las variables en el stack en funciones llamadas de regiones paralelas son PRIVADAS ¡ Las variables automáticas dentro de un bloque son PRIVADAS ¡ Las variables de índices en ciclos son privadas (salvo excepciones) n C/C+: La primera variable índice en el ciclo en ciclos anidados después de un #pragma omp for

Atributos del alcance de datos n El estatus por default puede modificarse default (shared

Atributos del alcance de datos n El estatus por default puede modificarse default (shared | none) n Clausulas del atributo de alcance shared(varname, …) private(varname, …)

La cláusula Private n Reproduce la variable por cada hilo n n Las variables

La cláusula Private n Reproduce la variable por cada hilo n n Las variables no son inicializadas; en C++ el objeto es construido por default Cualquier valor externo a la región paralela es indefinido void* work(float* c, int N) { float x, y; int i; #pragma omp parallel for private(x, y) for(i=0; i<N; i++) { x = a[i]; y = b[i]; c[i] = x + y; } }

Ejemplo: producto punto float dot_prod(float* a, float* b, int N) { float sum =

Ejemplo: producto punto float dot_prod(float* a, float* b, int N) { float sum = 0. 0; #pragma omp parallel for shared(sum) for(int i=0; i<N; i++) { sum += a[i] * b[i]; } return sum; } ¿Qué es incorrecto?

Proteger datos compartidos n Debe proteger el acceso a los datos compartidos que son

Proteger datos compartidos n Debe proteger el acceso a los datos compartidos que son modificables float dot_prod(float* a, float* b, int N) { float sum = 0. 0; #pragma omp parallel for shared(sum) for(int i=0; i<N; i++) { #pragma omp critical sum += a[i] * b[i]; } return sum; }

Open. MP* Bloques de construcción para regiones críticas n #pragma omp critical [(lock_name)] n

Open. MP* Bloques de construcción para regiones críticas n #pragma omp critical [(lock_name)] n Define una región crítica en un bloque estructurado float R 1, R 2; Los hilos esperan su turno –en un momento, solo uno llama consum() protegiendo R 1 y R 2 de de condiciones de concurso. Nombrar las regiones críticas es opcional, pero puede mejorar el rendimiento. #pragma omp parallel { float A, B; #pragma omp for(int i=0; i<niters; i++){ B = big_job(i); #pragma omp critical (R 1_lock) consum (B, &R 1); A = bigger_job(i); #pragma omp critical (R 2_lock) consum (A, &R 2); } }

Open. MP* Cláusula de reducción n reduction (op : list) Las variables en “list”

Open. MP* Cláusula de reducción n reduction (op : list) Las variables en “list” deben ser compartidas dentro de la región paralela Adentro de parallel o el bloque de construcción de trabajo en paralelo: n Se crea una copia PRIVADA de cada variable de la lista y se inicializa de acuerdo al “op” n Estas copias son actualizadas localmente por los hilos

Ejemplo de reducción #pragma omp parallel for reduction(+: sum) for(i=0; i<N; i++) { sum

Ejemplo de reducción #pragma omp parallel for reduction(+: sum) for(i=0; i<N; i++) { sum += a[i] * b[i]; } n n Una copia local de sum para cada hilo Todas las copias locales de sum se suman y se almacenan en una variable “global”

C/C++ Operaciones de reducción n n Un rango de operadores asociativos y conmutativos pueden

C/C++ Operaciones de reducción n n Un rango de operadores asociativos y conmutativos pueden usarse con la reducción Los valores iniciales son aquellos que tienen sentido Operador Valor Inicial + 0 Operador Valor Inicial & ~0 * 1 | 0 - 0 && 1 ^ 0 || 0

Ejemplo de integración numérica 1 4. 04. 0 f(x) = 2 dx = (1+x

Ejemplo de integración numérica 1 4. 04. 0 f(x) = 2 dx = (1+x ) 2) (1+x 0 void main() { int i; double x, sum = 0. 0; 2. 0 0. 0 static long num_steps=100000; double step, pi; X step = 1. 0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0. 5)*step; sum = sum + 4. 0/(1. 0 + x*x); } pi = step * sum; printf(“Pi = %fn”, pi); 1. 0 }

Calculando Pi static long num_steps=100000; double step, pi; n void main() { int i;

Calculando Pi static long num_steps=100000; double step, pi; n void main() { int i; double x, sum = 0. 0; step = 1. 0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0. 5)*step; sum = sum + 4. 0/(1. 0 + x*x); } pi = step * sum; printf(“Pi = %fn”, pi); } n n n Paralelize el código de integración numérica usando Open. MP ¿Qué variables se pueden compartir? ¿Qué variables deben ser privadas? ¿Qué variables deberían considerarse para reducción?

Asignando Iteraciones n La cláusula schedule afecta en como las iteraciones del ciclo se

Asignando Iteraciones n La cláusula schedule afecta en como las iteraciones del ciclo se mapean a los hilos schedule(static [, chunk]) n n Bloques de iteraciones de tamaño “chunk” a los hilos Distribución Round Robin schedule(dynamic[, chunk]) n n Los hilos timan un fragmento (chunk) de iteraciones Cuando terminan las iteraciones, el hilo solicita el siguiente fragmento

Asignando Iteraciones schedule(guided[, chunk]) n n Planificación dinámica comenzando desde el bloque más grande

Asignando Iteraciones schedule(guided[, chunk]) n n Planificación dinámica comenzando desde el bloque más grande El tamaño de los bloques se compacta; pero nunca más pequeño que “chunk”

Qué planificación utilizar Cláusula Schedule STATIC DYNAMIC GUIDED Cuando utilizar Predecible y trabajo similar

Qué planificación utilizar Cláusula Schedule STATIC DYNAMIC GUIDED Cuando utilizar Predecible y trabajo similar por iteración Impredecible, trabajo altamente variable por iteración Caso especial de dinámico para reducir la sobrecarga de planificación

Ejemplo de la cláusula Schedule #pragma omp parallel for schedule (static, 8) for( int

Ejemplo de la cláusula Schedule #pragma omp parallel for schedule (static, 8) for( int i = start; i <= end; i += 2 ) { if ( Test. For. Prime(i) ) g. Primes. Found++; } n Las iteraciones se dividen en pedazos de 8 n Si start = 3, el primer pedazo es i={3, 5, 7, 9, 11, 13, 15, 17}

Secciones paralelas n Secciones independientes de código se pueden ejecutar concurrentemente #pragma omp parallel

Secciones paralelas n Secciones independientes de código se pueden ejecutar concurrentemente #pragma omp parallel sections { #pragma omp section phase 1(); #pragma omp section phase 2(); #pragma omp section phase 3(); } Serial Paralela

Bloque de construcción Single n Denota un bloque de código que será ejecutado por

Bloque de construcción Single n Denota un bloque de código que será ejecutado por un solo hilo n n El hilo seleccionado es dependiente de la implementación Barrera implícita al final #pragma omp parallel { Do. Many. Things(); #pragma omp single { Exchange. Boundaries(); } // threads wait here for single Do. Many. More. Things(); }

Bloque de construcción Master n n Denota bloques de código que serán ejecutados solo

Bloque de construcción Master n n Denota bloques de código que serán ejecutados solo por el hilo maestro No hay barrera implícita al final #pragma omp parallel { Do. Many. Things(); #pragma omp master { // if not master skip to next stmt Exchange. Boundaries(); } Do. Many. More. Things(); }

Barreras implícitas n Varios bloques de construcción de Open. MP* tienen barreras implícitas n

Barreras implícitas n Varios bloques de construcción de Open. MP* tienen barreras implícitas n n Barreras innecesarias deterioran el rendimiento n n parallel for single Esperar hilos implica que no se trabaja! Suprime barreras implícitas cuando sea seguro con la cláusula nowait.

Cláusula Nowait #pragma omp for nowait for(. . . ) {. . . };

Cláusula Nowait #pragma omp for nowait for(. . . ) {. . . }; n #pragma single nowait { [. . . ] } Cuando los hilos esperarían entren cómputos independientes #pragma omp for schedule(dynamic, 1) nowait for(int i=0; i<n; i++) a[i] = big. Func 1(i); #pragma omp for schedule(dynamic, 1) for(int j=0; j<m; j++) b[j] = big. Func 2(j);

Barreras n n Sincronización explícita de barreras Cada hilo espera hasta que todos lleguen

Barreras n n Sincronización explícita de barreras Cada hilo espera hasta que todos lleguen #pragma omp parallel shared (A, B, C) { Do. Some. Work(A, B); printf(“Processed A into Bn”); #pragma omp barrier Do. Some. Work(B, C); printf(“Processed B into Cn”); }

Operaciones Atómicas n n Caso especial de una sección crítica Aplica solo para la

Operaciones Atómicas n n Caso especial de una sección crítica Aplica solo para la actualización de una posición de memoria #pragma omp parallel for shared(x, y, index, n) for (i = 0; i < n; i++) { #pragma omp atomic x[index[i]] += work 1(i); y[i] += work 2(i); }

API de Open. MP* n Obtener el número de hilo dentro de un equipo

API de Open. MP* n Obtener el número de hilo dentro de un equipo int omp_get_thread_num(void); n Obtener el número de hilos en un equipo int omp_get_num_threads(void); n Usualmente no se requiere para códigos de Open. MP n n Tiene usos específicos (debugging) Hay que incluir archivo de cabecera #include <omp. h>

Problemas de rendimiento n n Los hilos ociosos no hacen trabajo útil Divide el

Problemas de rendimiento n n Los hilos ociosos no hacen trabajo útil Divide el trabajo entre hilos lo más equitativamente posible n n Los hilos deben terminar trabajos paralelos al mismo tiempo La sincronización puede ser necesaria n Minimiza el tiempo de espera de recursos protegidos

Cargas de trabajo no balanceadas n Cargas de trabajo desigual produce hilos ociosos y

Cargas de trabajo no balanceadas n Cargas de trabajo desigual produce hilos ociosos y desperdicio de tiempo. #pragma omp for( ; ; ){ } } tiempo #pragma omp parallel { Ocupado Ocioso

Sincronización n Tiempo perdido por locks Ocupado Ocioso En SC #pragma omp parallel {

Sincronización n Tiempo perdido por locks Ocupado Ocioso En SC #pragma omp parallel { tiempo #pragma omp critical {. . . }

Afinando el rendimiento n n Los profilers usan muestreo para proveer datos sobre el

Afinando el rendimiento n n Los profilers usan muestreo para proveer datos sobre el rendimiento. Los profilers tradicionales están limitados para usarse con códigos de Open. MP*: n n Miden tiempo del CPU, no tiempo real No reportan contención de objetos de sincronización No pueden reportar carga de trabajo desbalanceada Muchos de ellos no tienen todo el soporte de Open. MP Los programadores necesitan profilers específicamente diseñadas para Open. MP.

Planificación estática: Hacerlo por uno mismo n Debe conocerse: n n n Número de

Planificación estática: Hacerlo por uno mismo n Debe conocerse: n n n Número de hilos (Nthrds) Cada identificador ID de cada hilo (id) Calcular iteraciones (start y end): #pragma omp parallel { int i, istart, iend; istart = id * N / Nthrds; iend = (id+1) * N / Nthrds; for(i=istart; i<iend; i++){ c[i] = a[i] + b[i]; } }