1k likes | 1.27k Vues
Introducción a la programación HPC. Josep Vidal Canet Alejandro Soriano Universitat de València. Objetivos. Conocer los principios básicos que rigen la programación de altas prestaciones (HPC) Hardware Software Conocer las tecnologías más utilizadas para implementar algoritmos paralelos
E N D
Introducción a la programación HPC Josep Vidal Canet Alejandro Soriano Universitat de València
Objetivos • Conocer los principios básicos que rigen la programación de altas prestaciones (HPC) • Hardware • Software • Conocer las tecnologías más utilizadas para implementar algoritmos paralelos • Posix Threads • MPI • OpenMP (Alex) • Las tendencias en el campo de HPC • Reducir el tiempo de ejecución
Motivación • Hay problemas que no se pueden resolver con un algorítmo secuencial • Predicción meteorológica • Hardware roadmap • Imposible seguir aumentando la velocidad del reloj a causa de la disipación de calor • Para aumentar el rendimiento -> aumentar el número de cores y threads per core • Configuración PCs actuales = 4 cores • Existen PCs (orientado segmento mercado profesional) con 8 cores (2 socket) • La tendencia del Hardware implica rediseñar el software para poder aprovechar toda la potencia del hardware • Pasar de aplicaciones secuenciales mono-hilo a paralelas multi-hilo • Actualmente existe software multihilo -> servidores web, J2EE, BBDD, etc … • En definitiva, paralelismo masivo en la era del paralelismo para las masas
Una nueva era en el diseño de sistemas • Los sistemas altamente paralelos construidos con múltiples procesadores pequeños, están ocupando un segmento cada vez mayor del mercado de servidores • $ 41,995.00 por 1 servidor con 32 cores basado en AMD • Generalmente, caracterizados por un rendimiento modesto en aplicaciones mono-hilo, buen rendimiento a nivel d chip (N cores) y excelente relación entre consumo/rendimiento • La tecnología de paralelismo, anteriormente utilizada en HPC (High Performance Computing) se está convirtiendo de uso generalizado en toda la pila de software.
Evolución numero de transistores en los Microprocesadores • La litografía permitirá seguir escalando nº de transistores por chip (densidad) dual-core Power6 tiene 790 millones
Tendencia en la frecuencia de reloj de los procesadores La disipación de calor limita el incremento en las velocidades del reloj
Chip Power Density La disipación de calor está limitando el aumento de la frecuencia de los relojes, dando lugar a la emergencia de chips multicore con menor consumo de energía Low power multicores replacing single high-power cores
Tendencia en procesadores multicore Cell = 9 cores, 10 threads
Crecimiento del número de threads por procesador Estamos entrando en la era de la computación masivamente multithread 1 Ejemplo: Sun Niagara; 1 chip = 8 cores x 8 threads/core = 64 threads
Tendencias en el software que complementan las del Hardware • Las aplicaciones cada vez son + escalables • Muchas nuevas aplicaciones son inherentemente escalables • Aplicaciones de búsqueda • Minería de datos • Ejemplos • Servidores de aplicaciones, web, BBDD • Google • Minería de contenidos multimedia • Muchas aplicaciones existentes, middleware y SO están siendo modificadas para ser + escalables: • Oracle y apache, pasaron de un modelo basado en procesos a hilos
Ejemplo Arquitectura Software/Hardware escalable (webges) power 6 16 cores 32 threads ORACLE Cluster servidors web Linux/Apache Dual-core AMD JVM1, JVMx JMVx DB2 UDBMaxappls JVMn JVMm LDAP server2 LDAP server1 DB2 persistencia sessions Explica DB2 OS/390 Servidors Aplicacions Fonts Dades Servidors WEB WAS Grid webges01 Switch Level 4 / pound Balanceig Càrrega db2jd jdbc + ctg Webges.. FireWall PIX Cisco webges11 AIX pseries 4 cores, 8 threads (power 5) RACF CICS SERVER pugin-cfg.xml OS/390 Z890 THREAD LIMIT AUTOMAT Soporta + d 500 peticiones x segundo
Programación paralela • Según la wikipedia: • es una técnica de programación basada en la ejecución simultánea, bien sea en un mismo ordenador (con uno o varios procesadores) o en un cluster de ordenadores, en cuyo caso se denomina computación distribuida. Al contrario que en la programación concurrente, esta técnica enfatiza la verdadera simultaneidad en el tiempo de la ejecución de las tareas. • Los sistemas con multiprocesador y multicomputadores consiguen un aumento del rendimiento si se utilizan estas técnicas. En los sistemas monoprocesador el beneficio en rendimiento no es tan evidente, ya que la CPU es compartida por múltiples procesos en el tiempo, lo que se denomina multiplexación o multiprogramación. • El mayor problema de la computación paralela radica en la complejidad de sincronizar unas tareas con otras, ya sea mediante secciones críticas, semáforos o paso de mensajes, para garantizar la exclusión mutua en las zonas del código en las que sea necesario. • Objetivo: reducir el tiempo de ejecución
Un ejemplo javac MyThread.java java –Xmx 512 MyThread public class MyThread extends Thread{ static final int CPU=5,N=100000000; private int[] A=new int[N]; private int[] B=new int[N]; SynchronizedCounter var=new SynchronizedCounter(); public void run(){ int roll= var.readANDadd(); //Atomic. Avoid condition race with mutex System.out.println("roll="+roll+" \n"); for (int i=(roll*(N/CPU)); i < ((roll+1)*(N/CPU));i++){ if ((i % 1000000) == 0) System.out.println("roll= "+roll+" i="+i+" \n"); B[i]=i; A[i]=B[i]*i; } } public static void main(String[] args) { for (int i=0;i<CPU;i++) { MyThread worker=new MyThread(); worker.start(); } } public static class SynchronizedCounter { //Binary Object lock private static int c; public synchronized int readANDadd(){ //Not concurrent code. return c++; //First return current value, then increment } } }
Modificación concurrente de datos compartidos • 2 threads intentando incrementar el valor de la variable i=1 (i++) • Th 1 i++; (i=2) • Th 2 i++; (i=3) • Thread 1: load value of i into a register on processor 1 (lets call it r1) [i=1, r1=1] • Thread 2: load value of i into a register on processor 2 (lets call it r2) [i=1, r1=1, r2=1] • Thread 1: increment r1 by 1 [i=1, r1=2, r2=1] • Thread 2: increment r2 by 1 [i=1, r1=2, r2=2] • Thread 1: store value in r1 back into i [i=2, r1=2, r2=2] • Thread 2: store value in r1 back into i [i=2, r1=2, r2=2] • Problema: Cuando graben el valor en memoria i=2, cuando tendría q valer 3 ! • Solución: Crear una sección crítica de manera q la operación d lectura y incremento sea atómica. • ¿Cómo? Mediante semáforos
Solución: Serializar acceso a datos compartidos • Cuando N threads modifican los mismos datos en paralelo, el resultado es impredecible • Solución: Pasar de ejecución paralela a secuencial con un semáforo • Como norma general, los N threads se ejecutaran en paralelo cuando modifiquen “su parte” d datos del problema y en secuencial cuando modifiquen datos compartidos
¿ Para qué sirve la computación paralela ? • 2 motivos principales • Reducir el tiempo de ejecución • Resolver problemas cada vez mas grandes y complejos • Otros • Utilizar recursos remotos – utilizando recursos disponibles en WAN • Reducción de costos – utilizando muchos nodos baratos en vez de un supercomputador caro • Salvar las restricciones de memoria – 1 servidor tiene recursos de memoria finitos. Para problemas grandes, utilizando la memoria de N servidores ayuda a superar este obstáculo
Taxonomía Flynn SISD Computador secuencial SIMD Computadores vectoriales (NEC, Fujitsu, Cray), procesadores con instrucciones de extensión multimedia (SSE3, Altivec), GPU’s MIMD Multiprocesadores, clusters, multi-core
Arquitecturas de memoria compartida Espacio de memoria global para todos los procesadores Tipos: UMA: Uniform Memory Access NUMA: Non-Uniform Memory Access cc-NUMA: Cache Coherent NUMA Ventajas: fácil programación; Desventajas: escalabilidad, precio
NUMA • Acceder de la CPU0 a la memoria d la: • CPU0:Muy rápido • CPU1:rápido • CPU2: rápido • CPU3: Menos rápido (2 saltos)
Arquitecturas de Memoria Distribuida Se requiere una red de comunicación para que los procesadores puedan acceder a la memoria no local • Características • No existe el concepto de memoria global • Procesadores independientes (no coherencia) • El programador explicita el intercambio de datos • Ventajas: escalabilidad, precio; Desventajas: programación
Arq. Híbridas: Distributed-Shared Memory Combinación de los dos modelos: Ejemplos: Multivac, Tirant Características Cada nodo es un multiprocesador (p.e. cc-NUMA) Comunicación para mover datos de un nodo a otro Actualmente, los supercomputadores suelen seguir este modelo
Paradigmas de Programación Paralela • Principales paradigmas o modelos de programación paralela: • Hilos (threads) • Paso de mensajes • Características: • Son una abstracción sobre la arquitectura • No hay una relación directa con la arquitectura (p.e. paso de mensajes sobre memoria compartida) • cesar.uv.es • En debian: apt-cache search mpich-shmem-bin • mpich-shmem-bin - MPI parallel computing system implementation, SHMEM version • No se puede hablar de “el mejor paradigma”
Paradigmas de programación paralela • Hilos • Un proceso puede tener múltiples caminos de ejecución • Todos los hilos comparten el mismo espacio de memoria • Necesidad de sincronizar el acceso a la memoria mediante semáforos • Se utilizan normalmente en arquitecturas de memoria compartida • Estándares: • Posix Threads: + flexible • OpenMP: + sencillo • Paso mensajes • Un conjunto de tareas que utilizan su propia memoria local para cálculos • Diversas tareas pueden residir en la misma máquina física, así como también en un número arbitrario de máquinas distintas (clusters) • Las tareas intercambian datos mediante el paso de mensajes • Estándares: PVM y MPI
Hilos vs Paso de mensajes • Hilos • No hay necesidad de comunicación explícita • Todos acceden al espacio de datos del problema (memoria compartida) • 1 proceso, se encarga de crear los hilos necesarios para balancear la computación necesaria para resolver el problema • Paso mensajes • 1 proceso se encarga de distribuir (enviando mensajes) los datos del problema a los restantes N-1 procesos remotos • Cuando los procesos terminan la computación necesaria para resolver su parte del problema, envían los resultados de vuelta
Metodologías de Programación Paralela • Los paradigmas se pueden abordar con diferentes metodologías • Paralelización automática (compilador paralelizante) • Paralelización semi-automática (directivas de compilador) • Lenguajes de programación nuevos • Extensión de lenguajes estándar • Librerías de subrutinas o funciones (API) • Ejemplos • OpenMP está basado en directivas • MPI y Posix Threads están basados en librerías de funciones
Single/Multiple Program Multiple Data Son modelos de programación de más alto nivel, aplicables a cualesquiera de los paradigmas descritos SPMD: el mismo programa se ejecuta por todas las tareas MPMD: cada tarea puede tener un programa distinto Los programas SPMD son más fáciles de mantener Suelen instrucciones condicionales pid=fork(); if (pid ==0) {printf(“Child\n”);}else{printf(“Master\n”);}
Esquemas de Programación Paralela • La paralelización (manual) suele implicar: • Particionado (de datos y/o tareas) • Comunicación y/o sincronización • En la práctica, hay algunos esquemas de paralelización que aparecen recurrentemente • Granja de tareas (maestro-trabajadores) • Segmentación (pipelining) • Divide y vencerás (encontrar máximo) • Otros: Ramificación y poda, algoritmos genéticos, autómatas celulares
Granja de tareas (maestro-trabajadores) • Esquema + habitual en prog. paralela • Particionado: Repartir el espacio de datos del problema entre N trabajadores • Cada uno normalmente ejecuta el mismo código pero sobre su parte de los datos • El maestro se encarga del particionado y la planificación de los trabajadores • En memoria compartida no se requiere comunicación para el particionado • Necesidad de sincronizar el acceso a la memoria compartida • Necesidad de balancear y sincronizar correctamente la ejecución de tareas
Maestro-esclavo con fork() #include <stdio.h> #define N 10 int main(){ int pid,i=0; /* identificador del proceso */ pid = fork(); while (i++ < N){ if ( pid < 0 ) { printf("Cannot fork !!\n"); exit(1); } if ( pid == 0 ) { /* Proceso hijo */ printf("I am child number %d \n",i); fflush(stdout); } else { /* Proceso padre */ printf("I am the father with PID:%d\n",pid); fflush(stdout); } } return 0; } Para optimizar la llamadaal sistema fork() linux utiliza la técnica d copy_on_write Añadir un sleep(1); para q los mensajes se intercalen
Ejemplo Maestro-esclavo: Aplicar 1 transformación a 1000 matrices d 1000x20 sub transform() { my $i=0; while ( $i < $f ){ # I want to create N slaves (childs) to parallelize the work my $pid=fork(); if ($pid == 0 ){ # We are the child do_work($i); exit 0 ; } else { $i++; $nchilds++ ; if ( $nchilds < $CPUS ){ next ; # If there are less childs than CPU, we continue } else { # If the number of childs is equal to the number of CPUS -> we must wait, # until a child ends. This is, we stop forking until a child ends its work. wait ; $nchilds--; } } } #f } El maestro se encarga d la creación y planificación d esclavos Para cada matriz, se crea un esclavo (hilo) para transformarla Se permiten hasta un máximo d N threads, donde N es igual al numero d CPUs
Trabajo a realizar x cada esclavo sub do_work { my $i=shift; my $aux=0; my $T="$txt"."/T"."$i" ; open(FILE,"<$T"); $T="$mod_txt"."/T"."$i" ; open(FILEMOD,">$T"); while (<FILE>) { my $line = $_; chomp $line; my @FIELDS = split(' '); foreach my $field (@FIELDS){ # Apply transformation: Matrix x Scalar $aux=$field*10; print FILEMOD $aux." "; } print FILEMOD "\n" ; } # <FILE> close(FILE); close(FILEMOD); }
Inicialización: Construcción d las 1000 matrices d 1000x20 (T0 ..T999) sub build_dataset() { • my $i=0, $j=0 , $k=0; • #create auxiliary directories. • `rm -r $txt ` ; `rm -r $mod_txt ` ; • `mkdir -p $txt` ; `mkdir -p $mod_txt` ; • while ($i < $f){ • my $T="$txt"."/T"."$i" ; • open(FILE,">$T"); • $j=0 ; • while ($j < $n){ • $k=0 ; • while($k < $m){ • print FILE $j." " ; • $k++ ; • } # k • print FILE "\n" ; • $j++ ; • } # j • close(FILE); • $i++ ; • } # i • }
Maestro-esclavo: Transformación 1000 matrices T0 T1 T3 T0 T2 T5 T7 T6 T1 T4 T8 T9 T10 T11 T2 ………… ………… ………… ………… ………… T998 T996 T999 T999 T997 Análisis temporal: Versión secuencial: 1000 x 30 = 30000 s. Tiempo total= 8 h y 20 minutos Versión paralela para 4 CPUs: 30000 / 4 =7500 segundos Tiempo total= 2 horas y 5 minutos Suponiendo q cada transformación sobrela tabla (matriz) tarde 30 segundos
Segmentación • Técnica originalmente utilizada en el diseño de procesadores • Consiste en dividir un trabajo en N etapas • Existe una relación de orden: • Una etapa no puede empezar hasta q no termine la predecesora Versión secuencial: 2 instrucciones en 15 ciclos Versión segmentada: 5 instrucciones terminadas enlos mismos ciclos de reloj http://cse.stanford.edu/class/sophomore-college/projects-00/risc/pipelining/pipelining1.mov
Problema transformación matrices T0/s3 T0/s1 T0/s2 T1/s1 T1/s2 T1/s3 T2/s3 T2/s2 T2/s1 ………… ………… ………… T23/s1 T23/s1 T23/s3 • Número Tablas = 24 , T=10.000 s = S1 + S2 + S3 (3 Etapas) • Step 1: matrix por escalar • Step 2: Transformar la matriz en aleatoria • Step 3: Ordenar las filas d la matriz • Versión Secuencial = 240.000 s Versión segmentada = 240.000 / 3 = 80.000 + 6666 = 86666 (teórico)
Segmentación + superescalar • HW: Lanzar N instrucciones en cada ciclo de reloj • SW: Lanzar N hilos, cada uno de ellos segmentado Versión segmentada: 10 instrucciones terminadas enlos mismos ciclos de reloj
Problema matrices: combinar maestro-esclavo con segmentación T1/s1 T0/s1 T2/s2 T3/s1 T0/s2 T2/s1 T3/s2 T0/s3 T1/s3 T2/s3 T3/s3 T1/s2 T4/s3 T7/s2 T4/s2 T6/s2 T5/s2 T7/s1 T6/s1 T4/s1 T5/s3 T6/s3 T7/s3 T5/s1 T11/s1 T9/s2 T9/s1 T10/s1 T11/s3 T8/s2 T9/s3 T8/s1 T10/s3 T10/s2 T8/s3 T11/s2 ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… T21/s3 T22/s1 T23/s1 T20/s2 T20/s1 T21/s2 T23/s2 T20/s3 T21/s1 T23/s3 T22/s3 T22/s2 • Solución algorítmica • Crear tantos procedimientos maestro – esclavos como etapas • Cada procedimiento maestro-esclavo se encargará de realizar la transformación correspondiente • Cada procedimiento lanzará a su vez N (4 en el ejemplo) trabajadores • Programaremos barreras para garantizar el orden de las transformaciones • evitar q una etapa se ejecute antes q su predecesora $myproc->start(\&matrix_X_scalar); $myproc->start(\&rand_matrix); sort_matrix_rows;
maestro-esclavo con segmentación sub sort_matrix_rows(){ my $i=0; my $aux=0; my $nchilds=0 ; `rm -r $inorder_txt`; `mkdir -p $inorder_txt`; while ( $i < $f ){ # I am the master. I want to create N slaves (childs) to parallelize the work my $pid=fork(); if ($pid == 0 ){ # We are the child. Barrier to wait until rand_matrix step finishes with table Ti $aux=$i+1; if (($aux < $f ) && ($rand_matrix_finished == 0)) { while (! -e "$random_txt"."/T"."$aux" ) { sleep(1); } } do_sort_matrix_rows_work($i); exit 0 ; } else { $i++; $nchilds++ ; if ( $nchilds < $CPUS ){ next ; } else { # Wai t until a child ends. This is, we stop forking until a child ends its work. wait ; $nchilds--; } } } #f }
Problema matrices: combinar maestro-esclavo con segmentación T3/s3 T1/s1 T0/s1 T2/s3 T2/s1 T2/s2 T3/s1 T0/s3 T0/s2 T3/s2 T1/s2 T1/s3 T7/s1 T6/s3 T5/s1 T6/s1 T6/s2 T5/s3 T5/s2 T4/s2 T7/s2 T4/s1 T4/s3 T7/s3 T9/s2 T11/s1 T8/s2 T9/s1 T10/s1 T8/s1 T10/s3 T9/s3 T10/s2 T11/s2 T11/s3 T8/s3 ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… T21/s1 T22/s1 T23/s1 T21/s2 T23/s2 T21/s3 T22/s3 T20/s1 T22/s2 T23/s3 T20/s3 T20/s2 • Necesitaremos 2 barreras $myproc->start(\&matrix_X_scalar); $myproc->start(\&rand_matrix); # Barrier to wait until matrix_X_scalar step finishes with table Ti $aux=$i+1; if (($aux < $f ) && ($rand_matrix_finished == 0)) { while (! -e "$random_txt"."/T"."$aux" ) { sleep(1); } } $myproc->start(\&sort_matrix); # Barrier to wait until rand_matrix step finishes with table Ti $aux=$i+1; if (($aux < $f ) && ($rand_matrix_finished == 0)) { while (! -e "$random_txt"."/T"."$aux" ) { sleep(1);} }
Problema matrices: combinar maestro-esclavo con segmentación T3/s1 T1/s1 T0/s1 T1/s3 T1/s2 T2/s2 T2/s3 T0/s2 T2/s1 T0/s3 T3/s2 T3/s3 T6/s1 T7/s1 T5/s1 T4/s1 T7/s3 T5/s3 T4/s2 T6/s3 T4/s3 T6/s2 T5/s2 T7/s2 T9/s3 T11/s1 T10/s1 T8/s2 T9/s1 T9/s2 T10/s3 T8/s1 T10/s2 T11/s2 T8/s3 T11/s3 ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… ………… T21/s1 T22/s1 T23/s1 T20/s2 T23/s3 T21/s2 T23/s2 T21/s3 T22/s3 T20/s1 T22/s2 T20/s3 • Número Tablas = 24 , T=10.000 s = S1 + S2 + S3 • Versión Secuencial = 120.000 s Maestro-esclavo segmentado = 120.000 / 4 = 30.000 / 3 = 10.000 + 6666 = 10666 (teórico)
Replica – Versión secuencial Export IXF Export IXF Load IXF Load IXF Unload TXT Unload TXT Load Target DB Load Target DB INDEX INDEX Statistics Statistics Tiempo t0 t1 • Solamente se ejecuta un proceso, en un determinado instante de tiempo • En este espacio de tiempo, hemos conseguido procesar casi 2 tablas • Sin embargo, cada transformación (etapa) consume un determinado tipo de recurso : Export IXF -> Net , Load, Unload -> I/O, Index + Statistics -> CPU • Por ejemplo mientras descargamos los datos del origen (Export IXF), consumimos ancho de banda de la red, pero las CPUs y el I/O están ociosos. • Es decir, estamos desperdiciando recursos!!!
Replica – Versión paralela Export IXF Export IXF Export IXF Export IXF Export IXF Export IXF Export IXF Export IXF Export IXF Export IXF Load IXF Load IXF Load IXF Load IXF Load IXF Load IXF Load IXF Load IXF Load IXF Load IXF Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Unload TXT Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB Load Target DB INDEX INDEX INDEX INDEX INDEX INDEX INDEX INDEX INDEX INDEX Statistics Statistics Statistics Statistics Statistics Statistics Statistics Statistics Statistics Statistics Tiempo t0 t1 t2 t3 t4 t5 t6 t7 • En este espacio de tiempo, hemos conseguido procesar 10 tablas (teóricamente)
Posix Threads • Objetivos • Comprender las ventajas de desarrollar aplicaciones paralelas basadas en hilos • Estudiar el estandard POSIX para hilos, llamado Pthreads • Estudiar las primitivas de control de threads: creación, terminación, join, sincronización, concurrencia, etc .. • Aprender a diseñar aplicaciones multi-hilo
Threads • Un thread es una unidad de trabajo para la CPU • Consiste en un flujo de instrucciones y un estado (pila, contador de programa y conjunto de registros). • Los procesos tradicionales de UNIX están basados en 1 hilo q tiene la posesión de toda la memoria y recursos del proceso • Los threads contenidos en un proceso pueden ser ejecutados y planificados independientemente • Muchos threads comparten el mismo espacio de memória • Además de para desarrollar aplicaciones HPC, se utilizan en otro SW: BBDD, servidores Web y de aplicaciones
Planificación de threads Los threads son ejecutados y planificados independientemente Processor affinity Modificación al planificador d Linux q permite indicar el procesador preferido para una tarea (proceso o thread)
Ventajas threads -> rendimiento • El cambio de contexto es muy pesado (ineficiente) en el caso de procesos • Hay q guardarla memoria del proceso y su estado a disco • Leer el estado de disco del siguiente y ponerlo en memoria para q se pueda ejecutar • I/O mucho + lenta q CPU
Ventajas threads -> rendimiento • Cambio de contexto mucho más eficiente • Simplemente hay q cambiar el estado del thread ( conjunto de registros + puntero d pila) • Como la memoria es común a los 2 threads no hay q guardarla en disco cuando cambiamos de contexto