Preparación y Adaptación de Códigos Científicos para su Ejecución Paralela TICAL 2018 Gilberto Díaz gilberto.diaz@uis.edu.co Universidad Industrial de Santander Centro de Súper Computación y Cálculo Científico (SC3) Bucaramanga - Colombia
Eterna Necesidad El software científico de cualquier área debe ejecutarse de forma ágil y eficiente.
Uno de los objetivos principales de todo software científico es reducir el tiempo de ejecución para obtener resultados en tiempos razonables. Reportes de Clima Alertas de Tsunami Difusión de enfermedades Etc. Eterna Necesidad
Actualmente existen distintas técnicas especializadas para reducir el tiempo de procesamiento de los datos. Técnicas para Acelerar Algoritmos
Técnicas para Acelerar Algoritmos Aceleración de algoritmos Código eficiente Compiladores Paralelismo Funcional Paralelismo de Datos Redes de alto desempeño Baja latencia Almacenamiento de alto desempeño Sistemas de Archivos Paralelos
Técnicas para Acelerar Algoritmos Aceleración de algoritmos Código eficiente Compiladores Paralelismo Funcional Paralelismo de Datos Estrategias para acelerar el código original Redes de alto desempeño Baja latencia Almacenamiento de alto desempeño Sistemas de Archivos Paralelos
Técnicas para Acelerar Algoritmos Para programar un código científico que se ejecute rápido, lamentablemente/ afortunadamente, se debe conocer el hardware donde se va a ejecutar. L2 A ALU C L2 Processor L1 L2 L1 B A B ALU C L1 L2 L1 Prefetch device L3 RAM A B A B ALU C ALU C
Técnicas para Acelerar Algoritmos Entre las estrategias tenemos Evitar saltos en el código Uso de memoria dinámica Aprovechar la meoria cache Linealizar estructuras de datos Especificar la arquitectura en tiempo de compilación
Evitar Saltos en el Código Los científicos generalmente no cuentan con formación en programación, sin embargo, escriben muchos códigos para modelar situaciones físicas. Esto genera programas ineficientes IF(NC5.EQ.1) GO TO 999... END DO... 999 CONTINUE WRITE(*,*)'*** Computation Terminated ***'..
Uso de Memoria Dinámica Usar estructuras de datos de tamaño adecuado evita el mal uso de la memoria RAM. #define TAM 1000.. float var[tam];. for(i=0; i<10; i++){ var[i] = x*pi;
Aprovechar la Memoria Cache Usar memoria contínua Matriz::Matriz(const int f, const int c){ int i, j; filas = f; columnas = c; elems = new float * [filas]; for(i=0; i<filas; i++){ elems[i] = new float[columnas]; } } Matriz::Matriz(const int f, const int c){ int i, j; filas = f; columnas = c; elems = new float * [filas]; elems[0] = new float [filas*columnas]; for(i=0; i<filas; i++) elems[i] = elems[0] + i * columnas; }
Aprovechar la Memoria Cache Intercambiar estructuras de repetición (loop interchange) for(i=0; i<filas; i++){ for(j=0;j<cols;j++){... } for(j=0; j<cols; j++){ for(j=0;i<filas;i++){... }
Aprovechar la Memoria Cache Integrar estructuras de repetición (loop fusion) for(i=0; i<tam; i++) c[i] = d[i]+3.0; for(i=0; i<tam; i++) a[i] = b[i]*4.0; for(i=0; i<tam; i++){ c[i] = d[i]+3.0; a[i] = b[i]*4.0; }
Linealizar Estructuras de Datos for(i=0; i<res.filas; i++) for(j=0; j<res.cols; j++) for(k=0; k<res.cols; k++) res.elems[i][j] = res.elems[i][j] + this->elems[i][k]* m.elems[k][j]; for(i=0; i<res.filas*res.cols; i++) for(j=0; j<res.filas; j++) res.elems[i] = res.elems[i]+ this->elems[j]* m.elems[j*m.colus];
Banderas de Optimización del Compilador CFLAGS="-march=corei7-avx -O2 -pipe"
Acelerar Algoritmos Utilicemos un ejemplo de rendering para explicar como podemos resolver un problema de forma más rápido
Acelerar Algoritmos Podemos dividir el problema en trozos más pequeños
Acelerar Algoritmos Y asignar cada trozo a un procesador distinto. Así, cada procesador resuelve más rápido un problema más pequeño en menor tiempo.
Paralelismo de Datos Acelerar Algoritmos a 00 a 01 a 02 b 00 b 01 b 02 c 00 c 01 c 02 a 10 a 11 a 12 x b 10 b 11 b 12 = c 10 c 11 c 12 a 20 a 21 a 22 b 20 b 21 b 22 c 20 c 21 c 22
Paralelismo de Datos Acelerar Algoritmos a 00 a 01 a 02 b 00 b 01 b 02 c 00 c 01 c 02 a 10 a 11 a 12 x b 10 b 11 b 12 = c 10 c 11 c 12 a 20 a 21 a 22 b 20 b 21 b 22 c 20 c 21 c 22
Paralelismo de Datos Acelerar Algoritmos a 00 a 01 a 02 b 00 b 01 b 02 c 00 c 01 c 02 a 10 a 11 a 12 x b 10 b 11 b 12 = c 10 c 11 c 12 a 20 a 21 a 22 b 20 b 21 b 22 c 20 c 21 c 22
Paralelismo de Datos Acelerar Algoritmos a 00 a 01 a 02 b 00 b 01 b 02 c 00 c 01 c 02 a 10 a 11 a 12 x b 10 b 11 b 12 = c 10 c 11 c 12 a 20 a 21 a 22 b 20 b 21 b 22 c 20 c 21 c 22
Paralelismo de Datos Acelerar Algoritmos c00 = a00b00 + a01b10 + a02b20 c01 = a00b01 + a01b11 + a02b21 c02 = a00b02 + a01b12 + a02b22... c22 = a20b02 + a21b12 + a22b22
Paralelismo de Datos Acelerar Algoritmos c00 = a00b00 + a01b10 + a02b20 c01 = a00b01 + a01b11 + a02b21 c02 = a00b02 + a01b12 + a02b22... c22 = a20b02 + a21b12 + a22b22
Acelerar Algoritmos Más formalmente Ecuación de Calor Discretizar Codificar for(i=0; i<n; i++) a[i] = b[i] + c[i];
Acelerar Algoritmos Acelerar un algoritmo implica generar varios hilos de ejecución utilizando diferentes técnicas. for(i=0; i<n; i++) a[i] = b[i] + c[i]; a[0] = b[0] + c[0];. a[n] = b[n] + c[n]; a[0] = b[0] + c[0]; a[1] = b[1] + c[1];.. a[m] = b[m] + c[m];.. a[n] = b[n] + c[n];
Modelos de Programación Paralela Un modelo de programación paralela o paradigma es un conjunto de tecnologías de software que permiten expresar algoritmos paralelos para implantar aplicaciones en la arquitectura adecuada.
Modelos de Programación Paralela Un modelo de programación paralela incluye distintas áreas: Aplicaciones Lenguajes de programación Compiladores Bibliotecas Sistemas de comunicación Dispositivos de I/O paralelos
Modelos de Programación Paralela Hoy en día es muy difícil realizar un programa paralelo de forma automática. Por esto, el usuario debe escoger el modelo apropiado, o una mezcla de ellos, para construir su programa
Modelos de Programación Paralela Un modelo de programación paralela es implemenntado de distintas formas: Como bibliotecas invocadas desde programas tradicionales Como extensiones de lenguajes de programación Como modelos de ejecución completamente nuevos
Modelos de Programación Paralela Una primera categorización de estos modelos se realiza de acuerdo al manejo de la memoria: Memoria compartida (shared memory) Memoria distribuida (distributed memory) Memoria compartida distribuida (distributed shared memory)
Memoria Compartida en Procesadores Tradicionales Los hilos se comunican utilizando la memoria Cores Procesadores GPUs Posix Threads OpenMP RAM
Memoria Compartida (OpenMP) OpenMP es un API conformado por tres elementos Directivas del compilador: es un conjunto de instrucciones que le permite al programador comunicarse con el compilador Biblioteca de funciones: rutinas para gestionar los parámetros paralelos: número de hilos participantes y en ID actual Variables de ambiente:
Memoria Compartida (OpenMP) OpenMP está disponible para C C++ Fortran
Memoria Compartida (OpenMP) El hilo de control se divide así mismo tantas veces como lo necesite (modelo fork-join) Master Thread Regiones paralelas
Memoria Compartida (OpenMP) La mayoría de los bloques de OpenMP son directivas del compilador llamadas pragmas Los pragmas definen las regiones paralelas C/C++ : #pragma omp parallel { code } #pragma omp parallel thread1 thread2 thread3
Memoria Compartida (OpenMP) #pragma omp parallel for for (i=0; i<n; i++){ Do_Work(i); }
Memoria Compartida Usando Aceleradores Los hilos se comunican utilizando la memoria CUDA OpenCL OpenACC Procesadores GPUs Cores RAM
Memoria Compartida (CUDA) Hay una amplia diferencia entre un procesador tradicional y los procesadores de los aceleradores (GPU): La mayoría de los transistores en una GPU están dedicados a procesamiento. En un CPU hay muchos transistores que deben gestionar el flujo de datos, la memoria chache, etc. Control UAL UAL UAL UAL Cache RAM RAM
Memoria Compartida (CUDA) CUDA es una tecnología que permite utilizar el poder de procesamiento masivamente paralelo de las GPUs de NVIDIA. Es un modelo de programación desarrollado por NVIDIA para sus GPUs. CUDA es una extensión del leguaje C para programación GPU.
Memoria Compartida (CUDA) La programación GPU, especialmente CUDA, es apropiada para enfrentar problemas donde la descomposición de dominio sea mayoritariamente la fuente de paralelismo
Memoria Compartida (CUDA) Jerarquía de los hijos de ejecución en CUDA Grid 0,0 0,1 0,2 1,0 1,1 1,2 2,0 2,1 2,2 Block 0,0 0,1 1,0 1,1 Thread
Memoria Compartida (CUDA Jerarquía de Memoria) Core 1 Registers Cache L1 Cache L2 CPU Core 2 Registers Cache L1 Cache L2 Bloque (0,0) GPU Bloque (1,0) Memoria Compartida Memoria Compartida Registros Registros Registros Registros Hilo (0,0) Hilo (1,0) Hilo (0,0) Hilo (1,0) Cache L3 Memoria Global Memoria Constante Memoria RAM
Memoria Compartida (CUDA) Multiplicación de matrices en un leguaje tradiconal #define N 20 float c[n][n]; void mulmat(float a[n][n], float b[n][n]){ int i,j,k; for(i=0; i<n; i++) for(j=0; j<n; j++) for(k=0; k<n; k++) c[i][j] = c[i][j] + a[i][k]*b[k][j]; }
Memoria Compartida (CUDA) El kernel correspondiente para la multiplicación de matrices es: global void MatrixMult(float *Md, float *Nd, float *Pd, int Width){ //Cálculo del índice de fila de Pd y M int Row blockidx.y * TILE_WIDTH + threadidx.y; //Cálculo del índice de columna de Pd y N int Col blockidx.x * TILE_WIDTH + threadidx.x; float Pvalue = 0; for( int k = 0; k < Width; ++k ) Pvalue += Md[Row*Width+k] * Nd[k*Width+Col]; Pd[Row*Width*Col] = Pvalue; }
Memoria Compartida (OpenACC) Es un estándar diseñado para simplificar la programación paralela de sistemas heterogéneos de CPU/GPU. Está disponible sólo para el juego de compiladores de PGI
Memoria Compartida (OpenACC) #pragma acc kernels { #pragma acc loop independent collapse(2) for ( int j = 1; j < n-1; j++ ) { for ( int i = 1; i < m-1; i++ ) { Anew[j][i] = 0.25 * (A[j][i+1] + A [j][i-1] + A[j-1][i] + A[j+1][i]); error = max (error,fabs(anew[j][i] - A[j][i])); } } } }
Memoria Distribuida (MPI) Cuando los recursos de un sólo computador no son sufcientes, entonces es necesario acudir a los recursos de otros computadores
Memoria Distribuida (MPI) Los hilos se comunican utilizando mensajes por la red MPI Cores GPUs Cores Procesadores RAM Cores GPUs Cores Procesadores RAM
Memoria Compartida Distribuida (Modelo Híbrido) Los hilos se comunican utilizando mensajes por la red y la memoria Cores GPUs Cores Procesadores RAM Cores GPUs Cores Procesadores RAM
Sistemas de Archivos Paralelos Lustre PVFS GPFS BeeFS File Asfdjlsfsdfkjsdfjsdhners Werweiujoifdsfshgvbcvs Sdfsjdfañsdfjdhegbdbbcv Sdfsdjflkjsdfskdjf asdfkj sdfkjasdfijwerlkjsdfasdfj