ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y...

193
ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y DISTRIBUIDO E IMPLEMENTACIÓN EN UN CLUSTER. IGNACIO CAMPOS RIVERA ESCUELA SUPERIOR DE INGENIEROS UNIVERSIDAD DE SEVILLA Febrero 2007

Transcript of ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y...

Page 1: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO

PARALELO Y DISTRIBUIDO E IMPLEMENTACIÓN EN UN CLUSTER.

IGNACIO CAMPOS RIVERA

ESCUELA SUPERIOR DE INGENIEROS UNIVERSIDAD DE SEVILLA

Febrero 2007

Page 2: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

PROYECTO FIN DE CARRERA: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y DISTRIBUIDO E IMPLEMENTACIÓN EN UN CLUSTER. Centro: ESCUELA SUPERIOR DE INGENIEROS. UNIVERSIDAD DE SEVILLA. Realizado por: IGNACIO CAMPOS RIVERA. Codirigido por: CATEDRÁTICO RAMÓN ABASCAL GARCÍA (Dpto. ESTRUCTURAS). DR. RAFAEL ESTEPA ALONSO (Dpto. TELEMÁTICA).

1

Page 3: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A mis padres y a mis ocho hermanos, por todo lo que me

han ayudado siempre, y por todo lo que me han soportado.

A Rebeca, gracias a la cual he podido terminar la carrera,

y de la que tanto sigo aprendiendo.

2

Page 4: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A Ramón, por su trato para conmigo, y por su

confianza en mí.

A Rafael, por darme la oportunidad de realizar este

proyecto y abrirme las puertas al mundo laboral.

A Luis, por su ayuda y consejo siempre que la

necesitaba.

3

Page 5: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

ÍNDICE

4

Page 6: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

1. INTRODUCCIÓN. ...............................................................................................................8 11..

2. COMPUTACIÓN DISTRIBUIDA. ..................................................................................12 22..

2.1. INTRODUCCIÓN. .................................................................................................................. 13 2.2. CLUSTER DE ORDENADORES. ......................................................................................... 16 2.3. CÁLCULO EN PARALELO.................................................................................................. 20

2.3.1. CONCEPTOS GENERALES DEL CÁCULO EN PARALELO. ............................................... 21 2.3.2. ARQUITECTURAS DE MEMORIA DE ORDENADORES PARALELOS.............................. 23 2.3.3. MODELOS DE PROGRAMACIÓN EN PARALELO. .............................................................. 25

2.4. DISEÑO DE PROGRAMAS EN PARALELO..................................................................... 30 2.4.1. PARALELIZACIÓN AUTOMÁTICA FRENTE A MANUAL.................................................. 30 2.4.2. COMPRENSIÓN DEL PROBLEMA A PARALELIZAR. ......................................................... 30 2.4.3. PARTICIÓN................................................................................................................................. 31 2.4.4. COMUNICACIONES.................................................................................................................. 32 2.4.5. SINCRONIZACIÓN .................................................................................................................... 34 2.4.6. DEPENDENCIA DE DATOS. .................................................................................................... 35 2.4.7. BALANCE DE CARGA.............................................................................................................. 35 2.4.8. GRANULARIDAD...................................................................................................................... 36 2.4.9. ENTRADA / SALIDA (E/S)........................................................................................................ 36 2.4.10. LÍMITES Y COSTES DE LA PROGRAMACIÓN EN PARALELO......................................... 37

2.5. MPI Vs. PVM. IMPLEMENTACIONES DE MPI............................................................... 40 2.5.1. SÍNTESIS DE PVM..................................................................................................................... 40 2.5.2. DIFERENCIAS ENTRE MPI Y PVM......................................................................................... 41 2.5.3. IMPLEMENTACIONES DE MPI. .............................................................................................. 41

2.6. PRINCIPIOS BÁSICOS DE FUNCIONAMIENTO DE MPI. ........................................... 44 2.6.1. INTRODUCCIÓN........................................................................................................................ 44 2.6.2. CARACTERÍSTICAS DE UN PROGRAMA MPI. .................................................................... 45 2.6.3. COMUNICACIONES PUNTO A PUNTO.................................................................................. 48 2.6.4. COMUNICACIONES COLECTIVAS. ....................................................................................... 51 2.6.5. COMUNICADORES. .................................................................................................................. 54 2.6.6. TOPOLOGÍAS VIRTUALES...................................................................................................... 56 2.6.7. ENTRADA / SALIDA PARALELA: MPI-2 Vs. MPI-1 ............................................................. 57

2.7. LIBRERÍAS DE MATEMÁTICAS PARALELAS. ............................................................. 60 2.7.1. INTRODUCCIÓN........................................................................................................................ 60 2.7.2. MULTIPLICACIÓN DE VECTORES Y MATRICES. .............................................................. 61 2.7.3. EVOLUCIÓN HASTA LLEGAR A PETSC. .............................................................................. 62

3. CÁLCULO NUMÉRICO EN PARALELO ASISTIDO POR LA LIBRERÍA DE CÁLCULO PETSC. .........................................................................................................64

33..

3.1. INTRODUCCIÓN. .................................................................................................................. 65

5

Page 7: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.2. MODELO DE PROGRAMACIÓN DE PETSC. .................................................................. 66 3.3. PANORÁMICA DE PETSC................................................................................................... 67

3.3.1. INICIO Y TERMINACIÓN DE UN PROGRAMA PETSC. ...................................................... 69 3.3.2. CHEQUEO DE ERRORES.......................................................................................................... 69 3.3.3. OTROS ASPECTOS IMPORTANTES DE LOS PROGRAMAS PETSC.................................. 70 3.3.4. ESCRITURA, COMPILACIÓN Y EJECUCIÓN DE PROGRAMAS PETSC........................... 74

3.4. VECTORES. ............................................................................................................................ 76 3.4.1. INTRODUCCIÓN........................................................................................................................ 76 3.4.2. CREACIÓN DE VECTORES...................................................................................................... 76 3.4.3. OPERACIONES FUNDAMENTALES CON VECTORES........................................................ 79 3.4.4. ORDENACIÓN E INDEXADO (NUMERACIÓN).................................................................... 80 3.4.5. DISEÑO DE DATOS PARALELOS Y VALORES DE CONTORNO (GHOST VALUES)..... 82

3.5. MATRICES.............................................................................................................................. 88 3.5.1. INTRODUCCIÓN........................................................................................................................ 88 3.5.2. CREACIÓN DE MATRICES. ..................................................................................................... 88 3.5.3. OPERACIONES CON MATRICES. ........................................................................................... 95

3.6. KSP: SOLVERS DE ECUACIONES LINEALES. .............................................................. 96 3.6.1. INTRODUCCIÓN........................................................................................................................ 96 3.6.2. OBJETOS KSP............................................................................................................................. 96

3.7. OTROS MÓDULOS DE PETSC. .......................................................................................... 99 3.7.1. INTRODUCCIÓN........................................................................................................................ 99 3.7.2. SOLVERS NO LINEALES. ........................................................................................................ 99

4. PRUEBAS. ........................................................................................................................101 44..

4.1. INTRODUCCIÓN. ................................................................................................................ 102 4.2. EJEMPLOS DE LA PETSC. ................................................................................................ 103

4.2.1. VECTORES. .............................................................................................................................. 103 4.2.2. MATRICES................................................................................................................................ 104

4.3. APLICACIONES CREADAS PARA EL PROYECTO..................................................... 105

5. CONCLUSIONES. ...........................................................................................................108 55..

6. REFERENCIAS WEB Y BIBLIOGRÁFICAS. ............................................................111 66..

7. ANEXO I: IMPLANTACIÓN DEL SISTEMA. ...........................................................114 77..

7.1. INTRODUCCIÓN. ................................................................................................................ 115 7.2. INSTALACIÓN DE CYGWIN. ........................................................................................... 116 7.3. INSTALACIÓN DE BLAS Y LAPACK.............................................................................. 119 7.4. INSTALACIÓN DE MPI. ..................................................................................................... 120 7.5. INSTALACIÓN DE LA LIBRERÍA PETSc....................................................................... 124 7.6. CONFIGURACIÓN DE LOS SERVIDORES SSHD Y RSHD......................................... 127

6

Page 8: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.7. FUNCIONAMIENTO DE MPI Y EJEMPLO DE EJECUCIÓN EN PARALELO. ...... 131

8. ANEXO II: CÓDIGO FUENTE DE LAS PRUEBAS. .................................................134 88..

8.1. INTRODUCCIÓN. ................................................................................................................ 135 8.2. HERRAMIENTAS PARA LA EJECUCIÓN EN PARALELO........................................ 136 8.3. CÓDIGOS PARA LENGUAJE C........................................................................................ 142 8.4. CÓDIGOS PARA LENGUAJE FORTRAN. ...................................................................... 147

7

Page 9: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

111... IINNTTRROODDUUCCCCIIÓÓNN..

8

Page 10: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Encontrar la solución de problemas de contacto es el principal objetivo del proyecto presente. Los problemas de contacto se modelan mediante sistemas de ecuaciones elásticas, las cuales se resuelven a través de técnicas como el Método de Elementos Finitos (M.E.F.) o el Método de Elementos de Contorno (M.E.C.).

Antes de llevar a cabo cualquiera de los dos procedimientos anteriores, es necesario

realizar un mallado de las estructuras que son objeto de estudio. Es común que las mallas resultantes sean muy densas, lo que equivale a un gran número de elementos, que se traduce en un elevado número de incógnitas a resolver.

Las figuras 1 y 2 son dos ejemplos de lo comentado. En cada figura, primero se presenta el esquema (lado izquierdo) del problema a tratar, y después el mallado (lado derecho).

Figura 1. Contacto (y rodadura) entre un anillo y un semiespacio elástico.

Figura 2. Contacto (y rodadura) entre dos anillos.

En un problema de contacto, el Método de Elementos Finitos o el Método de Elementos de Contorno, más las ecuaciones de contacto, dan lugar a un sistema no lineal. Mediante el método de Newton, es posible transformar este sistema no lineal en un sistema lineal del tipo A · x = b, en cada iteración del procedimiento.

9

Page 11: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Es bien sabida la forma de operar del método de Newton, por el cual se resuelve un sistema no lineal mediante sucesivas aproximaciones hasta obtener la solución. Estas aproximaciones dan lugar a sistemas Ax=b, pero con la peculiaridad de que, en el caso de problemas de contacto, son sistemas enormes. Existen diversas técnicas para resolver estos grandes sistemas. Las más conocidas son las que se basan en subespacios de Krylov. Respecto a esto, decir que un algoritmo muy conocido y utilizado es el denominado GMRES (Residuo Mínimo Generalizado), el cual se empleará en la resolución de los sistemas que se presentarán en el proyecto. El problema está en que la matriz del sistema es muy grande, y una sola máquina es incapaz de resolver este sistema en un tiempo razonable (suponiendo que la máquina no es una supercomputadora y la matriz es relativamente grande). Esto lleva a la necesidad de emplear técnicas de paralelización, las cuales se verán a lo largo del documento presente. Y este es nuestro propósito: conseguir resolver sistemas provenientes de problemas de contacto mediante técnicas de cálculo en paralelo.

Antes de entrar en materia, describiremos los distintos apartados de la memoria presente,

los cuales nos conducirán a adquirir los conocimientos necesarios para enmarcar el tema del cálculo en paralelo. Así mismo, se irán describiendo diferentes técnicas de paralelización, así como librerías destinadas a tal fin. Hay que destacar que uno de los incentivos a la hora de comenzar el proyecto, era ver el alcance de la librería de Cálculo en paralelo denominada PETSc, la cual se ha convertido en el centro y motivo de nuestro estudio. Todas las pruebas presentadas en el proyecto, destinadas a la ejecución de aplicaciones en paralelo, hacen uso de las rutinas de la librería PETSc.

El primer gran apartado del documento, hace referencia a la noción de “computación

distribuida”, y se corresponde con el capítulo 2 de esta memoria. Esto da lugar a un amplio repertorio de conceptos, técnicas, librerías y herramientas, los cuales proporcionan el soporte para llevar a cabo la implementación de aplicaciones distribuidas, que se utilizan para el Cálculo en paralelo. De esta importante sección, hay acentuar la parte dedicada al estudio de la librería MPI (Interfaz de Paso de Mensajes), que es la base para el funcionamiento en paralelo de nuestros programas de aplicación. MPI proporciona diversas rutinas con una interfaz claramente definida y de fácil manejo, destinadas a realizar todas las operaciones de comunicación que se llevan a cabo a la hora de ejecutar aplicaciones distribuidas.

Decir también que, a lo largo de este capítulo, se presentan unas directrices a tener en cuenta a la hora de programar en paralelo (“pensar en paralelo”), con el fin de aumentar la eficiencia de nuestras aplicaciones.

La segunda gran sección de esta memoria (capítulo 3), se centra en el estudio y manejo de

la ya mencionada librería PETSc, del inglés: Portable, Extensible Toolkit for Scientific Computation. PETSc fue creada por el Departamento de Energía de los Estados Unidos, dentro de la división MICS (Mathematics, Information, and Computational Sciences).

La librería está escrita en C, aunque permite trabajar directamente con los lenguajes de

programación C, C++ y Fortran (y, últimamente, también con Pitón). Sin embargo, a pesar del lenguaje utilizado, utiliza en todo momento la filosofía de “Programación Orientada a Objetos”, cosa que facilita en gran medida la fase de diseño de aplicaciones.

PETSc está diseñada para aplicaciones a gran escala, e incluye un extenso conjunto de

solvers de ecuaciones lineales y no lineales paralelas. La librería proporciona muchos de los mecanismos necesarios dentro códigos de aplicaciones paralelas, tales como rutinas de

10

Page 12: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

ensamblado de vectores y matrices paralelas, que permiten el solapamiento de las comunicaciones con las operaciones de computación. Además, PETSc incluye soporte para arrays paralelos distribuidos, muy utilizados en métodos de diferencias finitas.

En resumen, algunos de los módulos que incluye PETSc son: vectores paralelos, matrices paralelas, precondicionadores paralelos escalables, solvers lineales paralelos que utilizan métodos del subespacio de Krylov, solvers no lineales paralelos basados en el método de Newton, etc.

Cada uno de los módulos anteriores, son traducidos a objetos, susceptibles de ser utilizados en los códigos de aplicación del usuario.

PETSc necesita un soporte para sus rutinas de cálculo secuencial y paralelo. Este soporte

lo proporcionan las librerías MPI, BLAS y LAPACK, sobre las que habrá tiempo para hablar en el capítulo 2.

Para realizar las pruebas y ensayos pertinentes, se ha dispuesto de un cluster de ordenadores, en el cual se han ejecutado las aplicaciones paralelas creadas como fruto del proyecto presente. En el capítulo 4, se realiza una breve explicación de las pruebas llevadas a cabo, aunque el código fuente de las mismas se adjunta en el Anexo II del documento.

La ejecución de las pruebas en paralelo en el entorno que proporciona del cluster, requiere una instalación previa de todas las librerías y herramientas para la ejecución en paralelo. Este asunto se ha redactado de forma detallada en el Anexo I.

Por último, se invita a leer el capítulo 5, dedicado a las conclusiones extraídas de la

realización del proyecto, las cuales dan una idea del alcance del trabajo realizado.

11

Page 13: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

222... CCOOMMPPUUTTAACCIIÓÓNN DDIISSTTRRIIBBUUIIDDAA..

12

Page 14: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.1. INTRODUCCIÓN.

A la hora de hablar de computación distribuida, aparece el concepto de programación distribuida, que es un modelo de programación enfocado a desarrollar sistemas distribuidos, abiertos, escalables, transparentes y tolerantes a fallos. Casi cualquier lenguaje de programación que tenga acceso al más bajo nivel del hardware del sistema puede manejar la programación distribuida, teniendo en cuenta que hace falta una gran cantidad de tiempo y código.

La programación distribuida utiliza alguna de las arquitecturas básicas: cliente-servidor, 3-tier, n-tier, objetos distribuidos, etc. Existen lenguajes específicamente diseñados para programación distribuida, como son: Ada, Alef, E, Erlang, Limbo y Oz.

El sistema por antonomasia para lograr el cálculo distribuido es la supercomputadora,

que es una computadora con capacidades de cálculo muy superiores a las de cualquier ordenador de trabajo convencional. Hoy en día, el diseño de supercomputadoras se sustenta en cuatro importantes tecnologías, de las cuales, las dos primeras que citaremos son las verdaderamente denominadas supercomputadoras. Pasemos a verlas:

La tecnología de registros vectoriales, creada por Seymour Cray, considerado el padre

de la súper computación, quien inventó y patentó diversas tecnologías que condujeron a la creación de máquinas de computación ultra-rápidas. Esta tecnología permite la ejecución de innumerables operaciones aritméticas en paralelo.

El sistema conocido como M.P.P. (Massively Parallel Processors o Procesadores

Masivamente Paralelos), que consiste en la utilización de cientos y, a veces, miles de microprocesadores estrechamente coordinados.

La tecnología de computación distribuida propiamente dicha: los clusters y los grids,

de los que más tarde hablaremos. Por último, el cuasi-súper cómputo o computación de ciclos redundantes, también

llamada computación zombi. Recientemente, con el éxito de Internet, han surgido proyectos de computación distribuida a nivel mundial, en los que programas especiales aprovechan el tiempo ocioso de miles de ordenadores personales para realizar grandes tareas. Consiste en que un servidor o grupo de servidores distribuyen trabajo de procesamiento a un grupo de computadoras voluntarias a ceder capacidad de procesamiento no utilizada. A diferencia de las tres últimas categorías, el software que corre en estas plataformas debe ser capaz de dividir las tareas en bloques de cálculo independientes, que no se ensamblarán ni comunicarán durante grandes periodos de tiempo, como pueden ser horas. En esta categoría destacan BOINC y Folding@home.

Este tipo de máquinas, generalmente, presenta una arquitectura proyectada y optimizada

enteramente para la aplicación final en concreto. El inconveniente de utilizar supercomputadoras es su alto coste de adquisición. Por esta

razón, el uso de superordenadores auténticos está limitado a organismos gubernamentales, militares y grandes centros de investigación, donde se dispone de suficiente capital.

El resto de colectivos no pueden afrontar el costo económico que supone adquirir una máquina de estas características, y aquí es donde toma la máxima importancia la idea de

13

Page 15: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

poder disponer de esa potencia de cálculo, pero a un precio muy inferior. El concepto de cluster nació cuando los pioneros de la súper computación intentaban difundir diferentes procesos entre varias computadoras, para luego poder recoger los resultados que dichos procesos debían producir. Con un hardware más asequible, se pudo perfilar que podrían conseguirse resultados muy parecidos a los obtenidos con aquellas máquinas mucho más costosas, como se ha venido probando desde entonces.

Esto último, nos lleva a fijar nuestra atención en la computación distribuida. Es un

modelo relativamente nuevo, destinado a resolver problemas de computación masiva utilizando un gran número de computadoras organizadas en racimos incrustados en una infraestructura de telecomunicaciones distribuida.

Esta computación distribuida consiste en compartir recursos heterogéneos, basados en distintas plataformas, arquitecturas y lenguajes de programación, situados en distintos lugares y pertenecientes a diferentes dominios de administración sobre una red que utiliza estándares abiertos. En definitiva, es tratar de forma virtual los recursos informáticos y telemáticos disponibles.

La aparición de la computación distribuida se debe a la necesidad de resolver problemas demasiado grandes para cualquier supercomputadora, con el objetivo adicional de mantener la flexibilidad de trabajar en múltiples problemas más pequeños. Por tanto, la computación distribuida es naturalmente un entorno multiusuario; esto hace que las técnicas de autorización segura sean esenciales antes de permitir que los recursos informáticos sean controlados por usuarios remotos.

Basándonos en la funcionalidad, las redes de computación distribuida se clasifican en redes computacionales y redes de datos.

A continuación, describiremos brevemente algunas de las herramientas y aspectos

relacionados con la computación distribuida.

• Grid. La computación en grid o en malla es un nuevo paradigma de computación distribuida en

el cual todos los recursos de un número indeterminado de computadoras son englobados para ser tratados como un único superordenador de manera transparente.

Las computadoras asociadas al grid no están conectadas o enlazadas firmemente, es decir no tienen porqué estar en el mismo lugar geográfico.

El grid ofrece una forma de resolver grandes problemas, como el plegamiento de las proteínas y descubrimiento de medicamentos, construcción de modelos financieros, simulación de terremotos, inundaciones y otras catástrofes naturales, modelado del clima y el tiempo, etc.

En un sistema SSI (Single System Image), todas las computadoras vinculadas dependen de un sistema operativo común, diseñado al efecto. Este es el caso general de un cluster. En cambio, un grid es heterogéneo, en el sentido de que las computadoras pueden tener diferentes sistemas operativos.

• Globus. La herramienta Globus ha emergido como el estándar de facto para la capa intermedia

(middleware) del grid. Algunos de los servicios que ofrece Globus son: - La gestión de recursos: Protocolo de Gestión de Recursos en Rejilla.

14

Page 16: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- Servicios de Información: Servicio de Descubrimiento y Monitorización. - Gestión y Movimiento de Datos: Acceso Global al Almacenamiento Secundario y

FTP en grid, GridFTP. La mayoría de grids que se expanden sobre las comunidades académicas y de

investigación de Europa y Norteamérica están basadas en la herramienta Globus como núcleo de la capa intermedia.

• XML.

Los servicios Web basados en XML, ofrecen una forma de acceder a diversos servicios en un entorno distribuido. Recientemente, el mundo de la informática en grid y los servicios Web caminan juntos para ofrecer el grid como un servicio Web. La arquitectura está definida por la Open Grid Services Architecture (OGSA). La versión 3.0 de Globus Toolkit, será una implementación de referencia acorde con el estándar OGSA.

• Clustering. Otro método para crear sistemas de supercomputadoras es el clustering. Un cluster o

racimo de computadoras consiste en un grupo de ordenadores de bajo coste en relación al de una supercomputadora, conectados entre sí mediante una red de alta velocidad (Gigabit de fibra óptica, Myrinet, etc.) y un software que realiza la distribución de carga del trabajo entre los equipos. En un cluster, todos los nodos (ordenadores) se encuentran en el mismo lugar geográfico, conectados por una red local para englobar todos lo recursos.

Por lo general, éste tipo de sistemas cuentan con un centro de almacenamiento de datos único.

El sistema utilizado para realizar nuestro trabajo es un cluster, del cual haremos larga mención posteriormente.

• Aspectos de seguridad. El tema de la seguridad es delicado en el ámbito de la computación distribuida pues las

conexiones se hacen de forma remota, razón por la cual surgen problemas para controlar el acceso a los distintos nodos de la red.

Hemos visto que los dos grandes sistemas de computación distribuida son el grid y el cluster (sin mencionar las carísimas supercomputadoras), cada uno con sus ventajas e inconvenientes particulares. Si tenemos presente nuestro objetivo final, el cual es tener un conjunto de computadoras dedicadas exclusivamente al cálculo numérico distribuido, llegamos a la conclusión de que la solución conveniente para llevar a cabo nuestro trabajo es un cluster. Esto, además, coincide con la configuración que se dispone en el lugar de trabajo.

Así pues, nos centraremos en la descripción de la composición y el funcionamiento de un

cluster de ordenadores.

15

Page 17: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.2. CLUSTER DE ORDENADORES.

La arquitectura de un cluster convencional viene determinada por un conjunto de computadoras que se comunican por medio de una conexión de red local muy rápida, para trabajar en un proyecto que sería demasiado grande para una sola computadora, resolviéndolo en un tiempo razonable. Este conjunto de ordenadores se comporta como si fuese una única máquina.

El cómputo con clusters surge como resultado de la convergencia de varias tendencias actuales, que incluyen: la disponibilidad de microprocesadores económicos de alto rendimiento y redes de alta velocidad, el desarrollo de herramientas software para cómputo distribuido de alto rendimiento, así como la creciente necesidad de potencia computacional para aplicaciones que la requieran.

La tecnología de clusters ha evolucionado en apoyo de actividades que van desde aplicaciones de súper cómputo, hasta servidores Web y de comercio electrónico y bases de datos de alto rendimiento, entre otros usos. Ni que decir tiene el gran papel que juegan en la solución de problemas de Ciencia e Ingeniería, que es la disciplina que a nosotros compete.

Veamos la clasificación de los tipos de cluster existentes: • Alta disponibilidad (Fail-over o High-Availability): este tipo de cluster esta diseñado

para mantener uno o varios servicios disponibles, incluso a costa de rendimiento, ya que su foco principal es que el servicio jamás tenga interrupciones, como es el caso de una base de datos.

• Alto rendimiento (HPC o High Performance Computing): este tipo de cluster está

diseñado para obtener el máximo rendimiento de la aplicación utilizada, incluso a costa de la disponibilidad del sistema, es decir el cluster puede sufrir caídas. Este tipo de configuración esta orientada a procesos que requieran mucha capacidad de cálculo.

• Balanceo de carga (Load-balancing): este tipo de cluster esta diseñado para balancear

la carga de trabajo entre varios servidores; esto permite tener, por ejemplo, un sitio Web sin caídas por una carga excesiva de peticiones en un momento dado (excepto cuando se sobrepase la capacidad de todas las máquinas). Actualmente un cluster load-balancing es un fail-over, con el extra del balanceo de la carga y, a menudo, con mayor número de nodos.

En consecuencia, de un cluster se espera que presente combinaciones de los tres servicios

anteriores (alta disponibilidad, alto rendimiento y balanceo de carga) y, además, que sea escalable. Esta última característica es importante, ya que la forma de trabajar con un cluster es empezar con pocos nodos y comenzar probando el funcionamiento de diversas aplicaciones, y si todo va bien y necesitamos mejorar el rendimiento, se van añadiendo más nodos al conjunto. La adición de nuevos nodos al cluster no provoca cambio alguno en las aplicaciones ya desarrolladas, solamente en los resultados obtenidos, como puede ser la mejora en el rendimiento.

Una característica a destacar es la flexibilidad a la hora de construir un cluster. Todos los

nodos pueden tener la misma configuración de hardware y sistema operativo (cluster homogéneo), o bien, tener arquitecturas y sistemas operativos similares, no iguales (cluster

16

Page 18: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

semi-homogéneo), o por el contrario, tener diferente hardware y sistema operativo (cluster heterogéneo).

Para que un cluster funcione como tal, no basta solamente con conectar entre sí los ordenadores, sino que es necesario dotarlo de un sistema de manejo del cluster, que se encargue de interactuar con el usuario y los procesos que corren en él para optimizar el funcionamiento.

Pasemos a ver los componentes de un cluster. En general, un cluster necesita de varios

componentes software y hardware para poder funcionar. A saber: - Nodos (ordenadores - servidores). - Sistemas Operativos. - Conexiones de Red. - Middleware (capa de abstracción entre el usuario y los sistemas operativos). - Protocolos de comunicación y servicios. - Aplicaciones (pueden ser paralelas o no).

Veamos cada uno en detalle. Nodos.

Pueden ser simples ordenadores, sistemas multiprocesador o estaciones de trabajo (workstations).

Sistema Operativo (S.O.). Debe ser de fácil manejo y acceso, y permitir además múltiples procesos y usuarios. Ejemplos se S.O. son: GNU/Linux, Unix (Solaris / HP-Ux / Aix), Windows (NT / 2000 / 2003 Server), Mac OS X, S.O. especiales para Clusters, etc.

Conexiones de Red.

Los nodos de un cluster pueden conectarse mediante una simple red Ethernet, o a través de tecnologías especiales de alta velocidad como Fast-Ethernet, Gigabit-Ethernet, Myrinet, Infiniband, SCI, etc. Myrinet es una red de interconexión de clusters de altas prestaciones. La empresa fabricante de Myrinet es Myricom. Desde 1995, han ido mejorando en rendimiento, hasta obtener en la actualidad latencias de 3 microsegundos y anchos de banda de hasta 10Gbps. Una de sus principales características, además de su rendimiento, es que el procesamiento de las comunicaciones de red se hace a través de chips integrados en las tarjetas de red de Myrinet (Lanai chips), descargando a la CPU de parte del procesamiento de las comunicaciones. Físicamente, Myrinet consiste en dos cables de fibra óptica, upstream y downstream, conectados mediante un único conector. Las especiales características de Myrinet hacen que sea altamente escalable, gracias a la tecnología existente de conmutadores y routers. Su presencia en clusters de gran tamaño es importante. De hecho, en la lista del Top500 (www.top500.org), dentro de los clusters, la inmensa mayoría utilizan redes Myrinet. Para nuestro trabajo particular, no se utiliza una red Myrinet, sino Gigabit-Ethernet, de 1 Gbps, que es de lo que se dispone.

17

Page 19: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Middleware.

El middleware es un software que generalmente actúa entre el sistema operativo y las aplicaciones, con la finalidad de proveer a un cluster de lo siguiente:

- Una interfaz única de acceso al sistema, denominada SSI (Single System Image), la cual genera la sensación al usuario de que utiliza un único ordenador muy potente.

- Herramientas para la optimización y mantenimiento del sistema: migración de procesos, checkpoint-restart (congelar uno o varios procesos, mudarlos de servidor y continuar su funcionamiento en el nuevo host), balanceo de carga, tolerancia a fallos, etc.

- Escalabilidad: debe poder detectar automáticamente nuevos servidores conectados al cluster para proceder a su utilización.

Existen diversos tipos de middleware, como por ejemplo: MOSIX, OpenMOSIX, Cóndor, OpenSSI, etc. Al igual que ocurría con las conexiones de red, la inmensa mayoría de clusters utilizan middleware desarrollado por Myricom, y distribuido bajo la fórmula de Software Libre. Destacan las librerías a bajo nivel GM y MX, las implementaciones de MPI MPICH-GM y MPICH-MX y las implementaciones de Sockets de alto rendimiento Socktes-GM y Sockets-MX. El middleware recibe el trabajo entrante al cluster, y lo distribuye de manera que la aplicación se ejecute lo más rápido posible y el sistema no sufra sobrecargas en un nodo particular. Esto se realiza mediante políticas definidas en el sistema (automáticamente o por un administrador) que le indican dónde y cómo debe distribuir los procesos, a través de un sistema de monitorización, el cual controla la carga de cada CPU y la cantidad de procesos en cada máquina. El middleware también debe poder migrar procesos entre servidores con distintas finalidades:

- balancear la carga: si un servidor está muy cargado de procesos y otro está ocioso, pueden transferirse procesos a este último para liberar de carga al primero y optimizar el funcionamiento.

- mantenimiento de servidores: si hay procesos corriendo en un servidor que necesita mantenimiento o una actualización, es posible migrar los procesos a otro servidor y proceder a desconectar del cluster al primero.

- priorización de trabajos: en caso de tener varios procesos corriendo en el cluster, pero uno de ellos de mayor importancia que los demás, puede migrarse este proceso a los servidores que posean más o mejores recursos para acelerar su procesamiento.

Los modelos de clusters más conocidos por su amplia utilización en función del

middleware son: - NUMA (Non-Uniform Memory Access). - PVM (Parallel Virtual Machine). - MPI (Message Pass Interface), que es la que nosotros utilizamos para desarrollar

nuestro trabajo. Las máquinas de tipo NUMA, tienen acceso compartido a la memoria donde pueden

ejecutar su código de programa. En el kernel de Linux hay ya implementado NUMA, que hace variar el número de accesos a las diferentes regiones de memoria.

18

Page 20: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

MPI y PVM son herramientas ampliamente utilizadas, y son muy conocidas por aquellos que entiende de súper computación basada en GNU/Linux. MPI es el estándar abierto de bibliotecas de paso de mensajes. MPICH es una de las implementaciones más usadas de MPI; tras MPICH se puede encontrar LAM, otra implementación basada en MPI, que también son bibliotecas de código abierto. PVM es un middleware semejante a MPI, ampliamente utilizado en clusters Beowulf. PVM habita en el espacio de usuario, y tiene la ventaja de que no hacen falta modificaciones en el kernel de Linux. Básicamente, cada usuario con derechos suficientes puede ejecutar PVM.

En el apartado 2.4. tendrá lugar una discusión sobre las diferencias entre MPI y PVM,

y se explicará en detalle el funcionamiento de MPI, ya que es la solución escogida para desarrollar nuestro objetivo.

Para entender bien el fin de utilizar la MPI, es necesario realizar un estudio sobre el

Cálculo en Paralelo, que será de gran importancia para nuestro trabajo. La próxima sección se ha dedicado a dicha tarea.

19

Page 21: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.3. CÁLCULO EN PARALELO.

Empecemos viendo qué es el paradigma del Cálculo en Paralelo. Tradicionalmente, todo el software ha sido escrito para computación en serie:

- El programa se diseña para correr en un solo ordenador con una única CPU. - El problema a abordar es dividido en series discretas de instrucciones. - Las instrucciones son ejecutadas una detrás de otra. - Sólo una instrucción puede ser ejecutada en cada instante de tiempo.

En la figura 3, se muestra lo descrito para el caso de computación en serie.

Figura 3. División en instrucciones de un problema para ejecución en serie.

Teniendo en cuenta ya qué es la computación en serie, para el caso más sencillo, podemos

definir la computación en paralelo como la utilización simultánea de múltiples recursos de computación para resolver un problema. Esto es:

- El programa correrá utilizando múltiples CPU’s. - El problema a abordar es dividido en partes discretas que pueden ser resueltas

concurrentemente. - Cada parte es, además, descompuesta en series de instrucciones. - Las instrucciones procedentes de cada parte se ejecutan simultáneamente en

diferentes CPU’s.

El esquema del cálculo en paralelo se muestra en la figura 4. Los recursos de computación pueden incluir:

- Un simple ordenador con múltiples procesadores. - Un número arbitrario de ordenadores conectados en red. - Una combinación de ambos.

El problema a resolver, normalmente, muestra características tales como la habilidad para

ser: - Separados en partes discretas del trabajo, que pueden ser resueltas

simultáneamente. - Ejecutar múltiples instrucciones del programa en cualquier instante de tiempo.

20

Page 22: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- Resuelto en menos tiempo con múltiples recursos computacionales que con un recurso simple para la resolución en serie.

Figura 4. División en instrucciones de un problema para ejecución en paralelo.

La computación en paralelo es una evolución de la computación en serie que intenta emular lo que siempre ha sido el estado de ciertas cosas en el mundo real, muchos hechos complejos e interrelacionados que ocurren al mismo tiempo, pero dentro de una secuencia, por ejemplo: órbitas galácticas y planetarias, patrones del tiempo y del océano, tendencia de las placas tectónicas, línea de ensamblado de automóviles, etc.

Existen diversas razones para utilizar la computación en paralelo, como ahorrar tiempo,

resolver grandes problemas, proporcionar concurrencia, superar los límites de memoria existentes al utilizar un solo ordenador, etc.

2.3.1. CONCEPTOS GENERALES DEL CÁCULO EN PARALELO.

Presentaremos ahora algunos de los conceptos relacionados con la computación en paralelo. La mayoría de ellos serán discutidos posteriormente con más profundidad.

- Tarea: Sección lógica discreta de trabajo computacional. Suele ser un programa o un

conjunto de instrucciones que es ejecutable por un procesador. - Tarea Paralela: Tarea que puede ser ejecutada de forma segura (produciendo

resultados correctos) por múltiples procesadores simultáneamente. - Ejecución en serie: Ejecución de un programa de forma secuencial, una expresión en

cada instante de tiempo. No obstante, todas las tareas paralelas tendrán secciones de un programa paralelo que deben ser ejecutadas en serie.

- Ejecución en paralelo: Ejecución de un programa por más de una tarea, siendo cada

tarea capaz de ejecutar la misma expresión o una diferente, y todas en un mismo instante de tiempo.

21

Page 23: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- Memoria compartida: Desde el punto de vista del hardware, describe una arquitectura de ordenador donde todos los procesadores tienen acceso directo a una memoria física común. Desde el punto de vista del software, describe un modelo donde todas las tareas paralelas tienen la misma representación de la memoria, y pueden direccionar y acceder directamente a las mismas localizaciones de una memoria lógica, sin preocuparse de donde se encuentra la memoria física.

- Memoria distribuida: En el sentido del hardware, se refiere a un acceso a memoria

basado en red para una memoria física que no es común. Como modelo de programación, las tareas sólo pueden ver lógicamente la memoria de la máquina local, y deben utilizar comunicaciones para acceder a la memoria de otras máquinas donde se ejecutan el resto de tareas.

- Comunicaciones: Típicamente, las tareas paralelas necesitan intercambiar datos. Hay

varios caminos para realizar esto, tales como tener una memoria compartida en bus o sobre una red. Sin embargo, en la actualidad, al hecho de intercambiar datos se le denomina comunicaciones, independientemente del método empleado.

- Sincronización: Es la coordinación de tareas paralelas en tiempo real, a menudo

asociado a las comunicaciones. La forma en que se implementa la sincronización entre tareas es poniendo un punto de sincronismo dentro del código de una aplicación, de modo que las tareas no continuarán con su trabajo hasta que todas las tareas hayan llegado al mismo punto de sincronismo o al lógicamente equivalente. La sincronización implica la espera de, al menos, una tarea, y, por lo tanto, puede causar el incremento del tiempo de ejecución de la aplicación paralela. El tema del sincronismo es muy importante, y es uno de los temas más delicados a tratar a la hora de escribir aplicaciones en paralelo.

- Granularidad: En la computación en paralelo, la granularidad es una medida

cualitativa de la tasa de tiempo de computación entre tiempo de comunicaciones. En el límite, granularidad gruesa (fina) es cuando grandes (pequeñas) cantidades de trabajo computacional son realizadas en medio de eventos de comunicaciones.

- Aumento de la velocidad observado: Es la diferencia entre el tiempo de ejecución en

serie de una aplicación y el tiempo de ejecución en paralelo de la misma aplicación.

- Coste operativo paralelo: Cantidad de tiempo requerido para coordinar las tareas paralelas, contrario al tiempo útil de ejecución. El coste operativo paralelo puede incluir factores como el tiempo de arranque y terminación de una tarea, sincronización, comunicaciones de datos, coste del software impuesto por compiladores, librerías, herramientas y sistema operativo paralelos, etc.

- Masivamente paralelo: Término referido al hardware que incluye un sistema paralelo

dado, teniendo en cuenta muchos procesadores. El significado de ‘muchos’ va incrementándose, pero actualmente BG/L apuesta por seis procesadores.

- Escalabilidad: Capacidad de un sistema paralelo (hardware o software) para demostrar

un incremento proporcional en la velocidad de ejecución en paralelo con el aumento del número de procesadores. Un factor importante que influye en la escalabilidad es la forma en que se ha diseñado el código de la aplicación paralela a ejecutar.

22

Page 24: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.3.2. ARQUITECTURAS DE MEMORIA DE ORDENADORES PARALELOS.

2.3.2.1. MEMORIA COMPARTIDA.

Los ordenadores paralelos de memoria compartida varían mucho de unos a otros en cuanto a su arquitectura pero, generalmente, tienen en común la capacidad, para todos los procesadores, de acceder a toda la memoria mediante un espacio de direccionamiento global.

El esquema general de memoria compartida se pre

ores pueden operar de forma ind

mbios efectuados por un procesador en una loca

Figura 5. Memoria compartida por un

Las máquinas de memoria compartida se pueden dividir en dos clases principales, basadas

en l

las máquinas son del tipo SMP (Multiprocesador Simétrico – pro

terminada por dos o más SMP’s enlazados. Un SMP pue

na de las ventajas del método de memoria compartida es que el espacio de dire

ema es la falta de escalabilidad, ya

2.3.2.2. MEMORIA DISTRIBUIDA.

Al igual que los sistemas de memoria compartida, los sistemas de memoria distribuida son mu

senta en la figura 5. Múltiples procesadependiente compartiendo los mismos recursos de

memoria. Los calización concreta de la memoria son visibles por el

resto de procesadores.

conjunto de procesadores.

os tiempos de acceso a memoria: UMA (Acceso Uniforme a Memoria) y NUMA (Acceso No-Uniforme a Memoria).

En el caso de UMA, cesadores idénticos-), y el acceso a memoria y los tiempos asociados a dicho acceso son

idénticos para todos los procesadores. La arquitectura de NUMA viene dede acceder directamente a la memoria de otro SMP. No todos los procesadores tienen el

mismo tiempo de acceso a todas las memorias, ya que el acceso a través del enlace es más lento.

Uccionamiento global presenta un fácil manejo desde el punto de vista del programador a la

hora de acceder a memoria. Además, la compartición de datos entre tareas es rápido y uniforme, debido a la proximidad de la memoria a las CPU’s.

Por el contrario, una gran desventaja que presenta este sistque añadir más CPU’s incrementa de forma geométrica el tráfico asociado con la gestión

de la memoria. Asimismo, es responsabilidad del programador asegurar el acceso correcto a la memoria para garantizar la sincronización entre las tareas. A todo esto se le añade lo caro que resulta diseñar y producir máquinas de memoria compartida con cada incremento del número de procesadores.

y variados, pero comparten características comunes. Los sistemas de memoria distribuida requieren una red de comunicación para conectar las memorias de cada procesador. Esta configuración se muestra en la figura 6.

23

Page 25: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Cada procesador tiene su propia memoria local. Las direcciones de memoria de un procesador no se mapean al resto de procesadores, de modo que no existe el concepto de espacio de direccionamiento global a través de todos los procesadores.

Figura 6. Sistema de memoria distribuida.

Como cada procesador tiene su memoria local, cada cual opera independientemente del resto. Los cambios hechos por cada uno en su memoria local no tienen efecto sobre las memorias de los otros procesadores.

Cuando un procesador necesita acceder a los datos residentes en otro procesador, suele ser tarea del programador definir explícitamente definir cómo y cuando se comunican los datos. Asimismo, es responsabilidad del programador el lograr la sincronización entre tareas.

Este sistema, a diferencia del anterior (sistema de memoria compartida), sí que es

escalable en cuanto a lo que a memoria se refiere a la hora de aumentar el número de procesadores. Cada procesador puede acceder rápidamente a su propia memoria sin ninguna interfaz y sin ningún coste operativo incurrido, siempre manteniendo la coherencia de la caché.

Lógicamente, estos sistemas son de tipo NUMA, es decir, los tiempos de acceso a memoria son no uniformes.

2.3.2.3. HÍBRIDO MEMORIA COMPARTIDA - DISTRIBUIDA.

Hoy en día, los ordenadores más potentes en el mundo emplean una composición entre las arquitecturas de memoria compartida y distribuida, así se muestra en la figura 7. En la tabla 1, se presenta una comparativa entre las arquitecturas de memoria compartida y distribuida.

Figura 7. Sistema híbrido memoria compartida-distribuida.

24

Page 26: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

La componente de memoria compartida es, normalmente, una máquina SMP con coherencia de caché. Los procesadores de un SMP dado pueden direccionar la memoria de esa máquina de forma global.

Por otro lado, la componente de memoria distribuida se corresponde con el funcionamiento en red de múltiples SMP’s. Cada SMP solamente tiene constancia de su propia memoria, no de la memoria de otros SMP’s. Por tanto, son requeridas comunicaciones de red para mover datos de un SMP a otro.

Arquitectura UMA NUMA DISTRIBUIDA

Ejemplos

SMPs Sun Vexx DEC/Compaq SGI Challenge IBM POWER3

SGI Origin Sequent HP Exemplar DEC/Compaq IBM POWER4 (MCM)

Cray T3E Maspar IBM SP2

Comunicaciones

MPI Hilos OpenMP Mem. Compartida

MPI Hilos OpenMP Mem. Compartida

MPI

Escalabilidad Hasta decenas de procesadores.

Hasta cientos de procesadores.

Hasta miles de procesadores.

Características destacables.

Ancho de banda Memoria-CPU.

Ancho de banda Memoria-CPU. Tiempos de acceso no uniformes.

Administración del sistema. Programación complicada a la hora del desarrollo y el mantenimiento.

Disponibilidad del Software Miles de ISVs. Miles de ISVs. Cientos ISVs.

Tabla 1. Comparativa entre arquitecturas de memoria compartida y distribuida.

Las tendencias actuales parecen indicar que este tipo de arquitectura de manejo de

memoria continuará prevaleciendo, y se incrementará el uso de este tipo de sistemas para la computación en paralelo dentro del futuro visible.

Las ventajas e inconvenientes de este tipo de sistemas son la unión de las indicadas para cada arquitectura por separado.

2.3.3. MODELOS DE PROGRAMACIÓN EN PARALELO.

Existen varios modelos de programación en paralelo de uso común: - Memoria compartida. - Hilos (tareas paralelas). - Paso de mensajes. - Datos paralelos. - Híbrido.

25

Page 27: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Los modelos de programación existen como una abstracción sobre las arquitecturas de hardware y de memoria. Aunque no parezca claro a simple vista, estos modelos no son específicos para un tipo particular de máquina o arquitectura de memoria. En realidad, cualquiera de estos modelos, teóricamente, pueden ser implementados en cualquier hardware subyacente.

El modelo a utilizar suele ser una combinación entre lo disponible y la elección personal. No existe un modelo general y óptimo, aunque sí es verdad que hay mejores implementaciones que otras de algunos modelos.

En las siguientes secciones, se describe cada uno de los modelos mencionados.

2.3.3.1. MODELO DE MEMORIA COMPARTIDA.

En este modelo de programación, las tareas comparten un espacio de direcciones común, en el cual leen y escriben de forma asíncrona. Por ende, es necesario mecanismo como cerrojos y semáforos para controlar el acceso a la memoria compartida.

Desde el punto de vista del programador, la ventaja de utilizar este modelo es la escasa noción de “dominio” de datos, de modo que no es necesario especificar explícitamente la comunicación de datos entre tareas. Esto hace que se simplifique el desarrollo del programa.

Por el contrario, en términos de funcionamiento, la comprensión y gestión del área de datos puede llegar a ser muy complicado.

2.3.3.2. MODELO DE HILOS.

En el modelo de hilos de programación en paralelo (programación multihilo), un simple proceso puede tener caminos (hilos) de ejecución múltiples y concurrentes.

El programa principal es ejecutado por el sistema operativo. Dicho programa, ejecuta algo de trabajo en serie, y entonces crea un número determinado de tareas (hilos) que pueden ser ejecutados por el sistema operativo de forma concurrente. Cada hilo tiene sus datos locales, pero también comparte los recursos completos del programa principal. Esto ahorra el coste operativo asociado a la replicación de los recursos del programa para cada hilo. Cada hilo también se beneficia de la vista de la memoria global, ya que todos comparten el espacio de memoria del programa principal; tanto es así, que es la forma que tienen los hilos para intercomunicarse.

El programa principal permanece en funcionamiento para proveer los recursos compartidos necesarios hasta que todos los hilos han acabado su ejecución.

Los hilos son comúnmente asociados con arquitecturas y sistemas operativos de memoria compartida.

Desde la perspectiva de la programación, las implementaciones de hilos comprenden:

- Una librería de subrutinas que son llamadas dentro del código fuente paralelo. - Un conjunto de directivas para el compilador embebidas en el código fuente serie

o paralelo. En última instancia, es el programador es el responsable de determinar todo el

paralelismo. Diversos esfuerzos no relacionados para lograr la estandarización de este modelo han

dado lugar dos implementaciones muy diferentes para sistemas UNIX: POSIX Threads y OpenMP. Microsoft tiene su propia implementación de hilos.

26

Page 28: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.3.3.3. MODELO DE PASO DE MENSAJES.

El modelo de paso de mensajes presenta las siguientes características:

Un conjunto de tareas pueden utilizar sus propias memorias locales durante la computación. Múltiples tareas pueden residir en la misma máquina así como en un número arbitrario de máquinas.

Las tareas intercambian datos por medio de comunicaciones enviando y recibiendo mensajes.

Normalmente, la transferencia de datos requiere operaciones colectivas, que ejecutará cada proceso. Por ejemplo, una operación de tipo ‘send’ en un proceso comunicante debe tener su correspondiente operación ‘recieve’ en el proceso comunicado. Figura 8. Modelo de paso de mensajes.

Desde el punto de vista de la programación, las implementaciones de modelos de paso de

men

ad de librerías de paso de mensajes han estado disponibles des

n 1992, se creó el MPI Forum, con el principal objetivo de establecer una interfaz está

ste, reem

mpartida, las implementaciones de MPI, normalmente, no

ara el proyecto presente, se utiliza MPI como soporte para la implementación de apl

2.3.3.4. MODELO DE DATOS PARALELOS.

La mayoría de trabajos paralelos se centran en operaciones de ejecución de un conjunto de dato

sajes comúnmente consisten en una librería de subrutinas para ser incluidas en el código fuente. El programador es responsable de utilizar estas subrutinas adecuadamente para determinar el paralelismo.

Históricamente, una gran variedde los años 80. Estas implementaciones difieren sustancialmente de unas a otras, haciendo

difícil para los programadores el desarrollo de aplicaciones portables. Endar para las implementaciones de paso de mensajes. La primera parte de MPI (Interfaz

de Paso de Mensajes), entró en vigor en 1994. La segunda parte (MPI-2) a partir de 1996. Hoy en día, MPI es el estándar para paso de mensajes más importante que exiplazando al resto de modelos de paso de mensajes. La mayoría de plataformas más

populares, si no todas, ofrecen al menos una implementación de MPI. Unas pocas ofrecen una implementación completa de MPI-2.

Para arquitecturas de memoria coutilizan una red para comunicaciones entre tareas, sino que hacen uso de un sistema de

memoria compartida (copias de la memoria) por razones de rendimiento. Picaciones que se ejecutarán en paralelo en el cluster montado en el entorno de trabajo.

s. Típicamente, este conjunto de datos está organizado dentro de una estructura común, semejante a un array de una, dos o más dimensiones.

27

Page 29: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Un conjunto de tareas trabajan colectivamente en la misma estructura de datos; sin embargo, cada tarea trabaja en una partición diferente de dicha estructura. Todas las tareas realizan la misma operación en su partición de trabajo.

En arquitecturas de memoria compartida, todas las tareas deben tener acceso a la estructura de datos por medio del direccionamiento global a la memoria común. En arquitecturas de memoria distribuida, la estructura de datos es dividida, y reside como “trozos” en la memoria local de cada tarea.

La programación con el modelo de datos paralelos suele llevarse a cabo escribiendo un

programa con construcciones de datos paralelos. Estas construcciones pueden ser logradas mediante llamadas a una subrutina de una cierta librería, o directivas reconocidas por un compilador de datos paralelos.

La mayoría de las plataformas más comunes disponen de Fortran 90 y 95. HPF (High Performance Fortran), son extensiones de Fortran 90 para soportar la programación de datos en paralelo. HPF incluye, entre otras cosas, directivas para decirle al compilador cómo distribuir los datos paralelos.

Las implementaciones de este modelo en sistemas de memoria distribuida, normalmente, disponen de un compilador para convertir el programa a código estándar con llamadas a librerías de paso de mensajes (habitualmente MPI), para distribuir los datos a todos los procesos implicados en la computación paralela. Todo el paso de mensajes es transparente para el programador.

2.3.3.5. OTROS MODELOS.

Citaremos sólo los tres modelos más comunes distintos a los anteriores.

Híbrido. Este modelo es una combinación de dos o más modelos de programación en paralelo. Actualmente, un ejemplo común de modelo híbrido es la combinación de paso de mensajes (MPI) con un modelo de hilos (POSIX threads) o con un modelo de memoria compartida (OpenMP).

Este modelo híbrido se presta bien, a los entornos hardware cada vez más comunes de máquinas SMP en red.

Otro ejemplo común de un modelo híbrido, es la combinación del modelo de datos paralelos con el de paso de mensajes. Como ya se ha mencionado en la sección anterior, las implementaciones de datos en paralelo (F90, HPF) en arquitecturas de memoria distribuida utilizan, actualmente, el paso de mensajes para transmitir datos entre tareas, de forma transparente para el programador.

SPMD (Single Program Multiple Data). Actualmente, es un modelo de

programación de “alto nivel” que puede ser construido sobre cualquier combinación de los modelos de programación en paralelo ya mencionados.

Figura 9. Modelo SPMD.

28

Page 30: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Como se muestra en la figura 9, un mismo programa es ejecutado por todas las tareas simultáneamente. En cualquier instante de tiempo, las tareas pueden estar ejecutando las mismas o diferentes instrucciones dentro del mismo programa.

Los programas SPMD normalmente tienen la lógica necesaria programada para permitir a las diferentes tareas la ramificación o ejecución condicional de aquellas partes del programa que les son designadas para ejecutarlas. Esto es, las tareas no tienen que ejecutar necesariamente todo el programa, tal vez sólo una porción del mismo. Además, cada tarea puede utilizar datos diferentes.

MPMD (Multiple Program Multiple Data). Al igual que SPMD, MPMD es,

actualmente, es un modelo de programación de “alto nivel” que puede ser construido sobre cualquier combinación de los modelos de programación en paralelo ya mencionados.

Las aplicaciones MPMD típicamente tienen múltiples ficheros objetos ejecutables (programas); así se muestra en la figura 10.

Figura 10. Modelo MPMD.

Mientras la aplicación corre en paralelo, cada tarea puede ejecutar el mismo programa o uno diferente al del resto de tareas. Todas las tareas pueden usar diferentes datos.

29

Page 31: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.4. DISEÑO DE PROGRAMAS EN PARALELO.

2.4.1. PARALELIZACIÓN AUTOMÁTICA FRENTE A MANUAL.

El diseño y desarrollo de programas paralelos ha sido, característicamente, un proceso muy manual. El programador es típicamente el responsable de identificar e implementar actualmente el paralelismo de las aplicaciones.

A menudo, el desarrollo manual de códigos paralelos es un proceso complejo, que consume mucho tiempo, propenso a errores e iterativo.

Hoy en día, existen varias herramientas disponibles para asistir al programador a la hora de convertir programas en serie a programas paralelos. El tipo de herramienta más común utilizada para realizar el paso de programas de serie a paralelo es un compilador o preprocesador de paralelización. Generalmente, un compilador para códigos paralelos puede trabajar de dos formas distintas:

- Totalmente automático: el compilador analiza el código fuente e identifica oportunidades para el paralelismo. El análisis incluye inhibidores identificados para el paralelismo y, posiblemente, un coste de carga para ver si el paralelismo mejoraría el funcionamiento.

- Dirigido al programador: utilizando directivas del compilador o, tal vez, indicadores para el compilador, el programador le dice explícitamente al compilador cómo paralelizar el código. Puede que también sea posible utilizar este método en conjunción con el de paralelización automática.

Si se comienza a paralelizar a partir de un código serie existente, y se dispone de un

tiempo limitado o de un presupuesto reducido, entonces puede que la paralelización automática sea la solución. Sin embargo, hay varias advertencias importantes a tener en cuenta: pueden producirse malos resultados, se puede degradar el funcionamiento, es mucho menos flexible que la paralelización manual, limitado a un subconjunto de código (mayoritariamente bucles) o puede que no se lleve a cabo si el análisis previo indica que hay inhibidores o el código es muy complejo.

Por último, indicar que la mayoría de herramientas de paralelización implementadas son para Fortran.

Las siguientes secciones se dedican a explicar las consideraciones previas a tener en

cuenta a la hora del desarrollo de códigos paralelos de forma manual. Es importante tenerlas en cuenta, ya que para nuestro trabajo particular utilizaremos técnicas manuales de paralelización en paralelo, ya sea desde cero o a partir de un código fuente escrito en serie.

2.4.2. COMPRENSIÓN DEL PROBLEMA A PARALELIZAR.

Indudablemente, el primer paso en el desarrollo de software paralelo es comprender el problema que se desea resolver en paralelo. Si se está comenzando con un programa en serie, también es necesaria la comprensión del código existente.

Antes de perder tiempo en intentar desarrollar una solución en paralelo para un problema, es conveniente determinar si el problema actualmente puede o no ser paralelizado.

Hemos de identificar los puntos relevantes del programa: saber dónde se realiza la mayoría del trabajo real. Herramientas de análisis del funcionamiento y de perfiles pueden ayudar a realizar esta tarea. Es importante focalizar la paralelización en estos puntos relevantes e ignorar aquellas partes del programa que necesitan poco uso de CPU.

30

Page 32: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Otro aspecto importante a tener en cuenta es el tema de los cuellos de botella: hay que localizar aquellas áreas del programa que son desproporcionadamente lentas o que provocan que el trabajo paralelizable sea detenido o aplazado. Por ejemplo, todo lo referente a la entrada / salida de datos es, normalmente, algo que ralentiza un programa. Puede que sea posible reestructurar el programa o utilizar algoritmos diferentes para reducir o eliminar áreas lentas innecesarias.

Por otro lado, es de vital importancia encontrar inhibidores del paralelismo; una clase común de inhibidor del paralelismo es la dependencia de datos.

Es aconsejable estudiar varios algoritmos posibles para buscar mejoras en el funcionamiento de la aplicación paralela.

2.4.3. PARTICIÓN.

Uno de los primeros pasos en el diseño de un programa paralelo es dividir el problema en trozos discretos de trabajo que pueden ser distribuidos a múltiples tareas. Esto es conocido como descomposición o particionamiento.

Hay dos formas básicas para lograr la partición de trabajo computacional entre tareas paralelas: descomposición de dominio y descomposición funcional.

Descomposición de dominio.

En este tipo de partición, se descomponen los datos asociados con un problema. Entonces, cada tarea paralela trabaja en una porción de los datos, tal y como se muestra en la figura 11.

Figura 11. Partición mediante descomposición de dominio. Hay varios caminos para particionar datos. Se presentan en la figura 12, para una y dos

dimensiones. Para dos dimensiones (2D), se particiona en función de los índices de fila y columna (‘*’ es no partición para el índice al que se hace referencia).

31

Page 33: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Figura 12. Partición mediante descomposición de dominio.

Descomposición funcional. En este método, el objetivo se centra en la computación que va a ser realizada, más que en

los datos manipulados por la computación. El problema es descompuesto de acuerdo al trabajo que se va realizar. Entonces, cada tarea es una porción del trabajo total.

Figura 13. Partición mediante descomposición funcional.

2.4.4. COMUNICACIONES.

Algunos tipos de problemas pueden ser descompuestos y ejecutados en paralelo con nin

entr

tareas siempre implica un coste operativo.

guna, o casi ninguna, necesidad de compartir datos entre tareas. Sin embargo, la mayoría de las aplicaciones no son tan simples, y requieren que las tareas compartan datos entre ellas.

Hay un número importante de factores a considerar cuando se diseñan comunicacionese las tareas de un programa paralelo, a saber:

Coste de comunicaciones: - La comunicación entre

32

Page 34: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- Ciertos recursos de las máquinas y varios ciclos de CPU que podrían ser empleados para la computación son utilizados para empaquetar y transmitir datos.

- Frecuentemente, las comunicaciones requieren algún tipo de sincronización entre tareas, que puede resultar en tareas que se esperan unas a otras en vez de estar realizando el trabajo de computación.

- El cursar el tráfico de comunicaciones puede saturar el ancho de banda disponible de la red, agravando los problemas de rendimiento.

Latencia frente a ancho de banda:

- Latencia es el tiempo que tarda una tarea en enviar el mínimo mensaje (0 byte) desde un punto A a otro punto B.

- Ancho de banda es la cantidad de datos que pueden ser enviados por unidad de tiempo. Es el régimen binario.

- El envío de muchos mensajes pequeños puede causar que la latencia domine sobre el coste operativo de comunicaciones. A menudo, es más eficiente empaquetar mensajes pequeños dentro de otro mensaje más grande, así se incrementa el ancho de banda efectivo de las comunicaciones.

Visibilidad de las comunicaciones:

- Con el modelo de paso de mensajes, las comunicaciones son explícitas y, generalmente, bastante visibles y bajo el control del programador.

- Con el modelo de datos paralelos, las comunicaciones a menudo ocurren de forma transparente para el programador, particularmente en arquitecturas de memoria distribuida. Puede incluso que el programador no sepa exactamente cómo se están llevando a cabo las comunicaciones entre tareas.

Comunicaciones asíncronas frente a síncronas:

- Las comunicaciones síncronas requieren algún tipo de coordinación entre las tareas que están compartiendo datos. Esto puede ser explícitamente estructurado en código por el programador, o puede ocurrir a un nivel más bajo desconocido por el programador.

- A menudo, se hace referencia a las comunicaciones síncronas como comunicaciones “bloqueantes”, puesto que otro trabajo debe esperara hasta que las comunicaciones se hayan completado.

- A las comunicaciones asíncronas se hace referencia como comunicaciones “no bloqueantes”, puesto que pueden realizarse mientras tiene lugar otro trabajo.

- La computación entrelazada con las comunicaciones es el único y mayor beneficio para decidirse por utilizar comunicaciones asíncronas.

Alcance de las comunicaciones:

- Saber qué tareas se deben comunicar con otras es crítico durante la parte de diseño del código paralelo. Las dos formas de comunicación descritas a continuación, pueden ser implementadas síncronamente o asíncronamente:

a) Comunicación punto a punto: involucra a dos tareas, una actuando

como el transmisor / productor de datos y la otra como el receptor / consumidor.

b) Comunicación colectiva: implica la compartición de datos entre más de dos tareas, que pueden pertenecer a un mismo grupo o a otro

33

Page 35: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

distinto, según se especifique. Ejemplos de comunicaciones colectivas son: difusión, recolección, reducción y dispersión de datos entre tareas.

Eficiencia de las comunicaciones:

- Muy a menudo, el programador tomará una elección en consideración a los factores que pueden afectar al funcionamiento de las comunicaciones. Esto lleva a plantearse qué implementación de un modelo concreto debería utilizarse. Por ejemplo, para el modelo de paso de mensajes, una implementación de MPI será más rápida que otras.

- Otro aspecto a tener en cuenta es el tipo de operaciones de comunicación que deberían usarse. Como ya se ha comentado anteriormente, las operaciones de comunicación asíncrona pueden mejorar el funcionamiento global del programa.

- También habrá que tener en cuenta que algunas plataformas pueden ofrecer más medios que una simple red para llevar a cabo las comunicaciones. Por tanto, tendremos que elegir entre las diferentes plataformas disponibles.

2.4.5. SINCRONIZACIÓN

Veamos ahora una pequeña síntesis de los tipos de sincronización posibles:

Barrera: normalmente, implica a todas las tareas. Cada tarea realiza su trabajo hasta que alcanza la barrera. Entonces, la tarea que llega a la barrera se para o se bloquea. Cuando la última tarea alcanza la barrera, todas las tareas son sincronizadas. Lo que ocurre a partir de aquí, varía de unos programas a otros. Frecuentemente, se realizará un conjunto de trabajos en serie. En otros casos, las tareas son liberadas automáticamente para continuar con sus trabajos.

Cerrojo / semáforo: pueden involucrar cualquier número de tareas. Típicamente,

se utilizan para serializar (proteger) el acceso a los datos globales o a una sección de código. Solamente una tarea, en el mismo instante de tiempo, puede utilizar el cerrojo / semáforo / bandera.

La primera tarea que adquiere el cerrojo lo cierra; a continuación, esta tarea puede acceder de forma segura al código o datos protegidos. El resto de tareas pueden intentar adquirir el cerrojo, pero deben esperar hasta que la tarea que posee el cerrojo lo libere.

Este tipo de métodos pueden ser bloqueantes o no bloqueantes.

Operaciones de comunicación síncronas: involucra solamente a aquellas tareas que estén ejecutando una operación de comunicación. Cuando una tarea realiza una operación de este tipo, es necesario alguna forma de coordinación entre las tareas que participan en ella. Por ejemplo, cuando una tarea vaya a realizar una operación de envío de datos a otra tarea, la primera debe recibir un asentimiento positivo, así la segunda indica que está lista para recibir.

Este tema ya se ha descrito en la sección de comunicaciones.

34

Page 36: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.4.6. DEPENDENCIA DE DATOS.

Se dice que existe una dependencia entre sentencias de un programa cuando el orden de ejecución de las sentencias afecta al resultado del programa. Una dependencia de datos resulta del uso múltiple de la misma zona de almacenamiento por diferentes tareas.

Las dependencias son muy importantes para la programación en paralelo, ya que son uno de los inhibidores primarios para el paralelismo.

Aunque todas las dependencias son importantes para identificar cuándo es posible la programación en paralelo, las dependencias acarreadas por los bucles son particularmente importantes, puesto que los bucles son, posiblemente, el objetivo más común de los esfuerzos de paralelización.

En arquitecturas de memoria distribuida, se han de comunicar los datos requeridos en los puntos de sincronización. Para arquitecturas de memoria compartida, se han de sincronizar las operaciones de lectura / escritura entre tareas.

2.4.7. BALANCE DE CARGA.

El balance de carga hace referencia a la práctica de distribuir el trabajo entre tareas, de modo que todas las tareas estén ocupadas todo el tiempo. Esto se puede considerar como una minimización del tiempo en que las tareas se encuentran desocupadas.

El balance de carga es importante para los programas paralelos, por razones de funcionamiento. Por ejemplo, si todas las tareas están sujetas a un punto de sincronización de barrera, la tarea más lenta determinará el funcionamiento del resto.

Veamos algunos métodos para lograr el balance de carga:

Particionar equitativamente el trabajo que recibe cada tarea: - Para operaciones con matrices / arrays donde cada tarea realiza un trabajo

similar, se han de distribuir el conjunto de datos entre las distintas tareas. Es importante no olvidarse de las dependencias de datos.

- Para iteraciones de bucles donde el trabajo realizado en cada iteración es similar, se han de distribuir las iteraciones entre las tareas. Al igual que antes, la dependencia de datos es muy importante.

- Si se está utilizando una mezcla heterogénea de máquinas con distintas características de funcionamiento, se ha de utilizar algún tipo de herramienta de análisis para detectar cualquier carga no balanceada.

Asignación de trabajo de forma dinámica:

- Ciertas clases de problemas resultan en desequilibrio de carga, aun cuando los datos se han distribuido de forma equitativa entre las tareas. Ejemplo de esto son las matrices dispersas, donde algunas tareas tendrán datos en los que trabajar y otras no debido a que contienen la mayoría de ceros de la matriz.

- Cuando la cantidad de trabajo de que cada tarea realizará es intencionalmente variable, o se es incapaz de predecirlo, puede ser útil utilizar un método de “charco de tareas programado”. A medida que cada tarea finaliza su trabajo, pide una nueva porción de trabajo.

- Puede que llegue a ser necesario diseñar un algoritmo dentro del código que detecte y maneje los desequilibrios de carga dinámicamente.

35

Page 37: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.4.8. GRANULARIDAD.

En computación en paralelo, la granularidad es una medida cualitativa de la tasa de computación frente a comunicación. Típicamente, los periodos de computación son separados de los periodos de comunicación por eventos de sincronización.

Se distinguen dos tipos de granularidad: fina y gruesa. Veámoslas detenidamente:

Paralelismo de grano fino: En esta situación, se realizan cantidades relativamente pequeñas

de trabajo computacional entre eventos de comunicaciones: baja tasa de computación frente a comunicación. Esto facilita el balance de carga.

Granularidad fina implica alto coste operativo y menos

oportunidad para mejorar el rendimiento. Si la granularidad es demasiado fina, es posible que la sobrecarga

requerida para las comunicaciones y la sincronización entre tareas tome más tiempo que la computación.

Figura 14. Grano fino.

Paralelismo de grano grueso:

En este caso, se realizan grandes cantidades de computación entre

los eventos de comunicación / sincronización: alta tasa de computación frente a comunicación.

En contraposición al paralelismo de grano fino, con granularidad

gruesa hay más oportunidad de mejorar el funcionamiento; sin embargo, es más difícil lograr el balance de carga de forma eficiente.

Figura 15. Grano grueso.

El tipo granularidad más eficiente, en general, no es ni una ni otra, sino que depende del

algoritmo utilizado y del entorno hardware en que corre. En la mayoría de los casos, la sobrecarga asociada con las comunicaciones y la sincronización es alta en relación con la velocidad de ejecución, de modo que es ventajoso utilizar granularidad gruesa. El paralelismo de granularidad fina puede reducir la sobrecarga debida al desequilibrio de carga.

2.4.9. ENTRADA / SALIDA (E/S).

Las operaciones de E/S son estimadas, generalmente, como inhibidores del paralelismo. Los sistemas de E/S paralelos son relativamente nuevos, o no se encuentran disponibles para todas las plataformas. En un entorno donde todas las tareas ven el mismo espacio de archivos, las operaciones de escritura resultarán en sobre escritura de ficheros. Las operaciones de lectura se verán afectadas por la capacidad del servidor de ficheros para manejar múltiples

36

Page 38: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

peticiones de lectura al mismo tiempo. Las operaciones de E/S que necesitan ser conducidas a través de la red (sistema de ficheros no local, como es el caso de un sistema NFS), podrán causar cuellos de botella severos.

Por otro lado, decir que existen sistemas de ficheros paralelos disponibles, por ejemplo:

GPFS (General Parallel File System for AIX, de IBM), Lustre (para clusters Linux), PVFS/PVFS2 (Parallel Virtual File System, para clusters Linux), PanFS (Panasas ActiveScale File System, para clusters Linux), HP SFS (HP StorageWorks Scalable File Share, que es un sistema de ficheros paralelo basado en Lustre, también para clusters Linux).

La especificación de la interfaz de E/S paralela para MPI ha estado disponible desde 1996

como parte de MPI-2. Actualmente, existen implementaciones de pago y libres de MPI-2. Algunos aspectos a tener en cuenta a la hora de realizar operaciones de E/S paralelas son:

- Regla de oro: reducir todo lo posible el número de operaciones de E/S. - Si se dispone de un sistema de ficheros paralelo, utilizarlo con la mayor destreza

posible para conseguir un mayor rendimiento. - Confinar las operaciones de E/S a porciones en serie específicas del trabajo, y

entonces utilizar comunicaciones en paralelo para distribuir los datos a las tareas paralelas.

- Para sistemas de memoria distribuida con espacio de archivos compartido, realizar la E/S en el espacio de ficheros local, no en el compartido. Por ejemplo, cada procesador puede disponer de una carpeta temporal compartida en su espacio de archivos. Esto, normalmente, es mucho más eficiente que realizar la E/S sobre la red al directorio local.

- Crear nombres de archivos únicos para cada fichero de E/S de las tareas.

2.4.10. LÍMITES Y COSTES DE LA PROGRAMACIÓN EN PARALELO.

A continuación, se presenta una serie de consideraciones a tener en cuenta a la hora de trabajar en paralelo.

Ley de Amdahl: declara que el aumento de velocidad (speedup) del programa

potencial es definido por la fracción de código P que puede ser paralelizado:

Pspeedup

−=

11 (1)

Si ninguna parte del código puede ser paralelizada, P es cero, por tanto, speedeup es uno

(no se produce incremento de velocidad). Por el contrario, si P es uno (todo el código es paralelizable), speedup es infinito, en teoría (existen límites hard y software, evidentemente).

Si el 50% del código es paralelizable, quiere decir que el código paralelo será el doble de rápido que el código serie.

Si introducimos en el modelo el número de procesadores que realizan una fracción del

trabajo paralelo, la relación de Amdahl puede ser expresada como se presenta en (2).

37

Page 39: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

SNP

speedup+

=1

(2)

Los factores que intervienen en (2) son: - P: fracción de código serie paralelizable. - N: número de procesadores que trabajan en paralelo. - S: Fracción de código serie no paralelizable (S = 1 – P).

Es obvio que hay límites marcados para el paralelismo, marcados por la escalabilidad. A

continuación se muestra una tabla comparativa de lo mencionado.

P = 0.50 P = 0.90 P = 0.9910 1.82 5.26 9.17

100 1.98 9.17 50.251000 1.99 9.91 90.99

10000 1.99 9.91 99.02

speedupN

Tabla 2. Speedup en función de N y P. Sin embargo, ciertos problemas muestran un incremento en el funcionamiento al aumentar

el tamaño del problema. Este tipo de problemas que incrementan el porcentaje de tiempo paralelo con sus tamaños, son más escalables que los problemas con un porcentaje fijo de tiempo paralelo.

Complejidad.

En general, las aplicaciones paralelas son mucho más complejas que las correspondientes

aplicaciones serie, incluso puede que lo sean en un orden de magnitud. No sólo se tiene múltiples flujos de instrucciones ejecutándose a la vez, sino también datos fluyendo por ellos.

Los costes de complejidad son medidos virtualmente en tiempo de programación, teniendo en cuenta cada etapa del ciclo de desarrollo del software, esto es: diseño, codificación, depurado, ajustes y mantenimiento.

Adquirir buenas costumbres para desarrollar software es esencial cuando se trabaja con aplicaciones paralelas, especialmente si alguien ajeno a la labor actual tendrá que trabajar sobre ella posteriormente.

Portabilidad.

Gracias a la estandarización de varias API’s (Interfaces para el Programa de Aplicación),

tales como MPI, Hilos POSIX, HPF y OpenMP, las cuestiones de portabilidad no son tan problemáticas como antiguamente; sin embargo, aún existen pequeños asuntos que dificultan esta tarea.

Todos los temas actuales asociados con la portabilidad de programas serie son aplicables a la portabilidad de programas paralelos.

Aunque existan estándares para varios API’s, las implementaciones diferirán en algunos detalles, lo que se traduce en modificaciones del código para efectuar la portabilidad. Los sistemas operativos pueden jugar un papel crucial en los asuntos de portabilidad de códigos.

Las arquitecturas hardware son altamente variables de unas a otras, lo que afecta en gran medida a la portabilidad.

38

Page 40: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Requerimientos de recursos. El primer objetivo de programación paralela es decrementar el tiempo de ejecución; sin

embargo, en orden a lograr esto, se requiere mayor tiempo de CPU. Por ejemplo, un código paralelo que corre en una hora en ocho procesadores a la vez, en uno sólo procesador tardará ocho horas (tardará un poco menos, ya que en paralelo hay que tener en cuenta las comunicaciones interprocesos, etc.).

La cantidad de memoria requerida puede ser mayor para códigos paralelos que para

códigos en serie, debido a la necesidad de replicar datos y por la sobrecarga asociada con las librerías y subsistemas de soporte paralelo.

Para un programa paralelo de corta ejecución, se puede producir un empeoramiento del

funcionamiento comparado con el de su programa serie análogo. Los costes operativos asociados con el establecimiento del entorno paralelo, la creación y terminación de tareas, y las comunicaciones pueden consistir en una significante porción del tiempo total de ejecución para un programa paralelo corto. Esto es muy ineficiente.

Escalabilidad.

La capacidad de escalar el funcionamiento de un programa paralelo es el resultado de un

cierto número de factores interrelacionados. Añadir simplemente más máquinas es raramente la solución

El algoritmo utilizado para paralelizar puede tener límite de escalabilidad inherentes. En algún punto, añadir más recursos causa el decremento del rendimiento. La mayoría de las soluciones paralelas muestran esta característica en algún momento.

Los factores relacionados con el hardware juegan un papel significante en la escalabilidad. Por ejemplo: ancho de banda del bus memoria-CPU en una máquina SMP, ancho de banda de la red, cantidad de memoria disponible en una máquina dada o en un conjunto de ellas, velocidad de reloj del procesador, etc.

El software de las librerías y subsistemas de soporte, independiente de la aplicación paralela, puede limitar la escalabilidad.

39

Page 41: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.5. MPI Vs. PVM. IMPLEMENTACIONES DE MPI.

Ya se hablado sobre el modelo de programación paralelo de paso de mensajes. Concretamente, en la sección 2.3.8., se ha hecho una pequeña introducción al estándar MPI. No obstante, MPI no es la única implementación del modelo mencionado, sino que existen otros, y de entre ellos, el más popular junto con MPI es PVM. A continuación, hablaremos de estos dos modelos, y llegaremos a la conclusión de que MPI es la implementación que necesitamos para nuestro trabajo.

Veamos, primeramente, una breve descripción de PVM.

2.5.1. SÍNTESIS DE PVM.

El software PVM (Parallel Virtual Machine) proporciona un soporte unificado, dentro del cual los programas paralelos pueden ser desarrollados de forma eficiente y directa, utilizando el hardware existente. PVM habilita a un conjunto de sistemas de ordenadores heterogéneos para ser vistos como una máquina paralela virtual. PVM maneja, de forma transparente, todo el encaminamiento de mensajes, conversión de datos y programación de tareas, a través de una red de arquitecturas de ordenadores incompatibles. El software PVM es muy portable.

El modelo de computación de PVM es simple, pero muy general, y da cabida a una amplia

variedad de estructuras de programas de aplicación. La interfaz de programación es intencionadamente directa, de modo que permite la implementación de estructuras de programa simples, de forma intuitiva. La filosofía es la siguiente: el usuario escribe su código de aplicación como una colección de tareas que cooperan entre ellas. Cada tarea accede a los recursos de PVM a través de una librería de interfaces de rutinas estándar. Estas rutinas permiten la inicialización y terminación de tareas a través de la red, así como la comunicación y sincronización entre las mismas. Las primitivas de paso de mensajes de PVM están orientadas a operación heterogénea, involucrando construcciones fuertemente tipadas para buffering y transmisión. Las construcciones de comunicación incluyen estructuras de datos para el envío y recepción de mensajes, así como primitivas de alto nivel, tales como difusión, sincronización de barrera y suma global (reducción).

Las tareas PVM, pueden tener estructuras de dependencia y de control absoluto, es decir, en cualquier punto de la ejecución de una aplicación concurrente, cualquier tarea existente puede comenzar o parar otras tareas, o añadir o eliminar ordenadores de la máquina virtual. Todo proceso se puede comunicar y/o sincronizar con otro. Cualquier estructura de dependencia y de control específica puede ser implementada bajo el sistema PVM, con el apropiado uso de construcciones PVM y sentencias de control de flujo de lenguaje máquina.

Con PVM se pueden resolver grandes problemas de computación, aprovechando el

agregado de potencia y memoria del conjunto de ordenadores que pertenecen a la máquina virtual. Debido a su naturaleza general (específicamente, el concepto de máquina virtual), y también por su simple pero completa interfaz de programación, el sistema PVM ha logrado una gran aceptación en la comunidad científica de computación de alto rendimiento.

Como ya se ha comentado, en su momento vimos una pequeña introducción a MPI, de

modo que lo siguiente es comparar los dos modelos: MPI y PVM. En la sección 2.5.4., tendrá lugar una explicación más profunda sobre el funcionamiento de MPI, ya que es el modelo escogido para llevar a cabo nuestro trabajo.

40

Page 42: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.5.2. DIFERENCIAS ENTRE MPI Y PVM.

A menudo se comparan MPI y PVM. Normalmente, estas comparaciones empiezan con la no mencionada suposición de que ambas representan diferentes soluciones para el mismo problema. En realidad, a menudo los dos sistemas están resolviendo problemas diferentes.

Aunque MPI y PVM hayan surgido de orígenes diferentes, ambas son especificaciones de

librerías que pueden utilizarse para el cómputo en paralelo, por esta razón es natural compararlas. La complicación de comparar estos dos métodos es que surgen determinados problemas, tales como que ambos utilizan la misma palabra para conceptos distintos. Por ejemplo, un grupo de MPI y un grupo de PVM son objetos bastantes distintos, aunque tengan similaridades superficiales.

No veremos aquí casi ninguna diferencia. Si es necesario, se aconseja acudir a [22], donde

se realiza una comparativa de MPI y PVM bastante aclaratoria. Sí comentaremos una diferencia notable, por la importancia que tiene para nuestro trabajo, y que es una de las explicaciones de porqué se ha elegido MPI como solución adoptada. A continuación se comenta esta diferencia. PVM, a través de su máquina virtual (implementada como demonios PVM), proporciona un sistema operativo distribuido, simple pero útil. Algunas interfaces especiales, tales como “pvm_reg_tasker”, permiten al sistema PVM interactuar con otros sistemas de gestión de recursos. MPI no suministra una máquina virtual, ni siquiera MPI-2. Según el MPI Forum, MPI sería una librería para escribir programas de aplicación, no un sistema operativo distribuido. MPI, más bien, proporciona un camino, a través de un nuevo objeto MPI (MPI_Info), para comunicarse con cualquier mecanismo que suministre los servicios del sistema operativo distribuido. El cluster montado en nuestro entorno de trabajo es homogéneo, todos los nodos disponen del mismo sistema operativo (ya instalado) y tienen la misma configuración hardware. Esta es la razón de que utilicemos MPI, ya que el sistema operativo ya instalado en los nodos es el que se encarga de la gestión de recursos.

No obstante, la razón de mayor peso para escoger MPI frente a PVM, es que la librería de

cálculo en paralelo PETSc, que en breve describiremos, hace uso de MPI. Esta simple razón marca la forma de trabajar y sus implicaciones pertinentes.

2.5.3. IMPLEMENTACIONES DE MPI.

Existen diversos grupos trabajando en implementaciones de MPI. A continuación, mostraremos una lista con las principales.

MPICH: implementación MPI portable y de libre distribución, creado por

ANL/MSU. ANL es Argonne National Laboratory, que también es el que ha implementado la librería PETSc. Sobre MPI y PETSc se centra nuestro trabajo, siendo MPICH la implementación de MPI escogida. Es por ello, que es de agradecer todo el trabajo realizado por el Laboratorio Argonne, ya que nos ha facilitado en gran medida nuestra labor, sobre todo a la hora de la implantación del sistema.

MP-MPICH: MP viene de Multi-Plataforma. MP-MPICH está basado en MPICH

1.1.2. Actualmente, MP-MPICH consiste en dos partes que están integradas dentro del único árbol MPICH:

41

Page 43: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- NT-MPICH: es un puerto-Windows NT. Contiene un nuevo Dispositivo-Winsocket (ch_wsock) para comunicación de baja latencia vía TCP/IP, y una adaptación del dispositivo de memoria compartida ch_lfshmem. Contiene el dispositivo ch_wsock2, que básicamente es una combinación de ch_wsock y ch_lfshmem. NT-MPICH soporta las funciones de logging de MPE y MPI-IO (MPI-E/S) vía ROMIO. También están disponibles un servicio de ejecución remota y una shell gráfica.

- SCI-MPICH: es la primera implementación de MPI disponible de libre distribución, que se comunica directamente a través de una red SCI (Scalable Coherent Interface) rápida. SCI es un estándar cuya implementación más destacada es una red de área de sistema (SAN) de altas prestaciones para interconexión de clusters. SCI-MPICH está disponible para Solaris x86, Linux y también para Windows NT.

winmpich: es una implementación de MPI para Windows NT, creada por la

Universidad del Estado de Misisipi. WMPI II, WMPI 1.5 y PaTENT MPI: son implementaciones comerciales de

MPI. WMPI II es una implementación completa del estándar MPI-2 para las versiones de 32 y 64 bit de los sistemas operativos de Windows y Linux.

Máquina Virtual de Alto Rendimiento de Illinois: incluye una implementación

de MPI (basada en MPICH). Es un sucesor del proyecto Mensajes Rápidos.

MPI-BIP: es una implementación de MPI utilizando la API BIP.

ScaMPI: viene de Scali AS thread-safe, implementación de MPI de alto rendimiento. Actualmente corre sobre memoria compartida de SMP y SCI. Los sistemas operativos soportados son Solaris (x86 y SPARC), Linux (x86) y Windows NT (x86).

MPICH-Madeleine: implementación multi-protocolo de MPI.

Implementación UNIFY de la Universidad del Estado de Misisipi: proporciona

un subconjunto de MPI dentro del entorno PVM, pero sin sacrificar las llamadas PVM ya existentes.

Implementación LAM de MPI.

MPI para el Fujitsu AP1000: de la Universidad Nacional Australiana.

Cray MPI Product, para el T3D: de la Investigación Cray y el Centro de

Computación Paralela de Edimburgo.

MPI de IBM: par el SP.

MPI de IBM: par el OS/390.

MPI de SGI: disponible para máquinas SGI de 64 bit mips3 y mips4.

42

Page 44: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

PowerMPI, para sistemas Parsytec.

Implementaciones para multicomputador, para NT y para cluster comercial MPI/Pro: de MPI Software Technology, Inc. Implementación parcial de MPI para el cluster Macintosh G3.

STAMPI: es una librería de comunicación basada en MPI para clusters

heterogéneos.

Como ya se ha comentado, la implementación escogida de todas las citadas (y más que no se ha citado) de MPI es MPICH. La elección se debe a que a MPICH es de libre distribución, y proporciona buenos manuales de instalación y de usuario. Además, para la utilización de librería PETSc, parte central de nuestro trabajo, es una buena opción el escoger MPICH, ya que funciona a la perfección (así se ha demostrado en las pruebas realizadas).

MPICH se distribuye para varias plataformas distintas, así como en código fuente. Esta última ha sido nuestra elección, para así compilar este código fuente y adaptarlo a nuestro sistema, que trabaja sobre un entorno Cygwin (emulación de UNIX sobre Windows).

En el ANEXO I, se explica con detalle el proceso de instalación de MPICH en nuestro

sistema. Para obtener información adicional, se aconseja dirigirse a la guía de instalación de MPICH, que se incluye dentro del paquete mpich2-1.0.4p1.tar.gz, al que se hace referencia en el ANEXO I.

43

Page 45: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.6. PRINCIPIOS BÁSICOS DE FUNCIONAMIENTO DE MPI.

2.6.1. INTRODUCCIÓN.

En el inicio del apartado 2.3.3, se presentaba una introducción a los modelos de programación en paralelo, donde se incluía el modelo de paso de mensajes. Posteriormente, en la sección 2.3.3.5., se habló brevemente de los modelos de computación en paralelo SPMD (Single Program, Multiple Data) y MPMD (Multipe Program, Multiple Data).

A lo largo del tiempo, se han ido implementando diversas variantes de los modelos que se han ido proponiendo. Por ejemplo, HPF es una interfaz SPMD. Realmente, SPMD y MPMD son esencialmente lo mismo, puesto que cualquier modelo MPMD puede descomponerse en SPMD’s.

Una computación en paralelo, consiste en que varios procesos trabajan a la vez sobre algunos datos locales. Cada proceso tiene variables puramente locales, y no hay ningún mecanismo por el que cualquier proceso pueda acceder directamente a la memoria de otro.

La compartición de datos entre procesos tiene lugar a través del paso de mensajes, esto es, envío y recepción explícitos de datos entre procesos.

Hacer notar que el modelo involucra a procesos que, en principio, no necesariamente han de correr en diferentes procesadores.

Una primera razón para la utilización de este modelo, es que es extremadamente general, por tanto, puede ser implementado en una gran variedad de plataformas. De forma general, este modelo permite más control sobre la localización de datos y el flujo dentro de una aplicación paralela que, por ejemplo, el modelo de memoria compartida.

MPI, del inglés Message Pass Interface, es una implementación del paradigma de paso de

mensajes, que pretende ofrecer una realización del modelo SPMD. MPI es una librería de funciones (en C) o subrutinas (en Fortran) que pueden ser incluidas en el código fuente de nuestro programa de aplicación para lograr la comunicación de datos entre procesos.

MPI fue desarrollado tras dos años de discusiones dirigidas por el MPI Forum, un grupo

de sesenta personas representando unas cuarenta organizaciones. El estándar MPI-1 fue definido en 1994. Este especifica los nombres, secuencias de

llamada y resultados de subrutinas y funciones para ser invocados desde Fortran 77 y C, respectivamente. Todas las implementaciones de MPI deben ser conformes a las reglas definidas, de modo que se asegure la portabilidad. Los programas MPI deberían correr en cualquier plataforma que soporte el estándar MPI. La implementación detallada de las librerías se deja para los fabricantes, que son libres de producir versiones optimizadas para sus máquinas.

El estándar MPI-2 provee características adicionales no presentes en MPI-1, incluyendo herramientas para E/S paralela y gestión dinámica de procesos, entre otras.

MPI, en su interés de buscar la portabilidad del código, proporciona soporte para diversas

arquitecturas paralelas heterogéneas. Además, provee una gran cantidad de funcionalidad, incluyendo distintas formas de comunicación, rutinas especiales para operaciones colectivas comunes y la capacidad de manejar tipos de datos definidos por el usuario y diferentes topologías.

44

Page 46: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Los programas de paso de mensajes, consisten en múltiples instancias de un programa serie que se comunican por llamadas de librería. En líneas generales, estas llamadas se pueden dividir en cuatro clases:

1. Llamadas utilizadas para inicializar, gestionar y terminar las comunicaciones. 2. Llamadas usadas para la comunicación entre dos procesadores. 3. Llamadas para realizar operaciones de comunicaciones entre grupos de

procesadores. 4. Llamadas utilizadas para crear tipos de datos arbitrarios.

La primera clase, consiste en llamadas para comenzar las comunicaciones, identificando el

número de procesadores que se utilizan para la ejecución en paralelo, creando grupos de procesadores e identificando en qué procesador está corriendo una instancia particular de un programa.

La segunda clase de llamadas, denominadas operaciones de comunicaciones punto a punto, consiste en diferentes tipos de operaciones de envío y recepción.

La tercera clase son las operaciones colectivas, que proporcionan sincronización o ciertos tipos de operaciones de comunicaciones bien definidas entre grupos de procesos.

La última clase, proporciona flexibilidad en atención a estructuras de datos complicadas.

2.6.2. CARACTERÍSTICAS DE UN PROGRAMA MPI.

Todos los programas que hacen uso de MPI presentan la siguiente estructura:

1. Inclusión del fichero de cabecera MPI. 2. Declaraciones de variables. 3. Inicialización del entorno MPI. 4. Realización de la computación y de las llamadas de comunicación MPI. 5. Cierre de las comunicaciones MPI.

El fichero de cabecera de MPI contiene definiciones y prototipos de funciones. Para código escrito en C, este fichero de cabecera es ‘mpi.h’, y para Fortran es ‘mpif.h’.

Posteriormente, tras la declaración de variables, cada proceso llama a una rutina MPI que inicializa el entorno de paso de mensajes. Todas las llamadas a las rutinas de comunicación MPI deben ser posteriores a la inicialización.

Finalmente, antes de que finalice el programa, cada proceso debe llamar una rutina MPI que finaliza las comunicaciones. Ninguna rutina MPI debe ser llamada después de esto. Si algún proceso no alcanza este punto durante la ejecución, el programa se colgará.

Los nombres de todas las entidades MPI (rutinas, constantes, tipos, etc.), comienzan con

‘MPI_’, para evitar conflictos. Así, los nombres de las rutinas en Fortran tienen el formato “MPI_XXXXX (parámetros, IERR)” (todo en mayúsculas), y en C “MPI_Xxxxx (parámetros)”.

Las constantes MPI van todas en mayúsculas, tanto en C como en Fortran, por ejemplo: MPI_COMM_WORLD, MPI_REAL, etc.

Los nombres de los tipos definidos en C, corresponden a muchas entidades MPI (en Fortran son todos enteros), y siguen la convención de nombrado de funciones C ya descrita; por ejemplo, MPI_Comm es un tipo correspondiente a un comunicador MPI.

45

Page 47: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Todos los prototipos de rutinas MPI, comandos y constantes, se encuentran muy bien explicados en el manual Web de MPI [18].

2.6.2.1. RUTINAS MPI Y VALORES DE RETORNO.

Las rutinas MPI son implementadas como funciones en C y subrutinas en Fortran. En cualquier caso, se devuelve un código de error, el cual determina si la ejecución de la rutina fue o no exitosa.

En el caso de C, las funciones MPI devuelven un entero, que indica el estado de salida de la llamada, esto es:

int err; … err = MPI_Xxxx(parámetros) ; …

En Fortran, las subrutinas MPI tienen un argumento entero adicional, que siempre es el

último de la lista de argumentos, y que contiene el estado de error cuando retorna la llamada. Esto se muestra a continuación:

INTEGER IERR; … CALL MPI_XXXXX(parámetros, IERR) ; …

El código de error devuelto es MPI_SUCCESS si la rutina se ejecutó satisfactoriamente.

Si, por el contrario, ocurrió un error, el valor del entero devuelto depende de la implementación.

2.6.2.2. INDICADORES MPI.

MPI define y mantiene sus propias estructuras de datos internas, relacionadas con la comunicación y demás aspectos que conciernen el uso de MPI. Se puede hacer referencia a esos datos a través de indicadores, que son devueltos por distintas llamadas MPI, y pueden ser utilizados cono argumentos en otras llamadas MPI.

En C, los indicadores son punteros a tipos de datos especialmente definidos (creados mediante el mecanismo ‘typedef’ de C). Los arrays son indexados comenzando por ‘0’.

En Fortran, los indicadores son enteros (posiblemente arrays de enteros), y los arrays son indexados comenzando por ‘1’.

Por ejemplo, MPI_SUCCESS es un entero en C y en Fortran. MPI_COMM_WORLD en C es un objeto de tipo MPI_Comm (un comunicador), y en Fortran es un entero.

Los indicadores pueden ser copiados utilizando la operación de asignación estándar en C y en Fortran.

2.6.2.3. TIPOS DE DATOS MPI.

MPI proporciona sus propios tipos de datos de referencia, correspondientes a los distintos tipos de datos elementales de C y Fortran.

46

Page 48: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Así mismo, MPI permite la traducción automática entre las diferentes representaciones de tipos en un entorno heterogéneo. Como norma general, los tipos de datos MPI dados en una recepción deben coincidir con los tipos de datos especificados en un envío.

Además, MPI permite definir tipos de datos arbitrarios construidos a partir de los tipos básicos, aunque esto no se verá aquí (si es necesario, se aconseja dirigirse al estándar de MPI, cuya dirección Web se proporciona en la sección 6). En las tablas 3 y 4, se presenta una comparativa los tipos de datos MPI y los de C y Fortran.

Tipos básicos MPI Tipos básicos de Fortran

MPI_INTEGER integer MPI_REAL real MPI_DOUBLE_PRECISION double precisionMPI_COMPLEX complex MPI_CHARACTER character(1) MPI_LOGICAL logical MPI_BYTE (none) MPI_PACKED (none) Tabla 4. Tipos de datos MPI frente a tipos de datos

de Fortran.

Tipos básicos MPI Tipos básicos de C

MPI_CHAR signed char MPI_SHORT signed short int MPI_INT signed int MPI_LONG signed long int MPI_UNSIGNED_CHAR unsigned char MPI_UNSIGNED_SHORT unsigned short int MPI_UNSIGNED unsigned int MPI_UNSIGNED_LONG unsigned long int MPI_FLOAT float MPI_DOUBLE double MPI_LONG_DOUBLE long double MPI_BYTE (none) MPI_PACKED (none)

Tabla 3. Tipos de datos MPI frente a tipos de datos de C.

2.6.2.4. INICIALIZACIÓN DE MPI. La primera rutina llamada en cualquier programa MPI debe ser la rutina de inicialización

‘MPI_INIT’. Ésta establece el entorno MPI, devolviendo un código de error si ha habido algún problema. Además, esta rutina debe ser llamada una sola vez en cualquier programa.

En C, la declaración es la siguiente:

int err; … err = MPI_Init(&argc, &argv);

Observar que los argumentos de ‘MPI_Init’ son las direcciones de argc y argv, las

variables que contienen los argumentos de línea de comandos pasados al programa. En Fortran:

INTEGER IERR … CALL MPI_INIT(IERR)

47

Page 49: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.6.2.5. CIERRE DE LAS COMUNICACIONES MPI. Tras terminar toda la fase de computación y de comunicaciones MPI dentro de un

programa, es importante no olvidarse de destruir el entorno MPI creado al principio con MPI_INIT antes de terminar la ejecución. Para lograr esto, se hace uso de la rutina ‘MPI_FINALIZE’, que destruye las estructuras de datos MPI, cancela las operaciones no completadas, etc. Esta rutina debe ser llamada por todos los procesos, si no, el programa se colgará. La forma de llamar a esta rutina en C es:

int err; ... err = MPI_Finalize();

En Fortran:

INTEGER IERR ... call MPI_FINALIZE(IERR)

2.6.3. COMUNICACIONES PUNTO A PUNTO.

La comunicación punto a punto es la facilidad de comunicación fundamental proporcionada por la librería MPI. Este tipo de comunicación es conceptualmente simple: un proceso envía un mensaje y otro lo recibe. Los datos no son transferidos sin la participación de ambos procesos, es decir, se requiere explícitamente un envío y una recepción.Sin embargo, en la práctica no es tan sencillo. Por ejemplo, puede que un proceso tenga muchos mensajes esperando para ser recibidos. En este caso, un aspecto decisivo es cómo determinan MPI y el proceso receptor la forma en que se reciben los mensajes.

Otra cuestión importante es si las rutinas de envío y recepción de mensajes inician las operaciones de comunicación y retornan inmediatamente o esperan a que se complete la operación iniciada antes de retornar. Las operaciones de comunicación que están por debajo son las mismas en ambos casos, pero la interfaz de programación es muy diferente.

2.6.3.1. FUNDAMENTOS DE LAS COMUNICACIONES PUNTO A PUNTO.

Los tres aspectos fundamentales de las comunicaciones punto a punto MPI son:

fuente y destino, mensajes y envío y recepción de mensajes.

Las comunicaciones punto a punto requieren la participación activa de los procesos en

ambos lados, el proceso fuente envía y el proceso destino recibe. En general, los procesos fuente y destino operan de forma asíncrona. El proceso fuente puede terminar de enviar un mensaje mucho antes de que el proceso destino lo empiece a recibir (mensajes pendientes). Así mismo, el proceso destino puede que comience a recibir un mensaje que todavía no terminado de ser enviado.

48

Page 50: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Los mensajes pendientes no se mantienen en una simple cola FIFO, sino que poseen varios atributos que puede utilizar el proceso destino para determinar qué mensaje recibir.

Un mensaje está formado por un conjunto de bloques de datos, que constituyen el cuerpo

del mensaje, y por un envoltorio, que indica los procesos origen y destino. MPI utiliza tres partes de información para caracterizar el cuerpo del mensaje de forma

flexible: un buffer, el tipo de datos a enviar y el número de elementos del tipo de datos a ser enviados. El buffer hace referencia a la localización de comienzo en memoria donde se encuentran los datos salientes (para enviar) o donde se almacenan los datos entrantes (para recibir). En el caso más sencillo, el tipo de datos es un tipo elemental tal como flota/REAL, int/INTEGER, etc. En aplicaciones más avanzadas, puede ser un tipo definido por el usuario, que es combinación de los tipos básicos. MPI estandariza la designación de tipos elementales. Esto quiere decir que no hemos de preocuparnos por la representación de dichos tipos en las distintas máquinas que conforman un entorno heterogéneo.

El envoltorio tiene cuatro partes: el proceso fuente, el proceso destino, el comunicador que incluye a los procesos fuente y destino, y una etiqueta utilizada para clasificar mensajes.

El envío de mensajes es directo. La identidad del emisor está determinada de forma

implícita, pero el resto del mensaje (cuerpo y envoltorio) es dado explícitamente por el proceso emisor.

La recepción del mensaje no es tan simple, debido a los mensajes pendientes que aún no han sido procesados por el receptor. Para recibir un mensaje, un proceso especifica un envoltorio de un mensaje que MPI compara con los envoltorios de los mensajes pendientes. Si hay alguna coincidencia, se recibe un mensaje. En caso contrario, la operación de recepción no puede ser completada hasta que se envíe un mensaje coincidente. Además, el proceso receptor del mensaje debe proporcionar el almacenamiento suficiente para copiar el cuerpo del mensaje.

2.6.3.2. MODOS DE ENVÍO Y COMUNICACIONES BLOQUEANTES. MPI proporciona una gran flexibilidad a la hora de especificar cómo son enviados los

mensajes. Hay una gran variedad de modos de comunicación que definen el procedimiento utilizado para transmitir el mensaje, así como un conjunto de criterios para determinar cuando se completa un evento de comunicación (un envío o recepción particular). Por ejemplo, un envío síncrono es completado cuando el acuse de recibo del mensaje ha sido aceptado en su destino. Un envío bufferado es completado cuando los datos salientes han sido copiados a un buffer local (no se especifica nada sobre la llegada del mensaje a su destino).

En todos los casos, la finalización de un envío implica que es seguro sobrescribir las áreas de memoria donde los datos fueron almacenados originalmente. Hay cuatro modos de comunicación disponibles para el envío de datos: estándar, síncrono, bufferado y preparado.

Para recepción sólo hay un modo: una recepción es completada cuando los datos entrantes realmente han llegado, y están disponibles para su utilización.

Además del modo de envío, un envío o recepción puede ser bloqueante o no-bloqueante. Un envío o recepción bloqueante no regresa de la llamada a la subrutina hasta que la operación ha sido realmente completada. Así se asegura que el criterio de terminación pertinente ha sido satisfecho antes de que el proceso llamante pueda proceder con su ejecución.

49

Page 51: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Un envío o recepción no-bloqueante regresa inmediatamente de la subrutina, sin ninguna información sobre si el criterio de terminación ha sido satisfecho. Esto tiene la ventaja de que el procesador puede realizar otras tareas, mientras las comunicaciones tienen lugar en segundo plano. Posteriormente, se puede verificar si la operación ha sido realmente completada.

La utilización de comunicaciones bloqueantes o no-bloqueantes será función de las

necesidades de programación, dejándose esta tarea al programador de aplicaciones paralelas.

2.6.3.3. ENVÍO Y RECEPCIÓN BLOQUEANTES.

Las dos funciones, ‘MPI_SEND’ y ‘MPI_RECV’, son las rutinas de comunicación básicas en MPI. Ambas funciones bloquean al proceso llamante hasta que la operación de comunicación ha sido completada.

El bloqueo puede dar lugar a atascos del programa. Un atasco ocurre cuando dos o más procesos son bloqueados, y cada uno espera a otro para continuar con su ejecución, ya que ésta depende, a su vez, de la ejecución de otro proceso. Es importante detectar la posibilidad de que ocurran estos atascos, y hacer todo lo necesario para evitarlos.

Al realizar una llamada ‘MPI_SEND’, la rutina acepta los siguientes argumentos: del

cuerpo del mensaje tomará el buffer, el tipo de datos y el número de los mismos dentro del buffer; del envoltorio aceptará el proceso destino, la etiqueta con la clase del mensaje y el comunicador (el proceso fuente se define implícitamente).

Al llamar a ‘MPI_RECV’, los argumentos que toma dicha rutina son similares a los que

acepta ‘MPI_SEND’, con la diferencia de que algunos de los parámetros se utilizan de forma distinta. El cuerpo del mensaje tiene los mismos parámetros que los citados para el envío; el envoltorio contendrá el proceso fuente, la etiqueta de la clase del mensaje y el comunicador (el proceso destino se define de forma implícita). Además hay un argumento más: el estado, que da información sobre el mensaje que ha sido recibido.

Los argumentos del envoltorio determinan qué mensaje puede ser recibido por la llamada, de modo que el proceso fuente, la etiqueta y el comunicador deben coincidir con alguno de los mensajes pendientes, en orden a ser recibidos. Se pueden utilizar comodines para el valor del proceso fuente y de la etiqueta, de modo que se puedan recibir mensajes desde cualquier origen y con cualquier etiqueta. No se puede hacer lo mismo con los comunicadores.

En general, el emisor y el receptor deben estar de acuerdo en el tipo de datos del mensaje,

y es responsabilidad del programador garantizar este acuerdo. Si el emisor y el receptor utilizan tipos de datos incompatibles, el resultado será indefinido.

Cuando se envía un mensaje utilizando ‘MPI_SEND’, puede ocurrir una de las dos cosas

siguientes:

- El mensaje puede ser copiado dentro un buffer MPI interno y transferido a su destino más tarde, en segundo plano. Este tipo de comunicación es asíncrona.

- O bien, el mensaje se puede dejar donde está, en las variables del programa, hasta que el proceso destino esté listo para recibirlo. En ese momento, el mensaje se transfiere a su destino. En este caso, es necesaria una sincronización entre emisor y receptor del mensaje.

50

Page 52: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

La primera opción permite al proceso remitente dedicarse a otras cosas después de que se complete la copia. La segunda opción minimiza el uso de memoria, pero puede dar lugar a un retardo extra para el proceso remitente, retardo que puede ser significativo.

2.6.3.4. ENVÍO Y RECEPCIÓN NO-BLOQUEANTES.

MPI proporciona otra manera de invocar las operaciones de envío y recepción. Es posible separar el comienzo de una operación de envío o recepción de su finalización, haciendo dos llamadas separadas a MPI. La primera llamada inicia la operación y, la segunda, la completa. Entre las dos llamadas, el programa puede seguir ejecutando las operaciones que se precisen. Las operaciones de comunicación subyacentes son las mismas, tanto si se realiza el envío y recepción en una llamada simple o en dos llamadas separadas. La diferencia es la interfaz utilizada.

Al hecho de iniciar una operación de envío se le llama ordenar un envío, y para recepción se denomina ordenar una recepción. Una vez que un envío o recepción ha sido mandado, MPI proporciona dos maneras distintas de completarlo. Un proceso puede testear para ver si la operación ha sido completada, sin bloquear la finalización de dicha operación. Alternativamente, un proceso puede esperar a que la operación se complete.

Después de mandar un envío o recepción con una llamada a una rutina no-bloqueante, el proceso de mandado necesita alguna forma de referirse a la operación ordenada. MPI utiliza indicadores de petición para este propósito. Con estos indicadores, se puede chequear el estado de las operaciones ordenadas o esperar a su finalización.

Las rutinas de envío y recepción sin bloqueo son, respectivamente, ‘MPI_ISEND’ y

‘MPI_IRECV’. La secuencia de llamada para ambas rutinas es muy similar a sus rutinas análogas bloqueantes, pero incluyendo un indicador de petición. Ninguno de los argumentos pasados a estas rutinas debería ser leído o escrito hasta que se complete la operación en cuestión.

Las rutinas de finalización de la operaciones de envío y recepción son: ‘MPI_WAIT’ y sus variantes para espera de finalización con bloqueo, y ‘MPI_TEST’ y sus variantes para evitar el bloqueo y realizar un chequeo sobre la terminación de la operación.

El uso selectivo de rutinas no-bloqueantes hace mucho más fácil escribir código evitando

los posibles atascos, cosa que es bastante ventajosa, sobre todo en sistemas que presentan grandes latencias. Por contra, se incrementa la complejidad del código, haciendo más difícil el depurado y el mantenimiento del mismo.

2.6.4. COMUNICACIONES COLECTIVAS.

Aparte de las comunicaciones punto a punto entre pares individuales de procesadores, MPI incluye rutinas para realizar comunicaciones colectivas. Estas rutinas permiten que grandes grupos de procesadores se comuniquen de diferentes maneras, por ejemplo, comunicación de uno a muchos o de muchos a uno. Las rutinas de comunicación colectiva, están construidas utilizando rutinas de comunicación punto a punto, pero teniendo en cuenta algunos detalles importantes y usando los algoritmos más eficientes conocidos para realizar la operación.

51

Page 53: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

El programador debe asegurar que todos los procesos que han de comunicarse ejecuten las mismas operaciones colectivas, y en el mismo orden.

Las principales ventajas de utilizar rutinas de comunicación colectiva frente a las

construcciones equivalentes con comunicaciones punto a punto son:

a) Posibilidad de error significativamente reducida: con una sola línea de código realizamos una operación colectiva, lo que equivaldría a varias líneas de código con operaciones punto a punto.

b) El código fuente es mucho más legible, de modo que se simplifica la depuración y el mantenimiento del mismo.

c) Las formas optimizadas de rutinas colectivas son, a menudo, más rápidas que la operación equivalente expresada en términos de rutinas punto a punto.

MPI proporciona las siguientes rutinas de comunicación colectiva:

- Operación de difusión (broadcast). - Operaciones globales de reducción (reduction), tales como: sum, min, max o

reducciones definidas por el usuario. - Operación de recolección (gather) de datos desde todos los procesos a uno

solo. - Operación de dispersión (scatter) de datos desde un proceso a todos los demás. - Operación de sincronización de barrera (barrier) a través de todos los

procesos. - Operaciones avanzadas, donde todos los procesos reciben el mismo resultado

de una operación de recolección, dispersión o reducción. A continuación, veremos las operaciones colectivas mencionadas.

• Operación de difusión:

El tipo más simple de operación colectiva es la difusión (‘MPI_BROADCAST’). En este tipo de operación, un proceso envía una copia de los mismos datos al resto de procesos de su grupo (más adelante veremos que es esto de los grupos). Esta forma de operar se presenta en la figura 16. Cada fila de la figura representa un proceso distinto, y cada bloque sombreado Figura 16. Broadcast. en una columna representa la localización de una porción de los datos. Los bloques con la misma letra que están localizados en múltiples procesos, contienen copias de los mismos datos.

52

Page 54: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

• Operaciones de recolección y dispersión: Las operaciones de dispersión y

recolección son las encargadas de distribuir los datos de un procesador sobre un grupo de procesadores y viceversa, respectivamente. En la figura 17 se muestran las operaciones comentadas.

En una operación de dispersión (‘MPI_SCATTER’), todos los datos se encuentran inicialmente recogidos en un procesador. Después de la dispersión, los trozos de datos son distribuidos en diferentes Figura 17. Scatter & gather. procesadores.

La recolección (‘MPI_GATHER’) es la operación inversa a la dispersión. Acumula

partes de datos que se encuentran distribuidos a través de un conjunto de procesadores, y los reensambla en el orden apropiado en un simple procesador.

• Operaciones de reducción:

Una reducción es una operación colectiva, en la cual un proceso (proceso raíz) colecciona datos que se encuentran en un grupo de procesos, y los combina en un solo elemento de datos. Por ejemplo, la operación de reducción se puede utilizar para computar la suma de los elementos de un array que está distribuido en varios procesadores. Esto se muestra en la figura 18. Figura 18. Reducción.

También son posibles operaciones distintas a las aritméticas, tal y como se muestra en la tabla 5.

OPERACIÓN DESCRIPCIÓN MPI_MAX Máximo. MPI_MIN Mínimo. MPI_SUM Suma. MPI_PROD Producto. MPI_LAND AND lógico. MPI_BAND AND binario. MPI_LOR OR lógico. MPI_BOR OR binario. MPI_LXOR XOR lógico. MPI_BXOR XOR binario.

MPI_MINLOC Calcula un mínimo global y un índice ligado al valor mínimo. Puede ser utilizado para determinar qué proceso que contiene el valor mínimo.

MPI_MAXLOC Calcula un máximo global y un índice ligado al valor máximo. Al igual que antes, se puede saber cuál es el proceso que contiene el valor máximo.

Tabla 5. Operaciones predefinidas disponibles para ‘MPI_REDUCE’.

53

Page 55: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

• Operación de sincronización de barrera:

Hay ocasiones en las que algunos procesadores no pueden continuar con su ejecución hasta que otros procesadores han completado las instrucciones que están ejecutando. Un ejemplo común de este comportamiento ocurre cuando el proceso raíz lee datos y los transmite a otros procesadores. El resto de procesadores deben esperar hasta que se complete la operación de E/S y se terminen de mover los datos.

La rutina ‘MPI_BARRIER’ bloquea al proceso llamante hasta que todos los procesos del grupo han llamado a dicha función. Cuando ‘MPI_BARRIER’ devuelve el control al programa principal, todos los procesos son sincronizados en la barrera.

‘MPI_BARRIER’ está implementada mediante software, lo que provoca que pueda generar una sobrecarga sustancial en algunas máquinas. Esto implica que sólo se utilice sincronización por barrera en casos realmente necesarios.

• Operaciones avanzadas:

1. ‘MPI_ALLGATHER’: ofrece el mismo resultado que una operación de

recolección seguida de una difusión, pero de forma más eficiente. 2. ‘MPI_ALLREDUCE’: se utiliza para combinar los elementos de cada buffer

de entrada de los procesos. Almacena el valor final en el buffer de recepción de todos los procesos miembros del grupo.

3. Operaciones definidas por el usuario: la operación de reducción puede

definirse para que sea una operación arbitraria.

4. ‘MPI_GATHERV’ y ‘MPI_SCATTERV’: permiten trabajar con un número variante de datos (el sufijo ‘V’ de las rutinas viene de vector).

5. Otras variantes de Scattery y Gather.

2.6.5. COMUNICADORES.

Un comunicador es un indicador que representa un grupo de procesadores, que se puede comunicar con otro. Se requiere el nombre del comunicador como argumento para todas las comunicaciones punto a punto y colectivas.

El comunicador especificado en las llamadas de envío y recepción debe coincidir para que tenga lugar la comunicación. Los procesadores se pueden comunicar sólo si comparten un mismo comunicador. Puede que haya muchos comunicadores, y un proceso dado puede ser miembro de un número de diferentes comunicadores. Dentro de cada comunicador, los procesos son numerados consecutivamente, empezando por ‘0’. Este número de identificación es conocido como el ‘rank’ (orden) del proceso en ese comunicador. El rank se utiliza también para especificar la fuente y el destino en las llamadas de envío y recepción de datos. Si un proceso pertenece a más de un comunicador, su rank en cada uno puede ser distinto y, normalmente, lo será.

MPI proporciona automáticamente un comunicador básico llamado ‘MPI_COMM_WORLD’, que es un comunicador consistente en todos los procesos. Utilizando ‘MPI_COMM_WORLD’, cada proceso se puede comunicar con cada uno del resto de procesos.

54

Page 56: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

También es posible definir comunicadores adicionales consistentes en subconjuntos de los procesos disponibles. Con respecto a esto, decir que en MPI existen dos tipos de comunicadores: intra-comunicadores e inter-comunicadores. Los intra-comunicadores son, esencialmente, un conjunto de procesos que pueden enviarse mensajes los unos a los otros y estar involucrados en operaciones de comunicación colectiva. Por ejemplo, ‘MPI_COMM_WORLD’ es un intra-comunicador. Los inter-comunicadores, como su propio nombre indica, son utilizados para enviar mensajes entre procesos pertenecientes a intra-comunicadores disjuntos.

Un comunicador está formado por un grupo y un contexto. Un grupo es una colección ordenada de procesos. Si un grupo consiste en ‘p’ procesos, a cada proceso en el grupo se le asigna un único rank, que será un entero no negativo en el rango [0, p-1]. Un contexto puede ser visto como una etiqueta definida por el sistema que se asocia al grupo. Así, dos procesos que pertenecen al mismo grupo y que utilizan el mismo contexto pueden comunicarse. Este par grupo-contexto es la forma más básica de un comunicador.

Se pueden asociar más datos a un comunicador. En particular, se puede imponer una topología o estructura a los procesos de un comunicador, permitiendo un esquema de direccionamiento más natural.

Un proceso puede determinar su rank haciendo una llamada a la rutina

‘MPI_COMM_RANK’. La definición de esta rutina en C sería: int MPI_Comm_rank(MPI_Comm comm, int *rank);

El argumento ‘comm’ es una variable del tipo ‘MPI_Comm’, un comunicador. Por

ejemplo, se podría emplear ‘MPI_COMM_WORLD’ aquí o, alternativamente, se podría pasar el nombre de otro comunicador definido por el programador en la forma:

MPI_Comm comm;

En caso de Fortran, el prototipo de la rutina es:

MPI_COMM_RANK(COMM, RANK, IERR) En este caso, los argumentos ‘COMM’, ‘RANK’ e ‘IERR’ son todos del tipo ‘INTEGER’. Un proceso cualquiera, puede obtener el tamaño (o número de procesos) que pertenecen a

un comunicador. Esto se hace con la llamada a la rutina ‘MPI_COMM_SIZE’.

Existen diversas rutinas en la librería MPI que realizan diversas tareas relacionadas con la gestión de comunicadores. Enunciaremos algunas de ellas, las más importantes:

‘MPI_COMM_GROUP’: determina el indicador de grupo de un comunicador.

‘MPI_GROUP_INCL’: crea un nuevo grupo de un grupo existente, especificando

los procesos miembro por inclusión. ‘MPI_GROUP_EXCL’: crea un nuevo grupo de un grupo existente,

especificando los procesos miembro por exclusión. ‘MPI_GROUP_RANK’: obtiene el rank del grupo del proceso llamante.

55

Page 57: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

‘MPI_GROUP_FREE’: libera un grupo al sistema cuando no se necesita más. ‘MPI_COMM_CREATE’: crea un nuevo comunicador para incluir procesos

específicos de un comunicador existente. ‘MPI_COMM_SPLIT’: establece nuevos comunicadores a partir de uno

existente. Muchos cálculos científicos y de Ingeniería tratan con matrices o rejillas (especialmente rejillas Cartesianas) consistentes en filas y columnas. Esto, a su vez, lleva a la necesidad de mapear lógicamente los procesos a geometrías de rejilla similares. Por tanto, puede que sea necesario tratar los procesos de forma distinta a la tradicional; por ejemplo, en vez de tratarlos como filas individuales, puede que sea ventajoso o incluso necesario tratar con grupos de filas, o con otras configuraciones arbitrarias. ‘MPI_COMM_SPLIT’ permite la creación de nuevos comunicadores con las flexibilidades comentadas.

2.6.6. TOPOLOGÍAS VIRTUALES.

Como ya se ha comentado en la sección anterior, muchos problemas de cálculo científico y de ingeniería se reducen al final o a una serie de matrices o a alguna forma de operaciones en rejilla, ya sea integral, diferencial u otros métodos. Las dimensiones de las matrices o rejillas vienen determinadas, a menudo, por problemas físicos; de hecho, esta es la parte que más se acerca al nivel de abstracción de nuestro trabajo con estructuras.

Frecuentemente, en multiprocesamiento, estas matrices o rejillas a las que hacemos referencia, son particionadas o descompuestas en dominios, de modo que cada partición o subdominio se asigna a un proceso. Un ejemplo simple es el de una matriz m x n, descompuesta en p submatrices de dimensiones q x n, a cada una de las cuales se le asigna uno de los p procesos para trabajar sobre ellas. En este caso, cada proceso representa una submatriz distinta de manera directa. Sin embargo, un algoritmo puede ordenar que la matriz sea descompuesta en un rejilla lógica de dimensiones p x q, cuyos elementos son matrices r x s. Esto se puede deber a varias razones: por consideraciones de eficiencia, facilidad en la implementación del código, legibilidad del código, etc. Aunque es posible referirse a cada una de esos subdominios p x q mediante una numeración lineal, es obvio que un mapeado de una ordenación de procesos lineal a una numeración virtual en 2D, daría lugar a una representación computacional más clara y natural.

Para lograr este mapeado de numeración, así como otros esquemas de topología virtuales, la librería MPI proporciona dos tipos de rutinas: rutinas para topologías Cartesianas y rutinas para topologías gráficas.

No veremos aquí las rutinas mencionadas para no alargar este pequeño acercamiento a MPI, además de que no ha sido necesaria su utilización a la hora de trabajar con la librería de cálculo PETSc. La librería PETSc proporciona métodos de más alto nivel que MPI para la partición y numeración de matrices y rejillas. No obstante, PETSc hace uso de MPI como capa de nivel inferior para conseguir estos propósitos.

Como ya se ha comentado a lo largo de todo el acercamiento a la librería MPI, para

obtener información sobre el uso de cualquier rutina MPI se aconseja dirigirse a [18].

56

Page 58: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.6.7. ENTRADA / SALIDA PARALELA: MPI-2 Vs. MPI-1

2.6.7.1. INTRODUCCIÓN.

En este punto, veremos cómo múltiples procesos pueden compartir datos almacenados en espacios de memoria separados. Esto se logra mediante el envío de mensajes entre procesos, puesto que seguimos hablando de MPI. En esta introducción a la E/S paralela, nos centraremos en la E/S referida al manejo de ficheros.

El tema de la E/S paralela cubre la cuestión de cómo son distribuidos los datos entre los

dispositivos de E/S. Mientras el subsistema de memoria puede ser diferente de una máquina a otra, los métodos lógicos de acceso a memoria son, en general, comunes, esto es, debería aplicarse el mismo modelo de una máquina a otra. La E/S paralela es complicada por el asunto de que las configuraciones físicas y lógicas difieren de un ordenador a otro.

MPI-2 es la primera versión de MPI que incluye rutinas para el maneja de E/S paralela, no

ocurre así con MPI-1. El estilo de programación tradicional, enseña que los programas pueden ser

descompuestos por funciones en tres secciones principales:

Entrada. Computación. Salida.

Para aplicaciones científicas, la mayoría de lo que se ha visto hasta ahora sobre MPI se

dirige a la fase de computación. Con sistemas paralelos que permiten modelos de computación mayores, estas aplicaciones, a menudo, producen grandes cantidades de datos de salida.

En esta situación, la E/S serie de una máquina paralela puede dar lugar a importantes penalizaciones por diversas razones:

- Las grandes cantidades de datos generadas por las aplicaciones paralelas

presentarán un cuello de botella en serie si la E/S se realiza en un solo nodo. - Muchas máquinas multiprocesador se construyen a partir de varios procesadores

más lentos, que incrementan el tiempo de penalización a medida que la E/S serie se dirige a través de un solo procesador más lento.

- Algunos conjuntos de datos son demasiado largos para ser enviados de vuelta a un nodo para que los almacene en un fichero de E/S.

Puede ser que el tiempo requerido para la fase de E/S a un fichero, canalizada a través de

un solo procesador, sea del mismo orden o mayor que el tiempo requerido para la computación en paralelo. Esto lleva a replantearse el modelo de E/S, y a ver que el problema de tiempo del cálculo en paralelo no está muchas veces en la propia computación, sino en la E/S de datos. Por esta razón aparece la E/S paralela, para dar solución a los problemas que presenta la E/S serie.

La capacidad de paralelizar la E/S, puede ofrecer mejoras significantes del rendimiento de

nuestras aplicaciones paralelas. Como ejemplo de aplicaciones reales en la que esto es posible, destacar las grandes rejillas o mallas computacionales. Muchas aplicaciones nuevas,

57

Page 59: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

están utilizando mallas y rejillas de una resolución más fina de lo habitual. Con frecuencia, los cálculos realizados en cada nodo necesitan ser almacenados para análisis posteriores. Estas grandes rejillas o mallas computacionales incrementan los requerimientos de E/S por el gran número de puntos de datos a ser salvados. Además, incrementan el tiempo de E/S, porque los datos están siendo dirigidos a través de procesadores más lentos en máquinas masivamente paralelas.

Otro ejemplo de aplicaciones que incrementan su rendimiento con el uso de la E/S paralela, son aquellas que tienen pocos cálculos, pero muchas referencias on-line a bases de datos.

2.6.7.2. E/S SERIE.

Para ayudar a comprender lo que conlleva la utilización de la E/S paralela, es importante echar un vistazo a las características de la E/S serie, y entonces comparar ambos métodos.

En lo que a la estructura física se refiere, suele haber un procesador conectado un disco

físico. Con respecto a la estructura lógica, las características son:

- La E/S serie es la visión tradicional que tienen los lenguajes de alto nivel de la E/S en general.

- Existe un manejador de fichero único. - El acceso a ficheros puede ser secuencial o aleatorio, teniendo también en cuenta

los atributos de los ficheros, tales como sólo lectura/escritura, etc. - Los datos pueden ser escritos con o sin formato, en modo binario o modo texto. - Existen funciones integradas en el propio sistema para acceso a la E/S.

2.6.7.3. E/S PARALELA.

La E/S paralela se clasifica en dos categorías principales: descomposición física y descomposición lógica.

En la situación de descomposición física, múltiples procesadores escriben datos en

múltiples discos, discos que son locales a cada nodo. Toda la E/S es local, con lo cual el rendimiento es mayor: cada proceso abre su propio fichero en su sistema local. Se reservan un número determinado de nodos para realizar la E/S, por lo que el rendimiento de la E/S se incrementará con el número de nodos reservados para la E/S.

Este método es excelente para almacenar datos temporales que no se utilizarán tras la ejecución del programa. Los datos de los múltiples procesos se pueden combinar al final de la ejecución, si se desea.

En el caso de la descomposición lógica, múltiples procesadores escriben datos en un único

fichero de un único disco, de modo que los datos de los distintos procesos serán entrelazados. Desde el punto de vista de la terminología de lenguaje de alto nivel, el acceso a la E/S es directo.

MPI-2 implementa este método de E/S paralela, pero con la particularidad de que los datos se almacenan en ficheros distintos, uno por cada nodo. Cada fichero se identifica por el

58

Page 60: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

rank de cada proceso. Al final de la ejecución, los ficheros se combinan en un solo fichero de datos.

Las máquinas paralelas pueden tener sistemas de E/S paralela que se ajusten a una de las

dos categorías presentadas, aunque es posible tener sistemas híbridos que cumplan las características de ambas.

2.6.7.4. E/S PARALELA DE MPI-2.

Uno de los cambios significantes del paso de MPI-1 a MPI-2, fue la adición de la E/S paralela. Antes de la aparición de MPI-2, se podía lograr la E/S paralela sin llamadas a MPI con métodos tales como los sistemas de ficheros paralelos, donde se llevaba a cabo una partición lógica de ficheros.

La E/S paralela de MPI-2, proporciona una interfaz de alto nivel con soporte para la partición de ficheros de datos entre procesos, y una interfaz colectiva para soporte de transferencias completas de estructuras de datos entre ficheros y memorias de procesos. Ahora, la lectura y escritura de datos son tratadas mucho más como envío de mensajes a disco y recepción de mensajes desde los dispositivos de almacenamiento de datos. No se utilizan las rutinas estándar de E/S de C, C++ y Fortran.

Algunas de las características destacables de la E/S paralela de MPI-2 son:

- MPI-2 permite realizar la E/S paralela de forma similar al modo de enviar mensajes entre dos procesadores.

- No todas la implementaciones actuales de MPI implementan la E/S completa de MPI-2.

- MPI-2 soporta rutinas de E/S bloqueantes y no-bloqueantes, así como rutinas colectivas y no-colectivas.

- Incluye soporte para E/S asíncrona. - Proporciona rutinas para el acceso a datos mediante pasos (strides). - Tiene control sobre el esquema de ficheros físicos de los dispositivos de

almacenamiento (discos).

59

Page 61: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

2.7. LIBRERÍAS DE MATEMÁTICAS PARALELAS.

2.7.1. INTRODUCCIÓN.

La gran ventaja de las librerías de Matemáticas paralelas, es que no hemos de escribir el código MPI que se necesita como soporte para implementar un programa paralelo. Lo único que se ha de hacer es llamar a una subrutina de la librería de matemáticas, que ya incluye el código MPI necesario. Estas librerías son completas y legibles, y han sido optimizadas desde un punto de vista serie y paralelo, a partir de algoritmos paralelos excelentes.

Las librerías de Matemáticas de libre distribución más conocidas son:

BLAS: Basic Linear Algebra Subprograms. PBLAS: Parallel Basic Linear Algebra Subprograms. BLACS: Basic Linear Algebra Communication Subprograms. LAPACK: Linear Algebra PACKage. ScaLAPACK: Scalable Linear Algebra PACKage.

En la figura 19, se muestra la jerarquía que siguen estas librerías.

El término “local” que se observa en

la figura, quiere decir que las librerías son de tipo serie. LAPACK contiene y está construida sobre BLAS. Cada fabricante optimiza las librerías para sus propios productos, y trabaja para sistemas de procesador único. Típicamente, la optimización se realiza por medio de algoritmos bloqueantes, diseñados para mantener y reutilizar los datos críticos en los niveles inferiores de la jerarquía de memoria: registros –> caché primaria –> caché secundaria –> memoria local.

Figura 19. Jerarquía de las principales librerías Matemáticas.

Las librerías que yacen sobre el área con el término “global”, son las librerías paralelas.

De forma análoga a lo que ocurre en el caso de las librerías serie, ScaLAPACK contiene y está construida sobre PBLAS. Estas librerías se utilizan en sistemas multiprocesador para cálculos de Álgebra lineal en paralelo. En la figura 19, se puede observar que ScaLAPACK está construida también sobre BLACS, y esto se debe a que esta última librería es la encargada de transferir los datos de la memoria local de un procesador a otra. En realidad, las rutinas de la librería BLACS son rutinas de envoltorio que llaman a una librería de paso de mensajes de más bajo nivel, la cual suele ser MPI.

BLAS y PBLAS son las librerías que contienen las versiones serie y paralela,

respectivamente, de los procedimientos del Álgebra lineal básica. Cada librería contiene rutinas que pertenecen a uno de los tres niveles siguientes:

- Nivel 1: Operaciones vector – vector.

60

Page 62: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

- Nivel 2: Operaciones matriz – vector. - Nivel 3: Operaciones matriz – matriz.

LAPACK y ScaLAPACK contienen rutinas para cálculos de Álgebra lineal más sofi

rados

ScaLAPACK es una de las librerías paralelas más utilizadas y probadas en la comunidad cien

to de vectores y mat

2.7.2. MULTIPLICACIÓN DE VECTORES Y MATRICES.

En este apartado, veremos dos ejem mentan el paso de men

2.7.2.1. EJEMPLO 1

a figura 20 muestra el esquema de descomposición de un producto de matriz por vector b =

Figura 20. Descomposición de un producto paralelo matriz por vector.

as columnas de la matriz y los elementos del vector columna se reparten entre los

pro

l problema que se ha presentado está bien para implementarlo en Fortran, ya que este leng

trices por filas, y sería necesario que todos los procesos contengan el vector multiplicado en su totalidad, para poder realizar el producto fila de la matriz por vector. Esto se presenta en la figura 21.

sticados. Hay tres tipos de problemas avanzados que pueden resolver estas librerías: - Solución de un conjunto de ecuaciones lineales simultáneas. - Problemas de autovalores / autovectores. - Método de aproximación de mínimos cuad

tífica mundial. Sin embargo, no la hemos utilizado para alcanzar nuestros objetivos, sino que hemos hecho uso de la librería PETSc, la cual introduciremos en breve.

Antes de pasar a lo comentado, veremos cómo descomponer el producrices para la computación en paralelo. Esto será de gran ayuda a la hora de comprender el

funcionamiento de la PETSc.

plos de algoritmos que implesajes. En el primero, descompondremos la multiplicación vector-matriz, y en el segundo

la multiplicación matriz-matriz.

L A · c. Esto se logra dividiendo la matriz en columnas o grupos de columnas, las cuales se

reparten entre los distintos procesos que intervienen en la operación de cálculo paralela. Cada proceso computa su producto correspondiente (ya que son operaciones independientes, y no requieren comunicaciones) y, al final, se realiza una operación de suma de todos los vectores resultantes (podría ser una operación de reducción de MPI), dando lugar al resultado final.

Lcesadores mediante operaciones de dispersión (scatter) de MPI. Euaje almacena las matrices por columnas. Además, sólo es necesario difundir una parte

del vector multiplicado a cada proceso, no el vector entero. El caso del lenguaje C es el contrario, almacena las ma

61

Page 63: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Para realizar esto mismo en Fortran, lo primero es difundir (broadcast de MPI) el vector multiplicado completo a todos los procesos. Pos

y

De forma similar, se puede descomponer la multiplicación de una matriz por otra. La filos zan las rutinas MPI pertinentes para la distribución de bloques de datos entre los distintos procesos, cada uno de los cuales realiza con posterioridad sus ope

Figura 23. Producto paralelo matriz-matriz en C.

Aparte de las librerías paralelas presentadas en la introducción correspondiente a la secci cas o de cualquier otro tipo) que hacen

unto de extensiones para MPI que habilitan muchas funciones para trabajar con intercomunicadores, que previamente sólo lo

o estructurados y para computar el número de

PI.

teriormente, se distribuyen las filas (scatter de MPI) entre los procesos cada uno realiza su multiplicación independiente. Por último, se hace uso de la operación de recolección (gather de MPI) para obtener el vector final.

Figura 21. Producto paralelo matriz por

vector descompuesto por filas.

2.7.2.2. EJEMPLO 2

ofía es la misma, se utili

raciones independientes. Al final, se recolectan los resultados obtenidos por cada procesador y se unen de alguna forma para dar lugar al resultado final.

Para el caso de Fortran, la forma más eficiente se presenta en la figura 22, y para C en la figura 23.

Figura 22. Producto paralelo matriz-matriz en Fortran.

2.7.3. EVOLUCIÓN HASTA LLEGAR A PETSC.

ón 2.7.1., existen otras librerías paralelas (de Matemáti uso de MPI; veamos algunas importantes:

Las ya mencionadas PLAPACK y ScaLAPACK. Librería MPIX, que contiene un conj

hacían con intracomunicadores. Una versión paralela del nivel 3 de la librería BLAS.

METIS y ParMETIS, que son paquetes software serie y paralelo respectivamente, para la partición de gráficos nelementos de matrices dispersas.

MP_SOLVE, utilizado para resolver sistemas de ecuaciones irregulares y dispersos con múltiples vectores solución, en sistemas multiprocesador de memoria distribuida utilizando M

Prometheus, un sistema lineal basado en multirejilla paralela que sirve para resolver problemas no estructurados de elementos finitos en 3D.

62

Page 64: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

PARASOL, que es un entorno integrado para la solución de sistemas lineales dispersos; está escrito en Fortran 90 y utiliza MPI-1.1 para las comunicaciones.

as

PETSc: “The Portable, Extensible Toolkit for Scientific Computation”. PETSc es un conjunto de librerías numéricas para la solución paralela de sistemlineales dispersos, ecuaciones no lineales provenientes de la discretización de PDE’s, etc. El acercamiento y la familiarización con esta librería es el objetivo final de nuestro estudio, de ahí que se dedique una sección completa a dicho fin.

63

Page 65: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

333... CCÁÁLLCCUULLOO NNUUMMÉÉRRIICCOO EENN PPAARRAALLEELLOO

AASSIISSTTIIDDOO PPOORR LLAA LLIIBBRREERRÍÍAA DDEE CCÁÁLLCCUULLOO PPEETTSSCC..

64

Page 66: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.1. INTRODUCCIÓN.

Hemos llegado a la parte central del proyecto, que consiste en hacer uso del cálculo numérico asistido por la librería PETSc para el Cálculo de Estructuras. Para llegar a entender esto, es necesario hacer una explicación más o menos detallada del funcionamiento de PETSc y de las posibilidades que ofrece.

PETSc proporciona diversos paquetes software para la solución escalable (en paralelo) de

sistemas algebraicos, provenientes de discretizaciones de ecuaciones diferenciales parciales (PDE’s), así como bloques de construcción para la optimización y cálculo de autovalores.

Se apoya en MPI para las operaciones de comunicación, pero esconde la interfaz de programación de MPI mediante un envoltorio que añade la propia PETSc.

La librería PETSc está muy documentada, es de libre distribución y totalmente portable

para las plataformas Cray, SGI, IBM, HP, Sun, Linux, Windows y Apple. Los lenguajes para escritura de código posibles son Fortran, C y C++. El origen de PETSc es el fruto de diversas investigaciones del Departamento de Energía de los Estados Unidos.

Algunas de las herramientas que contiene la librería paralela son: diversas operaciones

paralelas de manejo de vectores / arrays, numerosas operaciones y formatos de matrices paralelas y no paralelas, solucionadotes (solver) lineales y no lineales, algunos integradores de ecuaciones diferenciales ordinarias, gestión limitada de datos / rejillas paralelas, etc.

La estructura de la librería PETSc se presenta en la figura 24.

Figura 24. Jerarquía de los diversos componentes que componen PETSc.

65

Page 67: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.2. MODELO DE PROGRAMACIÓN DE PETSC.

PETSc utiliza la filosofía de la programación orientada a objetos (OOP) para construir las aplicaciones paralelas. El diseño de aplicaciones no se centra en los datos de los objetos, sino más bien en las operaciones que se pueden realizar sobre dichos datos. Por ejemplo, un vector no es un array de números de una dimensión, sino un objeto abstracto sobre el que se definen operaciones tales como el producto escalar o la suma.

El modelo de programación con el que se trabaja presenta las siguientes características:

Sistema de memoria distribuida, no compartida: solamente se requiere un compilador estándar, todas las operaciones de acceso a datos en máquinas remotas se realizan mediante MPI.

Todos los detalles de las comunicaciones se esconden dentro de los objetos, de modo que el usuario no utiliza llamadas directas a MPI, sino que realiza las operaciones de comunicaciones desde un nivel más alto. No obstante, es posible realizar llamadas a rutinas de MPI desde cualquier parte del código.

Se utiliza un modelo de programación SPMD (Simple Program Multiple Data), es decir, todos los nodos que pertenecen al cluster, dentro del cual ejecutamos nuestras aplicaciones paralelas, corren el mismo programa; sin embargo, cada proceso que se ejecuta en cada nodo tiene sus propias variables, distintas a las del resto de nodos, aun cuando se llamen igual. Esto hace que el resultado de ejecutar el mismo programa en diversos nodos sea distinto en cada uno. Lo que cabe ahora cuestionarse es cómo se bifurca el programa si todos los procesos ejecutan el mismo, de modo que cada proceso ejecute la parte del código paralelo que le corresponde. La forma de hacer esto es mediante la variable rank, que identifica el orden de cada proceso en el entorno de comunicaciones MPI. En función de esta variable se pueden poner en el código sentencias de control de flujo para ramificar el programa.

Dentro de PETSc hay rutinas que son colectivas y otras que no lo son. Las operaciones colectivas se llevan a cabo mediante MPI y, en concreto, los comunicadores, que representan a todos aquellos procesos que tendrán que estarán involucrados en la computación. Los constructores de objetos PETSc son colectivos, y necesitan que se les pase un argumento que sea el nombre de un comunicador MPI para poder realizar su función. El comunicador que se utilizaba en MPI que involucraba a todos los procesos era MPI_COMM_WORLD, que ahora es sustituido por ‘PETSC_COMM_WORLD’. Si dentro del código existe una secuencia de órdenes colectivas, todos los procesos involucrados en las operaciones han de llamar a dichas operaciones en el mismo orden.

En ningún momento hay que perder de vista que PETSc es solamente una librería de

funciones (en C) y subrutinas (en Fortran), que proporcionan a nuestro código de aplicación la capacidad de ejecución de cálculo en paralelo mediante el soporte de otras librerías, como son MPI, BLAS y LAPACK.

También destaca una ventaja importante de PETSc, y es que las interfaces de las funciones y subrutinas disponibles son las mismas para C, C++ y Fortran, con algún pequeño matiz que en su momento se comentará.

66

Page 68: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.3. PANORÁMICA DE PETSC.

PETSc está compuesta de un conjunto de librerías, de la misma forma que las clases en C++ o JAVA. Algunos de los módulos que se incluyen en PETSc son:

− Vectores. − Matrices. − Conjunto de índices. − Arrays distribuidos. − Métodos del subespacio de Krylov (solvers lineales). − Precondicionadores. − Solvers no lineales. − Timesteppers para resolver PDE’s dependientes del tiempo.

La jerarquía a la que obedecen todas estas librerías se presenta en la figura 25.

Figura 25. Librerías que componen PETSc.

En la ruta donde se encuentra instalada PETSc, hay una estructura de subdirectorios que da una idea de la organización de PETSc. Los subdirectorios que dependen del directorio raíz son:

67

Page 69: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

docs: aquí se encuentra toda la documentación de PETSc. El subdirectorio ‘manualpages’ contiene un completo manual ‘HTML’ de las rutinas PETSc.

bin: utilidades para el uso con PETSc.

bmake: directorio base de los makefiles necesarios para compilar código PETSc.

Incluye subdirectorios para diferentes configuraciones.

include: todos los ficheros de cabecera para incluir que son visibles por el usuario.

include/finclude: ficheros de cabecera para usuario Fortran.

include/private: ficheros privados que no deberían utilizarse para programas de aplicación.

src: aquí se encuentra todo el código fuente para todas las librerías de PETSc, que

incluye:

- vec: vectores. ⇒ is: conjuntos de índices.

- mat: matrices. - dm:

⇒ da: arrays distribuidos. ⇒ ao: ordenaciones de aplicación.

- ksp: ⇒ ksp: aceleradores del Subespacio de Krylov. ⇒ pc: precondicionadores.

- snes: solvers no lineales. - ts: solvers de EDO’s y timestepping. - sys: rutinas generales relacionadas con el sistema:

⇒ plog: rutinas de profiling y registro de PETSc. ⇒ draw: gráficos simples.

- fortran: soporte para interfaces Fortran. - contrib: módulos anejos a PETSc, que no son parte oficial de la librería.

examples: programas de ejemplo. interface: secuencias de llamada de interfaz abstracta. Este código no atiende a

implementaciones particulares.

impls: código fuente de algunas implementaciones. utils: rutinas de utilidad.

Los nombres de directorio marcados en negrita, son aquellos que han sido de gran utilidad

a la hora de comprender y utilizar la librería paralela PETSc en el proyecto que tenemos entre manos.

68

Page 70: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.3.1. INICIO Y TERMINACIÓN DE UN PROGRAMA PETSC.

Al igual que ocurría en el caso de MPI, a la hora de escribir programas utilizando PETSc, hay que llamar a una rutina de inicialización del entorno y a otra de terminación. Estas son, respectivamente:

PetscInitialize(), que establece los servicios y datos estáticos e inicializa MPI si

no se ha hecho antes (‘PetscInitialize’ llama automáticamente a ‘MPI_Init’). PetscFinalize(), que presenta el resumen de registro de eventos a lo largo del

programa y cierra y libera los recursos utilizados.

NOTA: Al igual que con MPI, hay un manual en formato ‘HTML’ (al que se ha hecho

referencia en el apartado anterior) donde se encuentra la definición detallada de todas y cada una de las funciones de PETSc. Es un manual muy completo y útil. También se ha hecho una copia, que se encuentra en el directorio “unidadCD:\Documentación\manual_web_funciones_PETSc”. A la hora de programar, se aconseja acudir antes al manual citado antes de utilizar cualquier función.

Ya hemos dicho que todas las rutinas de comunicación colectiva de PETSc hacen uso de

MPI. No es necesario utilizar rutinas de paso de mensajes de MPI directamente aunque, a veces, no queda otra salida, por ejemplo en el caso de ‘MPI_BARRIER’, ya que PETSc no tiene envoltorio para dicha rutina. Desde cualquier parte del código se puede invocar a cualquier rutina MPI, ya que el fichero de definiciones ‘mpi.h’ se incluye dentro de los ficheros de cabecera de PETSc. No obstante, en general, se hace poco uso directo de rutinas MPI, más bien se utilizan las rutinas de envoltorio de PETSc, lo que nos sitúa en un nivel más alto de abstracción.

Se puede llamar a la rutina ‘MPI_Initialize’ antes de llamar a ‘PetscInitialize’ (por cuenta del usuario o porque lo hace otro paquete externo a PETSc), pero si se hace así, al llamar a ‘PetscFinalize’ al final del programa, habrá que llamar también a ‘MPI_Initialize’.

Las librerías de PETSc se encargan de mapear todas las variables análogas a las de MPI;

tal es el caso del comunicador ‘PETSC_COMM_WORLD’, ya citado, que se mapea al comunicador ‘MPI_COMM_WORLD’. Este comunicador hace referencia a todos los procesos que intervienen en la computación o en las comunicaciones. Si quisiéramos referirnos a un solo proceso, utilizaríamos el comunicador ‘PETSC_COMM_SELF’, que hace referencia al proceso llamante de la rutina PETSc actualmente llamada.

3.3.2. CHEQUEO DE ERRORES.

Todas las rutinas PETSc, al igual que ocurría con MPI, devuelven un código de error (variable del tipo ‘PestscErrorCode’). En el caso de C, el valor se devuelve como resultado de la función llamada; para Fortran, el código se devuelve dentro de una variable pasada como argumento a la subrutina. Ya se ha comentado la ventaja de que la interfaz de las funciones C y las subrutinas Fortran es la misma, con la pequeña diferencia de que las subrutinas Fortran necesitan un argumento más que es este código de error. El código de error es ‘0’ si todo ha ido bien, y de valor distinto de ‘0’ si ha habido algún problema.

69

Page 71: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Es un error muy común en códigos PETSc escritos en Fortran olvidarse del último argumento de las subrutinas, el código de error.

La macro ‘CHKERR(ierr)’ incluida con PETSc, verifica el valor del código de error devuelto por las rutinas. En el caso de códigos escritos en C, si hay algún error, esta rutina termina todos los procesos e imprime en pantalla una traza de errores detectados. En el caso de Fortran no ocurre igual; sí que se terminan todos los procesos, pero no se imprime una traza de errores porque no está soportado por las rutinas Fortran. No obstante, es fácil escribir un trozo de código que en función del valor del código de error imprima o no una traza de errores.

Existe otra macro, ‘SETERRQ’, la cual puede lanzar códigos de error cuando el usuario la utilice. Como antes, en el caso de Fortran, a diferencia de C, esta macro tampoco imprime una traza de los errores encontrados.

3.3.3. OTROS ASPECTOS IMPORTANTES DE LOS PROGRAMAS PETSC.

3.3.3.1. FICHEROS DE CABECERA PETSC. Los ficheros de cabecera, correspondientes a los módulos PETSc que se desean utilizar en

nuestro programa de aplicación, han de incluirse al principio del código. ⇒ Para el caso de programación en lenguaje C o C++, las sentencias de inclusión son

de la forma:

#include “petscksp.h”

En la sentencia de arriba, ‘petscksp.h’ es el fichero de cabecera para el módulo PETSc correspondiente a la librería de solvers lineales, que utilizan métodos del Subespacio de Krylov.

Como ya se ha visto, las distintas librerías que componen PETSc forman una jerarquía bien definida, que hace que las librerías de más alto nivel incluyan a las de niveles inferiores. Siendo esto así, cada vez que vayamos a crear un programa de aplicación, se habrá de incluir el fichero de cabecera del módulo PETSc de más alto nivel que vayamos a utilizar. El resto de módulos de más bajo nivel que se necesiten para la compilación serán automáticamente incluidos. Así, ‘petscksp.h’ incluye ‘petscmat.h’ (fichero de cabecera para matrices), este a ‘petscvec.h’ (vectores) y este último a ‘petsc.h’ (fichero base de la librería PETSc). Todos estos ficheros y los que no se han nombrado se encuentran en el subdirectorio “include” del directorio de instalación raíz de PETSc.

⇒ El caso de Fortran es algo distinto. Los ficheros de cabecera se encuentran en el

subdirectorio “include/finclude”, y la forma de incluirlos es la misma que en el caso de C/C++. El problema que se presenta al programar con Fortran es que los ficheros de cabecera no pueden ser incluidos más de una vez. Es por esta razón por la que se han de incluir los ficheros de cabecera de todos aquellos módulos PETSc que se necesiten, teniendo ahora en cuenta que los módulos de alto nivel no incluyen a los de bajo nivel (aunque sí que hacen uso de ellos).

Otro punto importante a tener en cuenta al programar con Fortran, es que los ficheros de código fuente han de tener la extensión ‘.F’ en vez de ‘.f’. Esto habilita el uso del preprocesador de C, de modo que se permite el uso de sentencias “#include”

70

Page 72: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

dentro del código Fortran. Esto es necesario porque PETSc está escrita en C, y los ficheros de cabecera han de procesarse antes de enlazar las librerías con nuestro código Fortran de aplicación. Si a todo esto le añadimos que en vez de programar en Fortran 77 programamos en Fortran 90, por ejemplo, entonces la extensión de los ficheros será ‘.F90’ en lugar de ‘.f’, de modo que le indiquemos al compilador de Fortran (‘g95’) que el código escrito es Fortran 90 y no 77.

NOTA: En el capítulo 11, del manual de usuario de PETSc incluido en la ruta “unidadCD:\Documentación\PETSc”, tienen lugar diversas discusiones sobre la utilización de Fortran con PETSc.

3.3.3.2. OPCIONES EN TIEMPO DE EJECUCIÓN.

En el ANEXO I, donde se explica la implantación del sistema, se muestra la forma de ejecutar un programa paralelo por medio de utilidades que acompañan al paquete software de MPI. Si a la hora de lanzar un programa paralelo le pasamos como argumento en línea de comandos la opción “-help”, el programa imprimirá una lista con todas las opciones disponibles que se le pueden pasar como argumentos del programa. Estas opciones se denominan “opciones en tiempo de ejecución”, ya que se le pasan al programa una vez se está ejecutando. Estas mismas opciones se pueden especificar dentro del código fuente, de modo que se incluyen en el programa en tiempo de compilación.

Cada programa PETSc, mantiene una base de datos con los nombres y valores de las opciones que se pueden escoger. Esta base de datos se crea al llamar a la rutina de inicialización ‘PetscInitialize()’. Es necesario ver la definición de la rutina en C/C++ y Fortran:

C/C++: PetscInitialize(int *argc, char **args, const char *file, const char *help) Fortran: PetscInitialize(character file, integer ierr)

En el caso de C/C++, ‘argc’ y ‘argv’ son las direcciones de los argumentos de línea de

comandos pasados al programa, y ‘file’ es el nombre de un fichero que contiene opciones adicionales. Además, se pueden especificar opciones al programar a través de la variable de entorno ‘PETSC_OPTIONS’. El orden de procesado de las opciones es:

1. Fichero. 2. Variable de entorno. 3. Línea de comandos.

La formato habitual a la hora de especificar opciones es: -módulo_opción valor, por

ejemplo “-ksp_type richardson”.

Cualquier subrutina en un programa PETSc puede añadir registros a la base de datos por medio del comando ‘PetscOptionsSetValue(char *name, char *value)’, aunque a penas se utiliza. Por el contrario, obtener opciones de la base de datos es muy común, y se realiza por medio de alguna de las siguientes rutinas, dependiendo del valor que queramos obtener:

‘PetscOptionsHasName’

71

Page 73: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

‘PetscOptionsGetInt’ ‘PetscOptionsGetReal’ ‘PetscOptionsGetString’ ‘PetscOptionsGetStringArray’ ‘PetscOptionsGetIntArray’ ‘PetscOptionsGetRealArray’

Si es necesario utilizar alguna de estas funciones, se aconseja mirar su interfaz en el

manual HTML de PETSc ya comentado en apartados anteriores. Se aprovecha este momento para indicar que en PETSc, al igual que ocurría con MPI, hay

unos tipos de variables definidos, que puede que no coincidan con los tipos de variables análogos de los lenguajes C o Fortran. Se pueden seguir utilizando los tipos de C o Fortran para escribir código, pero a la hora de llamar a las rutinas PETSc los argumentos han de ser de los tipos definidos por la librería, tales como PetscInt para enteros, PetscReal para números reales, PetscScalar para números reales de doble precisión, etc. No obstante, hay casos como el de PetscScalar, que se corresponde con un real de doble precisión de C/C++. Así mismo, PETSc proporciona rutinas tales como ‘PetscPrintf’, análoga a la función ‘printf’ de C, con la diferencia de que la primera reconoce el formato de los datos de PETSc, y la segunda no. Esto es importante tenerlo en cuenta a la hora de implementar códigos de aplicación, pero estas funciones no se utilizan con frecuencia.

3.3.3.3. PROFILING. El término “profile” en términos informáticos, hace referencia al fichero de control de un

programa, especialmente un fichero de texto leído automáticamente desde el directorio local de cada usuario, y pensado para ser fácilmente modificado por el usuario en orden a personalizar el comportamiento del programa. También se refiere a la acción de realizar un informe sobre las cantidades de tiempo consumidas por cada rutina, de forma que se puedan determinar los ajustes requeridos por el programa.

PETSc permite el “profiling” de programas de forma fácil y sencilla. Las rutinas registran

las actividades realizadas si se especifican ciertas opciones en tiempo de ejecución. Además, PETSc proporciona un mecanismo para imprimir información sobre operaciones de computación.

Las opciones de profiling pasadas al programa como argumentos en tiempo de ejecución son las siguientes:

-log_summary: imprime información sobre el funcionamiento del programa al

término del mismo. Esta opción ha sido muy utilizada en la fase de pruebas del proyecto, y ha sido de gran ayuda a la hora de determinar el tiempo de ejecución del programa, los parámetros utilizados, etc.

-info [infofile]: imprime información verbosa sobre el código a la salida estándar o a un fichero opcional. Esta opción proporciona detalles sobre algoritmos, estructuras de datos, etc. Esta opción, a diferencia de la anterior, supone una sobrecarga significativa, de modo que es mejor no utilizarla si lo que se quiere ver es el funcionamiento de la aplicación.

72

Page 74: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

-log_trace [logfile]: traza el comienzo y el final de todos los eventos PETSc. Esta opción, que puede ser utilizada en combinación con la anterior, es útil para ver dónde se queda colgado un programa, sin acudir al depurador.

Hay un paquete software denominado MPE (Multi-Processing Environment), que

forma parte de la implementación MPICH de MPI. El Entorno Multi-Procesamiento (MPE), pretende proporcionar a los programadores un conjunto completo de herramientas para el análisis del funcionamiento de sus programas MPI. Estas herramientas incluyen: un conjunto de librerías de profiling, un conjunto de programas de utilidad y un conjunto de herramientas gráficas.

Las librerías de profiling proporcionan una colección de rutinas que crean ficheros de log (de registro). Estos ficheros de log se pueden crear manualmente insertando llamadas MPE en el programa MPI o automáticamente, enlazando la aplicación con la librerías MPE requeridas. También es posible combinar ambos métodos.

PETSc utiliza la opción en tiempo de ejecución ‘–log_mpe’ para crear un fichero de registro de eventos, apropiado para ser visualizado por Jumpshot, que es programa incluido con MPE para visualizar ficheros de log. La forma de indicar esta opción a PETSc en tiempo de ejecución es:

-log_mpe [logfile]

NOTA: En el apartado 12.1.3, del manual de usuario de PETSc, se habla un poco

más sobre el registro de eventos particulares. PETSc registra automáticamente la creación de objetos, tiempos y contadores de punto

flotante para las rutinas propias de las librerías. El usuario, por su parte, puede complementar esta información monitorizando sus códigos de aplicación. Las partes del código a ser monitorizadas son los denominados eventos.

Las rutinas que utiliza PETSc para el registro de información (logging), se llaman “PetscLogXxxx”, y en particular, las rutinas que empiezan por “PetscLogEventXxxx” se dedican al registro de eventos. Para obtener información sobre la interfaz y el manejo de estas rutinas se aconseja dirigirse al ya referenciado manual HTML de rutinas PETSc.

3.3.3.4. PETSC CON UN SUBCONJUNTO DE PROCESOS.

Hay veces las aplicaciones paralelas son muy grandes, y se requiere dividir el trabajo en partes, asignando alguna parte a un subconjunto de procesos, y no a todos los procesos en su totalidad. Otras veces, no esta la razón de utilizar un subconjunto de procesos, sino que nos puede interesar que un proceso maestro coordine el trabajo de otros procesos esclavos a él.

Para lograr todo esto, habría que especificar un comunicador alternativo a ‘PETSC_COMM_WORLD’. La forma de hacerlo es mediante la rutina siguiente:

PetscSetCommWorld(MPI_Comm comm)

A esta rutina hay que llamarla antes que a ‘PetscInitialize’, pero después de ‘MPI_Init’.

No es frecuente el uso de esta rutina, ya que en vez de hacer esto, una buena alternativa sería llamar varias veces a ‘mpiexec’ de MPI (ver ANEXO I: mpiexec se encarga de ejecutar

73

Page 75: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

programas en paralelo), y a cada llamada se le pasa un conjunto disjunto de procesos. Así sería fácil dividir el trabajo.

3.3.4. ESCRITURA, COMPILACIÓN Y EJECUCIÓN DE PROGRAMAS PETSC.

Las herramientas utilizadas a la hora de crear aplicaciones paralelas son diversas. En nuestro caso, se ha escogido el editor emacs para la escritura del código fuente. Esta elección se debe simplemente a la familiarización con el editor. Los compiladores escogidos, más que nada por compatibilidad con las librerías, han sido gcc para compilar código escrito en C, g++ para código C++, y g95 para Fortran. Escoger los compiladores no fue tarea fácil, ya que al principio hubo problemas de compatibilidad de otros compiladores con las librerías PETSc y MPI.

Una vez escritos y compilados los programas, se ejecutan mediante los scripts de ejecución paralela que proporciona la librería MPI. En concreto, el script mpiexec es el encargado de realizar tal operación.

NOTA: En el capítulo 7 se habla un poco más sobre este tema.

La compilación no se lleva a cabo llamando directamente a los compiladores, sería un poco descabellado, ya que hay que indicar varios nombres de librerías que se utilizan con frecuencia: PETSc, MPI, BLAS, LAPACK y las propias de la aplicación. Hay programas muy cómodos de utilizar como el Fortran Visual de Compaq, que automatizan el proceso de compilación y el posterior enlazado de librerías; sin embargo, a la hora de instalar MPI o PETSc no era viable utilizar este tipo de herramientas.

Las fases de compilación y enlazado de código PETSc / MPI se realizan con la herramienta make, incluida dentro del paquete de Cygwin. Este comando hace uso de los denominados “makefiles”. El uso de esta herramienta facilita en gran medida el mantenimiento y depurado de programas.

Para comprender la forma de utilizar los makefiles con PETSc, lo mejor es ver un ejemplo, el cual se presenta a continuación.

CFLAGS = FFLAGS = CPPFLAGS = FPPFLAGS = LOCDIR = /home/josea/pruebas-petsc/pruebas_fortran EXAMPLESC = EXAMPLESF = struct.F90 diag_dom.F90 MANSEC = KSP include ${PETSC_DIR}/bmake/common/base struct: struct.o chkopts -${FLINKER} -o struct struct.o ${PETSC_MAT_LIB} ${RM} struct.o diag_dom: diag_dom.o chkopts -${FLINKER} -o diag_dom diag_dom.o ${PETSC_KSP_LIB} ${RM} diag_dom.o

74

Page 76: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

cl: rm -f *.*~ rm -f *~ rm -f \#*

En el código del makefile, vemos primeramente algunas variables bandera (flags), las

cuales determinan cómo será compilado el código; de entre ellas la más importante es “LOCDIR”, que determina el directorio local donde se encuentran los ficheros de código fuente a compilar y enlazar.

A continuación, viene una sentencia ‘include’ muy importante, puesto que es la incluye otros makefiles, que son los que proporcionan las definiciones y reglas necesarias para la instalación PETSc básica para una arquitectura de sistema común. En estos makefiles se definen las variables que posteriormente el usuario utilizará para sus propios makefiles, como es el caso aquí mostrado. Estas variables son, por ejemplo, “FLINKER”, que define el linkador Fortran que se utilizará, “PETSC_MAT_LIB”, que define la ruta donde se encuentra el paquete de matrices de PETSc, y muchas otras.

El último bloque de sentencias se utiliza para eliminar los ficheros intermedios creados en la fase de edición de código.

En este makefile de ejemplo presentado, podemos observar que se definen dos objetivos

para compilar dos ficheros: “struct.F90” y “diag_dom.F90”. La forma de invocar al comando make para compilar y enlazar estos ficheros con las librerías de PETSc es simplemente la siguiente: nos colocamos en el directorio donde se encuentra el makefile y ejecutamos las siguientes sentencias:

$ make struct $ make diag_dom

La primera instrucción compila el fichero struct.F90” y la segunda el fichero

“diag_dom.F90”. Notar que los argumentos que se le pasan al comando make son los objetivos definidos en el makefile, no el nombre de los ficheros.

Por otro lado, si el makefile creado para compilar nuestras aplicaciones no tiene el nombre “makefile”, habrá que indicárselo a make con la opción –f nombre_makefile. Para aprender más opciones sobre las formas de invocar make, se puede acudir al manual proporcionado con el paquete Cygwin, al cual se accede mediante la siguiente sentencia ejecutada en una consola de Cygwin:

$ man make

La mejor forma de aprender a utilizar estos makefiles, es viendo los ejemplos que se incluyen con la instalación de PETSc, o viendo los creados para las pruebas realizadas para el proyecto. No obstante, en el capítulo 15 del manual de usuario de PETSc se habla algo más sobre la construcción de estos ficheros de apoyo.

75

Page 77: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.4. VECTORES.

3.4.1. INTRODUCCIÓN.

Hasta ahora, lo que hemos visto a sido una descripción del entorno donde se desarrollan las aplicaciones PETSc, así como algunas características propias de la librería. Las siguientes grandes secciones, incluyendo la actual, se dedican a explicar en profundidad los principales módulos software que proporciona PETSc, y que han sido utilizados en la realización de aplicaciones paralelas para el Cálculo de Estructuras. Esta primera sección se dedica al tema de vectores, primer paquete de Álgebra lineal, que será la base para el entendimiento de las siguientes librerías que se explicarán.

Ya dijimos que PETSc utiliza la filosofía de la Programación Orientada a Objetos (OOP),

aun cuando C o Fortran no se ajustan a esta forma de programación (C++ sí, que también está soportado por PETSc). Pues bien, el objeto fundamental en la jerarquía de objetos de PETSc es el vector, denominado ‘Vec’ en términos de la librería. Los vectores se utilizan para almacenar soluciones de problemas, la parte del lado derecho del sistema Ax=b, etc.

En los siguientes apartados, veremos todo lo referente al manejo de este tipo de objetos,

así como algunos paquetes software relacionados con los vectores, como son los índices o los arrays distribuidos.

3.4.2. CREACIÓN DE VECTORES.

PETSc nos da la posibilidad de crear dos tipos de vectores, en función de nuestras necesidades y de si el sistema es mono o multi-procesador. Los dos tipos son:

Vector secuencial: todas sus componentes (campos) pertenecen al mismo procesador. Vector paralelo: sus componentes se encuentran distribuidas a través de un conjunto

de procesadores. El procedimiento de distribución de componentes sobre los distintos nodos del cluster, así como las distintas operaciones que se llevan a cabo sobre el vector, se realizan mediante MPI. Por esta razón, al vector paralelo también se le denomina vector MPI.

PETSc soporta objetos con estructura de datos neutral, lo que permite un modelo de

memoria distribuida, nada compartido, dentro de sistemas de procesadores individuales. El objeto “vector” de PETSc es un indicador (puntero) a las entradas del vector real; esto

permite que el vector paralelo se pueda distribuir a través de muchos procesos, pero tiene como contrapartida que no se puede acceder al vector de forma directa, sino a través de unas rutinas específicas que esconden un complejo de llamadas a MPI. PETSc permite trabajar con el vector global, no sólo con la parte local del vector que pertenece a un proceso particular, pero de nuevo, a través de un conjunto de operaciones que especifica la librería paralela. Por otro lado, el acceder al vector a través unas rutinas definidas por PETSc ayuda a no preocuparse de la complejidad de las operaciones de comunicación entre procesos, que siempre es un tanto compleja.

La creación de un vector se compone de tres fases:

76

Page 78: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

1. La creación del vector, propiamente dicha, donde cada proceso crea su vector particular, en el caso de vectores secuenciales, o donde cada proceso crea su parte local del vector paralelo, en el caso de vectores MPI. Los vectores paralelos (MPI) están formados por vectores secuenciales, y cada uno pertenece a un proceso, de modo que el vector global paralelo es la unión de todos estos vectores locales a los procesos.

2. La inserción de valores. Tras la creación del objeto, cada proceso inserta valores

en las partes del vector que considere necesarias, ya sean partes locales o no locales al proceso.

3. El ensamblado del vector. Esta es la fase que tiene lugar tras la inserción de

valores en el vector. Aquí se realiza todo el paso de mensajes de las componentes del vector no-locales a los procesos.

NOTA: No perder de vista que trabajamos con un modelo de programación paralela SPMD (Single Program, Multiple Data), es decir, todos los procesos ejecutan el mismo programa, aunque cada uno tiene su propios valores para las mismas variables, lo que da lugar a resultados distintos en cada nodo. Esto quiere decir que todos los procesos llaman a las mismas operaciones, y en el mismo orden; de modo que, en el caso particular de vectores, al crear, por ejemplo, un vector paralelo, todos los procesos lo crearán a la vez, y todos contribuirán con su parte local al vector MPI global. La definición de las interfaces de todas las rutinas se encuentran perfectamente detalladas en el manual HTML de PETSc, al que de nuevo se aconseja acudir cada vez que se requiera utilizar cualquier rutina.

Las rutinas que proporciona PETSc para la creación de vectores son ‘VecCreateSeq’ y

‘VecCreateMPI’ para el caso de vectores secuenciales y paralelos, respectivamente. Ambas rutinas requieren el nombre de un comunicador como argumento, que en el caso de ‘VecCreateSeq’ es ‘PETSC_COMM_SELF’, como no podría ser de otra manera. En el caso de la rutina para la creación de vectores MPI, se especifican dos dimensiones del vector, la local y la global. El usuario puede especificar ambas o una de las dos y la que resta dejar que la determine PETSc, indicando que dicha dimensión tenga el valor “PETSC_DECIDE”.

De forma equivalente, se pueden sustituir las dos rutinas anteriores por el conjunto de las tres siguientes: ‘VecCreate’, para crear el vector y especificar el comunicador utilizado, ‘VecSetSizes’, para especificar las dimensiones (local y global) del vector, y ‘VecSetFromOptions’, para coger de la base de datos la opción que indica el tipo de vector a utilizar. Si en línea de comandos se indicó el argumento “-vec_type mpi”, el vector que determinará la rutina ‘VecSetFromOptions’ será de tipo MPI, incluso en el caso uniproceso.

A la creación de un vector le sigue la inserción de valores en el mismo. Esta fase se lleva a

cabo mediante la rutina ‘VecSet’, para la inserción de un valor simple a todas las componentes del vector, o mediante ‘VecSetValues’, para la inserción de un conjunto de valores. Si la opción escogida es esta última, se puede llamar varias veces a dicha rutina en cualquiera o en todos los procesos, pero la fase de inserción no está completa. Por medio de la rutina ‘VecSetValues’, cualquier proceso puede insertar valores en cualquier componente del

77

Page 79: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

vector global, aun siendo componentes no locales. Por esta razón, es necesario pasar a la tercera fase, la fase de ensamblado (si se utiliza la rutina ‘VecSet’, el ensamblado se puede omitir). Decir que PETSc no proporciona ninguna rutina llamada “VecGetValues”, sino que proporciona métodos alternativos tales como el acceso a un array local, copia del vector paralelo, u operaciones de dispersión (scatter) de valores entre procesos (rutinas basadas en MPI pero con una interfaz PETSc, que esconde la complejidad de las comunicaciones MPI).

El ensamblado de un vector paralelo consiste en realizar todas las operaciones de

comunicación interprocesos necesarias para distribuir los valores no locales previamente insertados. Las rutinas a las que hay que invocar para realizar el ensamblado son ‘VecAssemblyBegin’ y ‘VecAssemblyEnd’, en medio de las cuales se pueden realizar operaciones de computación, de modo que se aproveche el tiempo en que se realizan las comunicaciones pertinentes. Si un proceso inserta valores en su parte local del vector paralelo, no es necesario realizar la fase de ensamblado, puesto que no hay que migrar valores a otros procesos.

Uno de los parámetros a indicar en la llamada a la rutina ‘VecSetValues’ es si se desea

insertar o añadir (sumar) valores a las componentes del vector en cuestión. En el caso de la inserción, se indica mediante el valor ‘INSERT_VALUES’, y en el caso de la adición el valor es ‘ADD_VALUES’. Es importante hacer notar que la inserción y la adición no se pueden combinar. Para poder mezclar ambas acciones, serían necesarias fases distintas de inserción y adición, con llamadas entre medio a las rutinas de ensamblado. Esta forma de trabajar elimina la incertidumbre del caso en que un proceso inserte y otro añada valores a las mismas localizaciones dentro del vector.

Una vez ensamblado el vector paralelo, un proceso puede obtener el tamaño de dicho

vector llamando a ‘VecGetSize’. A su vez, si un proceso desea determinar el tamaño local de la parte que le corresponde del vector global, puede hacerlo mediante ‘VecGetLocalSize’.

Es lógico querer visualizar los nuevos objetos de tipo vector creados. La librería PETSc, dispone de un módulo software para la creación de visualizadores (viewers), que son objetos utilizados para examinar, imprimir y guardar otros objetos. Existen diversas rutinas para trabajar con visualizadores, algunas de las cuales se presentan en la sección 14.3 del manual de usuario de PETSc. Incluso hay algunas para interactuar con MATLAB.

En el caso concreto de vectores, la rutina ‘VecView’ muestra en pantalla las componentes del vector pasado como argumento (de nuevo, se insta acudir al manual HTML de funciones PETSc).

La creación de vectores está bien, pero si queremos obtener vectores con el mismo

formato que el de uno recién creado, muchas veces interesaría más duplicarlos, así no habría que repetir las fases de creación, inserción de valores y ensamblado. Pues bien, PETSc suministra dos rutinas, ‘VecDuplicate’ y ‘VecDuplicateVecs’, para el duplicado de un vector en otro o en otros, respectivamente. La gran ventaja de introducir sentencias de duplicado de vectores en el código fuente, es que dicho código no depende del formato de los vectores utilizados.

Otras veces, por motivos implicados de una aplicación particular, puede ser útil crear vectores a partir de un array proporcionado por el usuario, cosa que se puede hacer con las rutinas ‘VecCreateSeqWithArray’ y ‘VecCreateMPIWithArray’. En este caso, no se deja a PETSc la opción de alojar en memoria el espacio para el vector, sino que lo hace el propio usuario; esto implica que es el propio programador el encargado de especificar la dimensión

78

Page 80: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

local del vector, teniendo cuidado de que el array sea lo suficientemente grande para contener los valores del vector local.

Tras la utilización de un vector, la única salida que tiene, como objeto que es, es ser

destruido. Esta operación de eliminación se lleva a cabo mediante ‘VecDestroy’, para un solo vector, o con ‘VecDestroyVecs’, para realizar lo mismo con un array de vectores.

3.4.3. OPERACIONES FUNDAMENTALES CON VECTORES.

Algunas de las operaciones básicas con vectores proporcionadas por PETSc se presentan en la tabla 6, sacada del manual de usuario de PETSc. En la rutina ‘VecNorm’, el tipo de la norma del vector puede ser 1, 2 o infinito.

RUTINA OPERACIÓN REALIZADAVecAXPY y = y + a · x VecAYPX y = x + a · y VecWAXPY w = a · x + y VecAXPBY y = a · x + b · y VecScale x = a · x VecDot r = x*' · y VecTDot r = x' · y VecNorm r = ||x||tipo VecSum r = ∑ xi VecCopy y = x VecSwap y = x mientras x = y VecPointwiseMult wi = xi · yi VecPointwiseDivide wi = xi / yi VecMDot r[i] = x*' · y[i] VecMTDot r[i] = x' · y[i] VecMAXPY y = y + ∑i ai · x[i] VecMax r = max xi VecMin r = min xi VecAbs xi = |xi| VecReciprocal xi = 1/xi VecShift xi = s + xi VecSet xi = α

Tabla 6. Operaciones básicas con vectores.

La tabla anterior es auto explicativa, y si se desea realizar alguna de estas operaciones, no

hay más que acudir al manual HTML de funciones para ver la interfaz y utilizarla. Siempre se aconseja ir al manual HTML porque, de la experiencia adquirida programando con PETSc, se ha llegado a la conclusión de que es el único sitio donde las interfaces de las rutinas están correctamente indicadas, sin errores.

Hay una rutina muy importante y muy utilizada en las pruebas:

‘VecGetOwnershipRange’. La llamada a dicha rutina permite a cada proceso particular obtener, de un vector paralelo distribuido a través de todos los procesos, el rango local que le pertenece. El rango local se especifica con un índice inicial y un índice final, que

79

Page 81: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

corresponden, respectivamente, a la primera y la última componente del vector global que pertenecen al proceso que invoca la rutina. Es importante hacer énfasis en el hecho de que los índices devueltos por esta rutina son índices globales, que indican la parte del vector paralelo que pertenece al proceso local que llama a la rutina. En la siguiente sección, se verán métodos adicionales para tratar con índices. La rutina ‘VecGerOwnershipRange’ se suele utilizar en conjunción con ‘VecSetValues’, que también hace uso de índices globales.

Es comprensible que sea un poco complicado asimilar todos estos conceptos de índices y rangos locales y globales, vector paralelo y secuencial, comunicador colectivo o individual, etc.; todos estos conceptos seguramente se aclararán y asentarán viendo los códigos de las pruebas realizadas. Una gran ventaja de PETSc, es que proporciona un código muy legible, debido a los nombres asignados a las rutinas, por lo que es fácil e intuitivo seguir el código fuente de las aplicaciones, que, a su vez, es la mejor forma de aprender a programar con PETSc (además de ir mirando el manual HTML de funciones).

Ya dijimos que no había una rutina análoga a ‘VecSetValues’ que extrajese valores de los

vectores, cosa que muchas veces es necesaria. Para tal fin, PETSc proporciona dos rutinas: ‘VecGetArray’, que devuelve un puntero a los elementos locales (del vector global completo) que pertenecen al proceso llamante; y ‘VecRestoreArray’, que elimina el puntero cuando ya no es necesario acceder más al vector local. El array (puntero al array) devuelto, no es una copia del vector local, sino que es un acceso directo a los elementos del vector (la parte del vector global que pertenece a un proceso particular, se encuentran almacenada localmente). Esta forma de trabajar da lugar a un aumento de la eficiencia, ya que no hay que crear ni destruir copias de datos.

Por último, puntualizar que hay rutinas con los nombres ‘VecxxxBegin’ y ‘VecxxxEnd’

que aumentan la eficiencia de las comunicaciones. Este tipo de rutinas aprovechan el hecho de que se vayan a realizar varias operaciones consecutivas que requieran comunicaciones y así unificarlas, manteniendo los cálculos implicados por separado. Sin embargo, al estar basadas en MPI-1, no están preparadas para solapar cálculos con comunicaciones, lo que se espera conseguir en un futuro al mejorar estas rutinas con MPI-2.

Hasta aquí, la parte básica del paquete de vectores. En lo que resta de la sección, veremos

algunos complementos del mismo paquete software, que servirán para facilitar la labor del programador.

3.4.4. ORDENACIÓN E INDEXADO (NUMERACIÓN).

En muchos problemas científicos, se requiere un procesamiento previo de la información antes de pasar a la fase de computación. Este procesado hace referencia a cómo se ordenan y numeran los diversos elementos que componen un problema, que es la base para empezar a realizar operaciones sobre estos datos. Existen diversas situaciones en las que esto ha de tener lugar, tales como códigos paralelos de PDE’s, en los que aparecen muchísimos vértices y grados de libertad. En nuestro caso, antes de realizar cálculos sobre una estructura concreta, es necesario realizar antes un mallado de la misma, y asignar a cada nodo resultante una identificación, para poder distinguir unos de otros. Tras realizar esto, es necesario numerarlos y ordenarlos de la forma que más convenga para nuestra aplicación.

80

Page 82: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A todo esto hay que añadir lo siguiente: como bien sabemos, los datos se distribuirán a través de los nodos del cluster de ordenadores. Pues bien, cada proceso tendrá su propia numeración local de los objetos de datos, frente a la numeración global del conjunto.

Para toda esta casuística, PETSc suministra dos herramientas básicas, traducidas a paquetes software distintos: AO (Application Ordering – Ordenación de la Aplicación) e IS (Index Set – Batería de Indíces). AO permite mapear entre diferentes esquemas de numeración global, por ejemplo, entre la ordenación que utiliza PETSc y la que desearía utilizar un usuario para su aplicación; IS permite mapear entre numeración local (un proceso particular) y global (el conjunto de procesos).

A continuación, veremos las dos herramientas por separado, las cuales trabajan juntas en un punto, el cual veremos.

3.4.4.1. ORDENACIONES DE UNA APLICACIÓN.

Muchas veces es necesario, otras veces deseable, mantener diferentes formas de ordenar los elementos de un problema. Esto es una tarea complicada, debido a que es difícil que cada proceso mantenga información sobre el mapeado entre las distintas ordenaciones. En la figura 26, se presenta un ejemplo de lo comentado: tenemos una aplicación que mantiene una ordenación (natural) determinada para un array de 2D, mientras que PETSc utiliza una ordenación completamente distinta, que corresponde a la utilizada por las rutinas del Álgebra lineal. Cada bloque de datos pertenece a un proceso, los cuales utilizan una de las dos ordenaciones, según se le indique.

Figura 26. Ordenaciones distintas para un mismo array de datos en 2D.

Para crear una nueva ordenación de aplicación, basta con llamar a la rutina

‘AOCreateBasic’, la cual acepta dos arrays como argumentos, uno para especificar los valores de la ordenación que utiliza la aplicación, y otro para los valores de la ordenación que utiliza PETSc. Una vez creado el objeto AO, se pueden utilizar las rutinas ‘AOPetscToApplication’ y ‘AOApplicationToPetsc’ para mapear de una ordenación a otra. Si un conjunto de procesos llamó a la rutina ‘AOCreateBasic’, el mismo conjunto (no un subconjunto) ha de llamar a estas dos últimas rutinas, en el caso de que sea necesario, ya que la base datos que mantiene el mapeado entre ordenaciones es paralela, de modo que todos los procesos que la crearon han de mantenerla.

La rutina ‘AODestroy’ elimina el contexto de ordenaciones creado. Así mismo, ‘AOView’ visualiza el objeto AO creado por las rutinas pertinentes.

81

Page 83: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.4.4.2. CONJUNTOS DE ÍNDICES. Un conjunto de índices (IS) es una generalización de un conjunto de enteros, utilizado

para definir dispersiones, recolecciones y operaciones similares sobre vectores y matrices. La rutina ‘ISCreateGeneral’, crea un conjunto de índices a partir de un array de enteros. Hay variantes de esta rutina, como es el caso de ‘ISCreateStride’, que crea un conjunto de índices que contiene una lista de enteros igualmente espaciados por un paso (stride), o como ‘ISCreateBlock’, que crea un conjunto de índices para un conjunto de bloques de enteros (mirar el manual HTML de funciones). Cuando no se necesita más un conjunto de índices, se puede destruir mediante la rutina ‘ISDestroy’.

Hay una rutina, llamada ‘AOCreateBasicIS’, que crea una ordenación para una

aplicación, no a partir de dos arrays de enteros (como así lo hacía la rutina ‘AOCreateBasic’), sino a partir de dos conjuntos de índices. Posteriormente, de forma análoga a como ocurre con ‘AOCreateBasic’, se puede llamar a las rutinas ‘AOPetscToApplicationIS’ y ‘AOApplicationToPetscIS’ para mapear objetos IS entre las ordenaciones de aplicación y de PETSc.

Con los objetos AO, se busca hallar un camino para mantener diferentes ordenaciones

para los mismos datos de un problema. Por otro lado, con los objetos IS se busca acceder localmente a los recursos globales y viceversa. PETSc proporciona unas rutinas para mapear los índices desde un esquema de numeración local (el de un proceso individual) al esquema de numeración global de PETSc, y viceversa. Con las rutinas ‘ISLocalToGlobalMappingCreate’ y ‘ISLocalToGlobalMappingDestroy’ se crea y se destruye, respectivamente, una estructura de índices para mapear entre una numeración local y otra global. Tras la creación, las rutinas ‘ISGlobalToLocalMappingApply’ y ‘ISLocalToGlobalMappingApply’ realizan el mapeado entre índices globales y locales. En el caso de un vector paralelo, esto es muy interesante, ya que ofrece la posibilidad de insertar valores con una numeración local para el proceso llamante, sin preocuparse por la numeración global del vector paralelo completo. Ya vimos que la rutina ‘VecSetValues’ utilizaba una numeración global para insertar valores en un vector paralelo. Ahora, haciendo uso de los conjuntos de índices, se pueden insertar valores mediante una numeración local. En concreto, la rutina ‘VecSetLocalToGlobalMapping’ llamada tras ‘ISLocalToGlobalMappingCreate’, mapea los índices locales a índices globales para acceder al vector, de modo que mediante la rutina ‘VecSetValuesLocal’ se puedan insertar valores utilizando índices locales, que automáticamente serán mapeados a los correspondientes índices globales, accediendo así al vector paralelo de forma correcta. Los índices locales seguirán una numeración propia de cada proceso, es decir, pertenecerán al rango de valores [0, size), donde size es el tamaño del vector local perteneciente al proceso (parte local del vector paralelo alojada en dicho proceso).

3.4.5. DISEÑO DE DATOS PARALELOS Y VALORES DE CONTORNO (GHOST VALUES).

Una situación típica en términos científicos, es la resolución de PDE’s (ecuaciones en derivadas parciales). Gestionar bien el área de datos es la clave para obtener un buen rendimiento en las aplicaciones paralelas basadas en la resolución de este tipo de problemas. Respecto al área de datos, decir que se puede tratar como una malla (o rejilla) estructurada o no estructurada. PETSc proporciona dos tipos de objetos para tratar sendos casos: los arrays

82

Page 84: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

distribuidos, para rejillas estructuradas, y los “VecScatter” (dispersión de vectores), para rejillas no estructuradas.

Para evaluar las funciones que representan las PDE’s, se requiere, en cada parte del proceso de resolución, obtener el valor del vector solución intermedio, el cual se encuentra distribuido entre los procesos que ejecutan la aplicación paralela. Cada proceso necesita los valores locales del vector paralelo que le corresponden, así como los valores de contorno (ghost values), que se encuentran almacenados en los procesos adyacentes. Este concepto se muestra en la figura 27.

Figura 27. Concepto de valores ghost (valores de borde).

En la resolución paralela del tipo de problemas que venimos comentando (problemas

derivados de PDE’s), intervienen cuatro puntos importantes a tener en cuenta:

1. Geometría de los datos: es importante identificar si los datos se presentan en forma de rejilla estructurada o no estructurada.

2. Creación de la estructura de soporte para los datos: tanto para los datos locales como para los valores ghost.

3. Actualización de valores ghost: cada proceso envía/recibe los correspondientes valores ghost a/de su vecino.

4. Fase de computación numérica local: cada proceso utiliza sus valores locales y los valores ghost para realizar las operaciones pertinentes.

Existen dos tipos de representaciones de los datos que almacena cada proceso: la

representación global y la local. En la global, cada proceso almacena un único conjunto local de nodos de la rejilla, y cada nodo pertenece a un único proceso. En la representación local, cada proceso almacena un único conjunto local de nodos de la rejilla, así como un conjunto de nodos ghost que pertenecen a los procesos adyacentes. Estas dos formas de representación se muestran en la figura 28. Las representaciones son las ya denominadas ordenaciones, las cuales se pueden manejar mediante los objetos AO que proporciona PETSc.

A continuación, pasaremos a ver los métodos que utiliza PETSc para tratar con rejillas,

que ya se han comentado: arrays distribuidos y ‘VecScatter’.

83

Page 85: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Figura 28. Representación global y local de los nodos pertenecientes a un proceso.

3.4.5.1. REJILLAS ESTRUCTURADAS: ARRAYS DISTRIBUIDOS Los arrays distribuidos (DA), son un paquete software de PETSc independiente al

módulo de vectores, pero ambos funcionan en conjunto para trabajar con rejillas lógicas de datos regulares y rectangulares, a la hora de distribuir datos no locales entre los distintos procesos. Los arrays distribuidos de PETSc, están diseñados para el único caso en que se pueda pensar que los datos están almacenados en un array lógico multidimensional estándar. Por esta razón, los DA’s no están pensados para paralelizar rejillas de datos no estructuradas.

Los datos de la aplicación paralela se almacenan en objetos de tipo vector, de tamaño apropiado. Los objetos DA sólo contienen la información del esquema de datos paralelos y la información de la comunicación de valores ghost, aunque se pueden utilizar para crear vectores y matrices con un diseño apropiado.

Al trabajar con DA’s, el primer paso a realizar es la creación de estos objetos. Se puede crear una estructura de datos de comunicación de un DA mediante las rutinas ‘DACreate1d’, ‘DACreate2d’ o ‘DACreate3d’, dependiendo de si la estructura de la rejilla estructurada regular es de 1D, 2D o 3D, respectivamente. Hay dos parámetros importantes a pasar como argumentos a estas rutinas: el número de puntos de rejilla y el número de procesos, ambos especificados para cada dirección. Otro parámetro a indicar es el tipo de estructura del array distribuido; los tipos disponibles son dos: estructura de tipo caja y de tipo estrella. Ambos se muestran en la figura 29. Esta definición del tipo de estructura del DA determinará los valores ghost que se utilizarán.

Ya hemos dicho que los objetos DA,

sólo contienen información sobre la estructura de datos paralela y las comunicaciones. Los datos se almacenarán en vectores. Y este es el siguiente paso, crear los vectores que contengan la información sobre los datos (incluidos los valores ghost).

Figura 29. Patrón de la estructura de datos para los valores ghost.

84

Page 86: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Cada objeto DA define el diseño de dos vectores: un vector global distribuido y un vector local que incluye espacio para los puntos ghost pertinentes. Cada proceso almacena una única porción del vector global, y cada proceso almacena su vector local completo más el alojamiento necesario para los valores ghost. El objeto DA informa sobre el tamaño y el esquema de sendos vectores, pero no aloja espacio para los valores de dichos vectores. La forma de trabajar es creando los dos vectores a partir de la información proporcionada por el objeto DA. Las rutinas que realizan esto son ‘DACreateGlobalVector’ y ‘DACreateLocalVector’. A partir de que se crean los objetos vector, se pueden utilizar todas las rutinas del módulo de vectores.

Cuando dentro de una aplicación un proceso necesita los valores ghost para realizar ciertas operaciones, hemos de conseguirlos mediante operaciones de dispersión (scatter) de dichos valores desde los procesos adyacentes al proceso local. Esto es, hemos de realizar una dispersión de algunos valores del vector global al vector local. PETSc suministra dos rutinas para realizar la operación de dispersión en dos pasos: ‘DAGlobalToLocalBegin’ y ‘DAGlobalToLocalEnd’. En medio de estas dos rutinas, las cuales realizan un entramado de comunicaciones, se pueden realizar operaciones de computación, de modo que podamos aumentar el rendimiento de nuestra aplicación. Al término de dichas operaciones, se dispone del vector local con los valores ghost actualizados, momento en el que se pueden llevar a cabo las operaciones necesarias. Una vez terminado el procesado del vector local, se pueden enviar los nuevos valores de dicho vector al vector global mediante una operación de dispersión, la cual realiza la rutina ‘DALocalToGlobal’.

Por último, señalar que existen dos rutinas, ‘DaLocalToLocalBegin’ y ‘DALocalToLocalEnd’, que se utilizan para copiar de un vector local que contiene puntos ghost no actualizados a otro vector local, el cual, ahora sí, contendrá los correspondientes valores ghost actualizados. La única cosa a tener en cuenta al realizar esta operación, es que ambos vectores locales han de tener el mismo diseño paralelo, es decir, han de ser compatibles con el mismo DA (generados por el mismo objeto DA, si cabe).

Realmente, de los dos vectores, el global y el local, se trabaja con el local a la hora de

realizar operaciones de computación, puesto que es el que contiene los valores ghost. Ya hemos visto cómo crear ambos vectores, así como la forma de actualizar los valores ghost. Sin embargo, esta forma de actualizar los valores de contorno es poco eficiente. PETSc provee las siguientes rutinas: ‘DAGetLocalVector’ que, como su propio nombre indica, obtiene el vector local correspondiente (con los valores ghost correctos), tras lo cual se realizan las operaciones necesarias en el proceso actual; y ‘DARestoreLocalVector’, que libera el vector de trabajo local cuando ya no se necesita. Estas dos rutinas apenas requieren tiempo de CPU para su funcionamiento.

Tras realizar todas estas fases de creación de diseño paralelo de vectores mediante objetos

DA, creación de los vectores correspondientes a los diseños realizados, etc., lo que resta es insertar valores en dichos vectores. Las rutinas destinadas a tal fin son dos, ‘DAVecGetArray’ para obtener un array en el que insertaremos los valores generados en nuestro programa de aplicación, y ‘DAVecRestoreArray’ para restaurar el array y mapear los valores al vector distribuido. A la primera rutina, se le puede especificar que el vector al cual queremos insertar valores sea el global o el local, pero una vez que obtengamos el array, sólo podremos acceder a los valores locales y a los valores ghost, el resto de valores estarán indefinidos. La forma de acceder al array es utilizando una indexación global, como si estuviéramos accediendo al vector global de forma directa (aunque sabemos que eso no es posible si no es a través de rutinas que utilizan arrays intermedios).

85

Page 87: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Decir que existen otras rutinas, como ‘DAGetCorners’ y ‘DAGetGhostCorners’, empleadas para obtener información sobre la estructura de la rejilla datos, en concreto el valor de los índices de la esquina inferior izquierda de la parte local correspondiente al objeto DA en cuestión.

Como última aplicación de los arrays distribuidos, puntualizar que es posible mapear entre las ordenaciones de aplicación y de PETSc utilizadas para gestionar las rejillas regulares rectangulares. En su momento vimos que el paquete software de objetos AO era el encargado de gestionar el tema de las ordenaciones de datos. A su vez, también vimos cómo PETSc utiliza una ordenación de los datos distinta a la ordenación natural de la que hace uso una aplicación particular (apartado 3.4.4.1., figura 26). Pues existen rutinas, pertenecientes al módulo de objetos DA, que complementan su funcionamiento con las rutinas del módulo de objetos AO. Por ejemplo, la rutina ‘DAGetAO’ obtiene el contexto de ordenación de datos de la estructura paralela representada por un array distribuido particular.

3.4.5.2. REJILLAS NO ESTRUCTURADAS: VECSCATTER.

Establecer los patrones de comunicación en rejillas no estructuradas es mucho más complejo que en el caso de rejillas estructuradas, debido a la dependencia con la malla y con la discretización.

En el apartado 3.4.4.2., se hizo una breve descripción del paquete de conjuntos índices (IS). PETSc utiliza este tipo de objetos para facilitar las dispersiones y recolecciones (scatters & gathers) utilizadas para actualizar los puntos ghost de problemas definidos sobre rejillas de datos no estructuradas. Así mismo, es muy probable que sea necesario utilizar los objetos AO (Application Ordering) para mapear entre el esquema de ordenación de nuestra aplicación y el de PETSc, de modo que consigamos que los valores situados en procesos adyacentes sean numerados de forma secuencial (así facilitar el manejo de los valores ghost).

La librería paralela PETSc proporciona, dentro del módulo de vectores, soporte completo

para las operaciones de dispersión y recolección. En términos de PETSc, se hace referencia a estas operaciones como dispersiones generalizadas, aunque en la actualidad son una combinación de dispersiones y recolecciones.

El programador puede seleccionar cualquier subconjunto de las componentes de un vector e insertarlas o añadirlas a cualquier subconjunto de las componentes de otro vector. Para tal fin, PETSc utiliza un término denominado contexto de dispersión, que es un objeto del tipo ‘VecScatter’, utilizado para referirse a un entorno de comunicaciones concreto. La razón de utilizar los contextos es que podemos diferenciar entre distintas operaciones de dispersión con vectores. La rutina ‘VecScatterCreate’ es la encargada de crear el contexto de dispersión, y hay que indicarle los vectores que intervendrán en la operación (pueden ser paralelos o secuenciales), así como el subconjunto de un vector que se copiará a otro subconjunto igual o distinto al del otro vector. El número de elementos de ambos subconjuntos ha de ser idéntico. ‘VecScatterDestroy’ se encarga de eliminar los datos creados por la rutina anterior.

La dispersión se lleva a cabo a través de ‘VecScatterBegin’ y ‘VecScatterEnd’, con una característica peculiar: una operación de dispersión puede realizarse a la inversa. Esto último se indica mediante un argumento pasado a estas dos rutinas, el cual puede tomar el valor ‘SCATTER_FORWARD’ (dispersión del vector global al local) o ‘SCATTER_REVERSE’ (dispersión del vector local al global).

Las operaciones de dispersión son colectivas, lo que implica que todos los procesos que sean propietarios de un vector paralelo, sobre el que se realiza una dispersión, han de llamar a las rutinas de dispersión.

86

Page 88: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Antes de pasar al siguiente punto, veamos dos características importantes de las dispersiones. La primera es que cualquier pareja de vectores que tenga el diseño paralelo conveniente (mismo número de elementos en cada proceso), puede utilizar el objeto VecScatter creado con la rutina ‘VecScatterCreate’. No importa que los vectores utilizados para la dispersión no hayan intervenido en la llamada a dicha rutina si, como ya se ha comentado, tienen el mismo esquema paralelo que el de los vectores utilizados en la llamada. Esto es una ventaja, ya que se puede reutilizar el contexto de dispersión, de modo que se reducen las comunicaciones y, por ende, aumenta el rendimiento de nuestra aplicación. La segunda característica a comentar, es que no existe una rutina que realice la función inversa a ‘VecSetValues’ (esto ya se ha apuntado en otra ocasión). Es decir, se pueden insertar valores en un vector paralelo con ‘VecSetValues’ y después ensamblar el vector con las rutinas correspondientes, pero no existe rutina alguna para obtener de la misma forma los valores. En su defecto, con PETSc se trabaja de forma que primero se realiza una dispersión de valores a un vector creado al efecto, y posteriormente se llama a la rutina ‘VecGetArray’ para mapear los valores del vector a un array pasado como argumento. Al final, es necesario llamar a ‘VecRestoreArray’ devolver al sistema el array empleado.

Al igual que ocurría en el caso de rejillas estructuradas, en rejillas no estructuradas

también es importante el uso de valores ghost (valores que se encuentran en los procesos inmediatamente adyacentes al proceso local, y que son fundamentales para realizar diversas operaciones de computación). Sin embargo, la forma de trabajar es algo distinta al caso de rejillas estructuradas. El método consiste en dos etapas de dispersión. Primeramente, se realiza una dispersión de valores desde el vector paralelo a los vectores locales de trabajo, que contienen espacio para valores ghost (‘SCATTER_FORWARD’). A continuación se realiza la computación requerida utilizando los vectores locales. Por último, se lleva a cabo una dispersión hacia atrás para introducir el resultado en el vector solución global (‘SCATTER_REVERSE’).

Antes de terminar, veamos una última cuestión relacionada con el tratamiento que ofrece

PETSc para rejillas no estructuradas. Todo lo que se ha hablado hasta ahora en este apartado presenta dos inconvenientes: es necesario memoria adicional para almacenar los vectores locales de trabajo (partes locales del vector global que incluyen espacio para los valores ghost), y además se necesita tiempo suplementario para copiar los valores del vector global a los vectores locales. PETSc tiene en cuenta estos inconvenientes, y proporciona rutinas para alojar espacio para los vectores globales con prealojamiento de memoria para los valores ghost. Estas rutinas son ‘VecCreateGhost’ y ‘VecCreateGhostWithArray’; a ambas se les pasa como argumento un array que contendrá las posiciones donde se encuentran los valores ghost dentro del vector global. La rutina ‘VecGhostGetLocalForm’ es la encargada de mapear el vector global paralelo al vector secuencial correspondiente, que incluirá los valores ghost. Este vector secuencial comparte el mismo espacio de memoria que el vector paralelo. Al vector MPI (paralelo) se accede mediante una numeración global y, al secuencial, con una numeración local (también para los valores ghost). La rutina complementaria a la anterior es ‘VecGhostRestoreLocalForm’, que produce el mismo efecto que el uso de ‘VecRestoreArray’ tras ‘VecGetArray’. En el caso de rejillas no estructuradas, las rutinas ‘VecGhostUpdateBegin’ y ‘VecGhostUpdateEnd’ cumplen la misma función que la que realizaban ‘VecScatterBegin’ y ‘VecScatterEnd’ en rejillas estructuradas, salvo por una salvedad, que en este caso no se necesitan copiar los valores locales al proceso, sólo los valores ghost, ya que los valores del vector local comparten el mismo array que los valores del vector global.

87

Page 89: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.5. MATRICES.

3.5.1. INTRODUCCIÓN.

Una vez visto el módulo de vectores, primer paquete de Álgebra Lineal proporcionado por PETSc y fundamental para entender los posteriores paquetes, pasemos a ver el módulo de matrices, segundo paquete de Álgebra Lineal. Las matrices son los objetos fundamentales que almacenan operadores lineales, como es el caso de los Jacobianos. A lo largo de esta sección, veremos cómo trata PETSc las matrices y qué posibilidades ofrece. La forma de gestionar los objetos de tipo matriz es la misma que en el caso de vectores: primero se crea una matriz, se insertan valores, se utiliza para varias operaciones de computación y, finalmente, se destruye. En este sentido, es útil pensar en matrices como conjunto de vectores, al menos en un principio, para así abordar el enrevesado tema de la paralelización.

PETSc proporciona alrededor de unos cincuenta formatos de matrices distintas, tanto para funcionamiento en sistemas uniprocesador como en paralelo. Sin embargo, los dos formatos principales son la matriz densa y la matriz de fila dispersa comprimida (compressed sparse row), también llamada matriz AIJ. Está última es la que más fomenta PETSc a la hora de realizar programas de aplicación, y así lo demuestra al realizar más rutinas que trabajan con este tipo de matrices que con otras. Una gran ventaja que proporciona PETSc, es que el código de las aplicaciones no depende del tipo de matrices utilizado.

3.5.2. CREACIÓN DE MATRICES.

Veamos ahora, de forma general sin particularizar el tipo de matriz empleado, las operaciones básicas para crear, ensamblar e insertar valores en una matriz genérica. Para tal fin, PETSc facilita una rutina denominada ‘MatCreate’, la cual crea un objeto de tipo matriz. Esta matriz será secuencial si el programa se ejecuta en un sistema uniprocesador o paralela si se ejecuta en un sistema multiproceso. El tipo de matriz se especifica en tiempo de ejecución a través de la ya comentada base de datos que mantiene el programa de aplicación con la rutina ‘MatSetFromOptions’, o lo especifica el propio programa a través de la rutina ‘MatSetType’. Los tipos de matrices disponibles más comunes son:

Matriz dispersa (AIJ): MPIAIJ (paralela), SEQAIJ (secuencial). Matriz dispersa en bloque: MPIBAIJ, SEQBAIJ. Matriz dispersa en bloque simétrica: MPISBAIJ, SEQSBAIJ. Matriz diagonal en bloque: MPIBDIAG, SEQBDIAG. Matriz densa: MPIDENSE, SEQDENSE. Matriz libre. Etcétera.

La razón de que haya tantos tipos de matrices distintos, se debe a que ninguna estructura

de datos general es apropiada para todos los tipos de problemas. Comentar que los formatos de matrices en bloque y diagonales proporcionan beneficios de rendimiento significantes.

Además, aparte de la gran selección de formatos de matrices que proporciona PETSc, decir también que es relativamente fácil extender la librería añadiendo nuevas estructuras de datos (aunque esto aquí no lo veremos).

En el caso de matrices paralelas, al igual que ocurría con vectores, cada proceso aporta su parte de la nueva matriz MPI que se creará. PETSc particiona las matrices por filas no por

88

Page 90: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

columnas, esto es, cada proceso aporta una submatriz de un número cualquier de filas y de N columnas. Todos los procesos tienen las mismas columnas (partición por filas). Esto se aclara en la figura 30. La suma de las filas de cada proceso dará lugar a la dimensión (primera) global de la matriz total. La segunda dimensión es N. La descomposición en filas consecutivas a través de los procesos, en el caso de matrices dispersas (AIJ), es un procedimiento sencillo, y resulta más fácil trabajar con otros códigos de otras aplicaciones.

Supongamos por un momento, que no queremos descomponer nuestra matriz en conjunto de filas, sino en otro tipo de conjunto, por ejemplo, por grupos de columnas. Por otro lado, sabemos que PETSc siempre descompone en conjunto de filas. La pregunta es cómo hacer lo primero teniendo en cuenta lo segundo. Y la respuesta se puede obtener fácilmente si recordamos el paquete de objetos AO que proporciona PETSc. Estos objetos se encargan de mapear entre la ordenación de aplicación y la de PETSc. Si ordenamos los índices de la matriz de nuestra aplicación por columnas, y mapeamos a los índices que utiliza PETSc (ordenados por filas), tendremos resuelto el problema. Todo esto está muy bien si lo que buscamos es “dividir” nuestra matriz por columnas (no es dividir, es simplemente una vista de la matriz, como si estuviera descompuesta por columnas), porque así lo requiera nuestro problema. Sin embargo, esto presenta claros problemas de eficiencia, debido a que PETSc distribuye a través de los procesos los grupos de filas y, si ahora ordenamos por columnas, cada columna estará distribuida a través de todos los procesos, no son locales a los mismos. De modo que, al realizar cualquier cálculo local con una columna, sería necesario realizar varias operaciones de comunicación desde todos los procesos al proceso local para obtener la columna completa. Esto disminuye en gran medida el rendimiento, por eso no es aconsejable en absoluto.

Con la llamada a ‘MatSetSizes’,

se especifican las dimensiones globales y locales de la matriz, esto es, cada proceso será poseedor de una parte de la matriz global, y cada parte de cada proceso tendrá sus propias dimensiones, cuyas sumas correspondientes serán las dimensiones de la matriz global. En general, el usuario especifica las dimensiones locales o las globales, no ambas. Esto hace que PETSc sea la que decida las dimensiones no especificadas por el usuario, de

Figura 30. Contribución de 3 procesos para modo que sea la librería la que formar una matriz paralela. controle el alojamiento de espacio

en memoria para la matriz. Esto se verá posteriormente con más detalle. ‘MatGetOwnershipRange’ se encarga de devolver las filas que pertenecen al proceso que llama a dicha rutina.

Una vez creada la matriz, tiene lugar la fase de inserción de valores, la cual se lleva a cabo mediante la rutina ‘MatSetValues’, accediendo a las posiciones de la matriz mediante una indexación global. Esta rutina inserta (INSERT_VALUES) o añade (ADD_VALUES) en la matriz un bloque de valores de dimensiones m x n. Ya se apuntó en su momento, que PETSc estaba escrita en C. Esto hace que, aunque escribamos código en Fortran, los índices para acceder a las filas y a las columnas de la matriz global empiezan por cero, que es la convención que sigue el lenguaje C (en Fortran empiezan por uno). Por otro lado, a la rutina indicada, se le pasa un array bidimensional que contiene los valores a insertar. Dicha rutina

89

Page 91: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

trata el array como si los valores estuvieran almacenados por filas (convención de C, opuesta al almacenamiento por columnas que utiliza Fortran); esto es, al insertar un valor en la posición (i,j), el elemento a obtener del array se encontrará en la posición [i * n + j], siendo ‘n’ el número de columnas del bloque de valores a insertar. No obstante, es posible indicarle a la rutina que los valores del array están almacenados por columnas a través de la rutina ‘MatSetOption’. Además, con esta rutina se puede especificar también si la matriz es simétrica, hermítica, etc.

Hay algunos formatos de matriz que almacenan los valores por bloques. En estos casos, es más eficiente llamar a la rutina ‘MatSetValuesBlocked’ que a ‘MatSetValues’. Esto último da lugar a la siguiente afirmación: una matriz es definida por su interfaz (las operaciones que se pueden realizar sobre ella), no por su estructura de datos.

Si nos acordamos de la fase que venía a continuación al trabajar con vectores, veremos

que es la misma que viene ahora al trabajar con matrices: fase de ensamblado. Esta etapa es necesaria para colocar en sus lugares correspondientes los valores no locales a los procesos, ya que estos últimos pueden insertar valores en cualquier posición de la matriz global (aunque, por razones de eficiencia, se aconseja que cada proceso genere valores para las posiciones locales al mismo). Las rutinas de ensamblado pertinentes son ‘MatAssemblyBegin’ y ‘MatAssemblyEnd’. De nuevo, es posible realizar operaciones de computación en medio de las dos llamadas a las rutinas anteriores, de modo que podamos aumentar el rendimiento de nuestra aplicación. Como en el caso de vectores, no se pueden mezclar fases de inserción y adición de valores sin intercalar rutinas de ensamblado. Ante este escenario, PETSc permite en la fase de ensamblado final, pasar un argumento a las rutinas anteriores denominado ‘MAT_FINAL_ASSEMBLY’ y, durante las fases de ensamblado intermedias, un argumento llamado ‘MAT_FLUSH_ASSEMBLY’. Con esto se evita que se realicen ciertas operaciones del ensamblado total que se realiza en la fase final.

Respecto a lo anterior, decir que para las matrices dispersas, las filas son comprimidas una vez terminada la fase de ensamblado final. A partir de este momento se pueden realizar operaciones de multiplicación, transposición, etc., con las matrices. Una operación que no es aconsejable, es insertar valores una vez terminada la fase final de ensamblado, ya que esto requiere copias de valores y, posiblemente, alojamiento adicional de memoria (sólo almacenan los valores distintos de cero). No obstante, puede que nuestro problema necesite cambiar valores y, por tanto, requiera varias fases de ensamblado. En esta situación, si nuestra matriz mantiene las mismas posiciones de valores distintos de cero, es posible llamar a la rutina ‘MatSetOption’ con el valor “MAT_NO_NEW_NONZERO_LOCATIONS”. Así, se reutiliza la estructura de datos, con el fin de aumentar la eficiencia de nuestra aplicación. A esta última rutina, hay que invocarla después del ensamblado completo de la primera matriz.

En la introducción a esta sección, se expuso que los dos formatos principales de matrices

que suministra PETSc son la matriz densa y la matriz de fila dispersa comprimida (o matriz AIJ). En los dos siguientes apartados, veremos ambos tipos por separado.

3.5.2.1. MATRICES DISPERSAS. Las matrices dispersas (sparse), también llamadas matrices AIJ o CSR (fila dispersa

comprimida), son el tipo de matriz que utiliza PETSc por defecto en sus códigos de aplicación. También es el tipo de matriz que se ha utilizado en las pruebas realizadas en el proyecto presente.

90

Page 92: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

En este tipo de matrices, PETSc almacena los elementos distintos de cero por filas, junto con un array indicando las posiciones (columnas) donde se encuentran los elementos dentro de una fila, así como un array de indicadores que apunten al comienzo de cada fila de la matriz.

La técnica utilizada, cuando es posible, para almacenar las matrices AIJ, tanto secuenciales como paralelas, es la de nodos-i (nodos idénticos). Este método se basa en asignar un número de nodo a cada fila de la matriz, y se almacenan sólo aquellas filas (nodos) que sean distintas a las demás. Filas idénticas tendrán el mismo número de nodo, pero sólo uno de ellos se alojará en memoria.

NOTA: En alguna ocasión, se ha comentado ya que Fortran utiliza la convención de indexar los arrays empezando por 1, mientras que C empieza por 0, que es la opción por defecto utilizada por PETSc (ya que la librería está escrita en C).

Cada vez que se llama a una rutina PETSc, es inevitable utilizar la indexación empezando por 0. Sin embargo, hay veces que interesa utilizar paquetes externos a PETSc escritos en Fortran, y que utilizan la indexación comenzando por 1. Para tales situaciones, PETSc permite indicar por línea de comandos con la opción –mat_aij_oneindex, que las rutinas que no son de PETSc utilizan indexación basada en 1 para acceder a las matrices creadas por la librería paralela.

Al igual que en el caso de vectores, las matrices pueden ser secuenciales o paralelas.

Veamos por separado ambos tipos, de los que se pueden esperar algunas características ya vistas en el tema de vectores; sin embargo, otras peculiaridades que se verán son propias de los objetos de tipo matriz.

MATRICES AIJ SECUENCIALES. La rutina encargada de crear una matriz AIJ secuencial es ‘MatCreateSeqAIJ’. Esta

rutina equivale a llamar a las rutinas ‘MatCreate’, ‘MatSetFromOptions’ (o ‘MatSetType’) y ‘MatSetSizes’ en conjunto y en este orden. A esta rutina se le puede especificar el número de elementos distintos de cero en cada fila de la matriz. Esto es una forma de realizar el prealojamiento de espacio en memoria para la matriz. PETSc también permite no especificar este argumento que indica los elementos no ceros, lo que implica que sea la librería la que realice el prealojamiento de espacio. Sin embargo, realizar prealojamiento dinámico de memoria, disminuye enormemente la eficiencia del programa, de modo que ha de ser el usuario quien realice semejante operación en grandes problemas de computación.

Como se acaba de comentar, PETSc permite especificar los elementos distintos de cero de la matriz. Para tal fin, la librería permite indicar los elementos no ceros para todas las filas (si todas tienen prácticamente el mismo número de no ceros) mediante un escalar o, por el contrario, pasar a la rutina un array de dimensión igual al número de filas de la matriz, que contenga los elementos no ceros para cada fila, aproximadamente. En ambos casos, cuanto más exactos seamos en la aproximación, menor número de prealojamientos dinámicos tendrá que hacer la librería y, en consecuencia, mayor rendimiento tendrá nuestra aplicación.

Si a priori no se conocen los elementos distintos de cero de la matriz, es usual realizar un pequeño trozo de código antes del prealojamiento de espacio de la matriz, para computar el número de elementos no cero. Esto requerirá una sobrecarga adicional, pero ni mucho menos

91

Page 93: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

comparable a la necesaria en el caso de no realizar el prealojamiento por parte del usuario, dejando a la librería que sea la que lo realice de forma dinámica. Esta es la forma más eficiente de trabajar en aquellos casos en que nos dan una matriz, por ejemplo la correspondiente a una estructura, y hemos de realizar el prealojamiento de espacio en memoria para nuestra aplicación.

Una vez creada la matriz y realizado el prealojamiento de memoria (ambas fases se

realizan mediante la misma subrutina), se insertan valores en las correspondientes posiciones (estos valores pueden ser generados por nuestra aplicación o recogidos de entidades externas, como puede ser un fichero). Tras la inserción de valores, se lleva a cabo la fase de ensamblado, explicada en la introducción del apartado de matrices.

MATRICES AIJ PARALELAS.

‘MatCreateMPIAIJ’ es la rutina proporcionada por PETSc para crear una matriz

dispersa paralela. En este momento se requiere un poco de atención adicional, pues siempre los temas de “objetos paralelos” son un poco más complejos de entender.

En el caso de vectores, vimos que dichos objetos eran distribuidos a través de todos los procesos que intervenían en el proceso de computación paralela, cada proceso poseía una parte del vector. El número de filas (componentes) del vector que pertenecían a un proceso las podía especificar el usuario o, por el contrario, podía ser PETSc la que las repartiera de una forma más o menos equitativa entre los procesos.

Ahora, en el caso de las matrices, ocurre algo similar, sólo que en dos dimensiones en vez de en una. Esto complica un poco el esquema (mental) que previamente ha de realizarse antes de nada. Partimos de que PETSc distribuye las matrices por filas a través de los procesos, y esto es algo inamovible. Lo que sí puede indicar el usuario es el número de filas que pertenecen a un proceso particular. Y esta es la base de todo, aunque se puede exprimir bastante.

Pensemos que, en general, uno de los fines de nuestra aplicación será obtener el resultado de una matriz por un vector (el caso de matriz por matriz es fácilmente extensible), o la solución del sistema Ax=b. En cualquier caso, aparece una multiplicación de una matriz ‘A’ por un vector ‘x’. La segunda dimensión global de la matriz ‘A’ (número de columnas) ha de ser idéntica a la dimensión global del vector ‘x’. Respecto a esto, existe un problema, que la matriz y el vector se encuentran distribuidos a través del conjunto de procesadores implicados en la aplicación paralela. En este sentido, hemos de tener en cuenta que la segunda dimensión local de la matriz (número de columnas locales) a de ser idéntica a la dimensión local del vector ‘x’ en cada proceso. Así mismo, en cada proceso, la primera dimensión local de la matriz (número de filas locales) ha de ser la misma que la dimensión local del vector solución ‘b’.

La rutina ‘MatCreateMPIAIJ’ permite que se le indiquen las dimensiones locales y

globales de la matriz a crear. PETSc da la opción de que sea la misma librería la que especifique la dimensión global o la local (no ambas). La forma de hacerlo es dando el valor ‘PETSC_DECIDE’ a los argumentos de la dimensiones en cuestión (globales o locales). La dimensión que no especifique PETSc ha de indicarla obligatoriamente el usuario (el usuario también puede especificar las dos, aunque ha de tener en cuenta la coherencia que ha de tener lugar entre las dimensiones globales y la suma de las dimensiones locales de cada proceso). Si el valor ‘PETSC_DECIDE’ no se utiliza para las dimensiones locales, el programador ha de

92

Page 94: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

tener en cuenta la discusión anterior sobre la compatibilidad de las dimensiones locales con las dimensiones de la matriz ‘A’ y de los vectores ‘x’ y ‘b’.

Un tema importante a tratar, es el asunto del prealojamiento de espacio en memoria para

la matriz paralela. El caso ahora es distinto al de matrices secuenciales. Primeramente, empecemos definiendo el concepto de submatriz diagonal de un proceso.

Sabemos que la matriz se distribuye por grupos de filas a través de los procesos que intervendrán en la ejecución de la aplicación paralela. El número de filas que pertenecen a un proceso, es la dimensión principal de la submatriz local; de modo que la matriz global es la unión de las submatrices almacenadas en cada proceso (todas las submatrices tendrán, lógicamente, el mismo número de columnas, pues la matriz se distribuye por filas completas). En este supuesto, la submatriz diagonal de un proceso es la correspondiente a la parte de la submatriz total, almacenada en dicho proceso, que es cuadrada (misma dimensión principal y secundaria) y, además, dicho bloque se encuentra en la diagonal de la matriz global. Para aclarar todo esto se presenta la figura 31.

Figura 31. Distribución de una matriz paralela a lo largo de los procesos en grupos de filas. Concepto de

submatriz diagonal y offset en cada proceso.

La submatriz offset es la parte de la submatriz local, perteneciente a un proceso, que no pertenece a la submatriz diagonal. Si existen varios bloques offset, la submatriz offset total es la unión de estos bloques. La figura 31 es, de nuevo, bastante aclaratoria.

Una vez definido lo que es la submatriz diagonal y offset de un proceso, podemos abordar

el asunto del prealojamiento de memoria para matrices AIJ paralelas. La forma de realizar el prealojamiento es indicando a la rutina ‘MatCreateMPIAIJ’ el número de elementos distintos de cero de la submatriz diagonal y de la submatriz offset, por separado. Al igual que en el caso de matrices AIJ secuenciales, ahora también se le puede indicar a la rutina en cuestión, el número de no ceros para todas las filas (mediante un escalar) o el número de no ceros para cada fila (mediante un array). La única diferencia es que aquí habrá dos escalares o dos arrays, uno para la submatriz diagonal y otro para la submatriz offset, ambas locales al proceso. Si se especifica el número de no ceros de alguna submatriz mediante un array, se

93

Page 95: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

ignora el correspondiente escalar destinado al mismo fin. Por ejemplo, si indicamos el número de no ceros de la submatriz diagonal local al proceso mediante un array, el argumento de tipo escalar destinado a proporcionar semejante información se ignora.

Al igual que en el caso secuencial, cuanto más precisos seamos a la hora de determinar el número de elementos distintos de cero de las submatrices, más eficiente será el funcionamiento de nuestra aplicación, puesto que la PETSc tendrá que realizar menor alojamiento de memoria dinámico (en tiempo de ejecución). Por esta razón, de nuevo, es aconsejable realizar un pequeño código anterior al prealojamiento de memoria para computar los no ceros de la matriz de nuestra aplicación, matriz que puede estar almacenada en un fichero. Si, por el contrario, dicha matriz fuera generada por nuestro código, por ejemplo de forma aleatoria, sabríamos, aproximadamente, el número de no ceros si sabemos el número de veces que insertaremos elementos en nuestra matriz, siempre y cuando seamos conscientes de la estructura que deseamos.

Es importante hacer notar que la separación de las submatrices locales diagonal y offset de

un proceso se realiza solamente para la fase de prealojamiento de memoria. Una vez terminada esta etapa, ya no es necesario hacer referencia a ambas submatrices.

Una vez concluida la fase de creación y prealojamiento de memoria de la matriz paralela, le sigue el paso de inserción de valores. A diferencia del caso secuencial, ahora la matriz se encuentra distribuida por submatrices a lo largo de los procesos que ejecutan la aplicación paralela. De este modo, cualquier proceso puede insertar valores en cualquier posición de la matriz global, sólo que es más eficiente que cada uno lo haga exclusivamente, si es posible, para los elementos de la matriz que son locales al proceso. Para lograr esto, cada proceso ha de saber el rango de filas de la matriz global que le pertenece, que se obtiene mediante la rutina ‘MatGetOwnershipRange’. De esta manera, en la posterior fase de ensamblado (llamadas a las rutinas ‘MatAssemblyBegin’ y ‘MatAssemblyEnd’), el número de valores no locales a colocar dentro de la matriz global será mínimo, lo que disminuye las comunicaciones, y que se traduce en un mayor rendimiento de la aplicación paralela.

Decir también, que tanto en el caso de matrices secuenciales como paralelas, si en el

momento de ejecutar el programa, indicamos por línea de comandos la opción “-info”, al realizar el ensamblado se imprimirá por pantalla la información correspondiente al prealojamiento de memoria para la matriz en cuestión.

3.5.2.2. MATRICES DENSAS.

Es poco lo que hay que decir sobre matrices densas. La librería PETSc suministra dos rutinas, ‘MatCreateSeqDense’ y ‘MatCreateMPIDense’, las cuales crean, respectivamente, una matriz densa y otra paralela. Ambas rutinas permiten a cada proceso llamante almacenar las matrices en un array local por columnas, como si estuviéramos utilizando arrays de dos dimensiones de Fortran. En adición, PETSc permite al usuario de Fortran, pasar a las rutinas un array donde se ubicará la matriz. Si dicho argumento presenta el valor “PETSC_NULL”, será PETSc la encargada de alojar el correspondiente espacio para la matriz.

Apuntar que PETSc proporciona más rutinas para la gestión de matrices dispersas que

para matrices densas. Tal es así, que PETSc no proporciona solvers directos para matrices densas; más bien, el objetivo de PETSc se centra en los solvers iterativos para matrices dispersas.

94

Page 96: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.5.3. OPERACIONES CON MATRICES.

De la misma manera que se hizo en el apartado de vectores, presentaremos a continuación una tabla (tabla 7) con las principales operaciones que se pueden realizar con matrices.

RUTINA OPERACIÓN REALIZADAMatAXPY Y = Y + a · X MatMult y = A · x MatMultAdd z = y + A · x MatMultTranspose y = AT · x MatMultTransposeAdd z = y + AT · x MatNorm r = ||A||type

MatDiagonalScale A = diag(l) · A · diag(r)MatScale A = a · A MatConvert B = A MatCopy B = A MatGetDiagonal x = diag(A) MatTranspose B = AT

MatZeroEntries A = 0 MatShift Y = Y + a · I MatMatMult C = A · B

Tabla 7. Operaciones básicas con matrices.

Las interfaces para estas rutinas se encuentran en el manual HTML de PETSc [26]. Comentaremos algunas de las rutinas que se presentan en la tabla y que pueden llevar a

confusión. La rutina ‘MatZeroEntries’, pone a cero los elementos de la matriz en cuestión, pero

mantiene la estructura de los datos de la matriz original, en particular se conservan los datos que indican las posiciones de los elementos distintos de cero. Es interesante la utilización de la ya comentada rutina ‘MatSetOption’ con el valor “MAT_NO_NEW_NONZERO_LOCATIONS”, que previene el alojamiento adicional de memoria, y descarta aquellos valores que se intentan insertar en posiciones no reservadas antes del ensamblado de la matriz original.

Para el caso de vectores, disponíamos de la rutina ‘VecGetArray’ para acceder de forma

directa a los elementos de un vector, y la rutina ‘VecRestoreArray’ era la encargada de devolver el array a la memoria. Ahora, para el caso de matrices, por medio de ‘MatGetRow’ se puede acceder directamente a las componentes de una matriz, aunque solamente es posible acceder a las filas locales al proceso llamante. Por medio de esta rutina, sólo se puede visualizar los valores de las componentes de la matriz, no modificarlos. Para lograrlo, sería necesario llamar a la rutina ‘MatSetValues’. Una vez examinado los valores, es conveniente llamar a ‘MatRestoreRow’, de modo que se libere el espacio adicional utilizado tras la llamada a ‘MaGetRow’.

Al igual que ocurría con vectores, en el caso de matrices, la rutina ‘MatView’ muestra el

contenido de un objeto de tipo matriz.

95

Page 97: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.6. KSP: SOLVERS DE ECUACIONES LINEALES.

3.6.1. INTRODUCCIÓN.

El nombre de KSP, viene del conjunto: método del subespacio de Krylov (KS) más un precondicionador (P).

El objeto KSP es la pieza principal de todo el entramado de objetos que proporciona PETSc. Este objeto, ofrece acceso uniforme y eficiente a todos los paquetes de solvers de sistemas lineales. Los tipos de solvers lineales pueden ser paralelos o secuenciales y directos o iterativos, utilizando las mismas secuencias de llamadas a la hora de invocarlos.

Los precondicionadores son otro tipo de objeto que suministra PETSc (objetos PC), y su uso es concluyente para resolver un sistema.

Ambos tipos de objetos, se proporcionan como módulos independientes dentro del árbol

de directorios de la librería PETSc, incluso pueden utilizarse individualmente. Sin embargo, es más útil emplear las interfaces proporcionadas por los objetos KSP para hacer uso de los precondicionadores, ya que, normalmente, los precondicionadores se utilizan en conjunto con los solvers de sistemas.

3.6.2. OBJETOS KSP.

Antes de resolver un sistema lineal mediante un objeto KSP, es necesario crear el contexto del solver lineal mediante la rutina ‘KSPCreate’, la cual devuelve el objeto KSP encargado de resolver el sistema de nuestra aplicación particular.

Tras crear el contexto del solver, es necesario especificar al objeto KSP la matriz del sistema a resolver, lo cual se logra mediante la rutina ‘KSPSetOperators’. Mediante esta rutina, también es posible especificar la matriz que actuará como precondicionador.

Con un mismo contexto KSP, es posible resolver varios sistemas diferentes, pero estos

han de tener las mismas dimensiones y ser resueltos con el mismo método de precondicionamiento. Sin embargo, hay que tener en cuenta que las matrices que representan estos sistemas pueden tener una estructura diferente de elementos distintos de cero. Esto se representa mediante un parámetro que se le pasa como argumento a la rutina ‘KSPSetOperators’.

Existen diversos métodos para resolver sistemas lineales. De todos ellos, PETSc

proporciona soporte para los mostrados en la tabla 8. Mediante la rutina ‘KSPSetType’, se puede especificar la técnica utilizada para resolver un sistema concreto. Si no se utiliza esta rutina, el método utilizado por defecto es el de GMRES (Residuo Mínimo Generalizado), que ha sido el empleado en la fase de pruebas del proyecto.

Respecto a esto, decir que la rutina ‘KSPGMRESSetRestart’ se encarga de ajustar el parámetro de reinicio del algoritmo GMRES. Para que surta efecto esta rutina, es necesario llamarla tras ‘KSPSetFromOptions’, que se verá con posterioridad.

En la introducción a esta sección se comentó que, aunque se podía acceder a los objetos

precondicionadores a través de las interfaces de los objetos KSP, también era posible utilizar rutinas que trabajasen directamente con estos objetos. Por ejemplo, tras obtener el objeto precondicionador utilizado con un objeto KSP mediante ‘KSPGetPC’, es posible indicar el tipo de precondicionador a utilizar mediante la rutina ‘PCSetType’. En la tabla 9, se indican

96

Page 98: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

los precondicionadores soportados por PETSc. Se aconseja consultar el manual HTML de PETSc antes de utilizar algún precondicionador concreto. También, en el apartado 4.4 del manual de usuario de PETSc se habla bastante del tema de los precondicionadores.

Método KSP Alias Richardson KSPRICHARDSON Chebychev KSPCHEBYSHEV

Gradiente Conjugado KSPCG Gradiente BiConjugado KSPBICG

Residuo Mínimo Generalizado (GMRES) KSPGMRES BiCGSTAB KSPBCGS

Gradiente Conjugado al cuadrado KSPCGS Residuo Cuasi-mínimo Libre-Transpuesto (1) KSPTFQMR Residuo Cuasi-mínimo Libre-Transpuesto (2) KSPTCQMR

Residuo Conjugado KSPCR Método de los mínimos cuadrados KSPLSQR

Shell para métodos no KSP. KSPPREONLY

Tabla 8. Métodos de resolución de sistemas lineales.

Precondicionador Alias Jacobi PCJACOBI

Jacobi en bloque PCBJACOBI SOR (y SSOR) PCSOR

Cholesky incompleto PCICC LU incompleto PCILU Schwarz aditivo PCASM

Solver lineal PCKSP

Combinación de precondicionadores PSCOMPOSITE

LU PCLU Cholesky PCCholesky

Sin precondicionador PCNONE Shell para precondicionador de usuario PCSHELL

Tabla 9. Precondicionadores soportados por PETSc.

Además de definir el contexto KSP y el precondicionador a utilizar, es posible indicar

diversas características del objeto KSP. Concretamente, por medio de la rutina ‘KSPSetTolerances’, muy utilizada en las pruebas, es posible especificar las tolerancias requeridas al resolver el sistema.

Todas las opciones vistas hasta ahora, tales como el tipo de método de resolución a

utilizar, el precondicionador o las tolerancias, se pueden indicar a través de línea de comandos, mediante las opciones de la base de datos que mantiene la aplicación. La rutina ‘KSPSetFromOptions’ es la encargada de recoger estas opciones y aplicarlas durante la

97

Page 99: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

ejecución del programa. Si se invoca a esta rutina, se sobrescribirán los argumentos especificados por las rutinas anteriores que hagan referencia a los mismos parámetros que los indicados por línea de comandos. Por ejemplo, es posible indicar el tipo de objeto KSP y precondicionador mediante las opciones de línea de comandos ‘-ksp_type’ y ‘pc_type’, respectivamente, en vez de emplear las rutinas destinadas a tal fin.

Existen muchas otras características de los objetos KSP y PC que se pueden ajustar

mediante las rutinas proporcionadas por la PETSc, tales como el tipo de ortogonalización del GMRES o como el lado de precondicionado (izquierda/derecha). En el manual HTML de PETSc se reúne un conjunto variado de rutinas, útiles para aplicaciones particulares, por esta razón se aconseja encarecidamente acudir al manual antes de implementar cualquier aplicación, ya que no todas las rutinas son aptas para todo tipo de aplicaciones, sino que cada cual tiene su función específica.

Una vez creado el contexto KSP y ajustados todos los parámetros, se puede pasar a

resolver el sistema con la rutina ‘KSPSolve’ (no hace falta decir la gran importancia de esta rutina, es la encargada de hallar el vector solución). Un poco menos importante es la rutina ‘KSPView’, que presenta un resumen de las características del solver del objeto KSP.

Tras la resolución del sistema, puede ser interesante conocer el número de iteraciones

realizadas por el solver iterativo, cosa que se puede conseguir a través de ‘KSPGetIterationNumber’.

Por último, decir que al término del programa, es importante liberar el objeto KSP creado

(como se hacer con cualquier otro tipo de objeto) mediante la llamada a ‘KSPDestroy’. Uno de los temas más importantes es el asunto de la convergencia del solver utilizado

para resolver un problema. A través de ciertas opciones, PETSc ofrece información sobre las iteraciones que va realizando el solver.

Existen tres formas de monitorización del solver:

1. Con las opciones de línea de comandos ‘-ksp_monitor’ y ‘-ksp_xmonitor’, es posible imprimir y representar, respectivamente, la norma residual precondicionada.

2. De la misma forma, mediante ‘-ksp_truemonitor’ y ‘-ksp_xtruemonitor’, es

posible visualizar la norma residual verdadera (la correspondiente a || b - Ax ||).

3. El usuario también tiene la opción de crear sus propias rutinas de monitorización (si es necesario, se aconseja indagar en el manual HTML de PETSc comenzando por la rutina ‘KSPSetMonitor’).

Con esto último, concluye la parte de solvers lineales a través de objetos KSP, los objetos más importantes y cuidados de PETSc. La siguiente sección, es un breve resumen de algunos de los paquetes restantes de la librería paralela que venimos estudiando.

98

Page 100: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3.7. OTROS MÓDULOS DE PETSC.

3.7.1. INTRODUCCIÓN.

En esta parte, haremos una pequeña introducción al soporte ofrecido por PETSc de solvers no lineales, correspondiente al paquete SNES, y solvers de ecuaciones diferenciales ordinarias escalables, del paquete TS.

El paquete TS de PETSc, proporciona un esqueleto para la solución escalable de

ecuaciones diferenciales ordinarias (provenientes de discretizaciones de ecuaciones en derivadas parciales dependientes del tiempo) y de problemas en régimen permanente.

El capítulo 6 del manual de usuario de PETSc presenta una descripción detalla de los objetos TS.

A continuación, realizaremos un pequeño acercamiento al paquete SNES de PETSc.

3.7.2. SOLVERS NO LINEALES.

El paquete SNES contiene un gran conjunto de rutinas numéricas con estructura de datos neutral, capaz de resolver problemas no lineales de grandes dimensiones.

La gran ventaja de SNES, es que da la posibilidad al usuario de personalizar los solvers no lineales en función de la aplicación a tratar.

Para problemas provenientes de ecuaciones en derivadas parciales (PDE’s), SNES

proporciona la solución general para F(u) = 0, donde F es una función tal que F: Rn → Rn. El usuario se encarga de proporcionar el código para evaluar F(u) y, opcionalmente, el código para evaluar el Jacobiano de F(u). También es posible utilizar aproximación por diferencia finita dispersa o diferenciación automática.

El núcleo del paquete SNES lo constituyen las técnicas basadas en el método de Newton, incluyendo:

- Procedimientos de búsqueda de línea. - Aproximaciones por región de verdad. - Continuación pseudo-transitoria. - Métodos de matrices libres.

SNES permite personalizar todas las fases del proceso de solución, esto es, el usuario

puede cambiar cualquier aspecto del proceso de solución en tiempo de ejecución. Las rutinas destacables de este paquete se presentan a continuación:

SNESCreate: crea el contexto para el solver no lineal. SNESSetFunction: por medio de esta rutina, el usuario especifica F(u). SNESSetJacobian: igual a la anterior, pero para el Jacobiano de F(u). SNESSetFromOptions: recoge las opciones de la base de datos. SNESSolve: resuelve el sistema no lineal. SNESView: visualización de las opciones en tiempo de ejecución del solver. SNESDestroy: destrucción del objeto SNES creado con anterioridad.

99

Page 101: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Habría que decir muchas más cosas sobre los objetos SNES; sin embargo, en la fase de

pruebas del proyecto, nos hemos limitado a resolver sistemas lineales, por lo que no hemos utilizado este tipo de objetos.

100

Page 102: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

444... PPRRUUEEBBAASS..

101

Page 103: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

4.1. INTRODUCCIÓN.

En este capítulo se presentan dos secciones claramente diferenciadas. La primera es una descripción breve de algunos de los ficheros de ejemplo de vectores y matrices, incluidos con la instalación de PETSc. Este apartado se ha realizado porque existen ciertas rutinas de interés, que vistas en un contexto de código de aplicación cobran bastante sentido.

La segunda parte del capítulo, explica lo que se ha realizado en la fase del proyecto de creación de aplicaciones de prueba. También se detalla todo brevemente, ya que los códigos de las aplicaciones, que se incluyen en el Anexo II, se encuentran suficientemente comentados.

102

Page 104: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

4.2. EJEMPLOS DE LA PETSC.

4.2.1. VECTORES.

Sólo describiremos aquellos ejemplos escritos en C, los ejemplos en Fortran son análogos.

Simplemente, iremos nombrando los ficheros y describiendo un poco las características principales. Todos los ficheros a los que se hace referencia se encuentran dentro del directorio de vectores.

ex1.c: utilización de la rutina ‘PetscRealPart’, para números complejos.

ex1e.c: mismo ejemplo al anterior, sólo que no se utiliza la macro de chequeo

‘CHKERRQ’, sino que se utiliza un método basado en los caracteres ‘_’ y ‘__’ que se añaden a los nombres de las rutinas para realizar la comprobación de errores.

ex2.c: empleo de la variable ‘rank’ (número de proceso), obtenida mediante la rutina

‘MPI_Comm_rank’. Creación de un vector paralelo.

ex3.c: diseño de un vector paralelo, y utilización de objetos visualizadores, para ver el contenido de las componentes de un vector.

ex5.c: registro de eventos y rutinas ‘PetscSleep’ (para que un proceso aguarde un

cierto tiempo) y ‘PetscBarrier’ (equivalente a la rutina ‘MPI_Barrier’, de MPI).

ex6.c: E/S a un fichero y creación de un vector a partir del fichero creado.

ex7.c: llamada a rutina de cálculo escrita en Fortran desde C. En el ejemplo ‘ex7f.F’ es al contrario.

ex8.c: utilización de la ordenación local para insertar valores dentro de un vector

paralelo.

ex9.c: manejo de las rutinas ‘PetscSynchronizedPrintf’ y ‘PetscSynchronizedFlush’. Ejemplo de utilización de las rutinas que trabajan con valores ghost.

ex11.c: uso de las rutinas ‘VecStrideNorm’ y ‘VecSetBlockSize’

ex12.c: ejemplo de rutinas que realizan operaciones de dispersión y recolección de

valores de vectores.

ex13.c: rutinas ‘PetscBarrier’ y ‘PetscObjetcPublish’.

ex15.c: registro de eventos e interacción de PETSc con el programa Mathematica.

ex16.c: dispersiones y recolecciones de valores de vectores que están divididos en bloques.

ex17.c: polimorfismo (para C++).

103

Page 105: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

4.2.2. MATRICES.

Seguiremos el mismo esquema que para el caso de vectores. Los ficheros a los que se hace referencia se encuentran almacenados en el subdirectorio de matrices de los paquetes de PETSc.

ex1.c: lectura de una matriz y un vector desde un fichero y posterior reordenación.

La opción “-log_summary” especificada en línea de comandos al ejecutar una aplicación paralela, da información sobre los tiempos de ejecución de dicha aplicación. Si antes de realizar la carga en memoria de un sistema grande, realizamos una precarga de un sistema menor, PETSc es capaz de calcular con más precisión los tiempos mencionados. Para realizar la precarga y carga de ambos sistemas, PETSc proporciona ciertas rutinas, algunas de las cuales se presentan en este ejemplo.

ex2.c: manejo de matrices densas secuenciales y multiplicación de una matriz por un

vector de forma directa y por subbloques.

ex4.c: lectura de una matriz ‘U’ y una matriz ‘V’ desde un fichero y posterior realización de la operación y = V · UT · x.

ex5.c: en este ejemplo, cada proceso abre su propio fichero y lee su porción de una

matriz paralela.

ex6.c: polimorfismo (para C++).

104

Page 106: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

4.3. APLICACIONES CREADAS PARA EL PROYECTO.

A la hora de escribir código de aplicaciones paralelas, se ha empleado Fortran como lenguaje principal, debido a requerimientos del proyecto.

No obstante, se ha realizado una aplicación en C, cuyo código fuente se encuentra en el fichero ‘prueba1.c’. Esta aplicación crea un vector ‘x’ y una matriz ‘A’. Posteriormente se insertan valores en ambos objetos mediante ordenación local y global, respectivamente. Por último se realiza el producto matriz por vector y se presenta el vector resultado por pantalla. Es un ejemplo de manejo de rutinas sencillas de PETSc, aunque es bastante didáctico para observar algunas peculiaridades de la programación orientada a objetos que utiliza la librería paralela.

El resto de pruebas se han realizado en Fortran. Haremos una breve descripción de cada

una de las aplicaciones paralelas creadas. Realmente, cada aplicación se apoya en los conocimientos de la creada con anterioridad, de forma que se va aumentando la complejidad.

Todas las pruebas aquí presentadas se encuentran en el directorio “unidadCD:/Pruebas/pruebas-petsc”.

prueba1.F90: Esta aplicación realiza lo mismo que ‘prueba1.c’, simplemente se ha

mapeado de C a Fortran. La gran ventaja de PETSc es que la interfaz de las rutinas que proporciona es prácticamente la misma para C que para Fortran.

struct.F90: El fin de esta aplicación es crear una matriz con una estructura

determinada (bloques bien definidos). Una vez creada la matriz e insertados los valores en las posiciones requeridas (los valores son generados aleatoriamente), se multiplica por un vector cuyas componentes valen la unidad. Por último, se presenta el resultado de la operación por pantalla.

KSPstruct.F90: La matriz que se utiliza es la misma que la de la prueba anterior,

sólo que ahora no se realiza una simple multiplicación matriz por vector. En vez de eso, primero se realiza el producto “A · x1”, obteniendo el vector ‘b’. Posteriormente, se resuelve el sistema “A · x2 = b” mediante un solver lineal dado por un objeto KSP, y esto da lugar al vector solución ‘x2’. Finalmente, se calcula la norma del vector de error (diferencia entre x1 y x2) para comprobar la fidelidad del resultado dado por el solver de KSP.

KSPstruct_Luis.F90: Esta aplicación es exactamente igual a la anterior, solo que

los bloques de la matriz no contienen valores aleatorios, sino que son determinados a conciencia, de manera que se observe el comportamiento de solver lineal ante valores aleatorios y deterministas. Tanto esta aplicación como la precedente, presentan una norma 2 del vector de error del orden de 10-7, que es un buen resultado.

diag_dom.F90: El código sigue el mismo esquema que el anterior, con la diferencia

de que ahora la matriz del sistema es llena (todos los elementos son números aleatorios) y los elementos de la diagonal están multiplicados por N2, la dimensión de la matriz al cuadrado (diagonal dominante).

diag_domN.F90: En esta aplicación, la matriz se dispone en forma de tablero de

ajedrez, es decir, por bloques de ceros y de elementos llenos alternados unos con

105

Page 107: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

otros. Los bloques llenos contienen números aleatorios, y los bloques de la diagonal están multiplicados por N, la dimensión de la matriz. Es interesante ver la parte del código donde se realiza el prealojamiento de espacio en memoria para la matriz a la hora de crearla.

Dentro del subdirectorio “pruebas_fortran/outs” se encuentran algunos resultados de la ejecución de las aplicaciones ‘diag_dom’ y ‘diag_domN’. En concreto, el fichero “DIAG_DOM_30e3.txt” es el resultado de la ejecución de ‘diag_dom’ para una matriz de 30.000 x 30.000 elementos. La ejecución de esta aplicación da una norma del vector de error de 0.6608E-06, con sólo dos iteraciones del algoritmo de GMRES, que es un resultado esperado sabiendo que la diagonal de la matriz está multiplicada por N2 (matriz con diagonal muy dominante, el GMRES funciona bien para este tipo de sistemas).

El resto de archivos corresponden a ejecuciones de la aplicación ‘diag_domN’, sistema del

tablero de ajedrez. Sin embargo, en este caso, los resultados no son tan buenos, debido a que el GMRES necesita que la matriz tenga unas ciertas características, como, por ejemplo, la disposición de los bloques de ceros en la matriz.

Veamos algunas de las conclusiones de estas pruebas. La tabla 10 refleja los resultados para la ejecución de la aplicación ‘diag_domN’, para una matriz de 10.000 x 10.000 elementos, sin precondicionador.

Num. Máx Iteraciones Num.vectores subespacio Krylov Tiempo ejecución Norma 2 vector error10.000 30 8.6 min. 11,0710.000 60 8.65 min. 11,0410.000 100 8.61 min. 11,0020.000 30 17.33 min. 11,0720.000 60 17.08 min. 11,04

Tabla 10. Matriz 10.000 x 10.000 (sin Precondicionador).

Se puede observar, que con 60 vectores para el parámetro de ‘restart’ del GMRES, al aumentar el número de iteraciones, la norma del vector de error (norma 2) no varía. En este caso, aumentar el número de vectores para la base utilizada en el proceso de ortogonalización no influye. Doblar el número de iteraciones dobla el tiempo de resolución y no reduce el error. Es importante estudiar bien los parámetros de tolerancia absoluta, relativa y de divergencia del GMRES, para así lograr la convergencia del algoritmo.

Para el caso de una matriz de 40.000 x 40.000 elementos, la tabla 11 muestra los resultados.

Num. Máx Iteraciones Num.vectores subespacio Krylov Tiempo ejecución Norma 2 vector error

10.000 30 2.20 horas 118,3020.000 30 4.30 horas 11,30

Tabla 11. Matriz 40.000 x 40.000 (sin Precondicionador).

El número de iteraciones afecta a la norma del vector de error si la dimensión de la matriz

es grande (como es este caso). Ahora, doblar el número de iteraciones dobla el tiempo de resolución y sí reduce el error, además en un orden de magnitud.

106

Page 108: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Para el sistema con matriz en disposición de tablero de ajedrez, en ningún caso se ha llegado a la convergencia, el GMRES siempre ha agotado el número máximo de iteraciones.

No obstante, con las pruebas realizadas se demuestra el correcto funcionamiento del cluster y de las aplicaciones paralelas realizadas con la librería PETSc. El asunto de la convergencia del GMRES es un tema que habría que abordar tras realizar un estudio de la forma de operar del algoritmo, lo que llevaría a un correcto ajuste de los parámetros de tolerancia y a un diseño adecuado de la matriz del sistema a resolver.

107

Page 109: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

555... CCOONNCCLLUUSSIIOONNEESS..

108

Page 110: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Las conclusiones que se pueden deducir de la realización del proyecto son diversas, de modo que vayamos por partes.

El tema del cálculo en paralelo no es, ni mucho menos, trivial. Son muchos los conceptos

que hay que tener en cuenta antes de empezar a implementar aplicaciones paralelas. Uno de los referentes a tener en cuenta es que no es lo mismo realizar una aplicación

pensando desde el primer momento en paralelo, que intentar paralelizar una aplicación que en la actualidad funciona de forma secuencial. Es difícil no desviar el pensamiento hacia la tendencia de programar en serie (aun cuando el cerebro humano trabaja constantemente en paralelo).

Programar en paralelo, es cambiar un poco la forma de pensar. Un mismo programa se ejecuta en varias máquinas al mismo tiempo, pero cada una mantiene un dominio distinto de valores para las mismas variables. Esta característica y muchas otras comentadas a lo largo de la memoria, hacen que la programación en paralelo sea un tanto complicada. Sin embargo, con relativamente poco tiempo de aprendizaje, es posible realizar programas que realmente tienen una gran aplicación en el ámbito científico.

Es importante destacar el gran soporte que ofrece la librería MPI para las comunicaciones

entre los ordenadores del cluster. A través de unas rutinas definidas, se esconde toda la complejidad que supondría realizar de forma directa todos los códigos correspondientes a las operaciones de comunicación entre procesos.

Decir también, que los scripts proporcionados por el paquete de MPI, son una herramienta muy potente, y prácticamente hacen transparente al usuario toda la fase de ejecución de aplicaciones en paralelo.

Además, es sorprendente la perfecta cooperación entre las librerías MPI y PETSc. Son muchas las conclusiones que se derivan de la programación con la librería de cálculo

científico en paralelo PETSc. Cualquier programador experimentado en la librería PETSc, seguramente pueda decir muchas cosas sobre ella. Sin embargo, cualquier usuario que programe utilizando la librería, por poco tiempo que sea, puede deducir importantes conclusiones.

Destacar la gran ventaja de que PETSc utilice la filosofía de la programación orientada a objetos, ya que facilita en gran medida el trabajo en la fase de desarrollo de aplicaciones. Por otro lado, decir que es bastante útil que PETSc utilice los mismos nombres de interfaces a rutinas para C y para Fortran.

La creación de grandes aplicaciones con PETSc es posible con sólo unas cuantas líneas de código.

Es asombroso la facilidad de creación de vectores y matrices paralelos (y secuenciales), así como el gran número de rutinas para manejo de estos objetos.

Una cuestión que ha quedado pendiente es el asunto de la convergencia de los algoritmos utilizados por los solvers de sistemas lineales. El algoritmo GMRES ha sido el escogido para resolver los sistemas planteados en la fase de pruebas. El tema está en que no es posible tener un algoritmo para resolver todos los tipos de sistemas existentes. El GMRES tiene varios parámetros de configuración, con los que se ha jugado para intentar conseguir la convergencia y, en la mayoría de los casos, ha sido inútil. En las pruebas se llegó a crear una matriz llena de 50.000 x 50.000 elementos (no se pudo con más por falta de memoria en el cluster); sin embargo, el solver no llegó a converger. La estructura de la matriz, esto es, la forma en que se disponen los bloques llenos y vacíos, es determinante para lograr que GMRES converja.

109

Page 111: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Pese a estos problemas con la convergencia del GMRES, los resultados obtenidos en las pruebas son esperanzadores, pues es posible que con un poco de tiempo y esfuerzo se consigan resolver los sistemas citados.

110

Page 112: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

666... RREEFFEERREENNCCIIAASS WWEEBB YY BBIIBBLLIIOOGGRRÁÁFFIICCAASS..

111

Page 113: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A continuación, se presenta un listado con las referencias web y bibliográficas utilizadas en el desarrollo del proyecto. Se han agrupado por temas comunes, para facilitar la consulta.

CÁLCULO PARALELO DISTRIBUIDO:

1. Ian Foster, Carl Kesselman (1999). La Rejilla: libro azul para una nueva infraestructura informática (The Grid: Blueprint for a New Computing Infrastructure). Morgan Kaufmann Publishers. ISBN. http://www.mkp.com/grids/

2. Fran Berman, Anthony J.G. Hey, Geoffrey Fox (2003). La rejilla informática: haciendo realidad la Infraestructura Global (Grid Computing: Making The Global Infrastructure a Reality). Wiley. ISBN. http://www.grid2002.org/

3. http://www.eu-brein.com/ 4. http://www.globus.org/ 5. http://www.ibm.com/grid 6. http://eu-datagrid.web.cern.ch/eu-datagrid/ 7. http://www.gridcomputing.com/ 8. http://linux.oreillynet.com/pub/a/linux/2002/04/25/enterprise.html 9. http://www.llnl.gov/computing/tutorials/parallel_comp 10. http://www.d2ol.com/ 11. http://folding.stanford.edu/spanish/

CLUSTERS:

12. http://openmosix.sourceforge.net/ 13. http://www.beowulf.org/ 14. http://www.beowulf-underground.org/ 15. http://www.linuxhpc.org/ 16. www.myri.com

MPI y PVM:

17. http://www.mcs.anl.gov/mpi 18. http://www-unix.mcs.anl.gov/mpi/www (manual web de rutinas y comandos). 19. http://www.mpi-forum.org/docs/docs.html (estándar). 20. http://www.lam-mpi.org/tutorials/nd/ 21. http://www.netlib.org/pvm3/book/node11.html 22. http://www-unix.mcs.anl.gov/~gropp/bib/papers/2002/mpiandpvm.pdf 23. http://www.llnl.gov/LCdocs/mpi/mpi.pdf

PETSC:

24. http://www.mcs.anl.gov/petsc 25. http://www-unix.mcs.anl.gov/petsc/petsc-as 26. manual HTML integrado en el paquete de PETSc.

112

Page 114: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Descargas:

27. ftp://ftp.rediris.es/mirror/ <<< pág.ppal. (accesos directos) 28. ftp://ftp.rediris.es/mirror/cygwin/release/ << cygwin 29. ftp://ftp.mcs.anl.gov/pub/ << mpi, petsc, etc. 30. ftp://ftp.mcs.anl.gov/pub/petsc/externalpackages/ << fblaslapack, etc.

Configuración de un servidor SSH:

31. http://pigtail.net/LRP/printsrv/cygwin-sshd.html 32. http://oceanpark.com/notes/howto_ssh_keychain_public_key_authentication_forw

arding.html

113

Page 115: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

777... AANNEEXXOO II:: IIMMPPLLAANNTTAACCIIÓÓNN DDEELL SSIISSTTEEMMAA..

114

Page 116: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.1. INTRODUCCIÓN.

El fin de este apartado es presentar, de forma clara, los pasos para instalar la librería de cálculo en paralelo PETSc, en un entorno UNIX (entorno Windows con Cygwin).

Supondremos ya instalado el sistema operativo Windows, y comenzaremos por ver la instalación de Cygwin, que es un popular emulador de UNIX para Windows. Cygwin nos permitirá instalar el resto de componentes que necesitamos.

Tras disponer de Cygwin, pasaremos a la instalación de las librerías BLAS, LAPACK y MPI, con toda la configuración posterior que conlleva. Estas tres librerías son el soporte de la PETSc. Por último, desplegaremos el complejo de librerías de PETSc, que es el objetivo final de nuestro trabajo.

Antes de nada, comentar que suponemos que existe el usuario “josea” en todos los nodos del cluster, al cual nos referiremos de ahora en adelante. Si cambiase el usuario (ya no fuese “josea”), habría que modificar la variable ‘usu’ del script “paralelo.sh”, y en todos los pasos siguientes de esta guía sustituir “josea” por el nombre del nuevo usuario.

Por otro lado, el nombre de la carpeta que comparten los nodos de nuestro cluster es “/c/temp”; si se quisiera cambiar esta configuración, de nuevo solamente habría que retocar una variable del script “paralelo.sh”.

115

Page 117: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.2. INSTALACIÓN DE CYGWIN.

Para instalar Cygwin, sólo es necesario descargarse la última versión de http://www.cygwin.com/. Realmente, Cygwin consiste en:

- Una librería dll (cygwin1.dll), que actúa como una capa de emulación de API

Linux. - Y una colección de herramientas, que proporcionan una visión del sistema como si

fuera Linux. La instalación es sencilla, simplemente hay que descargarse la aplicación “setup.exe” y

seguir los pasos que aparecen en pantalla. No obstante, también se puede conseguir del CD adjunto a la memoria, en el que se encuentran todos los paquetes software que se han utilizado en la implantación del sistema completo. Concretamente, en la carpeta “unidadCD:/software/cygwin” se encuentra todo lo necesario para esta parte.

A la hora de instalar Cygwin, es importante seleccionar los paquetes que nos servirán de

utilidad para las fases de instalación de librerías, diseño, implementación y depuración de código, así como para la fase de pruebas. El proceso de implantación del sistema en su conjunto (incluyendo MPI, PETSc, servidores ssh y rsh, etc.), es el que ha ido marcando los paquetes necesarios. Para que no haya problemas de dependencia de paquetes y de necesidad de los mismos en fases posteriores a la instalación de Cygwin, se presenta a continuación una tabla (tabla 12) con los distintos paquetes a marcar en el proceso de instalación de Cygwin. Como ya se ha comentado, esta tabla es fruto de la instalación en conjunto del sistema, al ver que se han ido necesitando, se iba llamando al programa de instalación / configuración de Cygwin. Si instalamos todos los paquetes necesarios desde el principio, evitamos el llamar varias veces a la aplicación.

Notar que es importante dejar los paquetes que vienen marcados por defecto, así como ir

marcando por orden los paquetes, en función del número de fila de la tabla (esto facilita el trabajo, ya que el programa de configuración va marcando las dependencias).

CATEGORÍA PAQUETES A MARCAR EN LA INSTALACIÓN DE CYGWIN Admin Marcar Todo. Base Marcar Todo (por defecto es así).

Python Marcar Todo.

Database libdb4.2, libpq4.

Devel

binutils, cmake, expat, gcc, gcc-core, gcc-g++, gcc-g77, gcc-java, gcc-mingw-core, gcc-mingw-g++, gcc-mingw-g77, gdb, gettext, gtk2-x11, gtk2-x11-runtime, ioperm, libiconv, libncurses-devel, libxml2, make, mingw-runtime, minires-devel, mktemp, pango, pango-runtime.

Doc cygwin-doc, expat, libxml2, man, texinfo.

Interpreters expat, gawk, libxml2, python.

116

Page 118: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Libs

crypt, expat, fontconfig, freetype2, gettext, glib2, gtk2-x11, gtk2-x11-runtime, ioperm, libxft, libxftl, libxft2, libbz2_1, libcharset1, libfontconfig1, libfreetype26, libiconv, libiconv2, libintl, libintl1, libintl2, libintl3, libjpg62, libncurses-devel, libncurses5, libncurses6, libncurses7, libncurses8, libnetpbm10, libpcre0, libpng12, libpopt0, libpq4, librecdline6, libtiff4, libxml2, mingw-runtime, minires, minires-devel, ncurses, openssl, openssl097, pango, pango-runtime, tcltk, termcap, w32api, zlib.

Net inetutils, nfs-server, openssh, openssl, openssl097, ping, tcp_wrappers, xinetd. System chkconfig, ioperm, man, ping, util-linux.

Text expat, groff, less, libxml2, pango, pango-runtime, texinfo, util-linux.

Utils bzip2, cygutils, e2fsprogs, rpm, util-linux, keychain. Web wget.

X11 xterm.

Tabla 12. Paquetes a marcar distribuidos por categorías.

Es posible que haya algunos paquetes que no sean necesarios, como es el caso de

“libjpg62”, pero son un número reducido que apenas ocupan espacio en disco, y como vienen marcados por defecto no nos complicaremos.

El primer paso es instalar Cygwin en el nodo maestro del cluster. Posteriormente, lo

instalaremos en el resto de nodos, haciendo uso de la aplicación de Windows “Conexión a Escritorio remoto”. Las aplicaciones paralelas que creamos sólo se lanzan desde el nodo maestro; por tanto, instalar Cygwin en todos los nodos no atiende a razones de necesidad en la fase de programación, sino solamente en la fase de implantación del sistema. La razón es que necesitamos instalar ciertos servidores en todos los nodos, tales como el SSHD, de modo que MPI tenga un soporte para las comunicaciones en red. Para lograr esto, es necesario instalar antes Cygwin, desde el cual lograremos establecer y configurar los servidores mencionados.

Marcaremos los mismos paquetes cada vez que instalemos Cygwin, de modo que desde cualquier nodo dispongamos de las mismas herramientas de desarrollo y diagnóstico de aplicaciones.

El popular editor emacs ha sido de gran utilidad en la fase de implementación de código. En la carpeta “unidadCD:/software/emacs” se encuentra lo necesario para su instalación.

NOTA: En el directorio “unidadCD:\software\Postinstalacion_de_cygwin_y_emacs”, se encuentran algunos ficheros que pueden ayudar a la hora de la configuración del sistema, posterior a la instalación de Cygwin.

Para nuestro trabajo es fundamental disponer de un compilador de Fortran, ya que la

mayoría de aplicaciones que hemos creado están implementadas en dicho lenguaje. Cygwin utiliza herramientas de libre distribución, tal es el caso del compilador de C de GNU: gcc. Para Fortran también existen compiladores de libre distribución, pero ninguno de los que se distribuye viene integrado con Cygwin. Por esta razón, es necesario instalar un compilador para Fortran, con una versión posterior a la de Fortran 77, de modo que podamos darle la funcionalidad deseada a nuestras aplicaciones. El compilador escogido es g95 (GNU Fortran 95), de libre distribución también. Todo lo necesario para la instalación de g95 se encuentra en el directorio “unidadCD:/software/g95”. En un principio, se intentó utilizar gfortran, pero

117

Page 119: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

no era compatible con g77, y se descartó esta opción al ver que no era posible reutilizar código escrito en Fortran 77.

La forma de instalar g95 es copiando el paquete g95-x86-cygwin.tgz en el directorio raíz de cygwin y descomprimiéndolo en la forma:

$ tar -xvzf g95-x86-cygwin.tgz

Comprobar que todo ha ido bien ejecutando lo siguiente:

$ g95 --version

Lo siguiente es añadir las siguientes órdenes al fichero “/home/josea/.bashrc”:

PATH=/usr/local/bin/:/home/josea/mpich2-install/bin/:/cygdrive/c/emacs-20.6/bin/:$PATH PETSC_DIR=/home/josea/petsc-2.3.2-p3 PETSC_ARCH=cygwin-c-debug export PETSC_ARCH export PETSC_DIR F77=f77 export F77 F90=g95 export F90

De esta manera, cada vez que se arranca de nuevo el cluster, los directorios para MPI y PETSc, así como los compiladores para Fortran y el editor emacs, están registrados en las variables de entorno necesarias para trabajar.

NOTA: utilizar un editor como emacs para no introducir encabezados ni elementos extraños como retornos de carro no deseados, etc.

El siguiente paso es la instalación de las librerías BLAS, LAPACK y MPI, pero antes es

necesario obtener el paquete petsc-2.3.2.tar.gz de “unidadCD:/software/PETSc” y descomprimirlo en “//C/temp/cygwin/home/josea/”de la forma:

$ gunzip -c petsc-2.3.2-p3.tar.gz | tar -xof –

La instalación de PETSc requiere la instalación previa de BLAS, LAPACK y MPI, pero, a

su vez, la instalación de estas tres librerías requiere ficheros contenidos en la carpeta petsc-2.3.2-p3, es por esto por lo que es ahora cuando se descomprime el paquete petsc-2.3.2-p3.tar.gz.

118

Page 120: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.3. INSTALACIÓN DE BLAS Y LAPACK.

El primer paso es obtener el paquete fblaslapack.tar.gz de “unidadCD:/Software/BLAS_LAPACK” y ejecutar lo siguiente para descomprimirlo en “//C/temp/cygwin/home/josea/”:

$ gunzip -c fblaslapack.tar.gz | tar -xof -

Lo que sigue a continuación es la compilación de las librerías. Para ello, ir al directorio donde se acaba de descomprimir el código fuente y utilizar el makefile para conseguir compilarlas. La forma de proceder es la que sigue:

$ cd /home/josea/fblaslapack $ make PETSC_ARCH=docsonly PETSC_DIR=/home/josea/petsc-2.3.2-p3 FC=g95

Realizar este paso crea 'libfblas.a' y 'liblapack.a' en el directorio

“//C/temp/cygwin/home/josea/fblaslapack”, que son las librerías que se “linkarán” con nuestros programas de aplicación.

119

Page 121: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.4. INSTALACIÓN DE MPI.

Como ya se comentó en su momento, se ha escogido la implementación MPICH como opción para instalar MPI en nuestro sistema. En concreto se ha elegido la versión MPICH2. Antes de empezar a explicar la manera de conseguir instalarla, se aconseja leer el manual “mpich2-doc-install.pdf”, contenido en “unidadCD:/Documentación/MPI_PVM”.

La forma de proceder es la siguiente:

1) Copiar el paquete mpich2-1.0.4p1.tar.gz al directorio “//C/temp/cygwin/home/josea/lib/”.

2) Descomprimir el paquete de la forma:

$ cd /home/josea/lib $ gunzip -c mpich2-1.0.4p1.tar.gz | tar xf –

3) Crear un directorio en que se instalará MPI (por defecto es “/usr/local/bin”):

$ mkdir /home/josea/mpich2-install

4) Crear un directorio para los ficheros necesarios para la instalación:

$ mkdir /tmp/josea $ mkdir /tmp/josea/mpich2-1.0.4p1

5) Elegir las opciones de configuración (ver la sección 2.3.1. del manual de

instalación para ver las opciones disponibles).

Observar la opción de instalación conjunta de mpd y gforker (gforker es necesario para probar la ejecución en paralelo en una sola máquina: --with-pm=mpd:gforker).

6) Configurar MPICH2, especificando el directorio de instalación, y corriendo el

script de configuración en el directorio origen. NO ejecutar lo siguiente:

$ cd /tmp/josea/mpich2-1.0.4p1/ $ /home/josea/lib/mpich2-1.0.4p1/configure -prefix =/home/josea/mpich2-install 2>&1 | tee

configure.log

Para probar el procesamiento en paralelo en una sola máquina, es necesario haber instalado el demonio gforker, análogo a mpd (pero trabajando en paralelo en una sola máquina).

Gforker es un gestor de procesos simple, que hace correr todos los procesos

en un solo nodo. Es una variante de mpiexec, que utiliza las llamadas al sistema fork y exec para crear los nuevos procesos dentro de la misma máquina. Para configurar MPI con el gestor de procesos gforker se añade la siguiente línea a la hora de configurar MPICH2:

--with-pm=gforker ...

120

Page 122: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Existe también la opción de construir la MPI con ambos procesos, mpd y

gforker:

--with-pm=mpd:gforker

La forma de construir MPI de esta forma sería la ya comentada anteriormente:

Cerramos cygwin y añadimos las siguientes líneas al fichero .bashrc si antes no las hemos añadido:

F77=f77 export F77 F90=g95 export F90

De este modo aseguramos que las variables de entorno presentadas indiquen al

script de configuración de la MPI que instale g95 (GNU Fortran 95).

Abrimos cygwin y realizamos lo siguiente (ahora sí ejecutamos):

$ cd /tmp/josea/mpich2-1.0.4p1/ $ /home/josea/lib/mpich2-1.0.4p1/configure –prefix=/home/josea/mpich2-

install/ --with-pm=mpd:gforker --enable-f90 2>&1 | tee configure.log

Lo anterior quiere decir que configuramos MPI para que sea compilable con

Fortran 90 - 95.

7) Construir MPICH2:

$ make 2>&1 | tee make.log

8) Instalar los comandos de MPICH2:

$ make install 2>&1 | tee install.log

Este paso colecciona todos los ejecutables requeridos y los scripts en el subdirectorio 'bin' del directorio especificado por el argumento 'prefix' a la hora de la configuración (2 pasos antes).

9) Añadir el subdirectorio 'bin' del directorio de instalación a la variable de

entorno 'PATH' si esto no se ha realizado en los pasos anteriores.

$ export PATH=/home/josea/mpich2-install/bin:$PATH

Esta línea se puede añadir también al fichero ‘.bashrc’ del cygwin, de modo que se realice lo comentado cada vez que se lanza una consola de cygwin.

121

Page 123: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Comprobar que todo está en orden hasta el momento haciendo:

$ which mpd $ which mpicc $ which mpif77 $ which mpif90 $ which mpiexec $ which mpirun

Desde cualquier parte se debería poder referenciar a los comandos en el subdirectorio 'bin' del directorio de instalación “/home/josea/mpich2-install”. Añadir este directorio ‘bin’ a la variable PATH si antes no se ha realizado.

NOTA: Es en este punto donde tenemos que duplicar este directorio

(/home/josea/mpich2-install) en las otras máquinas (nodos) del cluster, en el caso de que dicho directorio no esté en un sistema de ficheros compartido semejante a NFS (sería deseable compartir la carpeta /home/josea dentro del sistema – cluster). Esto es, hacer una copia de dicho directorio en este momento en todos los nodos del cluster, creando una estructura de directorios vacíos en cada nodo si hace falta (aunque es mejor guardar una copia de esta carpeta en este momento, instalar primero Cygwin y después pegar la carpeta en la ruta correspondiente), de modo que dicha carpeta sea accesible mediante la misma ruta en todos los nodos. En nuestro cluster, la ruta en formato Windows sería C:\temp\cygwin\home\josea\mpich2-install, y en formato Cygwin /cygdrive/c/temp/home/josea/mpich2-install.

10) MPICH2, a diferencia de MPICH, utiliza un gestor de procesos externos para un arranque escalable de trabajos MPI grandes. El gestor de procesos por defecto es llamado mpd, que es un anillo de demonios que corren en las máquinas donde se ejecutarán los programas MPI. En el fichero “C:\temp\cygwin\home\josea\lib\ mpich2-1.0.4p1\src\pm\mpd\README” se encuentran todos los detalles para interactuar con mpd.

Por razones de seguridad, mpd busca en el directorio home/josea un fichero llamado .mpd.conf, que contendrá una línea tal que así:

secretword=<secretword>

donde <secretword> es una palabra conocida solamente por el usuario. Esta palabra no debería ser la misma que la contraseña de acceso al sistema. Ejecutar los siguientes comandos para modificar los permisos de acceso del fichero mencionado:

$ cd $HOME $ touch .mpd.conf $ chmod 600 .mpd.conf

122

Page 124: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Es importante que el permiso de escritura del fichero “.mpd.conf” sea sólo para el usuario, si no, el script mpiexec no funcionará. La orden “chmod 600 .mpd.conf” hay que realizarla en cada nodo, sin excepción.

A continuación, con un editor añadimos la siguiente línea:

secretword=morpheus

‘morpheus’ es la contraseña aquí elegida.

Por último, hemos de comprobar que todo ha ido bien. La primera prueba lógica a llevar a cabo consiste en crear un anillo de un solo demonio mpd en la máquina local, comprobar que funciona algún comando mpd y deshacer el anillo. Realizamos lo siguiente:

$ mpd & $ mpdtrace $ mpdallexit

La salida del comando mpdtrace debería ser el nombre de la máquina en la que se está

ejecutando. El comando mpdallexit provoca que se destruya el anillo de demonios mpd. En los pasos siguientes del manual de instalación (pasos 12 a 16) – páginas 8 a 11 del manual–, se especifica cómo probar el funcionamiento en paralelo de las máquinas especificadas en un fichero llamado ‘.mpd.hosts’. Para realizar estos pasos, es necesario configurar los servidores sshd y rshd, que previamente hemos instalado con Cygwin. Esto lo haremos más tarde. Primero instalaremos la librería PETSc, después configuraremos los servidores indicados y, por último, probaremos el funcionamiento de MPI.

123

Page 125: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.5. INSTALACIÓN DE LA LIBRERÍA PETSc.

El primer paso es dirigirnos al directorio donde descomprimimos anteriormente el paquete de PETSc (final de la sección 7.2.):

$ cd /home/josea/petsc-2.3.2-p3

$ PETSC_DIR=`pwd` (ojo con las comillas del tipo ` ). $ export PETSC_DIR

Las dos últimas sentencias las omitimos si ya se realizó correctamente los pasos de la

sección 7.2 (concretamente, el tema de añadir las líneas citadas al fichero “/home/josea/.bashrc”).

Lo siguiente es configurar las opciones oportunas antes de compilar las librerías. Hemos

de especificar el compilador para C y para Fortran que utilizaremos, así como la ruta donde se encuentran las librerías BLAS, LAPACK y MPI. Para esto, hacemos uso de un script implementado en Python, de la forma siguiente:

$ ./config/configure.py --with-cc=gcc --with-fc=g95 --with-blas-lapack-

dir=/home/josea/fblaslapack --with-mpi-dir=/home/josea/mpich2-install La salida de la anterior acción debe ser algo muy parecido a lo siguiente:

============================================================== Configuring PETSc to compile on your system ============================================================== Compilers: C Compiler: gcc -Wall -Wwrite-strings -fomit-frame-pointer -Wno-strict-aliasing -g3 Fortran Compiler: g95 -Wall –g Linkers: Static linker: /usr/bin/ar cr Dynamic linker: gcc -shared -Wall -Wwrite-strings -fomit-frame-pointer -Wno-strict-aliasing -g3 PETSc: PETSC_ARCH: cygwin-c-debug PETSC_DIR: /home/josea/petsc-2.3.2-p3 ** ** Now build and test the libraries with "make all test" ** Clanguage: C Scalar type:real MPI: Includes: ['/home/josea/mpich2-install/include'] Library: ['/home/josea/mpich2-install/lib/libfmpich.a', 'libmpich.a', 'libpmpich.a', 'libmpich.a', 'libpmpich.a', 'libpmpich.a'] PETSc shared libraries: disabled PETSc dynamic libraries: disabled BLAS/LAPACK: -Wl,-rpath,/home/josea/fblaslapack -L/home/josea/fblaslapack –lflapack -Wl,-rpath,/home/josea/fblaslapack –L/home/josea/fblaslapack –lfblas

Antes de continuar hay que añadir la variable de entorno PETSC_ARCH al fichero “/home/josea/.bashrc” si antes no se ha realizado en la sección 7.2.

124

Page 126: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Ahora ya sí compilamos la librería PETSc, y lo hacemos con la ayuda de la herramienta make, que previamente habremos instalado con Cygwin:

$ make all

El final de la salida, resultado de la ejecución anterior, debe ser algo similar a:

g95 -c -Wall -g -I/home/josea/petsc-2.3.2-p3 -I/home/josea/petsc-2.3.2-p3/bmake/cygwin-c-debug -I/home/josea/petsc-2.3.2-p3/include -I/home/josea/mpich2-install/include -o somefort.o somefort.F Warning (115): Line 17 of somefort.F is being truncated Warning (115): Line 18 of somefort.F is being truncated Warning (115): Line 19 of somefort.F is being truncated Warning (115): Line 17 of somefort.F is being truncated Warning (115): Line 18 of somefort.F is being truncated Warning (115): Line 19 of somefort.F is being truncated /usr/bin/ar cr /home/josea/petsc-2.3.2-p3/lib/cygwin-c-debug/libpetsc.a somefort.o libfast in: /home/josea/petsc-2.3.2-p3/include libfast in: /home/josea/petsc-2.3.2-p3/include/finclude Completed building libraries ========================================= Shared libraries disabled

Se ha marcado en negrita las líneas donde se especifican los directorios donde se

encuentran las librerías de PETSc, que podemos utilizar para linkar con nuestros programas de aplicación. De esta manera, cada vez hagamos uso de librerías escritas en C (primera línea) o en Fortran (segunda línea), incluiremos directivas del tipo #include al principio de nuestro código para especificar las librerías que utilizaremos.

Los warnings que aparecen no son importantes. Se debe a que el compilador de Fortran 95

(g95) mira la extensión de los ficheros antes de compilar. Estos han de tener una de las siguientes extensiones:

• .f para código escrito en Fortran 77. • .F para Fortran 77 y llamada al preprocesador de C (cpp) para procesar las

directivas tales como los #include. • .f90 o .f95 para Fortran 90 / 95. • .F90 o F95 para Fortran 90 / 95 y llamada al preprocesador de C.

Como el fichero “somefort.F” que aparece no tiene extensión, por ejemplo, .F90, el compilador g95 entiende que el código está escrito en Fortran 77, y entonces trunca todas aquellas líneas de código que pasen de la columna 72.

Por último, probar que la instalación es correcta con:

$ make test

La salida de lo anterior, omitiendo los warnings ya explicados, es algo tal que así:

125

Page 127: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Running test examples to verify correct installation C/C++ example src/snes/examples/tutorials/ex19 run successfully with 1 MPI process C/C++ example src/snes/examples/tutorials/ex19 run successfully with 2 MPI processes Fortran example src/snes/examples/tutorials/ex5f run successfully with 1 MPI process Completed test examples

Como se puede apreciar, todo funciona correctamente haciendo uso de PETSc, que a su

vez se apoya en MPI, BLAS y LAPACK. Vemos que se prueba la ejecución de un programa escrito en C/C++, primero con un anillo de un proceso (un solo demonio mpd) y después con dos procesos (anillo de dos demonios mpd). Posteriormente se ejecuta un programa Fortran con un proceso. Todos corren perfectamente.

Resta conseguir ejecutar un programa en paralelo. En principio, se puede lanzar un demonio mpd por nodo, accediendo a cada nodo con la aplicación de ‘Conexión a Escritorio remoto’ (Windows XP) y, posteriormente, ejecutar el comando ‘mpiexec’ en el nodo maestro, para la ejecución en paralelo de nuestro programa de aplicación. El comando mpiexec se proporciona con el paquete de MPI, y si todo lo anterior se ha realizado, ya se encuentra disponible para poder utilizarlo. Esta orden es la que se encarga de gestionar el anillo de demonios mpd, y de utilizarlo adecuadamente para la ejecución en paralelo de programas. Más adelante veremos cómo es su interfaz.

Para no tener que arrancar manualmente un demonio en cada nodo con la ‘Conexión a Escritorio remoto’, existe el comando ‘mpdboot’, el cual realiza dicho trabajo de forma automática. Para que esto funcione, hemos de configurar adecuadamente los servidores sshd y rshd, que es lo que haremos a continuación. En realidad, MPI sólo utiliza sshd a la hora de ejecutar en paralelo un programa de aplicación; sin embargo, nosotros configuraremos tanto sshd como rshd, para poder realizar las pruebas que se indican en la guía de instalación de MPI, y así comprobar el correcto funcionamiento en paralelo.

Para configurar los servidores sshd y rshd, incluiremos en la siguiente sección un

breve resumen de cómo se realiza esto, que será suficiente para nuestros propósitos.

126

Page 128: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.6. CONFIGURACIÓN DE LOS SERVIDORES SSHD Y RSHD.

Suponemos que sshd y rshd ya se han instalado tras configurar las opciones de instalación de Cygwin. El sistema operativo que utilizamos para nuestro trabajo es Windows XP, aunque lo que se comentará también es válido para Windows 2003 Server y versiones posteriores.

El primer paso es acceder al sistema como administrador, para tener los privilegios necesarios para poder configurar los servidores en cuestión. Así mismo, hemos de verificar que la cuenta de usuario / administrador actual dispone de contraseña; si no, hemos de crearla y volver a acceder al sistema.

Tras acceder al sistema, nos dirigimos a MiPC > Propiedades > Avanzado > Variables de Entorno, y creamos una variable de sistema nueva llamada CYGWIN, con el valor ntsec tty.

A continuación, abrimos una consola de Cygwin, y ejecutamos el siguiente comando para generar las claves dsa que utilizará el servidor:

$ ssh-host-config

Cuando el script pida ciertos parámetros de configuración, responder lo siguiente:

- A “privilege separation”, responder “yes”. - A “create local user sshd”, responder “yes”. - A “install sshd as a service”, responder “yes”. - Introducir el valor siguiente cuando pregunte “CYGWIN=”: ntsec tty.

Posteriormente, hemos de iniciar el servicio SSH que acabamos de configurara. Para esto

ejecutamos: $ net start sshd, o bien: $ cygrunsrv --start sshd

La forma de para el servidor es análoga a la forma de arrancarla, sustituyendo en lo anterior “start” por “stop”.

Hemos de asegurarnos de que cada usuario Windows tiene una contraseña para acceder al sistema, si no, hemos de crearlas. Cerramos la ventana actual de Cygwin y abrimos una nueva, así nos aseguramos de que la información de usuario Windows armonice con la de Cygwin, si no, los usuarios no poden utilizar el servicio ssh. A continuación, hemos de guardar la información de las contraseñas y grupos Windows a un fichero local a Cygwin, para ello hacemos lo siguiente:

$ mkpasswd -cl > /etc/passwd $ mkgroup --local > /etc/group

Tras haber configurado el servidor sshd, con la siguiente acción comprobamos si el servidor sshd está activo:

$ cygrunsrv -V -Q sshd

127

Page 129: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Si al realizar una sesión SSH contra el servidor sshd local la conexión es rechazada, puede que sea problema de configuración del Firewall de Windows, y haya que permitir las conexiones TCP a través del puerto 22.

Para ejecutar programas en paralelo sobre MPI, hemos de conseguir que el servidor sshd

no pida una clave de acceso ante una conexión SSH desde cualquier equipo del cluster. Para conseguirlo, hemos de seguir los tres puntos siguientes.

1. Generar y almacenar claves públicas y privadas:

SSH utiliza un mecanismo de clave asimétrica. El primer paso es generar las claves

pública y privada DSA de 1024 bit, haciendo:

$ ssh-keygen -t dsa Esto creará un fichero de clave pública y otro de clave privada en el directorio actual. La

opción -f del comando ssh-keygen permite especificar el nombre de ambos ficheros, pero si se omite, el nombre por defecto será “id_dsa” para el fichero de clave privada (fichero de identidad) e “id_dsa.pub” para el fichero de clave pública.

En el host desde el cual nos conectaremos vía SSH, hemos de tener almacenado el fichero

de identidad en el directorio “$HOME/.ssh/”, a no ser que especifiquemos la ruta donde se encuentra dicho fichero con la opción -i.

En cada host al cual nos conectaremos vía ssh, hemos de añadir nuestra clave pública al

final del fichero “~user/.ssh/authorized_keys”. Para cada usuario del nodo destino nos conectaremos como: ssh -l user.

NOTA: sólo es necesario instalar la clave privada en el host desde donde empieza la

sesión ssh (cliente ssh). La clave pública se ha de tener en todos los nodos remotos a los que se desea conectarse (servidores ssh). En esos nodos no es necesario copiar la clave privada. En nuestro cluster, cada nodo debe tener su clave privada, y todos los demás deben tener la clave pública del resto de nodos distintos a ellos mismos. Esto es necesario para poder crear el anillo de demonios MPI. Realmente, en el nodo maestro corre un demonio mpd, para soporte de MPI, y otro demonio encargado de gestionar el anillo de demonios, y por lo tanto, gestiona las conexiones SSH con todos los nodos que intervienen en la ejecución de un programa paralelo. El tema de que todos los nodos tengan las claves públicas del resto, es para que sea un sistema donde cualquier nodo elegido pueda ser el maestro cuando queramos.

2. Ficheros de configuración:

Hemos de conseguir que en cada nodo al queremos conectarnos vía SSH, el fichero de configuración SSH (a menudo es “/etc/ssh/ssh_config”) contenga la directiva ‘ForwardAgent yes’, así como ‘PubkeyAuthentication yes’.

Posiblemente sea necesario ajustar la directiva siguiente como ‘StrictModes no’. Por supuesto, hacer todo esto requiere privilegios de súper usuario.

128

Page 130: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

3. Configurar keychain:

Al realizar una conexión SSH contra un nodo remoto, hemos de introducir la contraseña de usuario como el cual queremos conectarnos en el nodo destino. Para evitar tener que introducir la palabra de paso más de una vez al conectarnos vía SSH, podemos configurar la aplicación keychain, de modo que sólo tengamos que introducir la clave una vez para siempre. Para lograr esto, hemos de añadir las siguientes líneas al fichero “$HOME/.bash_profile”:

/usr/bin/keychain $HOME/.ssh/id_dsa source $HOME/.keychain/${HOSTNAME}-sh

4. Conexión SSH todos con todos:

Para que MPI funcione correctamente, ningún nodo ha de pedir clave de autentificación durante la ejecución de un programa paralelo, a la hora de crear el anillo de demonios mpd mediante conexiones SSH. Como siempre se pide una contraseña de acceso, al menos una vez con el método descrito en el paso 3, es importante realizar una conexión SSH de todos los nodos con todos (incluido uno mismo: localhost). Esto es sencillo realizarlo en el mismo momento en que se instala Cygwin y se realizan los tres pasos anteriores en cada nodo del cluster.

Una vez que hemos terminado de configurar el sevidor sshd, pasemos a configurar el otro

servidor: rshd. Este pertenece al paquete “inetd”, instalado en la configuración de Cygwin. El paquete inetd contiene los clientes y servidores ftp, ftpd, rsh, rshd y muchos más. inetd es un súper servidor, que controla al resto de servidores que tiene a su cargo.

Realmente, no hace falta instalar inetd, con sshd es suficiente para la ejecución de mpiexec (utiliza ssh). La razón de instalar y configurar inetd, es utilizar rshd para realizar las pruebas que verifican el funcionamiento de MPI.

El procedimiento para configurar inetd es el siguiente: 1. Primeramente, leer '/usr/share/doc/Cygwin/inetutils-1.3.2.README', que

aclarará mucho sobre el uso de inetd, o también ejecutar $ man inetd en una consola de Cygwin para obtener información detallada.

2.Utilizar '/usr/bin/iu-config' para configurar inetd. 3.Ejecutar: $ cygrunsrv -I inetd -d "CYGWIN inetd" -p /usr/sbin/inetd -a –D 4.Ejecutar: $ cygrunsrv -V -Q inetd, para ver el estado en que se encuentra el

servidor.

- si está parado, ejecutar: cygrunsrv –S inetd - para detenerlo sería: cygrunsrv –E inetd

129

Page 131: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

NOTA: En el fichero ‘/etc/inetd.conf’ está la configuración de los servidores que se

arrancan y son controlados por el súper servidor inetd. Además, en el directorio “/usr/share/doc/Cygwin/” se encuentran distintos manuales para el uso de varios servicios soportados por cygwin. El listado de este directorio se presenta a continuación.

CYGWIN_LICENSE gcc-core-3.4.4-1.README login.README Numeric-24.2.README gcc-g++-3.4.4-1.README man-1.5p.README alternatives-1.3.20a.README gcc-g77-3.4.4-1.README minires-1.00-1.README apache-1.3.33.README gcc-java-3.4.4-2.README mktemp-1.5.README apache2-2.2.2.README gdbm-1.8.3.README ncurses-5.5.README atk-1.10.3.README gettext-0.14.5.README netpbm-10.33.README bash-3.1.README ghostscript-8.50.README netpbm-doc-10.33.README bzip2-1.0.3.README glib-1.2.10.README openssh.README coreutils-5.97.README glib2-2.10.3.README openssl-0.9.8d.README crypt.README gtk+-1.2.10.README pango-1.8.1.README curl-7.15.4.README gtk2-x11-2.6.10.README pango-runtime-1.8.1.README cygrunsrv.README gtk2-x11-runtime-2.6.10.README pcre-6.6.README cygserver.README inetutils-1.3.2.README perl-5.8.7.README cygutils-1.3.0.README jbigkit-1.5.README ping-1.0.README cygwin-doc-1.4.README libgdbm-devel-1.8.3.README pygtk2-2.6.3.README e2fsprogs-1.35.README libglade2-2.5.1.README python-2.4.3.README editrights-1.01-1.README libiconv-1.9.2.README run-1.1.10.README emacs-21.2-13.README libncurses-devel-5.5.README tar-1.15.91.README expat-1.95.8.README libnetpbm-devel-10.33.README terminfo-5.5_20060323.README findutils-4.3.0.README libnetpbm10-10.33.README util-linux-2.12r.README fontconfig-2.2.2.README libpng-1.2.8.README xterm-202.README freetype2-2.1.9.README libxml2-2.6.26.README zlib-1.2.3.README

130

Page 132: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

7.7. FUNCIONAMIENTO DE MPI Y EJEMPLO DE EJECUCIÓN EN PARALELO.

Al final de la sección 7.4. nos paramos justo antes de probar el funcionamiento de MPI en varios nodos del cluster a la vez. Es en este momento cuando estamos en disposición de utilizar el comando mpdboot, proporcionado por el soporte de MPI para arrancar de forma automática un demonio mpd en las máquinas especificadas.

No es necesario ejecutar la aplicación de ‘Conexión a Escritorio remoto’ de Windows para arrancar los servidores sshd e inetd, siempre y cuando estén corriendo automáticamente en cada máquina al arrancarla. Si todo se ha hecho según lo explicado en los pasos anteriores, todo debería funcionar.

En la sección 7.4., vimos que la ejecución del comando mpdtrace en el nodo local era la

correcta. Probemos, a continuación, la ejecución de programas no propios de MPI en el nodo local y, posteriormente, en nodos remotos.

Ejecutemos lo siguiente:

$ mpd & $ mpiexec -n 1 /bin/hostname $ mpdallexit

Esto debería imprimir el nombre de la máquina en la que se ejecuta. Si hubiera algún

fallo, se aconseja acudir al apéndice A de la guía de instalación de MPI. En el desarrollo de la implantación del sistema, este apéndice fue de utilidad. Vemos que el comando para lanzar ejecuciones de un programa paralelo sobre MPI es mpiexec. En versiones anteriores de MPI, se utilizaba mpirun y, aunque sigue estando disponible en la versión que nosotros utilizamos, no tiene tantas opciones como mpiexec.

Lo próximo es crear un anillo de demonios mpd en un conjunto de máquinas. Antes, es

necesario crear un fichero llamado ‘mpd.hosts’, que contenga en cada línea el nombre de las máquinas que intervendrán en la ejecución del programa paralelo (ver el fichero ‘mpd.hosts’ del directorio “unidadCD:/Software/Postinstalacion_de_cygwin_y_emacs/dentro de home-josea”). El nombre de estas máquinas serán utilizadas como objetivos para ssh o rsh, de modo que es importante incluir el nombre de dominio completo si es necesario.

Para arrancar un demonio mpd en cada máquina, utilizamos el comando mpdboot, cuya interfaz es la siguiente:

$ mpdboot -n <número_nodos> -f mpd.hosts

El número de nodos donde arrancar un demonio mpd ha de ser menor o igual a uno más el

número de nodos del fichero mpd.hosts. Un mpd es siempre arrancado en la máquina donde se ejecuta mpdboot, y se cuenta para el número de nodos a arrancar, aparezca o no en el fichero de hosts. Por defecto, mpdboot arrancará un solo mpd por máquina, incluso aunque el nombre de la máquina aparezca en el fichero de hosts múltiples veces. La opción -1 puede ser utilizada para sobrescribir este comportamiento, pero no suele haber razón para que un usuario necesite múltiples mpd’s en una sola máquina.

A continuación, hemos de testear el anillo de mpd’s creado. Para tal fin, ejecutamos el comando mpdtrace sin argumentos, y la salida consistirá en una lista de los hosts donde corren los demonios mpd. Se puede ver el tiempo que se tarda en dar un número de vueltas

131

Page 133: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

determinadas en torno al anillo ejecutando: mpdringtest <num_vueltas>. Por ejemplo, ejecutar:

$ mpdtrace $ mpdringtest 100 $ mpdringtest 1000

Ahora es el momento de ver la ejecución en paralelo de una aplicación sobre el anillo de

demonios. Realizar lo siguiente:

$ mpiexec -n <num > hostname Lo anterior ejecuta el popular comando ‘hostname’ en ‘num’ máquinas. El número de

procesos necesitados no tiene porqué coincidir con el número de hosts en el anillo. Por defecto, los procesos son lanzados uno detrás de otro en los hosts que pertenecen al anillo mpd, de modo que no es necesario especificar los hosts cuando se ejecuta un programa con mpiexec.

Hay muchas opciones para mpiexec. Para visualizarlas, ejecutar mpiexec --help. Un ejemplo típico es:

$ mpiexec -n 1 -host máquina master : -n 19 slave Con lo anterior aseguramos que el proceso con rank 0 se ejecute en la máquina

especificada. Ejemplo de ejecución en paralelo: copiamos el programa ‘/home/josea/ex2.exe’ en la

carpeta “C:\temp” de cada máquina, y ejecutamos las siguientes líneas de código en una consola de cygwin:

$ cp ex2.exe /cygdrive/c//temp/ $ mpdboot -n 2 -f mpd.hosts $ mpdtrace $ mpiexec -n 2 /cygdrive/c/temp/ex2.exe $ mpdallexit

Cada máquina necesita tener el programa a ejecutar en paralelo en una ruta igual al resto de rutas de las otras máquinas del cluster. Por esta razón, cuando compilamos el programa en la máquina maestra (donde está instalada PETSc), hemos de copiar el programa ejecutable al resto de máquinas. Esto se puede hacer a mano. Sin embargo, lo mejor es crear un script al que se le pase como argumentos el nombre del programa y el número de nodos que deseamos que entren en la ejecución del programa en paralelo.

Dicho script se ha creado, y se llama ‘paralelo.sh’, para ejecución en una bash shell. El script se complementa con otros dos ficheros escritos en awk, que son ‘extrae_p.awk’ y ‘nodos.awk’; ambos se ficheros se han escrito para hacer más legible el código de paralelo.sh.

Existen dos maneras de ejecutar el bash shell script. Suponiendo que queremos 4 nodos para la ejecución en paralelo, y que nuestro programa paralelo se llama ‘programa1.exe’, la ejecución sería:

132

Page 134: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

1) $ bash paralelo.sh 4 programa1.exe O bien:

2) chmod +x paralelo.sh (le damos permisos de ejecución) ./paralelo.sh 4 programa1.exe

El funcionamiento del script es el siguiente. Primeramente, copia el programa a ejecutar en paralelo (pasado como línea de argumentos) a todas las máquinas que intervendrán en la ejecución. Los nombres de las máquinas se obtienen del fichero ‘mpd.hosts’, ya comentado al explicar el funcionamiento de MPI. Posteriormente, el script invoca al comando mpdboot para arrancar un demonio mpd en todas aquellas máquinas en las que se ha copiado el programa paralelo. A continuación, se ejecuta el comando mpiexec, que es el que en realidad se encarga de gestionar todo el funcionamiento paralelo y distribuido de nuestro programa de aplicación. Una vez terminada la ejecución de nuestro programa, se ha de invocar al comando mpdallexit para destruir los demonios mpd que se habían arrancado. Por último, el script procede a eliminar de todas las máquinas el programa copiado al principio.

Ejemplo de llamada para el ejercicio 1 de la sección matrices:

$ ./paralelo.sh 2 /cygdrive/c/temp/cygwin/home/josea/ex1.exe -n 2

Salida: Vector length 2 Vector length 2 4 6 All other values should be near zero VecScale 0 VecCopy 0 VecAXPY 0 VecAYPX 0 VecSwap 0 VecSwap 0 VecWAXPY 0 VecPointwiseMult 0 VecPointwiseDivide 0 VecMAXPY 0 0 0

133

Page 135: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

888... AANNEEXXOO IIII:: CCÓÓDDIIGGOO FFUUEENNTTEE DDEE LLAASS PPRRUUEEBBAASS..

134

Page 136: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

8.1. INTRODUCCIÓN.

En esta sección, se presentan los códigos fuente de las aplicaciones paralelas diseñadas en la fase de pruebas del proyecto. Así mismo, se incluyen algunos ficheros de apoyo utilizados en la compilación y ejecución de las aplicaciones, tales como makefiles, scripts, etc.

Los ficheros se intentarán presentar por orden cronológico de realización a lo largo del proyecto. Comentar que se realizó una prueba para lenguaje C, y diversas pruebas para Fortran, ya que éste último es el que se utilizará como lenguaje predeterminado en futuras implementaciones de aplicaciones.

Todos los códigos están bastante comentados, buscando el ser auto explicativos. No obstante, es posible que se realice alguna aclaración al inicio o final de los mismos.

El estudio de estos códigos en profundidad es crucial para entender el funcionamiento de

todo lo explicado sobre la librería PETSc, y diversas características de las rutinas empleadas en el código que no serían apreciables si no se observaran en un contexto de aplicación concreto.

La forma de trabajar al escribir nuevos códigos PETSc, es utilizar como esqueleto los códigos aquí presentados, o los códigos de ejemplo que proporciona PETSc integrados en el paquete de instalación. Al menos, durante un principio más o menos largo, ésta es la forma más sencilla y eficaz de trabajar, ya que evita errores debidos a despistes y se reutiliza código, que siempre es aconsejable, pues será más eficiente si ya se probó en solitario y se ajustó a los requerimientos del sistema en que se desarrollan las aplicaciones. Además, así es como se hizo en la fase de pruebas del proyecto.

Sin más dilación, pasemos a ver los códigos. Estos también se encuentran en el directorio

“unidadCD:/Pruebas/pruebas-petsc/pruebas_C”, para los códigos escritos en lenguaje C, y en “unidadCD:/Pruebas/pruebas-petsc/pruebas_fortran”, para los códigos escritos en Fortran. Decir también, que en el directorio “unidadCD:/Software/SCRIPT PARA EJECUCION PARALELO (copiar en home)” se encuentra el script “paralelo.sh”, para ejecución en paralelo de programas (ya comentado en la sección 7), así como los ficheros de apoyo al script “nodos.awk” y “extrae_p.awk”, escritos en lenguaje AWK. Estos tres ficheros también se presentan aquí.

135

Page 137: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

8.2. HERRAMIENTAS PARA LA EJECUCIÓN EN PARALELO.

La ejecución en paralelo se realiza mediante scripts escritos en Python, y los suministra MPI. Indicar que es necesario copiar, antes de la ejecución, los ejecutables de los programas en todas las máquinas que intervendrán en el funcionamiento paralelo. También es necesario especificar algunas variables de entorno, fundamentales para el correcto funcionamiento de las ejecuciones.

Para agrupar toda esta problemática, se optó por escribir un shell script, que realizara todo lo comentado. Este script es paralelo.sh, cuyo código se muestra a continuación. Seguidamente, se presentan dos ficheros escritos en AWK, y que se utilizan como apoyo para el script mencionado.

#!/bin/bash # ############################################################################## # # # Fichero: 'paralelo.sh' # # # # Descripcion: Script para la copia de un programa en todos los ordenadores # # del cluster, y posterior ejecución de dicho programa en # # paralelo en un numero de nodos indicado por linea de # # comandos. # # # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/10/2006 # # Actualizacion: 16/11/2006 # # # # DEPENDENCIAS: 'nodos.awk' y 'extrae_p.awk' # # # ############################################################################## # IMPORTANE: # # Suponemos que el usuario 'josea' existe y tiene una misma cuenta con un # mismo password en cada nodo del cluster. # # Si esto no fuese así, hemos de crearlo o cambiar el nombre de la variable # 'usu' # usu=josea # # # Del mismo modo, la carpeta compartida por defecto en cada maquina es # '//maq_n/temp', y dentro de ella se encuentra /cygwin/home. Si queremos # cambiar esta configuracion, hemos de cambiar la variable 'comp'. # OJO: no poner '/' ni al principio ni al final en esta variable. # comp=temp/cygwin/home # # # Comprobamos el numero de argumentos pasados al script # $1 >> numero de nodos # $2 >> nombre programa a ejecutar en paralelo # $i, i=3,.. >> argumentos del programa NODOS=$1 PROG=$2 ############################################################### ## Funcion para copiar un programa en todos los nodos, ##

136

Page 138: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

## en //maq_num/$comp ## ## ## ## Utilizamos 'awk' para conseguir nombre de cada maquina ## ## Dichas maquinas estan registradas en: \ ## ## '/cygdrive/c/$comp/$usu/mpd.hosts' ## # # function copiap () { local i=0 while [ $i -lt $NODOS ]; do maq=`cat /home/$usu/mpd.hosts | awk -f nodos.awk NUM_MAQ=$i` #Carpeta compartida en //maquina_n/comp cp $1 //$maq/$comp #Si hubiera que copiar mas ficheros (de apoyo al programa, etc.) #este es el sitio para añadir las sentencias necesarias. #Por ejemplo: cp /home/josea/dim.txt //maq/comp #Esto lo hacemos asi para no complicar mas el script, aunque se #podria realizar teniendo en cuenta el numero de argumentos #pasados al script ... local i=`expr $i + 1` done } ## ## ############################################################### ############################################################### ## Funcion para eliminar el programa de todos los nodos ## ## tras acabar la ejecucion. ## # # function rmp () { local i=0 while [ $i -lt $NODOS ]; do maq=`cat /home/$usu/mpd.hosts | awk -f nodos.awk NUM_MAQ=$i` #Carpeta compartida en //maquina_n/comp rm -f //$maq/$comp/$1 local i=`expr $i + 1` done } ## ## ############################################################### # Comprobamos el numero de argumentos pasados, al menos han de existir los #argumentos 'numero_nodos' y 'nombre_prog.exe' => $# >= 2 if [ $# -lt 2 ]; then echo "*****************************************************************" echo "* *" echo "* Uso: bash paralelo.sh num_nodos nombre_prog.exe args_programa *"

137

Page 139: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

echo "* *"

echo "* O bien: *" echo "* *" echo "* Primero dar permiso de ejecucion: chmod +x paralelo.sh *" echo "* Uso: ./paralelo.sh num_nodos nombre_prog.exe args_programa *" echo "* *" echo "* << Dar ruta absoluta (syntaxis Cygwin) ubicación programa >> *" echo "* *" echo "*****************************************************************" exit fi ############################################################### # Copiar programa a todos los nodos implicados ############################################################### copiap $PROG ############################################################### # Ejecucion del programa con MPICH2 mediante el script 'mpiexec' ############################################################### # # Primero arrancamos un demonio por nodo con 'mpdboot' # mpdboot -n $NODOS -f /home/$usu/mpd.hosts # # # Coleccionamos argumentos del programa en una variable 'args' num_args=`expr $# - 3` fich_temp=temp.txt j=0 echo -n "" > $fich_temp while [ $j -le $num_args ]; do echo -n "$3 " >> $fich_temp shift j=`expr $j + 1` done #args=`cat $fich_temp | awk '{printf("%s", $0)'` args=`cat $fich_temp` # Eliminamos el fichero temporal rm -f $fich_temp ############################################################### #### Ejecución en paralelo #### ## ## #MAQ_LOCAL=`/usr/bin/hostname` PROG=`echo -n $PROG | awk -f extrae_p.awk` # La opcion '-log_summary' provoca que el programa presente # un resumen de lo realizado al final de la ejecucion. mpiexec -n $NODOS /cygdrive/c/$comp/$PROG $args -log_summary # # ###############################################################

138

Page 140: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

############################################################### # Cerramos los demonios en todas las maquinas # ############################################################### # echo "" # echo "IMP: Cerrar anillo demonios 'mpd' con 'mpdallexit' desde nodo maestro" # echo "" echo -n "Destruyendo anillo de demonios mpd con 'mpdallexit'... " mpdallexit echo "OK" #Eliminamos el programa paralelo de todos los nodos rmp $PROG ############################################################################## # Fin de 'paralelo.sh' ##############################################################################

139

Page 141: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A continuación, se presenta el fichero nodos.awk.

#!/bin/bash ## ############################################################################## # # # Fichero: 'nodos.awk' # # # # Descripcion: Devuelve el nombre de la maquina especificado en el fichero # # '/cygdrive/c/$comp/$usu/mpd.hosts', y que se encuentra en la # # linea numero 'NUM_MAQ'. # # # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/06/2006 # # # ############################################################################## BEGIN { i=0; } { # Buscamos la linea que corresponda al numero de maquina buscada if(i == NUM_MAQ){ printf("%s", $1); } i++; } ############################################################################### #Fin de 'nodos.awk' ###############################################################################

140

Page 142: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Ahora, el fichero extrae_p.awk. #!/bin/bash ## ############################################################################## # # # Fichero: 'extrae_p.awk' # # # # Descripcion: Devuelve el nombre del programa a ejecutar en paralelo, # # pasado como argumento al script 'paralelo.sh' pero con la # # ruta absoluta. # # # # Autor: Ignacio Campos Rivera # # Fecha: 26/06/2006 # # # ############################################################################## BEGIN { i = 0; } { s=$0; i = split (s, a, /\//); printf("%s", a[i]); } ############################################################################## # Fin de 'extrae_p.awk' # ##############################################################################

141

Page 143: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

8.3. CÓDIGOS PARA LENGUAJE C.

Solamente se ha escrito un programa en C, debido a que las aplicaciones paralelas de interés se han realizado en Fortran. El código de la aplicación en cuestión se encuentra almacenado en el fichero ‘prueba1.c’, y le acompaña el correspondiente makefile para la compilación (y enlazado) del código fuente.

El fichero ‘prueba1.c’ se presenta a continuación.

/***************************************************************************** * * Descripcion: Ejemplo de uso de algunas de las rutinas mas importantes de * las librerias de matrices y vectores de PETSc. * * Autor: Ignacio Campos Rivera, 31/10/2006 * *****************************************************************************/ static char help[] = "\n**** Ejemplo de creacion de matrices y vectores y\n operaciones implicadas. ****\n\n"; #include <stdlib.h> // para la funcion rand() #include "petscmat.h" #undef __FUNCT__ #define __FUNCT__ "main" int main(int argc, char **args) { Vec x, b; // realizaremos la operacion Ax = b, Mat A; // con A y x conocidos, b sera el resultado. /* especificamos el valor por defecto de 'm' (numero de filas locales de la matriz paralela), por si no se especifica como argumento del programa (opcion de base de datos). */ PetscInt m = 10, N, Istart, Iend, i, j; PetscInt nl, *gindices, rstart, rend; PetscScalar v; PetscMPIInt rank, size; PetscErrorCode ierr; // Automaticamente llama a MPI_Init() PetscInitialize(&argc, &args, (char *)0, help); // Obtenemos numero procesadores (procesos si secuencial) de que disponemos. ierr = MPI_Comm_size(PETSC_COMM_WORLD, &size); CHKERRQ(ierr); // Obtenemos el numero de proceso local. ierr = MPI_Comm_rank(PETSC_COMM_WORLD, &rank); CHKERRQ(ierr); // Conseguimos el valor entero para una opcion particular en la base de datos // En este caso, 'm' es el numero de filas locales por proceso (todos // mismo numero de filas). ierr = PetscOptionsGetInt(PETSC_NULL, "-m", &m, PETSC_NULL); CHKERRQ(ierr);

142

Page 144: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

// Numero de columnas (numero global). N = m * size; /* Creamos una matriz dispersa distribuida, especificando la dimension local, es decir, el numero de filas que pertenencen localmente a un proceso. La dimension de la submatriz local sera m*N, donde N = m*size (matriz cuadrada). La dimension de la matriz global sera M*N, donde N = M = m*size. >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no realiza el prealojamiento de memoria, es por ello que es necesario llamar a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la siguiente: MatCreateMPIAIJ(MPI_Comm comm,PetscInt m,PetscInt n,PetscInt M, PetscInt N,PetscInt d_nz, const PetscInt d_nnz[], PetscInt o_nz,const PetscInt o_nnz[],Mat *A) */ ierr = MatCreateMPIAIJ(PETSC_COMM_WORLD ,m, PETSC_DECIDE, PETSC_DETERMINE,\ N, m, PETSC_NULL, N - m, PETSC_NULL, &A); CHKERRQ(ierr); ierr = MatSetFromOptions(A); CHKERRQ(ierr); /* Actualmente, todos los formatos de matrices paralelas PETSc son particionadas por trozos contiguos de filas a traves de los procesadores. Es por esta razon por lo que debemos determinar que filas de la matriz son locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() */ ierr = MatGetOwnershipRange(A,&Istart,&Iend); CHKERRQ(ierr); /* Establecemos los valores para los elementos de la matriz con la funcion rand(), que genera numeros aleatorios. Cada procesador necesita insertar solamente los valores de los elementos locales que le pertenezcan, pero cualquier elemento no local introducido sera enviado al procesador apropiado durante el ensamblado de la matriz. Siempre hemos de referirnos a las filas y las columnas de la matriz con ordenacion global a la hora de insertar elementos. */ for (i=Istart; i<Iend; i++) { for (j=0; j<N; j++) { v = rand() * 0.0000001 * (rank + 1); ierr = MatSetValues(A,1,&i,1,&j,&v,ADD_VALUES); CHKERRQ(ierr); } } /* Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos rutinas mientras se realiza el paso de mensajes. */ ierr = MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY); CHKERRQ(ierr); ierr = MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY); CHKERRQ(ierr); /* Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension global, el particionamiento paralelo es determinado en tiempo de ejecucion.

143

Page 145: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

El especificar la dimension global viene por la razon de que el vector sea compatible con las dimensiones de la matriz a la hora de realizar el producto. Primero creamos un vector desde cero y, posteriormente, lo duplicamos. */ ierr = VecCreate(PETSC_COMM_WORLD, &x); CHKERRQ(ierr); ierr = VecSetSizes(x, PETSC_DECIDE, N); CHKERRQ(ierr); ierr = VecSetFromOptions(x); CHKERRQ(ierr); ierr = VecDuplicate(x, &b); CHKERRQ(ierr); ierr = VecSet(x, 1.0); CHKERRQ(ierr); //Se podria obtener la longitud local del vector con: // ierr = VecGetLocalSize(x, size_x); CHKERRQ(ierr); ierr = VecAssemblyBegin(x);CHKERRQ(ierr); ierr = VecAssemblyEnd(x);CHKERRQ(ierr); /* Ahora haremos que cada procesador introduzca valores en su parte local del vector paralelo. Para ello, es necesario determinar la ordenacion local a global para el vector. Cada procesador genera una lista de indices globales para cada indice local. Si hubiesemos especificado el tamanyo local del vector en vez del global, necesitariamos obtener el tamanyo global de la forma: ierr = VecGetSize(x,&N);CHKERRQ(ierr); */ // Obtenemos el rango local del vector paralelo: ierr = VecGetOwnershipRange(x, &rstart, &rend); CHKERRQ(ierr); // numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart; //PetscPrintf(PETSC_COMM_SELF,"\n--< nl %D s %D e %D >--\n", nl, rstart, // rend); ierr = PetscMalloc(nl*sizeof(PetscInt), &gindices); CHKERRQ(ierr); gindices[0] = rstart; //PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[0]=%D\n", // (PetscInt)rank,gindices[0]); for (i=0; i < nl-1; i++) { gindices[i+1] = gindices[i] + 1; // PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[%D]=%D\n", // (PetscInt)rank, i+1,gindices[i+1]); } // Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar // los limites if (gindices[0] == -1) gindices[0] = N - 1; if (gindices[nl-1] == N) gindices[nl-1] = 0; /* Realizamos el mapeado de indices locales a globales, de modo que utilizamos los indices locales para introducir elementos y automaticamente se introducen en el vector paralelo con su correspondiente indice global mapeado. */ { ISLocalToGlobalMapping ltog; //El comunicador a utilizar debe contener unicamente el proceso local ierr = ISLocalToGlobalMappingCreate(PETSC_COMM_SELF, nl, gindices, &ltog); CHKERRQ(ierr); ierr = VecSetLocalToGlobalMapping(x, ltog); CHKERRQ(ierr); ierr = ISLocalToGlobalMappingDestroy(ltog); CHKERRQ(ierr); } ierr = PetscFree(gindices); CHKERRQ(ierr);

144

Page 146: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

// CUALQUIER VECTOR DUPLICADO DE 'x' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO /* Determinamos ahora los elementos del vector paralelo. - Cada procesador determina sus valores locales utilizando ordenacion local. - Cada procesador puede contribuir para cualquier entrada del vector paralelo, independientemente de a que procesador pertenezca el elemento. Cualquier contribucion no local sera transferida al procesador apropiado durante el proceso de ensamblado. - Para colocar elementos con la ordenacion global se utiliza VecSetValues() - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas juntas. */ for (i=0; i < nl; i++) { v = (PetscScalar)rank + 1; ierr = VecSetValuesLocal(x,1,&i,&v,INSERT_VALUES); CHKERRQ(ierr); } /* Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos rutinas mientras se realiza el paso de mensajes. */ ierr = VecAssemblyBegin(x);CHKERRQ(ierr); ierr = VecAssemblyEnd(x);CHKERRQ(ierr); /* Vemos la matriz y el vector. Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo a un fichero. */ if(!rank) printf("\n\t ***** Matriz paralela A: *****\n\n"); ierr = MatView(A, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr); if(!rank) printf("\n\t ***** Vector paralelo x: *****\n\n"); ierr = VecView(x, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr); /* */ // A continuacion realizamos la operacion Ax = b: ierr = MatMult(A, x, b);CHKERRQ(ierr); CHKERRQ(ierr); // Visualizamos el resultado if(!rank) printf("\n ***** Vector paralelo b, donde Ax=b: *****\n\n"); ierr = VecView(b, PETSC_VIEWER_STDOUT_WORLD); CHKERRQ(ierr); // Destruimos la matriz y los vectores paralelos ierr = MatDestroy(A); CHKERRQ(ierr); ierr = VecDestroy(x); CHKERRQ(ierr); ierr = VecDestroy(b); CHKERRQ(ierr); ierr = PetscFinalize(); CHKERRQ(ierr); return 0; } /***************************************************************************** * Fin de 'prueba1.c' *****************************************************************************/

145

Page 147: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A continuación, el makefile correspondiente.

############################################################################### # # # Descripcion: makefile para ficheros de prueba de la PETSc # # # # Utilización: teclear en linea de comandos: make prueba1 # # # # Autor: Ignacio Campos Rivera # # # ############################################################################### CFLAGS = FFLAGS = CPPFLAGS = FPPFLAGS = LOCDIR = /home/Ignacio/pruebas-petsc EXAMPLESC = prueba1.c EXAMPLESF = MANSEC = Mat include ${PETSC_DIR}/bmake/common/base prueba1: prueba1.o chkopts -${CLINKER} -o prueba1 prueba1.o ${PETSC_MAT_LIB} ${RM} prueba1.o

146

Page 148: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

8.4. CÓDIGOS PARA LENGUAJE FORTRAN.

Se han escrito diversos códigos de aplicaciones mediante el lenguaje Fortran. Iremos presentando uno a uno, realizando previamente una pequeña introducción a cada código.

Primeramente, presentaremos el makefile encargado de compilar todas las pruebas

realizadas en Fortran.

############################################################################### # # # Descripcion: makefile para ficheros de prueba de la PETSc # # # # Utilización: teclear en linea de comandos: make “objetivo” , donde # # “objetivo” es, por ejemplo, struct o KSPstruct, dependiendo # # del codigo que queramos compilar. # # # # Autor: Ignacio Campos Rivera # # # ###############################################################################

CFLAGS = FFLAGS = CPPFLAGS = FPPFLAGS = LOCDIR = /home/Ignacio/pruebas-petsc/pruebas_fortran EXAMPLESC = ex1.c EXAMPLESF = prueba1.F90 struct.F90 KSPstruct.F90 KSPstruct_Luis.F90 \ diag_dom.F90 diag_domN.F90 MANSEC = KSP include ${PETSC_DIR}/bmake/common/base struct: struct.o chkopts -${FLINKER} -o struct struct.o ${PETSC_MAT_LIB} ${RM} struct.o KSPstruct: KSPstruct.o chkopts -${FLINKER} -o KSPstruct KSPstruct.o ${PETSC_KSP_LIB} ${RM} KSPstruct.o KSPstruct_Luis: KSPstruct_Luis.o chkopts -${FLINKER} -o KSPstruct_Luis KSPstruct_Luis.o ${PETSC_KSP_LIB} ${RM} KSPstruct_Luis.o diag_dom: diag_dom.o chkopts -${FLINKER} -o diag_dom diag_dom.o ${PETSC_KSP_LIB} ${RM} diag_dom.o diag_domN: diag_domN.o chkopts -${FLINKER} -o diag_domN diag_domN.o ${PETSC_KSP_LIB} ${RM} diag_domN.o prueba1: prueba1.o chkopts -${FLINKER} -o prueba1 prueba1.o ${PETSC_MAT_LIB} ${RM} prueba1.o cl: rm -f *.*~ rm -f *~ rm -f \#*

147

Page 149: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

El siguiente código a mostrar se encuentra en prueba1.F90, que es el mismo que el contenido en el fichero ‘prueba1.c’ de la sección anterior, pero mapeado de C a Fortran.

!***************************************************************************** !* !* Descripcion: Ejemplo de uso de algunas de las rutinas mas importantes de !* las librerias de matrices y vectores de la PETSc, en Fortran !* !* Autor: Ignacio Campos Rivera, 10/11/2006 !* !***************************************************************************** program main implicit none #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" Vec :: x, b ! realizaremos la operacion Ax = b, Mat :: A ! con A y x conocidos, b sera el resultado. ! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos). PetscInt :: m = 10, N, Istart, Iend, i, j PetscInt :: nl, rstart, rend PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr PetscScalar :: rand integer,parameter :: seed = 86456 ISLocalToGlobalMapping ltog ! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Conseguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos ! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr) ! Numero de columnas (numero global). N = m * size

148

Page 150: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! La dimension de la submatriz local sera m*N, donde N = m*size (matriz ! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. ! ! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, m, PETSC_NULL_INTEGER, & N - m, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr) call MatSetOption(A, MAT_COLUMN_ORIENTED) !IMPORTANTE ! ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero ! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz. ! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos. ! ! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. ! ! call itime(timeArray) ! v = rand(timeArray(1) + timeArray(2) + timeArray(3)) call srand(seed) ! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. do i=Istart, Iend-1, 1 do j=0, N-1, 1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do

149

Page 151: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

end do ! ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se ! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! ! El especificar la dimension global viene por la razon de que el vector ! sea compatible con las dimensiones de la matriz a la hora de realizar el ! producto. Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr) call VecDuplicate(x, b, ierr) call VecSet(x, 1.0, ierr) ! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr) call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr) ! ! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local. ! ! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! ierr = VecGetSize(x,&N);CHKERRQ(ierr); ! ! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(x, rstart, rend, ierr) ! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart ! PetscPrintf(PETSC_COMM_SELF,"\n--< nl %D s %D e %D >--\n", nl, rstart, rend); ALLOCATE ( gindices(nl) ) gindices(1) = rstart ! PetscPrintf(PETSC_COMM_SELF,"\nrank:%D, gindices[0]=%D\n", ! (PetscInt)rank,gindices[0]); do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do

150

Page 152: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1).eq.-1) gindices(1) = N if ( gindices(nl).eq. N+1) gindices(nl) = 1 ! Realizamos el mapeado de indices locales a globales, de modo que ! utilizamos los indices locales para introducir elementos y ! automaticamente se introducen en el vector paralelo con su ! correspondiente indice global mapeado. ! ! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(x, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr) DEALLOCATE(gindices) ! CUALQUIER VECTOR DUPLICADO DE 'x' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO ! ! Determinamos ahora los elementos del vector paralelo. ! ! - Cada procesador determina sus valores locales utilizando ordenacion ! local. ! ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas. ! ! OJO: VecSetValuesLocal() utiliza indices basados en 0 para Fortran, al igual que ! en C do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(x,1,i,v,INSERT_VALUES, ierr) end do ! ! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos ! rutinas mientras se realiza el paso de mensajes. ! call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr) ! ! Vemos la matriz y el vector. Omitir los siguientes pasos si son ! dimensiones grandes, o imprimirlo a un fichero. ! if(rank.eq.0) write(6,*) ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,*) ' ***** Vector paralelo x: *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr)

151

Page 153: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! A continuacion realizamos la operacion Ax = b: call MatMult(A, x, b, ierr) ! Visualizamos el resultado if(rank.eq.0) write(6,*) ' ***** Vector paralelo b (Ax=b): *****' call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Destruimos la matriz y los vectores paralelos call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b, ierr) call PetscFinalize(ierr) end !***************************************************************************** !* Fin de 'prueba1.F90' !*****************************************************************************/

152

Page 154: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A continuación, se presenta el código fuente de la aplicación ‘struct.F90’.

!***************************************************************************** !* !* Descripcion: Matriz sistema de estructuras !* !* Autor: ICR, 13/11/2006 !* !* Numero procesadores: 3, solo 3, por ahora. !***************************************************************************** program main implicit none ! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a ! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" ! _____________________________________________________________________________ ! ! { Esquema del sistema } ! !Num.Proceso ! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b ! ! ! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________ Mat :: A Vec :: x, b PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2 PetscScalar :: v, uno = 1.0 PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836

153

Page 155: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Para prealojar espacio en memoria para la matriz paralela: PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:) ! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. ! PetscTruth :: flg ! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if ! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error) if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if ! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf ! Cerramos el fichero close(10)

! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2 ! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG ! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la ! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz. ! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank) ! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1 o_nz(r) = 0 ! resto de elementos son 0

154

Page 156: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

end do do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do ! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do ! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m)) do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do end select ! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! ! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! ! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor ! del numero de columnas locales (n =m), ya que al crear el vector 'x' ! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la ! dimension global de 'x', entonces podemos decirle a la PETSc que decida el ! valor de n de la matriz en la forma n=PETSC_DECIDE. ! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr) ! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr)

155

Page 157: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de ! memoria, los destruimos. DEALLOCATE(d_nz, o_nz) ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. ! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento. ! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la ! matriz con ordenacion global a la hora de insertar elementos. call srand(seed) ! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. ! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}. ! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro ! (indexacion segun convenio de C). ! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend select case (rank) ! >>>> Proceso 0 <<<< case (0) ! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1 bC1: do j=nn1, n1-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1 end do ! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn1, Iend-1 bC1_: do j=0, nn1-1

156

Page 158: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_ v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do ! >>>> Proceso 1 <<<< case (1) ! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2 bC2: do j=n1+nn2, n1+n2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2 end do ! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_ v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do ! >>>> Proceso 2 <<<< case (2) ! Insertamos valores para: I1¬ e I2¬ k = nl1 k2 = nl1 + nl2 do i=Istart, Iend-1 v = 1.0 call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr)

157

Page 159: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

k = k + 1 k2 = k2 + 1 end do end select ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en ! medio de las dos rutinas mientras se realiza el paso de mensajes. call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x, ierr) call VecDuplicate(x, b, ierr) call VecSet(x, uno, ierr) ! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr) call VecAssemblyBegin(x, ierr) call VecAssemblyEnd(x, ierr) ! Visualizamos la matriz A y el vector x. ! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero. if(rank.eq.0) write(6,'(/,T20,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo x: *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) ! A continuacion realizamos la operacion Ax = b: call MatMult(A, x, b, ierr) ! Visualizamos el resultado1 if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b (Ax=b): *****' call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Liberamos la memoria alojada para la matriz y los vectores call MatDestroy(A, ierr)

158

Page 160: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

call VecDestroy(x, ierr) call VecDestroy(b, ierr) call PetscFinalize(ierr) end !***************************************************************************** !* Fin de 'struct.F90' * !*****************************************************************************

159

Page 161: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

El siguiente código corresponde al fichero ‘KSPstruct.F90’. !***************************************************************************** !* Descripcion: Matriz sistema de estructuras * !* Primero haremos la multiplicacion A·x1 y obtendremos b. * !* Posteriormente resolveremos con KSP el sistema A·x2 = b * !* y comprobaremos que el valor de x1 y x2 es aproximadamente * !* el mismo en ambos casos (viendo la norma del vector x1-x2). * !* * !* Autor: ICR, 14/11/2006 * !* * !* Numero procesadores: 3, solo 3, por ahora. * !***************************************************************************** program main implicit none ! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a ! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h" ! _____________________________________________________________________________ ! ! { Esquema del sistema } ! !Num.Proceso ! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b ! ! ! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________ KSP :: ksp PC :: pc Mat :: A Vec :: x1, x2, b PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2, its

160

Page 162: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

PetscScalar :: v, uno = 1.0, uno_neg = -1.0 PetscReal :: norma, tol = 1.d-7 PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836 ! Para prealojar espacio en memoria para la matriz paralela: PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:) ! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. ! PetscTruth :: flg ! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if ! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error) if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if ! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf ! Cerramos el fichero close(10) ! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2 ! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG ! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la ! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz. ! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank)

161

Page 163: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1 o_nz(r) = 0 ! resto de elementos son 0 end do do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do ! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do ! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m)) do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do end select ! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! ! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! ! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor ! del numero de columnas locales (n =m), ya que al crear el vector 'x' ! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la ! dimension global de 'x', entonces podemos decirle a la PETSc que decida el ! valor de n de la matriz en la forma n=PETSC_DECIDE.

162

Page 164: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr) ! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr) ! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de ! memoria, los destruimos. DEALLOCATE(d_nz, o_nz) ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. ! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento. ! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la ! matriz con ordenacion global a la hora de insertar elementos. call srand(seed) ! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. ! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}. ! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro ! (indexacion segun convenio de C). ! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend select case (rank) ! >>>> Proceso 0 <<<< case (0) ! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1 bC1: do j=nn1, n1-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1

163

Page 165: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

end do ! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn1, Iend-1 bC1_: do j=0, nn1-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_ v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do ! >>>> Proceso 1 <<<< case (1) ! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2 bC2: do j=n1+nn2, n1+n2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2 end do ! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1 v = (rand() * 10) * (rank + 1) call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_ v = 1.0 if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do

164

Page 166: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! >>>> Proceso 2 <<<< case (2) ! Insertamos valores para: I1¬ e I2¬ k = nn1 k2 = n1 + nn2 do i=Istart, Iend-1 v = 1.0 call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr) k = k + 1 k2 = k2 + 1 end do end select ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en ! medio de las dos rutinas mientras se realiza el paso de mensajes. call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. ! Aqui x = x1 call VecCreate(PETSC_COMM_WORLD, x1, ierr) call VecSetSizes(x1, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x1, ierr) call VecDuplicate(x1, b, ierr) call VecDuplicate(x1, x2, ierr) ! Para resolver con KSP posteriormente call VecSet(x1, uno, ierr) ! Se podria obtener la longitud local del vector con: ! VecGetLocalSize(x, size_x, ierr) call VecAssemblyBegin(x1, ierr) call VecAssemblyEnd(x1, ierr) ! Visualizamos la matriz A y el vector x. ! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero. if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo x1: *****' call VecView(x1, PETSC_VIEWER_STDOUT_WORLD, ierr)

165

Page 167: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

!______________________________________________________________________________ ! A continuacion realizamos la operacion Ax = b: call MatMult(A, x1, b, ierr) ! Visualizamos el resultado1 if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b (Ax=b): *****' call VecView(b, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(3/)') ! ____________________________________________________________________________ ! Resolvemos ahora el sistema lineal mediante KSP ! Ahora x = x2 ! Creamos el contexto para el solver lineal call KSPCreate(PETSC_COMM_WORLD, ksp, ierr) ! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve ! tambien de matriz de precondicion. call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr) ! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. ! ! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions(). call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr) ! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> ! ! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores. call KSPSetFromOptions(ksp, ierr) ! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b, x2, ierr) ! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Visualizamos el resultado: if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x2: *****' call VecView(x2, PETSC_VIEWER_STDOUT_WORLD, ierr)

166

Page 168: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Chequeamos el error: call VecAXPY(x2, uno_neg, x1, ierr) call VecNorm(x2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr) ! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if ! Todos los procesos han de esperar a que el maestro realice el bloque ! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr) ! Liberamos la memoria alojada para la matriz, los vectores y el contexto KSP. call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x1, ierr) call VecDestroy(x2, ierr) call VecDestroy(b, ierr) call PetscFinalize(ierr) end !***************************************************************************** !* Fin de 'KSPstruct.F90' !*****************************************************************************

167

Page 169: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

El código siguiente, se corresponde con el contenido en el fichero ‘KSPstruct_Luis.F90’. !***************************************************************************** !* * !* Descripcion: Matriz sistema de estructuras * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error). * !* * !* Autor: ICR, 14/11/2006 * !* * !* Numero procesadores: 3, solo 3, por ahora. * !***************************************************************************** program main implicit none ! El pre-procesador de C se encargara de incluir las funciones necesarias ! declaradas en las siguientes localizaciones. A diferencia de C, en Fortran ! hay que incluir todos los ficheros necesarios, aun cuando unos incluyan a ! otros. #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h" ! _____________________________________________________________________________ ! ! { Esquema del sistema } ! !Num.Proceso ! | _ nn1 nl1 nn2 nl2 nnf _ _ _ _ _ ! V | ------------------------------------ | | | | | ! /[ nn1 | K1 | C1 | 0 | 0 | 0 | | u1 | | f1 | ! P0 | ------------------------------------ | | ------- | | ----- | ! \[ nl1 | C1¬ | 0 | 0 | 0 | I1 | | lambda1 | | 0 | ! | ------------------------------------ | . | ------- | = | ----- | ! /[ nn2 | 0 | 0 | K2 | C2 | 0 | | u2 | | f2 | ! P1 | ------------------------------------ | | ------- | | ----- | ! \[ nl2 | 0 | 0 | C2¬ | 0 | I2 | | lambda2 | | 0 | ! | ------------------------------------ | | ------- | | ----- | ! P2 -[ nnf | 0 | I1¬ | 0 | I2¬ | 0 | | v | | 0 | ! | ------------------------------------ | | | | | ! - - - - - - ! A (M x N) x b ! ! ! Notacion: '¬' = transpuesta ! -------- n1 = nn1 + nl1 ! n2 = nn2 + nl2 ! M = N = n1 + n2 + nnf ! _____________________________________________________________________________ ! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas).

168

Page 170: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

KSP :: ksp PC :: pc Mat :: A Vec :: x, b1, b2 PetscInt :: nn1, nn2, nl1, nl2, nnf PetscInt :: n1, n2, m, MG, NG PetscInt :: Istart,Iend, i, j, k, k2, its, imprimir = 1 PetscScalar :: v, cero = 0.0, uno = 1.0, uno_neg = -1.0 PetscScalar :: cuatro = 4.0, dos = 2.0, dos_neg = -2.0 PetscReal :: norma, tol = 1.d-7 PetscMPIInt :: rank, size PetscErrorCode :: ierr integer :: error=2, r integer,parameter :: seed = 836 ! Para insertar valores en un vector mediante indexacion local PetscInt,ALLOCATABLE :: gindices(:) ISLocalToGlobalMapping ltog PetscInt :: nl, rstart, rend ! Para prealojar espacio en memoria para la matriz paralela: PetscInt,ALLOCATABLE :: d_nz(:), o_nz(:) ! La siguiente variable es necesaria al utilizar PetscOptionsGetXxx(), a la ! hora de obtener valores con la opcion 'base de datos'. PetscTruth :: flg ! PetscInitialize() automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero de procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr) ! Comprobamos que haya exactamente 3 procesadores, si no, abortamos ejecucion if (size /= 3) then if(rank.eq.0) write(6,'(/,A,/)') & "Numero de procesadores necesarios: SOLO 3" stop 10001 end if ! Obtenemos las dimensiones de las submatrices contenidas en un fichero adjunto open(UNIT=10, FILE="dim.txt", STATUS="OLD", ACTION="READ", & IOSTAT=error) if (error /= 0) then if(rank.eq.0) write(6,'(/,A,/)') & "Error de lectura de fichero 'dimension.txt'" stop 10002 end if ! La primera linea es un comentario, la ignoramos read (10,*) read (10,*) nn1, nn2, nl1, nl2, nnf ! Cerramos el fichero close(10)

169

Page 171: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Dimensiones de las submatrices diagonales: n1 = nn1 + nl1 n2 = nn2 + nl2 ! Dimensiones globales de la matriz paralela: MG = n1 + n2 + nnf NG = MG ! Especificamos el numero de filas de la matriz global que corresponderan ! a cada procesador. Tambien especificamos el numero de no-ceros de la ! matriz diagonal y de la offset de cada procesador. Lo ideal es realizar ! un barrido sobre la matriz a tratar y asi obtener el numero exacto, ! aproximadamente, de no-ceros en cada fila de la matriz. ! En funcion del numero de proceso tendremos unas u otras dimensiones select case (rank) ! >>>> Proceso 0 <<<< case (0) m = n1 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn1 d_nz(r) = n1 ! no-ceros para K1-C1 o_nz(r) = 0 ! resto de elementos son 0 end do do r=nn1+1, n1 d_nz(r) = nn1 ! no-ceros para C1¬ o_nz(r) = 1 ! Matriz identidad I1, solo '1s' en la diagonal end do ! >>>> Proceso 1 <<<< case (1) m = n2 ALLOCATE (d_nz(m), o_nz(m)) do r=1, nn2 d_nz(r) = n2 ! no-ceros para K2-C2 o_nz(r) = 0 ! resto de elementos son 0 end do do r=nn2 +1, n2 d_nz(r) = nn2 ! no-ceros para C2¬ o_nz(r) = 1 ! Matriz identidad I2, solo '1s' en la diagonal end do ! >>>> Proceso 2 <<<< case (2) m = nnf ALLOCATE (d_nz(m), o_nz(m)) do r=1, nnf d_nz(r) = 0 ! no-ceros de la diagonal (son todos ceros) o_nz(r) = 1 + 1 ! el resto de elementos son los '1s' de I1¬ e I2¬ end do end select ! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! ! >>> Ver el ejemplo 'ex2.c' de matrices, es interesante. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no

170

Page 172: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! ! IMPORTANTE: Como las submatrices diagonales son cuadradas, n=m ! PETSc da un fallo (dificil de detectar) si no especificamos el valor ! del numero de columnas locales (n =m), ya que al crear el vector 'x' ! especificaremos su dimension LOCAL. Si, por el contrario, especificamos la ! dimension global de 'x', entonces podemos decirle a la PETSc que decida el ! valor de n de la matriz en la forma n=PETSC_DECIDE. ! Sabemos que M = MG = SUMATORIO(m_proceso_local_i), pero le diremos a la PETSc ! que lo determine en tiempo de ejecucion. call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, m, PETSC_DETERMINE,& NG, 0, d_nz, 0, o_nz, A, ierr) call MatSetFromOptions(A, ierr) ! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED, ierr) ! Ya no se van a utilizar mas los arrays de indicacion de prealojamiento de ! memoria, los destruimos. DEALLOCATE(d_nz, o_nz) ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas en trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. ! Cada procesador necesita insertar solamente los valores de los elementos ! locales que le pertenezcan, pero cualquier elemento no local introducido ! sera enviado al procesador apropiado durante el ensamblado de la matriz. ! Importante poner el valor ADD_VALUES en vez de INSERT_VALUES si mas de un ! procesador interviene a la hora de colocar un valor en un elemento. ! IMPORTANTE: Siempre hemos de referirnos a las filas y las columnas de la ! matriz con ordenacion global a la hora de insertar elementos. call srand(seed) ! MUY IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. ! Nota: Istart - Iend = {n1 en P0, n2 en P1, nnf en P3}. ! IMPORTANTE: Istart tambien empieza por '0' en el procesador maestro ! (indexacion segun convenio de C). ! print *, "n1 = ", n1, "n2 = ", n2, "nnf = ", nnf, "MG = NG = ", NG ! print *, "rank = ", rank, "Iend-Istart = ", Iend - Istart ! print *, "rank = ", rank, "Istart = ", Istart, "Iend = ", Iend

171

Page 173: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

select case (rank) ! >>>> Proceso 0 <<<< case (0) ! Insertamos valores para: K1 y C1 do i=Istart, Istart+nn1-1 bK1: do j=0, nn1-1 if(j.eq.i-Istart .AND. j.ne.nn1-1) then v = cuatro else if (j.eq.i-Istart .AND. j.eq.nn1-1) then v = dos else if (j.eq.i-Istart+1 .AND. (i-Istart.ge.0 .AND. & i-Istart.le.nn1-2)) then v = dos_neg else if (j.eq.i-Istart-1 .AND. (i-Istart.ge.1 .AND. & i-Istart.le.nn1-1)) then v = dos_neg else v = cero end if call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK1 bC1: do j=nn1, n1-1 !v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1 end do ! Insertamos valores para: C1¬ e I1 ! Si I1 es diagonal, el primer '1' de I1 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn1, Iend-1 bC1_: do j=0, nn1-1 !v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC1_ v = uno_neg if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do ! >>>> Proceso 1 <<<< case (1) ! Insertamos valores para: K2 y C2 do i=Istart, Istart+nn2-1 bK2: do j=n1, n1+nn2-1

172

Page 174: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

if (j-n1.eq.i-Istart .AND. (j.ne.n1 .AND. j.ne.n1+nn2-1) ) then v = cuatro

else if(j-n1.eq.i-Istart .AND. (j.eq.n1 .OR. j.eq.n1+nn2-1)) then

v = dos else if (j-n1.eq.i-Istart+1 .AND. (i-Istart.ge.0 .AND. & i .le. Istart+nn1-2)) then v = dos_neg else if (j-n1.eq.i-Istart-1 .AND. (i-Istart.ge.1 .AND. & i-Istart.le.nn1-1)) then v = dos_neg else v = cero end if call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bK2 bC2: do j=n1+nn2, n1+n2-1 !v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2 end do ! Insertamos valores para: C2¬ e I2 ! Si I2 es diagonal, el primer '1' de I2 esta en n1+n2, el ultimo ! esta en n1+n2+nnf-1 k = n1+n2 do i=Istart+nn2, Iend-1 bC2_: do j=n1, n1+nn2-1 !v = (rand() * 10) * (rank + 1) v = uno call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do bC2_ v = uno_neg if(k <= n1+n2+nnf-1) then call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) k = k + 1 end if end do ! >>>> Proceso 2 <<<< case (2) ! Insertamos valores para: I1¬ e I2¬ k = nn1 k2 = n1 + nn2 v = uno_neg do i=Istart, Iend-1 call MatSetValues(A, 1, i, 1, k, v, ADD_VALUES, ierr) call MatSetValues(A, 1, i, 1, k2, v, ADD_VALUES, ierr)

173

Page 175: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

k = k + 1 k2 = k2 + 1 end do end select ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos, que no involucren el uso de la matriz A, en ! medio de las dos rutinas mientras se realiza el paso de mensajes. call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. ! Aqui x = x1 call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, m, PETSC_DECIDE, ierr) call VecSetFromOptions(x, ierr) call VecDuplicate(x, b1, ierr) ! Para resolver con KSP call VecDuplicate(x, b2, ierr) ! Para comprobar con la multiplicacion ! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr) ! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart ALLOCATE ( gindices(nl) ) gindices(1) = rstart do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do ! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1).eq.-1) gindices(1) = NG if ( gindices(nl).eq. NG+1) gindices(nl) = 1 ! Realizamos el mapeado de indices locales a globales, de modo que ! utilizamos los indices locales para introducir elementos y ! automaticamente se introducen en el vector paralelo con su ! correspondiente indice global mapeado. ! ! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr) DEALLOCATE(gindices) ! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO ! ! Determinamos ahora los elementos del vector paralelo. !

174

Page 176: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! - Cada procesador determina sus valores locales utilizando ordenacion ! local. ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas. ! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran tambien, ! como en C. do i=0, nl-1, 1 ! Todos los valores de f1 y f2 son '0', excepto el ultimo elemento ! de f2, que vale '1'. f2 pertenece al procesador 1. v = cero if( rank.eq.1 .AND. i.eq.nn2-1 ) v = uno call VecSetValuesLocal(b1,1,i,v,INSERT_VALUES, ierr) end do ! ! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos ! rutinas mientras se realiza el paso de mensajes. ! call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr) ! Visualizamos la matriz A y el vector b1. ! Omitir los siguientes pasos si son dimensiones grandes, o imprimirlo ! a un fichero. if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b1: *****' call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if ! ____________________________________________________________________________ ! Resolvemos ahora el sistema lineal mediante KSP ! Creamos el contexto para el solver lineal call KSPCreate(PETSC_COMM_WORLD, ksp, ierr) ! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve ! tambien de matriz de precondicion. call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr) ! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. ! ! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian

175

Page 177: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions(). call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr) ! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> ! ! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores. call KSPSetFromOptions(ksp, ierr) ! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr) ! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)') call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Visualizamos el resultado: if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if !______________________________________________________________________________ ! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr) ! Visualizamos el resultado if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****' call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if !______________________________________________________________________________ ! Chequeamos el error: call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr) ! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /)

176

Page 178: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if ! Todos los procesos han de esperar a que el maestro realice el bloque ! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr) ! Liberamos la memoria alojada para la matriz, los vectores y el contexto KSP. call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr) call PetscFinalize(ierr) end !***************************************************************************** !* Fin de 'KSPstruct_Luis.F90' * !*****************************************************************************

177

Page 179: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

Ahora, se presenta el código contenido en ‘diag_dom.F90’.

!***************************************************************************** !* * !* Descripcion: Matriz llena con diagonal dominante * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error) * !* * !* Opciones BD: -m : numero de filas por proceso * !* -i : 1=imprimir matriz y vectores, 0=no imprimir (elementos * !* grandes) * !* * !* Numero de procesadores: n * !* * !* Autor: ICR, 15/11/2006 * !* * !***************************************************************************** program main implicit none #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h" KSP :: ksp PC :: pc Vec :: x, b1, b2 Mat :: A ! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos). PetscInt :: m = 10, N, Istart, Iend, i, j PetscInt :: nl, rstart, rend ! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas). PetscInt :: imprimir = 1, its PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v, uno_neg = -1.0 PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr ! Declare the type of the rand() function PetscScalar :: rand integer,parameter :: seed = 86456 PetscReal :: norma, tol = 1.d-7 ISLocalToGlobalMapping ltog

178

Page 180: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Consiguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos ! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr) ! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr) ! Numero de columnas (numero global). N = m * size ! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! La dimension de la submatriz local sera m*N, donde N = m*size (matriz ! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, m, PETSC_NULL_INTEGER, & N - m, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr) ! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED) ! ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero ! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz. ! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos.

179

Page 181: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. ! ! call itime(timeArray) ! Get the current time ! v = rand(timeArray(1) + timeArray(2) + timeArray(3)) call srand(seed) ! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. do i=Istart, Iend-1 do j=0, N-1 v = rand() ! * (rank + 1) if(i-Istart+rank*m .eq. j) v = v * N**2 call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end do end do ! ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se ! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! ! El especificar la dimension global viene por la razon de que el vector ! sea compatible con las dimensiones de la matriz a la hora de realizar el ! producto. Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr) call VecDuplicate(x, b1, ierr) call VecDuplicate(x, b2, ierr) ! ! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local. ! ! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! VecGetSize(x, N, ierr) ! ! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr)

180

Page 182: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart ALLOCATE ( gindices(nl) ) gindices(1) = rstart do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do ! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1) .eq. -1) gindices(1) = N if ( gindices(nl) .eq. N+1) gindices(nl) = 1 ! Realizamos el mapeado de indices locales a globales, de modo que ! utilizamos los indices locales para introducir elementos y ! automaticamente se introducen en el vector paralelo con su ! correspondiente indice global mapeado. ! ! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr) DEALLOCATE(gindices) ! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO ! Determinamos ahora los elementos del vector paralelo. ! ! - Cada procesador determina sus valores locales utilizando ordenacion ! local. ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas. ! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran, como en C do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(b1, 1, i, v, INSERT_VALUES, ierr) end do ! Ensamblamos el vector paralelo, utilizando las rutinas MatAssemblyBegin() ! y MatAssemblyEnd(). Se pueden realizar calculos en medio de las dos ! rutinas mientras se realiza el paso de mensajes. call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr) ! ! Vemos la matriz y el vector. Omitir los siguientes pasos si son ! dimensiones grandes con la opcion -i 0, o imprimir a un fichero. ! if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') &

181

Page 183: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

' ***** Vector paralelo b1: *****' call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if ! Resolvemos ahora el sistema lineal mediante KSP ! Creamos el contexto para el solver lineal call KSPCreate(PETSC_COMM_WORLD, ksp, ierr) ! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve ! tambien de matriz de precondicion. call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr) ! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. ! ! - Las 3 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions(). call KSPGetPC(ksp, pc, ierr) call PCSetType(pc, PCJACOBI, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_INTEGER, ierr) ! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> ! ! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores. call KSPSetFromOptions(ksp, ierr) ! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr) ! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)') call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Visualizamos el resultado: if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if !______________________________________________________________________________ ! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr) ! Visualizamos el resultado

182

Page 184: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****' call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if !______________________________________________________________________________ ! Chequeamos el error: call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr) ! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if (norma .gt. 1.e-12) then write(6,100) norma, its else write(6,200) its endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if ! Todos los procesos han de esperar a que el maestro realice el bloque ! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr) ! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP call KSPDestroy(ksp); call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr) call PetscFinalize(ierr) end !***************************************************************************** !* Fin de 'diag_dom.F90' * !*****************************************************************************/

183

Page 185: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

A continuación, el código fuente contenido en ‘diag_domN.F90’. !***************************************************************************** !* * !* Descripcion: Matriz llena en disposición de tablero de ajedrez * !* con diagonal dominante (multiplicada por N) * !* * !* Primero, resolveremos con KSP el sistema A·x = b1 * !* Posteriormente, realizaremos la multiplicacion A·x y * !* obtendremos b2. * !* Finalmente, comprobaremos que el valor de b1 y b2 es * !* aproximadamente el mismo en ambos casos (viendo la norma * !* del error) * !* * !* Opciones BD: -m : numero de filas por proceso * !* -i : 1=imprimir matriz y vectores, 0=no imprimir (elementos * !* grandes) * !* * !* Numero de procesadores: n * !* * !* Autor: ICR, 15/11/2006 * !* * !***************************************************************************** program main implicit none #include "include/finclude/petsc.h" #include "include/finclude/petscvec.h" #include "include/finclude/petscda.h" #include "include/finclude/petscis.h" #include "include/finclude/petscmat.h" #include "include/finclude/petscksp.h" #include "include/finclude/petscpc.h" KSP :: ksp PC :: pc Vec :: x, b1, b2 Mat :: A ! especificamos el valor por defecto de 'm' (numero de filas locales de la ! matriz paralela), por si no se especifica como argumento del programa ! (opcion de base de datos). PetscInt :: m = 10, N, Istart, Iend, i, j, k PetscInt :: nl, rstart, rend ! Con la variable 'PetscInt :: imprimir' controlamos si queremos o no ! que se impriman las matrices y vectores en el nodo maestro. Esto es ! util a la hora de trabajar con matrices grandes (para no imprimirlas). PetscInt :: imprimir = 1, dos = 2, d_nz, o_nz ! Parametros de GMRES PetscInt :: numvec = 100, its, maxits = 10000 PetscInt,ALLOCATABLE :: gindices(:) PetscScalar :: v, uno_neg = -1.0 PetscMPIInt :: rank, size PetscTruth :: flg PetscErrorCode :: ierr

184

Page 186: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

PetscScalar :: rand integer,parameter :: seed = 86456 PetscReal :: norma, tol = 1.d-7 ISLocalToGlobalMapping ltog

! Automaticamente llama a MPI_Init() call PetscInitialize(PETSC_NULL_CHARACTER, ierr) ! Obtenemos numero procesadores (procesos si secuencial) de que disponemos. call MPI_Comm_size(PETSC_COMM_WORLD, size, ierr) ! Obtenemos el numero de proceso local. call MPI_Comm_rank(PETSC_COMM_WORLD, rank, ierr) ! Consiguimos el valor entero para una opcion particular en la base de datos ! En este caso, 'm' es el numero de filas locales por proceso (todos ! mismo numero de filas). call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-m', m, flg, ierr) ! Con la opcion base de datos -i ..., si el valor es 0 no se imprimira nada ! en el nodo maestro; e.o.c. si lo hara. call PetscOptionsGetInt(PETSC_NULL_CHARACTER, '-i', imprimir, flg, ierr) ! Numero de columnas (numero global). N = m * size ! Creamos una matriz dispersa distribuida, especificando la dimension local, ! es decir, el numero de filas que pertenencen localmente a un proceso. ! La dimension de la submatriz local sera m*N, donde N = m*size (matriz ! cuadrada). La dimension de la matriz global sera M*N, donde ! N = M = m*size. ! ! Por defecto, la rutina MatCreate() crea una matriz dispersa AIJ, pero no ! realiza el prealojamiento de memoria, es por ello que es necesario llamar ! a la funcion MatCreateMPIAIJ(). La declaracion de dicha funcion es la ! siguiente: ! MatCreateMPIAIJ(MPI_Comm comm, PetscInt m, PetscInt n, PetscInt M, ! PetscInt N, PetscInt d_nz, PetscInt d_nnz, ! PetscInt o_nz, PetscInt o_nnz, Mat A, ! PetscErrorCode ierr) ! OJO con el prealojamiento: Numero de bloques m*m llenar por fila: ! ! d_nz = m ! ! size: par => o_nz = (size/2 -1) * m (el de la diagonal no se cuenta) ! ! | (size/2) * m si rank par ! size: impar => o_nz = | ! | (size/2 -1) * m si rank impar ! ! ! (size/2 es division entera) d_nz = m if(mod(size,dos) .eq. 0) then o_nz = (size/2 -1) * m else if(mod(rank,dos) .eq. 0) then o_nz = (size/2) * m else o_nz = (size/2 -1) * m end if

185

Page 187: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

end if call MatCreateMPIAIJ(PETSC_COMM_WORLD, m, PETSC_DECIDE, & PETSC_DETERMINE, N, d_nz, PETSC_NULL_INTEGER, & o_nz, PETSC_NULL_INTEGER, A, ierr) call MatSetFromOptions(A, ierr) ! Lo siguiente es importante al trabajar con Fortran call MatSetOption(A, MAT_COLUMN_ORIENTED) ! ! Actualmente, todos los formatos de matrices paralelas PETSc son ! particionadas por trozos contiguos de filas a traves de los procesadores. ! Es por esta razon por lo que debemos determinar que filas de la matriz son ! locales a cada proceso. Esto se hace con la funcion MatGetOwnershipRange() ! call MatGetOwnershipRange(A, Istart, Iend, ierr) ! Establecemos los valores para los elementos de la matriz con la funcion ! rand(), que genera numeros aleatorios. Cada procesador necesita insertar ! solamente los valores de los elementos locales que le pertenezcan, pero ! cualquier elemento no local introducido sera enviado al procesador ! apropiado durante el ensamblado de la matriz. ! Siempre hemos de referirnos a las filas y las columnas de la matriz con ! ordenacion global a la hora de insertar elementos. ! ! In order to get a different sequence each time, we initialize the ! seed of the random number function with the sum of the current ! hour, minute, and second. ! ! call itime(timeArray) ! Get the current time ! v = rand(timeArray(1) + timeArray(2) + timeArray(3)) call srand(seed) ! IMPORTANTE: aunque en Fortran los indices de vectores y matrices ! comienzan por '1', la PETSc esta implementada con C ! (indices empiezan por '0'). Es por esta razon por la ! cual el indice 'j' empieza en '0'. ! Proceso PAR => llenar bloques m*m PARES con numeros aleatorios ! Proceso IMPAR => llenar bloques m*m IMPARES con numeros aleatorios ! Bloques de la diagonal => multiplicar valores por N (dim. global matriz) ! Numeros de procesos = size = numeros de bloques en cada fila ! PROCESOS PARES if(mod(rank,dos) .eq. 0) then do i=Istart, Iend-1 k = 0 ! k indica el numero de bloque do j=0, N-1 ! k aumenta con cada m columnas(1 bloque) if(mod(j, m).eq.0 .AND. j.ne.0) k = k +1 ! En los procesos pares, llenamos los bloques pares if(mod(k,dos) .eq. 0) then v = rand() ! * (rank + 1) ! Los valores de la diagonal los escalamos con N

186

Page 188: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

if(k .eq. rank) v = v * N call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end if end do end do ! PROCESOS IMPARES else do i=Istart, Iend-1 k = 0 ! k indica el numero de bloque do j=0, N-1 ! k aumenta con cada m columnas(1 bloque) if(mod(j, m).eq.0 .AND. j.ne.0) k = k +1 ! En los procesos impares, llenamos los bloques impares if(mod(k,dos) .ne. 0) then v = rand() ! * (rank + 1) ! Los valores de la diagonal los escalamos con N if(k .eq. rank) v = v * N call MatSetValues(A, 1, i, 1, j, v, ADD_VALUES, ierr) end if end do end do end if ! Ensamblamos la matriz, utilizando las rutinas MatAssemblyBegin() y ! MatAssemblyEnd(). ! Se pueden realizar calculos en medio de las dos rutinas mientras se ! realiza el paso de mensajes. ! call MatAssemblyBegin(A, MAT_FINAL_ASSEMBLY, ierr) call MatAssemblyEnd(A, MAT_FINAL_ASSEMBLY, ierr) ! ! Creamos ahora los vectores paralelos. Cuando utilizamos VecCreate(), ! VecSetSizes() y VecSetFromOptions(), y solo especificamos la dimension ! global, el particionamiento paralelo es determinado en tiempo de ! ejecucion. ! ! El especificar la dimension global viene por la razon de que el vector ! sea compatible con las dimensiones de la matriz a la hora de realizar el ! producto. Primero creamos un vector desde cero y, posteriormente, lo ! duplicamos. ! call VecCreate(PETSC_COMM_WORLD, x, ierr) call VecSetSizes(x, PETSC_DECIDE, N, ierr) call VecSetFromOptions(x, ierr) call VecDuplicate(x, b1, ierr) call VecDuplicate(x, b2, ierr) ! ! Ahora haremos que cada procesador introduzca valores en su parte local ! del vector paralelo. Para ello, es necesario determinar la ordenacion ! local a global para el vector. Cada procesador genera una lista de indices ! globales para cada indice local. !

187

Page 189: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

! Si hubiesemos especificado el tamanyo local del vector en vez del global, ! necesitariamos obtener el tamanyo global de la forma: ! VecGetSize(x, N, ierr) ! ! Obtenemos el rango local del vector paralelo: call VecGetOwnershipRange(b1, rstart, rend, ierr) ! numero de elementos locales (rstart y rend son numeros de filas GLOBALES): nl = rend - rstart ALLOCATE ( gindices(nl) ) gindices(1) = rstart do i=1, nl-1, 1 gindices(i+1) = gindices(i) + 1 end do ! Mapeamos el primer y el ultimo punto como periodicos en el caso de rebasar ! los limites if ( gindices(1) .eq. -1) gindices(1) = N if ( gindices(nl) .eq. N+1) gindices(nl) = 1 ! Realizamos el mapeado de indices locales a globales, de modo que ! utilizamos los indices locales para introducir elementos y ! automaticamente se introducen en el vector paralelo con su ! correspondiente indice global mapeado. ! ! El comunicador a utilizar debe contener unicamente el proceso local call ISLocalToGlobalMappingCreate(PETSC_COMM_SELF,nl,gindices,ltog,ierr) call VecSetLocalToGlobalMapping(b1, ltog, ierr) call ISLocalToGlobalMappingDestroy(ltog, ierr) DEALLOCATE(gindices) ! CUALQUIER VECTOR DUPLICADO DE 'b1' DESDE ESTE PUNTO HEREDA EL MISMO MAPEADO ! Determinamos ahora los elementos del vector paralelo. ! ! - Cada procesador determina sus valores locales utilizando ordenacion ! local. ! - Cada procesador puede contribuir para cualquier entrada del vector ! paralelo, independientemente de a que procesador pertenezca el elemento. ! Cualquier contribucion no local sera transferida al procesador apropiado ! durante el proceso de ensamblado. ! - Para colocar elementos con la ordenacion global se utiliza VecSetValues() ! - El modo ADD_VALUES indica que todas las contribuciones seran anyadidas ! juntas. ! OJO: VecSetValuesLocal() utiliza indices basados en '0' en Fortran, como en C do i=0, nl-1, 1 v = rank + 1.0 call VecSetValuesLocal(b1, 1, i, v, INSERT_VALUES, ierr) end do ! Ensamblamos el vector paralelo, utilizando las rutinas VecAssemblyBegin() ! y VecAssemblyEnd(). Se pueden realizar calculos en medio de las dos ! rutinas mientras se realiza el paso de mensajes. call VecAssemblyBegin(b1, ierr) call VecAssemblyEnd(b1, ierr) ! ! Vemos la matriz y el vector. Omitir los siguientes pasos si son ! dimensiones grandes con la opcion -i 0, o imprimir a un fichero. !

188

Page 190: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Matriz paralela A: *****' call MatView(A, PETSC_VIEWER_STDOUT_WORLD, ierr) if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b1: *****' call VecView(b1, PETSC_VIEWER_STDOUT_WORLD, ierr) end if ! Resolvemos ahora el sistema lineal mediante KSP ! Creamos el contexto para el solver lineal call KSPCreate(PETSC_COMM_WORLD, ksp, ierr) ! Fijamos los operadores. Aqui la matriz que define el sistema lineal sirve ! tambien de matriz de precondicion. call KSPSetOperators(ksp,A,A,DIFFERENT_NONZERO_PATTERN, ierr) ! Ajustamos los valores por defecto del solver lineal para este problema (esto ! es opcional): ! - Extrayendo los contextos KSP y PC desde el contexto KSP, podemos llamar ! directamente a cualquier rutina KSP y PC para fijar varias opciones. ! ! - Las 4 siguientes sentencias son opcionales; todos estos parametros podrian ! ser especificados alternativamente en tiempo de ejecucion llamando a ! KSPSetFromOptions(). call KSPGetPC(ksp, pc, ierr) ! call PCSetType(pc, PCJACOBI, ierr) call PCSetType(pc, PCNONE, ierr) ! call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & ! PETSC_DEFAULT_DOUBLE_PRECISION, & ! PETSC_DEFAULT_INTEGER, ierr) call KSPSetTolerances(ksp, tol, PETSC_DEFAULT_DOUBLE_PRECISION, & PETSC_DEFAULT_DOUBLE_PRECISION, & maxits, ierr) ! Colocar las opciones en tiempo de ejecucion, por ejemplo: ! -ksp_type <type> -pc_type <type> -ksp_monitor -ksp_rtol <rtol> ! ! Estas opciones sustituiran aquellas definidas en las 3 sentencias anteriores. call KSPSetFromOptions(ksp, ierr) ! Ajustamos el numero de vectores del subespacio con la variable 'restart'. ! Esta sentencia ha de ir despues de la anterior, si no, no realiza nada. call KSPGMRESSetRestart(ksp, numvec, ierr) ! RESOLVEMOS EL SISTEMA LINEAL: call KSPSolve(ksp, b1, x, ierr) ! Visualizamos la informacion del solver; en vez de hacer esto, podriamos ! utilizar la opcion -ksp_view para imprimir esta informacion en pantalla ! cuando concluyan las operaciones llevadas a cabo por KSPSolve(). if(rank.eq.0) write(6,'(3/)')

189

Page 191: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) call KSPView(ksp, PETSC_VIEWER_STDOUT_WORLD, ierr) ! Visualizamos el resultado: ! Visualizamos el resultado: if(imprimir.ne.0) then if(imprimir.ne.0) then if(rank.eq.0) write(6,'(3/,T5,A,/)') & if(rank.eq.0) write(6,'(3/,T5,A,/)') & ' ***** Vector paralelo x (solucion): *****' ' ***** Vector paralelo x (solucion): *****' call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) call VecView(x, PETSC_VIEWER_STDOUT_WORLD, ierr) end if end if !______________________________________________________________________________ !______________________________________________________________________________ ! A continuacion realizamos la operacion Ax = b2: ! A continuacion realizamos la operacion Ax = b2: call MatMult(A, x, b2, ierr) call MatMult(A, x, b2, ierr) ! Visualizamos el resultado ! Visualizamos el resultado if(imprimir.ne.0) then if(imprimir.ne.0) then if(rank.eq.0) write(6,'(/,T5,A,/)') & if(rank.eq.0) write(6,'(/,T5,A,/)') & ' ***** Vector paralelo b2 (Ax=b2): *****' ' ***** Vector paralelo b2 (Ax=b2): *****' call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) call VecView(b2, PETSC_VIEWER_STDOUT_WORLD, ierr) end if end if !______________________________________________________________________________ !______________________________________________________________________________ ! Chequeamos el error: ! Chequeamos el error: call VecAXPY(b2, uno_neg, b1, ierr) call VecAXPY(b2, uno_neg, b1, ierr) call VecNorm(b2, NORM_2, norma, ierr) call VecNorm(b2, NORM_2, norma, ierr) call KSPGetIterationNumber(ksp, its, ierr) call KSPGetIterationNumber(ksp, its, ierr) ! Esto solamente lo realiza el proceso maestro ! Esto solamente lo realiza el proceso maestro if(rank.eq.0) then if(rank.eq.0) then if (norma .gt. 1.e-12) then if (norma .gt. 1.e-12) then write(6,100) norma, its write(6,100) norma, its else else write(6,200) its write(6,200) its endif endif 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 100 format(2/,'>>> Norma de error = ',E10.4, ', Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) 200 format(2/,'>>> Norma de error < 1.e-12, Iteraciones =', I5, /) end if end if ! Todos los procesos han de esperar a que el maestro realice el bloque ! Todos los procesos han de esperar a que el maestro realice el bloque ! anterior de sentencias ! anterior de sentencias call MPI_Barrier(PETSC_COMM_WORLD, ierr) call MPI_Barrier(PETSC_COMM_WORLD, ierr) ! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP ! Destruimos la matriz y los vectores paralelos, asi como el contexto KSP call KSPDestroy(ksp); call KSPDestroy(ksp); call MatDestroy(A, ierr) call MatDestroy(A, ierr) call VecDestroy(x, ierr) call VecDestroy(x, ierr) call VecDestroy(b1, ierr) call VecDestroy(b1, ierr) call VecDestroy(b2, ierr) call VecDestroy(b2, ierr) call PetscFinalize(ierr) call PetscFinalize(ierr) end end !***************************************************************************** !***************************************************************************** !* Fin de 'diag_domN.F90' !* Fin de 'diag_domN.F90' !*****************************************************************************/ !*****************************************************************************/

190 190

Page 192: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

191

Page 193: ESTUDIO DE LIBRERÍAS DE CÁLCULO CIENTÍFICO PARALELO Y ...bibing.us.es/proyectos/abreproy/11374/fichero... · Encontrar la solución de problemas de contacto es el principal objetivo

192