PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL CÓDIGO DEL PROYECTO IX. Código Bloque III: Vector de Términos Independientes El objeto de esta sección es implementar un código para la última parte del problema que corresponde a la generación del vector de elementos independientes a partir de las condiciones de contorno definidas en la sección de mallado y que están contenidas en los archivos neumann.dat para las condiciones de contorno de NEUMANN y en dirichlet.dat para la condiciones de contorno según DIRICHLET. Inicialmente esta sección aunque constituye un cuello de botella, no es muy dificultosa debido a: 1. Se aprovecharían trozos de código ya usados en la resolución de los bloques anteriores. 2. No necesita mucha paralelización debido a que las condiciones neumann y dirichlet sólo son aplicables a los elementos frontera que son un número reducido (menos del 1% del total de los nodos). Sin embargo se intentó optimizar la solución mediante paralelización. En la figura 1 se representa la estrategia usada para abordar la adaptación de este bloque a CUDA. GENERACIÓN DE VECTOR DE TÉRMINOS INDEPENDIENTES BLOQUE DE PARTIDA EN CPU LECTURA ARCHIVOS DE CONDICIONES DE CONTORNO GENERACIÓN DE VECTOR DE TÉRMINOS INDEPENDIENTES: NEUMANN LECTURA MATRIZ A EN FORMATO MM GENERACIÓN DE VECTOR ES AUXILIARES Y VECTOR U PARA CONDICONES DE CÁLCULO DE B-A*U Y GENERACION DE B: VECTOR DE TÉRMINOS INDEPENDIENTES. ADAPTACIÓN CUDA Fig.1: Adaptación del independientes a CUDA. bloque de generación del vector de términos 157 PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL CÓDIGO DEL PROYECTO 1. ALGORITMO Este bloque pretende incorporar las condiciones NEUMANN y DIRICHLET al problema. Parte de los archivos triangulos.dat, coordenadas.dat y en concreto neumann.dat para NEUMANN y dirichlet.dat par DIRICHLET y genera un vector de términos independientes. Se calcula un vector "b" inicial con las condiciones de NEUMANN. Posteriormente, se genera dirichlet.dat llamado "u". un array con los valores ofrecidos por Finalmente, tomando como entrada la matriz A, calculada y generada previamente, se calcula b-A*u y se almacena en “b” siendo este el vector reordenado de términos independientes del sistema. Para ello se desarrolló un código principal main. El objeto de dicho bloque es la ejecución secuencial de los distintos pasos. El orden es el mismo al establecido en el código matlab. Para Cada tarea paralelizable se realiza una llamada a una función Host que desarrolla los apartados particulares de cada sección. A partir de estos bloques Host se realizan llamadas a los bloque kernel que serían ejecutados en la GPU. Se probaron, para comparar la eficiencia, kernels sencillos para rutinas muy repetitivas como por ejemplo la inicialización de un vector. Esta tarea permite comprobar si la inicialización de un vector asignando valores constantes en la CPU es más o menos rápida que crear el vector copiarlo a la GPU asignarle valores y volver a copiarlo a la CPU. Es de relevancia recordar que existen bibliotecas que contienen codificada esta rutina. El programa empieza con la lectura de los archivos con datos de entradas y almacenando esos datos en arrays apropiados. Además también se lee en formato COO la matriz A generada en el punto anterior. Se generan vectores auxiliares y se inicializan mediante códigos sencillos en paralelo como medida de la eficiencia. 158 PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL CÓDIGO DEL PROYECTO i. Para las condiciones NEUMANN: 1- Se determina el número total de elementos. 2- Se reservan los arrays apropiados. Recordemos que un lado NEUMANN está definido por su vértice 1 su vértice 2 y el subdominio sobre el que está definido. Resultando en un array de 3 columnas. 3- LLamada a kernel para NEUMANN. Donde el índice es el índice del lado NEUMANN según el orden de almacenamiento en el archivo correspondiente. Se realizan los cálculos en la GPU y se almacenan los valores en un array inicialmente vacío. Para seleccionar la condición apropiada según el subdominio al que se pertenezca se usa “switch”. Se debe tener cuidado de insertar una instrucción break detrás de cada caso en la cláusula switch. 4- Una vez devueltos los resultados en VLADO este vector es usado para rellenar los valores del vector de términos independientes. Recordemos que cada valor en Vlado corresponde a un lado NEUMANN definido por dos vértices y que en B cada vértice le corresponderá un valor. Por tanto se desdoblan los valores mediante las dos líneas de código siguientes: B[((int)LADO[3*i])-1]+=VLADO[i]; B[((int)LADO[3*i+1])-1]+=VLADO[i]; Al igual que en el código MATLAB de partida se actualizan los valores acumulándolos. ii. Para las condiciones DIRICHLET: Seguidamente se ejecuta condiciones de dirichlet. el bloque para la incorporación de las Este bloque es más complicado que el anterior porque implica además del cálculo o asignación de valores según dirichlet, la realización de la operación b=b-Au; donde “A” es la matriz de rigidez y “u” el vector de valores de dirichlet. 159 PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL CÓDIGO DEL PROYECTO Por tanto se divide en 3 pasos: 1- conversión de formato de A Recordemos que la matriz A estaba almacenada en formato COO sin embargo para usar el código de producto de matriz dispersa por vector de NVIDIA se debe convertir la matriz a un formato más eficiente. Se eligió el CSR. En el código de conversión, a simple vista aparecen secciones candidatas para ser paralelizadas por ser bucles, sin embargo el hecho de que se acumulan sucesivamente arrastrando el dato, implica escrituras concurrentes que podrían generar problemas. Esto y junto al enorme coste que suponen las copias de memoria de CPU->GPU->CPU hizo que se prefiriera una solución secuencial. 2- Condiciones de dirichlet: Se implementa a su vez en dos fases: a: Generación del vector auxiliar Uaux que determina el número total de lados dirichlet y reserva memoria para los arrays auxiliares NODOSD y SUBDD. En NODOSD se almacenan los vértices que forman cada lado y en SUBDD se almacena el subdominio al que pertenece ese lado desdoblado en vértices. Uaux contiene en la posición i, correspondiente al nodo dirichlet i+1, el valor del subdominio al que pertenece. En los datos de partida se definieron 4 subdominios: 1 y 4 como lados DIRICHLET y 2 y 3 como NEUMANN. Los vértices no dirichlet intervienen en el proceso. tienen un “0” almacenado y no Uaux actúa a modo de guía para la siguiente etapa indicando el valor del subdominio al que pertenecen los vertices dirichlet. b: Generación del vector “u” que almacena los valores de los nodos dirichlet. Donde el código de esta sección es aplicable sólo a los nodos dirichlet. Se calcula el valor correspondiente y se almacena en el vector “u”. 3- Producto, resta y resultado final: El producto se implementa mediante la versión CSR de SpMV de NVIDIA. Es para aprovechar las ventajas de que A es una matriz dispersa. Finalmente se realiza la resta que corresponde a la operación “b-Au”, que se ha implementado usando funciones de la biblioteca CUBLAS. 160 PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL CÓDIGO DEL PROYECTO 2. OBSERVACIONES La eficiencia de este código y su rapidez no se puede explicar por la paralelización ya que muchas tareas conllevan cálculos que afectan a menos del 1% de los nodos del mallado (por ejemplo del orden de millares de elementos en lugar de millones). Una parte especialmente sensible del código ha sido la lectura de los archivos de entrada y su conversión a formatos apropiados para la ejecución de las operaciones necesarias, especialmente la conversión de formato COO a CSR. 161