Optimización de las Comunicaciones Colectivas en Paso
Transcripción
Optimización de las Comunicaciones Colectivas en Paso
FACULTADE DE INFORMÁTICA UNIVERSIDADE DA CORUÑA Departamento de Electrónica e Sistemas Proyecto Fin de Carrera de Ingenierı́a Informática Optimización de las Comunicaciones Colectivas en Paso de Mensajes para Java en Sistemas Multi-core Autor: Sabela Ramos Garea Directores: Guillermo López Taboada Juan Touriño Domı́nguez A Coruña, 2 de julio de 2009 Especificación Tı́tulo: Optimización de las Comunicaciones Colectivas en Paso de Mensajes para Java en Sistemas Multi-core Clase: Investigación y Desarrollo Autor : Sabela Ramos Garea Director : Guillermo López Taboada Juan Touriño Domı́nguez Tribunal : Fecha de lectura: Calificación: i Agradecimientos Me gustarı́a dar las gracias a todos los que me han apoyado y han contribuido a que este proyecto haya salido adelante. Desde mis directores hasta mi familia, pasando por Pablo, Lucı́a, Chus y muchos otros que han estado conmigo cuando lo he necesitado. Muchas gracias. iii Resumen Este proyecto presenta el análisis, diseño e implementación de una biblioteca de operaciones colectivas en paso de mensajes para Java sobre sistemas multi-core. El objetivo perseguido es una mejora de la eficiencia de los códigos Java a la hora de explotar el paralelismo proporcionado por el aumento del número de cores en los microprocesadores actuales y el auge de las arquitecturas clúster. Estos dos últimos factores, unidos al uso extendido del lenguaje Java, llevan a la necesidad de incrementar el rendimiento, lo cual es posible mediante la adaptación de los patrones de comunicación en estos sistemas. El hecho de utilizar el lenguaje Java no se debe solamente a su amplia difusión, sino que viene reforzado por la inclusión, en el núcleo del lenguaje, de un completo soporte multithread y de comunicaciones en red. Por ello, la programación mediante el paradigma de paso de mensajes, el más utilizado en computación de altas prestaciones (High Performance Computing, HPC), en Java es una alternativa emergente a la hora de trabajar en arquitecturas clúster con nodos multi-core. En este escenario la eficiencia de las operaciones colectivas es crı́tica a la hora de obtener rendimientos escalables en Java, siendo crucial la optimización de las primitivas colectivas de paso de mensajes. Para la realización del proyecto, buscando un diseño portable, se han utilizado como base las primitivas de comunicación punto a punto del estándar de paso de mensajes en Java, MPJ (Message Passing in Java), y se han desarrollado una serie de algoritmos con los que se pretende mejorar el rendimiento de las comunicaciones colectivas en sistemas multi-core. La evaluación experimental de la biblioteca desarrollada ha mostrado aumentos significativos del rendimiento en un clúster multi-core, de hasta dos órdenes de magnitud, comparados con otras bibliotecas existentes. Además, se proporciona un v mecanismo automático de selección de algoritmos, adaptable a distintas configuraciones de número de procesos y de tamaño de mensaje. La portabilidad de la biblioteca de operaciones colectivas desarrollada se ha puesto de manifiesto con su integración en MPJ Express, una implementación del estándar MPJ muy extendida, permitiendo su uso a una amplia comunidad de desarrolladores en Java para paso de mensajes. Palabras Clave Java, Paso de Mensajes, Sistemas Multi-core, Clúster, MPJ, Comunicaciones Colectivas. Índice general 1. Introducción 1 1.1. Motivación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2. Programación mediante Paso de Mensajes en Java . . . . . . . . . . . . 3 1.2.1. Soluciones Wrappers Basadas en JNI . . . . . . . . . . . . . . . . 5 1.2.2. Implementaciones Java Puro (Java 100 %) . . . . . . . . . . . . . 6 1.2.3. Implementaciones Hı́bridas (Comunicaciones Java Puro/Nativas) 7 1.2.4. Extensiones del Lenguaje Java . . . . . . . . . . . . . . . . . . . 8 1.3. Acerca de esta Memoria . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ 11 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ . . . . . . . . 11 2.1.1. Descripción de las Operaciones Colectivas . . . . . . . . . . . . . 11 2.2. Diseño de Operaciones Colectivas en MPJ . . . . . . . . . . . . . . . . . 27 2.2.1. Estructura General de las Bibliotecas MPJ . . . . . . . . . . . . 27 2.2.2. Integración de la Biblioteca Desarrollada . . . . . . . . . . . . . . 30 2.2.3. Primitivas Básicas Utilizadas en la Implementación . . . . . . . . 31 3. Implementación de Primitivas Colectivas MPJ 3.1. Algoritmos Implementados 35 . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.1.1. Broadcast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 3.1.2. Gather y Gatherv . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.1.3. Scatter y Scatterv . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.1.4. Allgather y Allgatherv . . . . . . . . . . . . . . . . . . . . . . . . 46 vii ÍNDICE GENERAL viii 3.1.5. Alltoall y Alltoallv . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.1.6. Reduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.1.7. Allreduce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.1.8. Reduce-Scatter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.1.9. Scan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.1.10. Barrier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 4. Integración y Optimización en Sistemas Multi-core 63 4.1. MPJ Express como Marco para la Integración . . . . . . . . . . . . . . . 63 4.1.1. Correcciones sobre MPJ Express . . . . . . . . . . . . . . . . . . 64 4.2. Integración y Selección Automática de Algoritmos . . . . . . . . . . . . 66 4.3. Código de Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.4. Incremento de la Escalabilidad en Operaciones Colectivas . . . . . . . . 72 5. Evaluación del Rendimiento 75 5.1. Configuración Experimental . . . . . . . . . . . . . . . . . . . . . . . . . 75 5.2. Análisis del Rendimiento de la Biblioteca de Colectivas Implementada . 75 5.3. Análisis Comparativo del Rendimiento de Colectivas MPJ vs. MPI . . . 81 5.4. Resultados con una Aplicación Java HPC (jGadget) . . . . . . . . . . . 85 6. Principales Aportaciones y Conclusiones 87 6.1. Principales Aportaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 6.2. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 A. Planificación 91 B. Resultados Experimentales Adicionales 97 Índice de figuras 2.1. Funcionamiento de la colectiva Broadcast . . . . . . . . . . . . . . . . . 13 2.2. Funcionamiento de la colectiva Scatter . . . . . . . . . . . . . . . . . . . 14 2.3. Funcionamiento de la colectiva Gather . . . . . . . . . . . . . . . . . . . 16 2.4. Funcionamiento de la colectiva Allgather . . . . . . . . . . . . . . . . . . 19 2.5. Funcionamiento de la colectiva Reduce . . . . . . . . . . . . . . . . . . . 20 2.6. Funcionamiento de la colectiva Allreduce . . . . . . . . . . . . . . . . . . 22 2.7. Funcionamiento de la colectiva Reduce-scatter . . . . . . . . . . . . . . . 23 2.8. Funcionamiento de la colectiva Alltoall . . . . . . . . . . . . . . . . . . . 24 2.9. Funcionamiento de la colectiva Scan . . . . . . . . . . . . . . . . . . . . 26 2.10. Clases principales en MPJ . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.1. Pseudocódigo de Broadcast bFT . . . . . . . . . . . . . . . . . . . . . . 36 3.2. Pseudocódigo de Broadcast nbFT . . . . . . . . . . . . . . . . . . . . . . 37 3.3. Funcionamiento de Broadcast en MPJ Express . . . . . . . . . . . . . . 38 3.4. Pseudocódigo de Broadcast con Minimum Spanning Tree (MST) . . . . 39 3.5. Funcionamiento de Broadcast MST . . . . . . . . . . . . . . . . . . . . . 39 3.6. Pseudocódigo de Gather nbFT . . . . . . . . . . . . . . . . . . . . . . . 41 3.7. Pseudocódigo de Gather FT . . . . . . . . . . . . . . . . . . . . . . . . . 42 3.8. Pseudocódigo de Gather con Minimum Spanning Tree (MST) . . . . . . 43 3.9. Funcionamiento de Gather MST . . . . . . . . . . . . . . . . . . . . . . 43 3.10. Pseudocódigo de Scatter nbFT . . . . . . . . . . . . . . . . . . . . . . . 44 3.11. Pseudocódigo de Scatter con Minimum Spanning Tree (MST) . . . . . . 45 3.12. Funcionamiento de Scatter MST . . . . . . . . . . . . . . . . . . . . . . 45 ix x ÍNDICE DE FIGURAS 3.13. Pseudocódigo de Allgather nbFT . . . . . . . . . . . . . . . . . . . . . . 46 3.14. Pseudocódigo Allgather BDE . . . . . . . . . . . . . . . . . . . . . . . . 47 3.15. Funcionamiento de Allgather BDE . . . . . . . . . . . . . . . . . . . . . 48 3.16. Pseudocódigo de Allgather BKT . . . . . . . . . . . . . . . . . . . . . . 48 3.17. Funcionamiento de Allgather BKT . . . . . . . . . . . . . . . . . . . . . 49 3.18. Pseudocódigo Alltoall nbFT . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.19. Pseudocódigo de Alltoall bFT . . . . . . . . . . . . . . . . . . . . . . . . 51 3.20. Pseudocódigo de Alltoall nb1FT . . . . . . . . . . . . . . . . . . . . . . 51 3.21. Pseudocódigo de Alltoall nb2FT . . . . . . . . . . . . . . . . . . . . . . 51 3.22. Pseudocódigo de Reduce nbFT . . . . . . . . . . . . . . . . . . . . . . . 53 3.23. Pseudocódigo de Reduce bFT . . . . . . . . . . . . . . . . . . . . . . . . 53 3.24. Pseudocódigo de Reduce con Minimum Spanning Tree (MST) . . . . . . 54 3.25. Funcionamiento de Reduce MST . . . . . . . . . . . . . . . . . . . . . . 54 3.26. Pseudocódigo de Allreduce nbFT . . . . . . . . . . . . . . . . . . . . . . 55 3.27. Pseudocódigo de Allreduce BDE . . . . . . . . . . . . . . . . . . . . . . 56 3.28. Funcionamiento de Allreduce BDE . . . . . . . . . . . . . . . . . . . . . 56 3.29. Pseudocódigo de Reduce-Scatter BDE . . . . . . . . . . . . . . . . . . . 58 3.30. Funcionamiento de Reduce-Scatter BDE . . . . . . . . . . . . . . . . . . 59 3.31. Pseudocódigo de Reduce-Scatter BKT . . . . . . . . . . . . . . . . . . . 59 3.32. Reduce-Scatter BKT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.33. Pseudocódigo de Scan nbFT . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.34. Pseudocódigo de Scan Secuencial . . . . . . . . . . . . . . . . . . . . . . 61 3.35. Funcionamiento de la Barrera usando un árbol binario . . . . . . . . . . 62 4.1. Clases principales en MPJ . . . . . . . . . . . . . . . . . . . . . . . . . . 66 5.1. Resultados experimentales de Broadcast con 32 cores . . . . . . . . . . . 77 5.2. Resultados experimentales de Scatter con 32 cores . . . . . . . . . . . . 78 5.3. Resultados experimentales de Allgather con 32 cores . . . . . . . . . . . 79 5.4. Resultados experimentales de Alltoall con 32 cores . . . . . . . . . . . . 79 5.5. Resultados experimentales de Reduce con 32 cores . . . . . . . . . . . . 80 ÍNDICE DE FIGURAS xi 5.6. Resultados experimentales de Allreduce con 32 cores . . . . . . . . . . . 80 5.7. Comparación MPI y MPJ para Broadcast con 32 cores . . . . . . . . . . 82 5.8. Comparación MPI y MPJ para Scatter con 32 cores . . . . . . . . . . . 82 5.9. Comparación MPI y MPJ para Allgather con 32 cores . . . . . . . . . . 83 5.10. Comparación MPI y MPJ para Alltoall con 32 cores . . . . . . . . . . . 83 5.11. Comparación MPI y MPJ para Reduce con 32 cores . . . . . . . . . . . 84 5.12. Comparación MPI y MPJ para Allreduce con 32 cores . . . . . . . . . . 84 5.13. Resultados obtenidos con la aplicación jGadget . . . . . . . . . . . . . . 86 A.1. Actividades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 A.2. Diagrama de Gantt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 A.3. Detalle de planificación de realización de colectivas . . . . . . . . . . . . 95 B.1. Comparación de algoritmos para Broadcast con 16 cores . . . . . . . . . 99 B.2. Comparación de algoritmos para Scatter con 16 cores . . . . . . . . . . . 100 B.3. Comparación de algoritmos para Gather con 16 cores . . . . . . . . . . . 101 B.4. Comparación de algoritmos para Allgather con 16 cores . . . . . . . . . 102 B.5. Comparación de algoritmos para Alltoall con 16 cores . . . . . . . . . . 103 B.6. Comparación de algoritmos para Reduce con 16 cores . . . . . . . . . . 104 B.7. Comparación de algoritmos para Reduce-Scatter con 16 cores . . . . . . 105 B.8. Comparación de algoritmos para Allreduce con 16 cores . . . . . . . . . 106 B.9. Comparación de algoritmos para Scan con 16 cores . . . . . . . . . . . . 107 Índice de cuadros 3.1. Algoritmos para primitivas colectivas en bibliotecas MPJ. . . . . . . . . 40 4.1. Ejemplo de Configuración de Algoritmos para las Operaciones Colectivas 70 A.1. Coste estimado de realización del proyecto. . . . . . . . . . . . . . . . . xiii 92 Capı́tulo 1 Introducción 1.1. Motivación La imposibilidad de seguir incrementando el rendimiendo de un procesador monocore, dado su elevado consumo, ha conducido en los últimos años al aumento de capacidad de procesamiento en base al incremento del número de procesadores presentes en una máquina y/o el número de máquinas que cooperan entre sı́ para llevar a cabo un trabajo común. Debido al amplio uso de este tipo de arquitecturas multi-core, es necesario utilizar paradigmas de programación que las tengan en cuenta y sean capaces de aprovechar sus ventajas. Aparecen ası́ dos aproximaciones, dependiendo de si los procesadores implicados comparten o no memoria. En el caso de arquitecturas de múltiples procesadores con memoria compartida, la programación se basa en la comunicación de los diferentes threads o procesos mediante la lectura de variables compartidas situadas en el espacio global de direcciones. Además, es necesaria la sincronización externa de los procesos usando semáforos, secciones crı́ticas, etc. Un ejemplo de este paradigma de programación serı́a OpenMP, que permite, mediante el uso de directivas que se pueden incluir en código fuente Fortran o C, trabajar con memoria compartida a través del reparto de trabajo entre los distintos threads y su gestión (creación, destrucción, sincronización, etc). Cuando hablamos de memoria distribuida, es decir, cuando no hay un espacio global de direcciones, sino que cada procesador mantiene una memoria local y privada, la co1 2 1. Introducción municación y sincronización se realiza mediante el intercambio de mensajes entre ellos. El estándar en este caso es MPI (Message Passing Interface) [1], que proporciona una serie de llamadas a primitivas de paso de mensajes que se pueden incluı́r en programas escritos en Fortran o C. El paradigma de memoria compartida permite una programación más sencilla, mientras que, en memoria distribuida, el programador debe manejar explı́citamente toda las comunicaciones entre procesadores. Además, en el segundo caso, es necesario un reparto inicial del trabajo y de los datos, lo que puede implicar replicación de los mismos, ya que cada procesador sólo puede acceder a su memoria local. Sin embargo, este lı́mite en el acceso puede ser beneficioso, pues el uso de memoria compartida da lugar a que, al aumentar el número de procesadores, se incremente el número de accesos a memoria que entren en conflicto y, por lo tanto, disminuya la escalabilidad. En un modelo de memoria distribuida, la localidad de los datos proporciona rendimientos más escalables. Sobre estos dos tipos básicos de modelos de memoria, aparecen variaciones cuyo objetivo es aunar ventajas e incrementar la eficiencia, como son las arquitecturas de memoria fı́sicamente distribuida y lógicamente compartida, que pretenden mantener las ventajas de una arquitectura de memoria distribuida (sobre todo, escalabilidad) a la vez que proporciona la facilidad de programación de las arquitecturas de memoria compartida. Sin embargo, en algunos casos, dependiendo de la implementación de esta solución, es más eficiente utilizar también programación mediante paso de mensajes. Con la aparición de los procesadores multi-core y su utilización en clusters, resulta de sumo interés combinar la programación paralela a nivel de nodo con memoria compartida, con la programación mediante paso de mensajes a nivel de cluster. Como Java implementa los threads de modo nativo, una biblioteca de paso de mensajes sobre Java resulta de gran utilidad. Además, Java presenta numerosas ventajas frente a alternativas más tradicionales en programación paralela (C y Fortran), como seguridad y robustez, portabilidad, expresividad, sencillez, gestión automática de memoria, orientación a objetos, etc., lo que le ha llevado a convertirse en una de las plataformas más extendidas en la actualidad. No obstante, en ámbitos donde el rendimiento es crı́tico, como en computación de altas prestaciones (High Performance Computing, HPC), no 1.2. Programación mediante Paso de Mensajes en Java 3 es tan popular, aunque su rendimiento se ha ido incrementando significativamente al pasar de una ejecución interpretada a la compilación a código nativo en tiempo de ejeTM cución realizada por compiladores JIT (Just-In-Time) y máquinas virtuales HotSpot . Ası́, hoy en dı́a Java alcanza rendimientos similares a los del código nativo, siendo una alternativa competitiva en HPC. A pesar de que son varias las implementaciones de bibliotecas de paso de mensajes disponibles para Java [2], cada una incluyendo su propia biblioteca de operaciones colectivas, éstas suelen ofrecer un rendimiento limitado. Esto es debido, especialmente, a que implementan algoritmos poco escalables y a que no aprovechan las particularidades del sistema sobre el que se ejecutan. La implementación y selección de algoritmos eficientes para primitivas colectivas en tiempo de ejecución ya fue objeto de estudio y discusión en bibliotecas nativas [3] [4] [5] [6], pero nunca hasta la fecha en Java. El objetivo de la biblioteca desarrollada es mejorar la eficiencia de las primitivas colectivas de paso de mensajes mediante el uso de algoritmos escalables, seleccionables en tiempo de ejecución, proporcionando mayor eficiencia en operaciones colectivas en Java. 1.2. Programación mediante Paso de Mensajes en Java En los lenguajes compilados a código nativo (C o Fortran), MPI es la interfaz estándar para bibliotecas de paso de mensajes. En cuanto a Java, existen numerosas bibliotecas de paso de mensajes [2], aunque la mayorı́a ha optado por implementar su propia API, similar a la de MPI. Las dos propuestas más relevantes para tener un API estándar son mpiJava 1.2 API [7] y JGF MPJ (Message-Passing interface for Java) API [8] [9] [10] propuesta por el Java Grande Forum (JGF) [10] para estandarizar un API similar a MPI en Java. Las principales diferencias entre estas dos APIS aparecen en las convenciones de nombrado de variables y métodos. Java es un lenguaje con un desarrollo orientado a las redes de computadores, cosa bastante obvia si nos fijamos en la posibilidad de programar directamente con sockets usando el API del lenguaje, la invocación de métodos remotos (RMI) o los métodos de serialización de objetos, permitiendo el envı́o de los mismos a través de la red y la carga dinámica de clases. 4 1. Introducción De los mecanismos presentes en el API de Java, podemos destacar dos, debido a su importancia en la implementación del paradigma de paso de mensajes, además de la programación con sockets, que son JNI (Java Native Interface) y RMI (Remote Method Invocation). JNI, o interfaz nativa de Java, es un mecanismo bidireccional de conexión entre Java y código nativo. Esto permite que podamos invocar código Java desde aplicaciones escritas en otros lenguajes como C, o bien, utilizar código nativo desde un código Java. En su segunda forma de uso, permite, por ejemplo, invocar código más eficiente en zonas de rendimiento crı́tico. No obstante, hay que tener en cuenta que puede tener consecuencias sobre la seguridad. RMI, o la invocación de métodos remotos, es lo que nos permite invocar métodos de objetos creados en otras máquinas y, por lo tanto, el manejo distribuido de los objetos, evitando el uso de sockets. Es necesario definir interfaces para indicar cuáles son los métodos que es posible invocar y el registro del objeto como remoto. Por lo tanto, para la implementación de soluciones de paso de mensajes en Java tenemos tres opciones: Java RMI, JNI, o bibliotecas de sockets en Java. Cada solución presenta sus propias ventajas e inconvenientes. El uso de Java RMI, al ser una aproximación 100 % Java, asegura portabilidad, pero puede no ser la solución más eficiente, especialmente en presencia de redes de altas prestaciones como Infiniband o Myrinet. En cuanto al uso de JNI, puede presentar problemas de portabilidad, aunque normalmente proporciona altos rendimientos. Finalmente, la utilización de sockets en Java requiere un gran esfuerzo de desarrollo, sobre todo para proporcionar soluciones escalables, pero permite implementaciones 100 % Java mucho más eficientes que las bibliotecas basadas en RMI. A pesar de que la mayorı́a del middleware de comunicaciones existente en Java está basado en RMI, las bibliotecas de paso de mensajes en Java, buscando mayor eficiencia, se han decantado, sobre todo en los últimos años, por las otras dos aproximaciones (wrapper y basada en sockets). 1.2. Programación mediante Paso de Mensajes en Java 1.2.1. 5 Soluciones Wrappers Basadas en JNI JavaPVM [11] ofrece una interfaz similar a PVM e invoca las funciones del mismo utilizando JNI. El proyecto parece llevar inactivo bastante tiempo (al menos, desde 1998). mpiJava [7] [12] [13] [14] es una interfaz Java para MPI desarrollada por el NPAC (Northeast Parallel Architectures Center) de la Universidad de Syracusa en EEUU. En la actualidad, forma parte de HPJava, un entorno para programación cientı́fica y orientado al paralelismo de datos que está basado en una versión extendida de Java. mpiJava está implementado con JNI para acceder al código nativo de MPI. La última versión incluida en HPJava data del 2003, aunque a partir de marzo del 2007 se comenzó un proceso de liberación para que la comunidad se encargue de mejorarla. La especificación de MPJ, referente para las implementaciones de paso de mensajes en Java, realizada por el Message Passing Working Group (del Java Grande Forum), se ha basado fuertemente en mpiJava. JavaMPI [9] [13] es otra implementación que utiliza JNI (o mejor dicho, su predecesor NMI) sobre MPI. Soportaba LAM-MPI o MPICH. El proyecto fue abandonado en 1998. M-JavaMPI [13] es un proyecto no disponible al público. Su objetivo era proveer de un marco de desarrollo para aplicaciones MPI con tolerancia a fallos y soporte para balanceo de carga dinámico. Al igual que los anteriores, utiliza código nativo de MPI, pero su forma de enlace es diferente, ya que se hace mediante demonios y memoria compartida. Ha dado lugar al proyecto G-JavaMPI para Grid. JavaWMPI [13] proporciona una interfaz sobre WMPI (implementación de MPI para Windows). 6 1. Introducción 1.2.2. Implementaciones Java Puro (Java 100 %) JPVM [15] presenta una interfaz similar a la que proporciona PVM para C y Fortran. Aquı́ las comunicaciones se basan en la utilización directa de sockets. El proyecto fue desarrollado en la Universidad de Virginia y permanece inactivo desde Febrero de 2000. P2P-MPI [16] [17] es un proyecto de software libre actualmente activo que sigue las especificaciones de MPJ. Presenta tolerancia a fallos ya que permite ejecutar tareas de forma redundante. Puede usarse tanto en clústers como en grids. JMPI [13] aparece con fines académicos en la Universidad de Massachussets. Cumple las especificaciones de MPJ con la excepción de que no implementa primitivas colectivas de reducción. Utiliza RMI y serialización de objetos. JMPI [13] [18], con el mismo nombre que la anterior, es una aproximación comercial de MPI Software Technology (actualmente Verari). MPIJ [13] [9] formó parte del proyecto DOGMA (Distributed Object Group Metacomputing Architecture) hasta la versión 2.0, e implementaba un gran subconjunto de MPJ. CCJ [13] es un proyecto que incluye varias caracterı́sticas de MPI pero sin seguir la especificación MPJ en detalle. Diverge de MPJ en que intenta ser “más natural” para Java, integrándose con los threads y objetos, en lugar de trabajar con arrays. Utiliza RMI y para mejorar el rendimiento se apoya en el sistema de compilación de Manta y en Manta RMI. La última versión es del 2001. PJMPI [13] es una biblioteca que sigue el estándar MPJ. Fue desarrollado en la Universidad de Adelaida conjuntamente al entorno de paso de mensajes JUMP que le sirve de base. jmpi [13] [19] es una implementación ya abandonada que utiliza JPVM. Parallel Java [20] [21], o PJ, es una API y middleware para programación paralela 1.2. Programación mediante Paso de Mensajes en Java 7 en memoria compartida y/o distribuida desarrollado en el Rochester Institute of Technology como un proyecto de software libre. Jcluster [22] [23] proporciona un entorno de programación paralela en Java. Balancea la carga de forma automática con un algoritmo aleatorio y presenta interfaces de paso de mensajes similares a PVM y MPI. Permite el uso de múltiples nodos y utiliza UDP como protocolo de comunicación. MPJava [24], diseñado en la Universidad de Maryland, proporciona una interfaz basada en MPJ que utiliza las capacidades de I/O mejoradas usando el paquete java.nio (Java New I/O). JOPI [25] [26] (Java Object-Passing Interface) proporciona un interfaz similar a MPI para intercambiar objetos entre procesos. 1.2.3. Implementaciones Hı́bridas (Comunicaciones Java Puro/Nativas) Este tipo de implementaciones pueden ser usadas con comunicaciones Java puro o delegando en implementaciones nativas. MPJ Express [27] [28] es una implementación MPJ desarrollada en la Universidad de Reading. Utiliza Java NIO y Myrinet en comunicaciones, con la posibilidad de cambiar el protocolo de comunicación en tiempo de ejecución. La implementación inicial se basaba en mpiJava, con su misma API. El soporte para el API de MPJ se proporcionó en versiones posteriores. MPJ/Ibis [29] es parte del proyecto Ibis, un proyecto de software libre de la Vrije Universiteit de Amsterdam, cuyo objetivo es crear una plataforma eficiente para la computación distribuida. MPJ/Ibis presenta una implementación de MPJ en Java usando como base la capa de comunicaciones IPL (Ibis Portability Layer). Al igual que MPJ Express, presenta la posibilidad de delegar en implementaciones nativas MPI (actualmente compatible con GM para redes Myrinet). F-MPJ (Fast MPJ) [30] es un proyecto del Grupo de Arquitectura de Computadores de la Universidad de A Coruña para cubrir la necesidad de una biblioteca de comunicaciones no bloqueantes eficientes en Java. Implementa un subconjunto de 8 1. Introducción MPJ utilizando sockets y puede evitar la serialización si utiliza Java Fast Sockets (JFS) [31]. 1.2.4. Extensiones del Lenguaje Java Hay otro tipo de proyectos que no pretenden desarrollar una interfaz de paso de mensajes en Java, sino extender el lenguaje para su utilización en computación de altas prestaciones. Entre estos, podrı́amos citar JavaNOW [32] [33], que presenta un entorno de trabajo en redes de ordenadores y que provee primitivas de comunicaciones colectivas, multithreading, etc. Los mismos objetivos se buscan con el entorno IceT [34], de la Emory University de Atlanta, que pretende explotar la portabilidad y las ventajas de Java añadiendo técnicas de computación concurrente y distribuida. Otro de estos proyectos es JavaParty [35], que extiende las capacidades de Java para utilizarlo en entornos de computación distribuida utilizando clases remotas sin RMI. El entorno JavaParty podrı́a ser visto como una JVM (Java Virtual Machine) distribuida. El gran número de proyectos realizados (y en actual desarrollo) en Java para paso de mensajes, hace patente el interés en este paradigma de programación en Java. Sin embargo, una caracterı́stica común a todos ellos es que el objetivo fundamental es proporcionar una interfaz de paso de mensajes relegando la optimización del rendimiento a un segundo plano. Por esta razón, este proyecto se centrará en el desarrollo de una biblioteca de primitivas colectivas de paso de mensajes en Java utilizando algoritmos eficientes y escalables, buscando además la portabilidad de las mismas, es decir, que sean utilizables en el mayor número de bibliotecas de paso de mensajes existentes, siempre y cuando sigan el estándar MPJ del Java Grande Forum [8] [9] [10]. 1.3. Acerca de esta Memoria Esta memoria se compone de los siguientes apartados: 1.3. Acerca de esta Memoria 9 Introducción : Breve descripción de los objetivos, contextualización del trabajo y motivación. Análisis y Diseño de la Biblioteca de Colectivas MPJ : Análisis detallado del estándar MPJ en cuanto a primitivas colectivas, primitivas punto a punto necesarias en la implementación y estructura de una biblioteca MPJ completa. A continuación, se describe el diseño de la integración de la biblioteca de primitivas basada en el estándar. Implementación de Primitivas Colectivas MPJ : Definición y discusión de los algoritmos implementados para cada función colectiva. Integración y Optimización en Sistemas Multi-core : Descripción de cómo se ha llevado a cabo la integración de las funciones colectivas en una biblioteca MPJ existente, optimización de comunicaciones en clusters multi-core y selección de algoritmos en tiempo de ejecución. Evaluación del Rendimiento : Benchmarking de la biblioteca y análisis de los resultados obtenidos en comparación con MPI y con una implementación MPJ existente. Rendimiento obtenido con una aplicación Java HPC utilizada en Cosmologı́a (jGadget). Principales Aportaciones y Conclusiones : Recopilación de las novedades más relevantes que aporta este proyecto y conclusiones. Anexo: Planificación y Memoria Económica : Planificación temporal y estimación del coste del trabajo. Anexo: Resultados Experimentales Adicionales : Resultados previos obtenidos en el computador Finis Terrae del Cesga. Capı́tulo 2 Análisis y Diseño de la Biblioteca de Colectivas MPJ 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ En las bibliotecas de paso de mensajes, además de las funciones básicas de comunicación punto a punto, es decir, envı́os y recepciones que implican únicamente a dos procesos (emisor y receptor), existen una serie de funciones que permiten englobar en una llamada patrones de comunicación complejos. Estas operaciones, conocidas como “colectivas”, implementan casos habituales de intercambio de información entre más de dos procesos. Ya que la eficiencia que presentan estas funciones en las bibliotecas actuales de MPJ es limitada y su uso es muy frecuente, nos centraremos en su optimización. Además, debido a que el estándar de operaciones colectivas en paso de mensajes es ampliamente utilizado y satisface las necesidades de los desarrolladores, se estima que no es necesaria una extensión de su funcionalidad. 2.1.1. Descripción de las Operaciones Colectivas En primer lugar, veremos una descripción de las funciones colectivas implementadas, tanto de relocalización o movimiento de datos (Broadcast, Scatter, Scatterv, Gather, 11 12 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ Gatherv, Allgather, Allgatherv, Alltoall y Alltoallv), como de computación o reducción (Reduce, Allreduce, Reduce-scatter y Scan). Las primeras se limitan a realizar la transferencia de datos entre procesos mientras que las segundas aplican una determinada operación a los datos una vez que se ha realizado la oportuna relocalización de los mismos. Finalmente, se incluye la primitiva colectiva Barrier que realiza una sincronización de procesos. Más adelante, analizaremos diferentes algoritmos para cada una de ellas. Las primitivas colectivas se utilizan dentro de un contexto formado por varios procesos. Este contexto recibe el nombre de Comunicador. El Comunicador representa un grupo de procesos que intercambian información. La forma de indicar cuál es el Comunicador en el que se va a realizar la operación es expresar la primitiva colectiva como método del Comunicador a utilizar. Es decir, suponiendo que nuestro Comunicador es Comm, la llamada a una barrera serı́a de la forma Comm.Barrier(). Cada descripción viene acompañada de un dibujo explicativo, como el de la Figura 2.1, excepto en la colectiva Barrier, puesto que su funcionamiento es muy intuitivo. En cada diagrama, los cuadros de colores dispuestos en vertical representan las posiciones de un vector. Los cuadros dispuestos en fila dentro de un mismo procesador (Pi ) representan una misma posición del vector resultado de aplicar una operación de reducción sobre esos datos (como ejemplo, ver Figura 2.5). También aparece, con cada colectiva, la firma del método tal y como se especifica en el estándar MPJ [9] junto a la descripción de cada atributo y de su funcionamiento. Esta descripción es una aportación del presente proyecto, buscando proporcionar una adecuada documentación para la biblioteca desarrollada. Se ha redactado en inglés debido a que se ha integrado en una implementación de MPJ (MPJ Express) con amplia difusión internacional. Broadcast : implementa el envı́o de un mensaje desde un proceso raı́z al resto de procesos. Al terminar, todos los procesos poseen una copia de ese mismo mensaje. 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ 13 Figura 2.1: Funcionamiento de la colectiva Broadcast La documentación que se ha redactado para ser incluida en la biblioteca desarrollada es la siguiente: public void Bcast( java.lang.Object mpi.Datatype datatype, int buf, int offset, int count, root ) Usage • It broadcasts a message from the process with rank root to all processes of the group, itself included. It is called by all members of the group using the same arguments. On return, the contents of root’s communication buffer have been copied to all processes. The type signature of count, datatype on any process must be equal to the type signature of count, datatype at the root. buf inout buffer array offset initial offset in buffer count number of items in buffer datatype datatype of each item in buffer root rank of broadcast root Scatter : al igual que la anterior, el emisor es un único proceso, llamado raı́z, y todos los demás actúan de receptores. La diferencia estriba en que, en el caso del Broadcast, todos reciben el mismo mensaje; mientras que, en el Scatter, a cada proceso llega un fragmento del mensaje enviado. Los fragmentos son enviados por orden, de forma que el n-ésimo proceso recibe el n-ésimo trozo de mensaje. Hay una variante (Scatterv) en la que el tamaño del mensaje enviado a cada nodo puede 14 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ ser diferente. Además, en esta variante, se permite especificar el desplazamiento de cada fragmento con respecto a la posición del primer elemento del mensaje. Figura 2.2: Funcionamiento de la colectiva Scatter Las firmas y documentación de los métodos se describen a continuación. public void Scatter( java.lang.Object int sendcount, mpi.Datatype int recvoffset, int sendbuf, int sendoffset, sendtype, java.lang.Object recvcount, mpi.Datatype recvbuf, recvtype, int root ) Usage • Scatter is the inverse of the operation Gather . It is similar to Bcast but every process receives different data. The root sends a message which is split into n equal segments and the ith segment is received by the ith process in the group. The type signature associated with sendcount, sendtype at the root must be equal to the type signature associated with recvcount, recvtype at all processes. The argument root must have identical values on all processes. 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ 15 sendbuf send buffer array (significant only at root) sendoffset initial offset in send buffer sendcount number of items to send to each process (significant only at root sendtype datatype of each item in send buffer (significant only at root) recvbuf receive buffer array recvoffset initial offset in receive buffer recvcount number of items to receive recvtype datatype of each item in receive buffer root rank of sending process public void Scatterv( java.lang.Object sendbuf, int int [] sendcount, int [] displs, mpi.Datatype java.lang.Object mpi.Datatype recvbuf, int recvtype, int recvoffset, int sendoffset, sendtype, recvcount, root ) Usage • Scatterv is the inverse of the operation Gatherv. It extends the operation Scatter by allowing sending different counts of data to each process, since sendcounts is now an array. It also allows more flexibility as to where the data is taken from on the root, by providing a new argument, displs. The type signature implied by sendcount[i], sendtype at the root must be equal to the type signature implied by recvcount, recvtype at process i. 16 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ sendbuf send buffer array (significant only at root) sendoffset initial offset in send buffer sendcounts number of items to send to each process displs displacements from which to take outgoing data to each process sendtype datatype of each item in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer recvcount number of items to receive recvtype datatype of each item in receive buffer root rank of sending process Gather : es la operación inversa a Scatter. Todos los procesos envı́an a un único receptor o raı́z. Este proceso raı́z recoge los mensajes que le envı́a cada uno de los demás y compone un único objeto destino ordenando los mensajes recibidos según el número o rango del procesador emisor. Al igual que para la primitiva anterior, existe un Gatherv en que los mensajes pueden ser de diverso tamaño y es posible especificar el desplazamiento, con respecto al inicio del mensaje, para indicar la posición que ocupará la información recibida dentro del objeto destino. Figura 2.3: Funcionamiento de la colectiva Gather Aquı́ se pueden ver las firmas de los métodos con la documentación redactada para la biblioteca desarrollada: public void Gather( java.lang.Object int sendcount, mpi.Datatype int recvoffset, int sendbuf, int sendoffset, sendtype, java.lang.Object recvcount, mpi.Datatype recvbuf, recvtype, int root ) 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ 17 Usage • Each process (root process included) sends the contents of its send buffer to the root process, which receives these contents in its recv-buffer. The messages are concatenated in rank order. The type signature of sendcount, sendtype on any process must be equal to the type signature of recvcount, recvtype at the root. sendbuf send buffer array sendoffset initial offset in send buffer sendcount number of items to send sendtype datatype of each item in send buffer recvbuf receive buffer array (significant only at root) recvoffset initial offset in receive buffer (significant only at root) recvcount number of items to receive from each process recvtype datatype of each item in receive buffer root rank of receiving process public void Gatherv( java.lang.Object sendbuf, int int sendcount, mpi.Datatype int recvoffset, int [] recvcount, int [] displs, mpi.Datatype recvtype, int sendoffset, sendtype, java.lang.Object recvbuf, root ) Usage • It extends the functionality of Gather by allowing varying counts of data from each process. It also allows more flexibility as to where the data is placed on the root, by providing a new argument, displs. Messages are placed in the receive buffer of the root process in rank order. 18 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ sendbuf send buffer array sendoffset initial offset in send buffer sendcount number of items to send sendtype datatype of each item in send buffer recvbuf receive buffer array (significant only at root) recvoffset initial offset in receive buffer (significant only at root) recvcount number of elements received from each process (significant only at root) displs displacements at which to place incoming data from each process (significant only at root) recvtype datatype of each item in receive buffer (significant only at root) root rank of receiving process The size of arrays recvcounts and displs should be the size of the group. Entry i of displs specifies the displacement relative to element recvoffset of recvbuf at which to place incoming data. sendtype and recvtype must be the same. Allgather : representa una variante de la función anterior en la que el resultado del Gather aparece en todos los procesos. Serı́a lo equivalente a realizar un Gather seguido de un Broadcast usando en ambos el mismo proceso raı́z. Al final, todos los participantes obtienen el mismo mensaje, resultado de unir las informaciones enviadas por cada uno de ellos. También presenta una variante, Allgatherv, en la que los tamaños de los mensajes enviados por cada proceso pueden variar y se puede especificar el desplazamiento de cada fragmento con respecto al inicio del mensaje final. Esta es la documentación que se ha redactado en este proyecto para la biblioteca desarrollada: 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ 19 Figura 2.4: Funcionamiento de la colectiva Allgather public void Allgather( java.lang.Object int sendcount, mpi.Datatype int recvoffset, int sendbuf, int sendoffset, sendtype, java.lang.Object recvcount, mpi.Datatype recvbuf, recvtype ) Usage • It is similar to Gather, but all processes receive the result. The block of data sent from the process jth is received by every process and placed in the jth block of the buffer recvbuf. The type signature associated with sendcount, sendtype, at a process must be equal to the type signature associated with recvcount, recvtype at any other process. sendbuf send buffer array sendoffset initial offset in send buffer sendcount number of items to send sendtype datatype of each item in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer recvcount number of items to receive from each process recvtype datatype of each item in receive buffer public void Allgatherv( java.lang.Object sendbuf, int sendoffset, int sendcount, mpi.Datatype int recvoffset, int [] recvcount, int [] displs, mpi.Datatype recvty- pe ) sendtype, java.lang.Object recvbuf, 20 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ Usage • It is similar to Gatherv, but all processes receive the result. The block of data sent from jth process is received by every process and placed in the jth block of the buffer recvbuf. These blocks need not all be the same size. The type signature associated with sendcount, sendtype, at process j must be equal to the type signature associated with recvcounts[j], recvtype at any other process. sendbuf send buffer array sendoffset initial offset in send buffer sendcount number of items to send sendtype datatype of each item in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer recvcounts number of received elements from each process displs displacements at which to place incoming data recvtype datatype of each item in receive buffer Reduce : todos los procesos envı́an un mensaje al proceso raı́z, pero éste no se limita a recibirlos, sino que el resultado final es obtenido mediante la aplicación una operación de reducción sobre todos los mensajes recibidos. La función de reducción puede estar predefinida o ser implementada por el usuario. Figura 2.5: Funcionamiento de la colectiva Reduce Se muestra a continuación la firma del método descrito acompañado de la documentación que ha sido redactada para la biblioteca desarrollada: 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ public void Reduce( java.lang.Object java.lang.Object mpi.Datatype recvbuf, int datatype, mpi.Op 21 sendbuf, int recvoffset, int op, int sendoffset, count, root ) Usage • It combines elements in input buffer of each process using the reduce operation, and it returns the combined value in the output buffer of the root process. Arguments count, datatype, op and root must be the same in all processes. Input and output buffers have the same length and elements of the same type. Each process can provide one element, or a sequence of elements, in which case the combine operation is executed element-wise on each entry of the sequence. . sendbuf send buffer array sendoffset initial offset in send buffer recvbuf receive buffer array (significant only at root) recvoffset initial offset in receive buffer count number of items in send buffer datatype data type of each item in send buffer op reduce operation root rank of root process op can be a predefined operation or a user-defined operation. The predefined operations are available in Java as MPI.MAX, MPI.MIN, MPI.SUM, MPI.PROD, MPI.LAND, MPI.BAND, MPI.LOR, MPI.BOR, MPI.LXOR, MPI.BXOR, MPI.MINLOC and MPI.MAXLOC. The operation is always assumed to be associative. The datatype must be compatible with op. Allreduce : en este caso, el resultado de la reducción aparece, no sólo en un proceso, sino en todos. El resultado final debe ser el mismo en todos los procesos. Serı́a equivalente a la realización de un Reduce seguido de un Broadcast del resultado. 22 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ Las operaciones de reducción aplicables son las mismas que en la función anterior. Figura 2.6: Funcionamiento de la colectiva Allreduce La firma y la documentación redactada para la biblioteca desarrollada, son los siguientes: public void Allreduce( java.lang.Object java.lang.Object mpi.Datatype recvbuf, int datatype, mpi.Op sendbuf, int recvoffset, int sendoffset, count, op ) Usage • Same as Reduce except that the result appears in the receive buffer of all processes in the group. All processes must receive identical results. sendbuf send buffer array sendoffset initial offset in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer count number of items in send buffer datatype data type of each item in send buffer op reduce operation Reduce-scatter : con esta colectiva, el resultado de la reducción aparece repartido entre los procesos. Como su nombre indica, tiene el mismo efecto que realizar primero un Reduce y luego un Scatterv del resultado. A continuación, se incluye la firma del método y la documentación: 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ 23 Figura 2.7: Funcionamiento de la colectiva Reduce-scatter public void Reduce scatter( java.lang.Object int sendoffset, java.lang.Object int [] recvcounts, mpi.Datatype sendbuf, recvbuf, int recvoffset, datatype, mpi.Op op ) Usage • It combines elements in input buffer of each process using the reduce operation, and scatters the combined values over the output buffers of the processes. The ith segment in result vector is sent to process with rank i and stored in the receive buffer defined by recvbuf, recvcounts[i] and datatype. sendbuf send buffer array sendoffset initial offset in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer recvcounts numbers of result elements distributed to each process. Array must be identical on all calling processes. datatype datatype of each item in send buffer op reduce operation Alltoall : los datos se envı́an “desde todos y hacia todos”. Podrı́a considerarse como una serie de Scatters desde cada uno de los procesadores. Existe también la colectiva Alltoallv en que los mensajes enviados pueden ser de diferentes tamaños y se puede especificar un desplazamiento con respecto a la posición inicial del 24 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ mensaje, tanto para los fragmentos enviados como para los recibidos. Figura 2.8: Funcionamiento de la colectiva Alltoall Éstas son las firmas de los métodos descritos, acompañados de la documentación que se incluye en la biblioteca desarrollada: public void Alltoall( java.lang.Object int sendcount, mpi.Datatype int recvoffset, int sendbuf, int sendoffset, sendtype, java.lang.Object recvcount, mpi.Datatype recvbuf, recvtype ) Usage • Extension of Allgather to the case where each process sends distinct data to each of the receivers.The jth block sent from process i is received by process j and is placed in the ith block of recvbuf. The type signature associated with sendcount, sendtype, at a process must be equal to the type signature associated with recvcount, recvtype at any other process. sendbuf send buffer array sendoffset initial offset in send buffer sendcount number of items sent to each process sendtype datatype send buffer items recvbuf receive buffer array recvoffset initial offset in receive buffer recvcount number of items received from any process recvtype datatype of receive buffer items 2.1. Análisis de Requisitos en Operaciones Colectivas en MPJ public void Alltoallv( java.lang.Object sendbuf, int int [] sendcounts, int [] sdispls, mpi.Datatype java.lang.Object recvbuf, int int [] rdispls, mpi.Datatype 25 sendoffset, sendtype, recvoffset, int [] recvcounts, recvtype ) Usage • It adds flexibility to Alltoall: location of data for send is specified by sdispls and location to place data on receive side is specified by rdispls. The jth block sent from process i is received by process j and is placed in the ith block of recvbuf. These blocks need not all have the same size. The type signature associated with sendcount[j], sendtype at process i must be equal to the type signature associated with recvcount[i], recvtype at process j. sendbuf send buffer array sendoffset initial offset in send buffer sendcounts number of items sent to each process sdispls displacements from which to take outgoing data. Entry j specifies the displacement from which to take the outgoing data destined for process j sendtype datatype send buffer items recvbuf receive buffer array recvoffset initial offset in receive buffer recvcounts number of elements received from each process rdispls displacements at which to place incoming data. Entry i specifies the displacement at which to place the incoming data from process i recvtype datatype of each item in receive buffer 26 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ Scan : realiza una reducción de prefijo paralelo. Es decir, en cada proceso, aparecerá el resultado de aplicar la operación de reducción a los mensajes enviados desde el primer proceso hasta él mismo (incluido). Figura 2.9: Funcionamiento de la colectiva Scan La firma del método y la documentación redactada para la biblioteca desarrollada se describen a continuación: public void Scan( java.lang.Object java.lang.Object mpi.Datatype recvbuf, int datatype, mpi.Op sendbuf, int recvoffset, int sendoffset, count, op ) Usage • It performs a prefix reduction on data distributed across the group. The operation returns, in the recvbuf of the process with rank i, the reduction of the values in the send buffers of processes with ranks 0,...,i (inclusive). Operations supported, semantics and constraints of arguments are as for Reduce. sendbuf send buffer array sendoffset initial offset in send buffer recvbuf receive buffer array recvoffset initial offset in receive buffer count number of items in input buffer datatype data type of each item in input buffer op reduce operation 2.2. Diseño de Operaciones Colectivas en MPJ 27 Barrier : implementa una sincronización entre todos los procesos implicados, los cuales, a medida que llegan a la barrera, esperan hasta que todos los demás hayan alcanzado ese punto. Una vez que esto ocurre, continuan su ejecución. A continuación, se pueden observar la firma y la documentación incluidas en la biblioteca desarrollada: public void Barrier( ) Usage • A call to Barrier blocks the caller until all processes in the group have called it. The call returns at any process only after all group members have entered the call. This function can be used when a synchronization of all processes is needed. 2.2. Diseño de Operaciones Colectivas en MPJ Una vez visto cómo funciona cada una de las funciones a implementar y su descripción en el estándar a través de su firma, es el momento de diseñar dicha implementación. 2.2.1. Estructura General de las Bibliotecas MPJ El API de MPJ se basa claramente en el del estándar MPI de C y Fortran. No obstante, MPJ sigue un enfoque orientado a objetos para capturar mejor las caracterı́sticas del lenguaje. Para ello, la especificación se basa en el estándar de MPI-2 para C++, en el que se definen ciertas jerarquı́as y aparecen determinadas funciones de librerı́a como métodos de clases. A pesar de haber buscado un diseño con cierta orientación a objetos, hay algunas clases que engloban las principales funcionalidades, y el diseño orientado a objetos sólo se centra en separar tipos de datos, operaciones y conceptos diferentes que, en un enfoque procedimental, se dividirı́an en módulos. Esto es ası́ porque con MPJ se pretende obtener, ante todo, una implementación eficiente cuyo rendimiento se verı́a mermado con una mayor profusión de objetos con menores funcionalidades y 28 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ delegación entre ellos. Por lo tanto, se intenta mantener cierta eficiencia en detrimento de un diseño más elaborado. mpj MPJ Group Datatype Comm Intracomm Cartcomm Status Intercomm Request Op Prequest Graphcomm UserFunction Figura 2.10: Clases principales en MPJ En la Figura 2.10 podemos ver las principales clases que componen el estándar MPJ. En algunas implementaciones, como por ejemplo, MPJ Express [27] [28], tanto el paquete como la clase MPJ, reciben el nombre MPI. Esta clase MPJ contiene sólo variables estáticas de configuración. Entre ellas, una instancia de un Communicator que constituye un marco general de comunicaciones para una configuración de procesos por defecto, más conocido como “COMM WORLD”, ya que contiene a todos los procesos MPJ existentes. La clase Datatype define los diferentes tipos de datos encapsulados en las comunicaciones. Los elementos enviados entre procesos deben ser de tipos primitivos o sus contrapartidas objetuales, o bien objetos serializables. Datatype indicará un tipo MPIcompatible consistente con el tipo de los elementos del mensaje. Los tipos básicos MPIcompatibles incluidos en el API de MPJ, son: MPJ.BYTE, MPJ.CHAR, MPJ.SHORT, MPJ.BOOLEAN, MPJ.INT, MPJ.LONG, MPJ.FLOAT, MPJ.DOUBLE y MPJ.OBJECT. También ofrece métodos para crear tipos derivados. La parte más importante y compleja es la que atañe a la jerarquı́a de comunicadores, ya que es donde realmente se implementa el paso de mensajes. En la superclase “Comm” se definen las comunicaciones básicas punto a punto, mientras que en las 2.2. Diseño de Operaciones Colectivas en MPJ 29 subclases, aparecen comunicaciones más complejas y/o especı́ficas. Todas las comunicaciones se basan en el intercambio de mensajes entre procesos que forman parte de un mismo Comunicador. Asimismo, presenta utilidades para diferentes empaquetamientos de datos en el envı́o. Los grupos de procesos se definen en la clase Group. La subclase Intracomm es la que contiene las primitivas colectivas definidas anteriormente, con lo cual, es en la que se centra el presente proyecto ya que representa comunicaciones entre procesos del mismo grupo. Entre estas operaciones se encuentran las que realizan operaciones de reducción. Para este tipo de operaciones se utiliza la clase Op. Esta clase presenta diferentes extensiones que se dividen en dos grupos: las operaciones predefinidas y las operaciones de usuario. Las operaciones disponibles en Java son: MPJ.SUM, MPJ.PROD, MPJ.MIN, MPJ.MAX, MPJ.BOR, MPJ.BAND, MPJ.BXOR, MPJ.LOR, MPJ.LAND, MPJ.LXOR, MPJ.MINLOC y MPJ.MAXLOC. Estas dos últimas (MINLOC y MAXLOC) se modelan de la misma manera que en Fortran, por ello, aparecen otros tipos predefinidos que son MPI.SHORT2, MPJ.INT2, MPJ.LONG2, MPJ.FLOAT2 y MPJ.DOUBLE2. Para las operaciones de usuario, existe un método en Op que permite ligar una función, que tendrá que extender la clase abstracta UserFunction, a un objeto de este tipo. Graphcomm y Cartcomm ofrecen funciones especı́ficas para comunicadores con topologı́as en grafo y cartesianas respectivamente. La subclase Intercomm se utiliza para intercambio de información entre procesos de distintos grupos. Status y Request se usan para encapsular el estado de una operación de intercambio de mensajes. La primera de ellas es la que representa realmente el estado. La segunda, como se ha visto en el apartado anterior, permite, en operaciones en las que el proceso no se queda bloqueado esperando su finalización, comprobar que sus envı́os/recepciones han terminado y, si es necesario, suspender la ejecución hasta que eso ocurra. La subclase Prequest se utiliza en comunicaciones persistentes. Para el tratamiento de las excepciones, se proporciona una clase MPJException, que hereda de la clase estándar Exception. También existen subclases para representar los códigos de error de MPI. 30 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ 2.2.2. Integración de la Biblioteca Desarrollada La biblioteca de primitivas colectivas, siguiendo el diseño expuesto en el estándar, ha de integrarse en la clase Intracomm. Una de las aportaciones de este trabajo, es la posibilidad de seleccionar diferentes algoritmos en tiempo de ejecución escogiendo el más adecuado para una determinada situación. Para ello, podemos incorporar a la interfaz pública de Intracomm una serie de métodos que implementen los diferentes algoritmos. También podrı́amos añadir una subclase con los nuevos métodos. Sin embargo, la aproximación que se acerca más al estándar, que interfiere menos en la jerarquı́a y que es la implementada en este proyecto, es la que consiste en incorporar los métodos a la clase Intracomm y hacer que el método público ofrecido por la interfaz definida en el estándar sea el que elija el método a ejecutar. Para ello, podemos configurar una serie de variables de entorno o ficheros de configuración que indiquen el algoritmo a escoger en cada caso. Además, se habilita el soporte para la selección automática del algoritmo a utilizar, lo que permite despreocuparse de la configuración y evitar el control de la especificación de los algoritmos usados. No obstante, esto no impide el control exhaustivo del algoritmo a ejecutar, facilitando la tarea de pruebas de los algoritmos. Para ello, el enfoque que se ha seguido es mantener una serie de variables estáticas que indicarán un algoritmo por defecto para cada colectiva. Estas variables son configurables por el usuario, que puede ası́ decidir cuál será el algoritmo a ejecutar en cada caso. Por otro lado, otra variable estática permite decidir si la selección del algoritmo en cada colectiva será automática o manual. En el caso de que queramos tener una selección automática, tendremos que configurar unos parámetros que indiquen qué algoritmo escoger en cada caso. Los parámetros más adecuados serı́an el tamaño de mensaje y el número de procesadores involucrados. El caso ideal serı́a tener unas clases de pruebas que permitan crear un fichero de configuración automáticamente para ser utilizado más adelante en las ejecuciones. El desarrollo de este soporte es una de las lı́neas de investigación que se podrı́an seguir en base al presente proyecto. 2.2. Diseño de Operaciones Colectivas en MPJ 2.2.3. 31 Primitivas Básicas Utilizadas en la Implementación Uno de los objetivos más relevantes del diseño a realizar es la portabilidad. Ası́, nuestra implementación se basará en primitivas punto a punto del estándar MPJ. Otro objetivo importante del diseño es incrementar la escalabilidad de la solución mediante el aprovechamiento de operaciones no bloqueantes. Las primitivas punto a punto del estándar MPJ comprenden envı́os y recepciones tanto bloqueantes (send/recv) como no bloqueantes (isend/irecv). En las primeras, el proceso que llama a la función se queda suspendido esperando a que ésta se lleve a cabo. Por el contrario, en las operaciones no bloqueantes, el proceso emisor/receptor continúa su ejecución tras realizar la llamada a la función de envı́o o recepción. Para comprobar que se ha ejecutado correctamente, existe una función Wait que suspende la ejecución del proceso hasta que se completa el envı́o o recepción del mensaje según proceda. Con este tipo de primitivas, es posible solapar la ejecución de tareas que no se ven afectadas por el intercambio de mensajes, tanto si son tareas de computación como de comunicación. La firma de estos métodos es la siguiente [9]:1 public void Comm.Send (java.lang.Object Datatype datatype, int dest, int buf, int send buffer array offset initial offset in send buffer count number of items in send buffer datatype data type of each item in input buffer dest rank of destination tag message tag public Status Comm.Recv (java.lang.Object 1 datatype, int count, tag) buf Datatype offset, int source, int buf, int offset, int count, tag) En todos ellos, se hace referencia a un objeto de tipo Comm que se refiere a un grupo de procesos que intercambian información, es decir, un Comunicador 32 2. Análisis y Diseño de la Biblioteca de Colectivas MPJ buf receive buffer array offset initial offset in receive buffer count number of items in receive buffer datatype data type of each item in input buffer source rank of source tag message tag returns status object La primitiva de recepción, devuelve un objeto de tipo Status que encapsula el estado de una operación de este tipo. public Request Comm.Isend (java.lang.Object Datatype datatype, int dest, int buf, int send buffer array offset initial offset in send buffer count number of items in send buffer datatype data type of each item in input buffer dest rank of destination tag message tag returns communication request public Request Comm.Irecv (java.lang.Object datatype, int count, offset, int count, tag) buf Datatype offset, int source, int buf, int tag) buf receive buffer array offset initial offset in receive buffer count number of items in receive buffer datatype data type of each item in input buffer source rank of source tag message tag returns communication request Las primitivas no bloqueantes devuelven un objeto Request asociado a la operación. 2.2. Diseño de Operaciones Colectivas en MPJ 33 Este objeto presenta los métodos Wait() y Test(), que devuelven otro objeto de tipo Status indicando si la operación ha finalizado con éxito. La primera de las funciones, además, deja al proceso inactivo (evitando el consumo de recursos como tiempo de CPU) hasta que se completa la función a la que está asociada. Además de este tipo de funciones, también se utilizan otras definidas en el estándar como la que permite obtener el número de procesos involucrados en una comunicación (public int Comm.Size()), es decir, el número de procesos que integran un Comunicador; o la que obtiene el rango o identificador de un proceso determinado dentro del grupo (public int Comm.Rank()). Capı́tulo 3 Implementación de Primitivas Colectivas MPJ Una vez analizadas las necesidades a resolver por una biblioteca de operaciones colectivas en paso de mensajes en Java, y diseñadas las soluciones a aplicar, puede acometerse la fase de implementación. Para cada primitiva colectiva se han implementado y analizado diferentes algoritmos de modo que sea posible escoger el más conveniente en función de las caracterı́sticas de la aplicación y el entorno de ejecución. El Cuadro 3 presenta una lista de los algoritmos implementados y permite compararlos con los incluidos en las bibliotecas de operaciones colectivas de MPJ Express y MPJ/Ibis. El prefijo “b” se refiere al uso de primitivas punto a punto bloqueantes, mientras que el prefijo “nb” indica que se usan comunicaciones punto a punto no bloqueantes. Los algoritmos de MPJ Express y MPJ/Ibis, por lo general, son poco escalables, especialmente los implementados en MPJ/Ibis. Los algoritmos que se incluyen en la implementación de la biblioteca presentada en este proyecto son los que aparecen en la última columna, etiquetados bajo “Nueva Biblioteca”. Cuando existen variantes de una determinada implementación, éstas aparecen numeradas (por ejemplo nb1FT y nb2FT). Los algoritmos utilizados pueden ser clasificados en siete tipos: Flat Tree (FT) o lineal, Minimun-Spanning Tree (MST), Binomial Tree (BT) o árbol binario, Four-ary Tree (FaT), Bucket (BKT) o cı́clico, BiDirectional Exchange (BDE) y secuencial. A continuación, se describen estos algoritmos para cada colectiva. 35 36 3. Implementación de Primitivas Colectivas MPJ Aunque la biblioteca de colectivas es portable porque sigue el estándar MPJ, para poder realizar pruebas y tener una implementación real, es necesario utilizar una biblioteca MPJ existente que proporcione una API estándar sobre la que trabajar. En este caso, como veremos más adelante, se ha escogido MPJ Express por ser una de las más utilizadas. Al integrar nuestra biblioteca, se tuvieron en consideración las colectivas implementadas en MPJ Express cuando éstas presentaban algoritmos relativamente escalables. En esos casos, la biblioteca desarrollada incluye también esas implementaciones. 3.1. 3.1.1. Algoritmos Implementados Broadcast Para implementar esta colectiva, un algoritmo muy simple y directo es el que usa un árbol plano o Flat Tree, enviando los datos secuencialmente desde la raı́z a todos los demás procesos. Para comprobar cómo se comporta en las pruebas y por ser el algoritmo más sencillo, la primera implementación del Broadcast utiliza un árbol plano (FT) con envı́os y recepciones bloqueantes (bFT, ver pseudocódigo en Figura 3.1). Procedure Bcast(x,root)(bFT) if me = root then for i=0,...,npes-1 do if me ! = i then Send (x,pi ); else Recv (x,root); Figura 3.1: Pseudocódigo de Broadcast bFT Analizando un poco este algoritmo, vemos que es el proceso raı́z (root en la Figura 3.1) el que siempre envı́a el mensaje x a todos los demás (me representa el proceso que ejecuta el pseudocódigo y npes el número total de procesos). Al usar primitivas punto a punto bloqueantes, en cada envı́o hay que esperar a que el proceso receptor haya 3.1. Algoritmos Implementados 37 completado la operación antes de continuar con el siguiente. Una posible optimización es que el proceso raı́z pueda realizar todos los envı́os sin esperas entre ellos, pues cada envı́o no depende del anterior, y que espere a que todos los datos se hayan recibido al final. Esto serı́a posible con una aproximación que sustituyese los envı́os y recepciones bloqueantes por primitivas no bloqueantes. Ası́, en el proceso raı́z es posible solapar varios envı́os sin tener que esperar a que cada mensaje sea recibido. En los demás procesos, sin embargo, no aporta ninguna mejora, ya que, inmediatamente después de la recepción no bloqueante, tendrı́amos que añadir una llamada Wait() que dejase dormido al proceso hasta que se completase la recepción. Por lo tanto, no se podrı́a solapar ninguna actividad entre la recepción no bloqueante y la comprobación de que la operación se haya completado (nbFT, ver pseudocódigo en Figura 3.2). Procedure Bcast(x,root)(nbFT) if me = root then for i=0,...,npes-1 do if me ! = i then sreqi =Isend (x,pi ); for i=0,...,npes-1 do if me ! = i then Wait (sreqi ); else Recv (x,root); Figura 3.2: Pseudocódigo de Broadcast nbFT En MPJ Express, el algoritmo por defecto es una variante de Flat Tree (FT) con envı́os y recepciones bloqueantes. Utiliza un árbol de procesos configurado en el constructor de la clase en el que cada proceso tiene como máximo 4 sucesores (Four-ary Tree o FaT). Con esta configuración, los envı́os los realiza cada proceso a sus descendientes y ası́ sucesivamente hasta llegar a los procesos hoja, tal y como se puede observar en la Figura 3.3. Además, se lleva a cabo un envı́o/recepción inicial si el proceso raı́z de la operación no es el proceso con rango 0. 38 3. Implementación de Primitivas Colectivas MPJ 1 2 3 3 3 2 2 2 3 root Figura 3.3: Funcionamiento de Broadcast en MPJ Express Otro algoritmo con el que se puede implementar la operación de broadcast es el conocido como Minimum-Spanning Tree, o MST. Para describir este algoritmo, imaginemos que tenemos un conjunto de p nodos numerados de 0 a p-1. En primer lugar, se particiona el conjunto en dos subconjuntos disjuntos de la forma {0, . . . , m} y {m+1, . . . , p-1}, siendo m = ⌊ p/2 ⌋ el proceso intermedio (mid en el pseudocódigo). Como destino, se escoge un nodo que esté en el subconjunto que no contiene al proceso raı́z. El mensaje es enviado desde el nodo raı́z al destino, tras lo cual, tanto raı́z como destino, se convierten en raı́ces de una llamada recursiva a broadcast en sus respectivos subconjuntos de nodos. En la Figura 3.4 se puede ver el pseudocódigo de este algoritmo, cuyos parámetros, además del mensaje, incluye el root, y dos nodos auxiliares, left y right. Estos dos últimos se refieren, respectivamente, al nodo más a la izquierda y al nodo más a la derecha del subconjunto actual. Para comenzar, cada proceso tiene que realizar la llamada MSTBcast(x, root, 0, p-1), donde root es el proceso raı́z de la operación de Broadcast. En la Figura 3.5 se muestra un ejemplo del funcionamiento de este algoritmo. MST reduce el número de comunicaciones entre procesadores de distintos grupos, pero puede ser más lento que un nbFT en caso de que el número de procesadores sea muy pequeño, ya que tiene una latencia inicial mayor. Estos dos algoritmos podrı́an combinarse de forma que se utilizase un árbol plano dentro de cada grupo y un MST entre grupos. 3.1. Algoritmos Implementados 39 Procedure MSTBcast(x,root,left,right) if left = right then return; mid = ⌊(left+right)/2⌋; if root ≤ mid then dest=right; else dest = left; if me = root then Send (x,dest); if me = dest then Recv (x,root); if me ≤ mid and root ≤ mid then MSTBcast (x,root,left,mid); else if me ≤ mid and root > mid then MSTBcast (x,dest,left,mid); else if me > mid and root ≤ mid then MSTBcast (x,dest,mid+1,right); else if me > mid and root > mid then MSTBcast (x,root,mid+1,right); Figura 3.4: Pseudocódigo de Broadcast con Minimum Spanning Tree (MST) P0 P1 P2 P3 P0 P2 P3 x x Situación Inicial P0 P1 P1 P2 x x Paso 2 Paso 1 P3 P0 P1 P2 P3 x x x x Situación Final Figura 3.5: Funcionamiento de Broadcast MST 40 3. Implementación de Primitivas Colectivas MPJ Primitiva Bcast MPJ Express FaT MPJ/Ibis BT Gather nbFT bFT Gatherv nbFT bFT Scatter nbFT bFT Scatterv nbFT bFT Allgather nbFT BT BKT (double ring) Allgatherv nbFT BT BKT Alltoall nbFT nbFT Alltoallv nbFT nbFT Reduce bFT BT(op. conmutativa) bFT(op. no conmutativa) Allreduce nbFT BT BDE bFTReduce+ nbFTScatter {BTReduce or bFTReduce} + bFTScatterv nbFT bFT nbFTGather + bFaTBcast BT (Exotic) bFT ReduceScatter Scan Barrier Nueva Biblioteca bFT nbFT FaT MST bFT nbFT nb1FT MST bFT nbFT nb1FT MST nbFT MST nbFT MST nbFT BT (Exotic) nbBDE bBKT nbBKT Gather + Bcast nbFT BT (Exotic) nbBDE bBKT nbBKT Gather + Bcast bFT nbFT nb1FT nb2FT bFT nbFT nb1FT nb2FT bFT nbFT MST nbFT BT (Exotic) bBDE nbBDE Reduce + Bcast Reduce + Scatter bBDE nbBDE bBKT nbBKT nbFT secuencial nbFTGather + bFaTBcast Gather + Bcast Árbol binario Cuadro 3.1: Algoritmos para primitivas colectivas en bibliotecas MPJ. 3.1. Algoritmos Implementados 3.1.2. 41 Gather y Gatherv La implementación por defecto que se presenta en MPJ Express de la función Gather utiliza un árbol plano con envı́os no bloqueantes y recepciones bloqueantes (nbFT). El pseudocódigo correspondiente a esta implementación es el que se muestra en la Figura 3.6 Procedure Gather(x,root)(nbFT) if me ! = root then sreq =Isend (xme ,root); Wait (sreq); else for i=0,...,npes-1 do if me ! = i then Recv (xi ,pi ); Figura 3.6: Pseudocódigo de Gather nbFT No obstante, esta implementación no es muy eficiente, como puede observarse en el pseudocódigo: la primitiva no bloqueante no permite el solapamiento de trabajo entre su utilización y la espera por su finalización. Serı́a más óptimo que la colectiva no bloqueante se utilizase dentro del bucle, solapando comunicaciones. De esta forma, se permite que el proceso que recibe todos los mensajes pueda realizar todas las llamadas antes de esperar a que termine cada una de ellas. El uso de una función no bloqueante en el envı́o, en este caso, no reporta ningún beneficio. En la biblioteca desarrollada en este proyecto, además de proporcionar esta nueva versión no bloqueante (nb1FT) teóricamente más eficiente, se ha implementado un árbol grueso con primitivas punto a punto bloqueantes (bFT). El pseudocódigo de ambas funciones se muestra en la Figura 3.7. Al igual que en el Broadcast, se puede utilizar el algoritmo MST. En este caso, primero se realizan todas las subdivisiones posibles y después se realizan los envı́os y recepciones, para, posteriormente, ir componiendo el mensaje final. Además, otra diferencia con la función anterior, es que se distribuye la tarea de recibir datos (desde 42 3. Implementación de Primitivas Colectivas MPJ Procedure Gather(x,root)(nb1FT) Procedure Gather(x,root)(bFT) if me = root then for i=0,...,npes-1 do if me ! = i then Recv (xi ,pi ); else Send (xme ,root); if me = root then for i=0,...,npes-1 do if me ! = i then rreqi =Irecv (xi ,pi ); for i=0,...,npes-1 do if me ! = i then Wait (rreqi ); else Send (xme ,root); Figura 3.7: Pseudocódigo de Gather FT el proceso srce en cada caso). Es decir, el proceso raı́z no tiene que recibir directamente de todos los demás. También hay que tener en cuenta que no se transmite siempre el mismo mensaje, sino que se van aglutinando mensajes a medida que se avanza en el árbol de procesos. El pseudocódigo de esta función se puede ver en la Figura 3.8 y su funcionamiento en la Figura 3.9. Además de las consideraciones hechas para el MST del Broadcast, es necesario buffering en nodos intermedios, pues aunque el único destinatario de todos los mensajes es el nodo raı́z, las comunicaciones se distribuyen. Para la variante Gatherv se pueden implementar los mismos algoritmos con la salvedad de que hay que tener en cuenta que no todos los procesos envı́an mensajes del mismo tamaño y que pueden tener diferentes desplazamientos. La versión del algoritmo implementada en MPJ Express es también similar a la implementación de la función Gather (nbFT). 3.1. Algoritmos Implementados 43 Procedure MSTGather(x,root,left,right) if left = right then return; mid = ⌊(left+right)/2⌋; if root ≤ mid then srce=right; else srce = left; if me ≤ mid and root ≤ mid then MSTGather (x,root,left,mid); else if me ≤ mid and root > mid then MSTGather (x,srce,left,mid); else if me > mid and root ≤ mid then MSTGather (x,srce,mid+1,right); else if me > mid and root > mid then MSTGather (x,root,mid+1,right); if root ≤ if me if me else if me if me mid then = srce then Send (xmid+1:right ,root); = root then Recv (xmid+1:right ,srce); = srce then Send (xlef t:mid ,root); = root then Recv (xlef t:mid ,srce); Figura 3.8: Pseudocódigo de Gather con Minimum Spanning Tree (MST) P0 P1 P2 P3 P0 Situación inicial P0 P1 P2 Paso 2 P1 P2 P3 Paso 1 P3 P0 P1 P2 Situación final Figura 3.9: Funcionamiento de Gather MST P3 44 3. Implementación de Primitivas Colectivas MPJ 3.1.3. Scatter y Scatterv Scatter representa la operación inversa a Gather, con lo cual, todos los algoritmos vistos en la sección anterior son válidos si invertimos las operaciones. En este caso, la implementación por defecto combina de forma adecuada las primitivas bloqueantes y no bloqueantes en un algoritmo de árbol plano. Las recepciones son bloqueantes, puesto que cada proceso sólo tiene que recibir una vez. Los envı́os, dentro del bucle, son no bloqueantes, de forma que se pueden solapar y sólo se espera por su finalización tras realizar el último envı́o. El pseudocódigo de este algoritmo (nbFT) es el que aparece en la Figura 3.10. Procedure Scatter(x,root)(nbFT) if me ! = root then for i=0,...,npes-1 do if me ! = i then sreqi =Isend (xi ,pi ); for i=0,...,npes-1 do Wait (sreqi ); else Recv (xme ,root); Figura 3.10: Pseudocódigo de Scatter nbFT El algoritmo MST es muy parecido al utilizado en Broadcast, teniendo en cuenta que cada proceso recibe un mensaje distinto. Las Figuras 3.11 y 3.12 muestran su pseudocódigo y funcionamiento. Al igual que en Gather, los nodos van a almacenar, de forma temporal, más datos que los que van dirigidos finalmente a ellos. En Scatterv es posible utilizar los algoritmos planteados para Scatter teniendo en cuenta que los mensajes enviados a cada proceso pueden tener diferente tamaño y desplazamiento. 3.1. Algoritmos Implementados 45 Procedure MSTScatter(x,root,left,right) if left = right then return; mid = ⌊(left+right)/2⌋; if root ≤ mid then dest=right; else dest = left; if root ≤ if me if me else if me if me mid then = root then Send (xmid+1:right ,dest); = dest then Recv (xmid+1:right ,root); = root then Send (xlef t:mid ,dest); = dest then Recv (xlef t:mid ,root); if me ≤ mid and root ≤ mid then MSTScatter (x,root,left,mid); else if me ≤ mid and root > mid then MSTScatter (x,dest,left,mid); else if me > mid and root ≤ mid then MSTScatter (x,dest,mid+1,right); else if me > mid and root > mid then MSTScatter (x,root,mid+1,right); Figura 3.11: Pseudocódigo de Scatter con Minimum Spanning Tree (MST) P0 P1 P2 P3 P0 Situación Inicial P0 P1 P2 Paso 2 P1 P2 P3 Paso 1 P3 P0 P1 P2 Situación Final Figura 3.12: Funcionamiento de Scatter MST P3 46 3. Implementación de Primitivas Colectivas MPJ 3.1.4. Allgather y Allgatherv La primitiva Allgather es una variante del Gather en el que todos los procesos implicados reciben una copia del resultado final. En MPJ Express, la implementación por defecto (nbFT) utiliza envı́os no bloqueantes desde cada procesador hacia todos los demás y recepciones bloqueantes (ver pseudocódigo en la Figura 3.13). Procedure Allgather(x,root)(nbFT) for i=0,...,npes-1 do if me ! = i then sreqi =Isend (xi ,pi ); for i=0,...,npes-1 do if me ! = i then Recv (xi ,pi ); for i=0,...,npes-1 do if me ! = i then Wait (sreqi ); Figura 3.13: Pseudocódigo de Allgather nbFT No obstante, podemos encontrar también implementada una versión que, en MPJ Express, denominan “Exotic”, sólo útil en caso de que el número de procesos sea potencia de 2. Este algoritmo realmente implementa un patrón de comunicaciones BT (Binomial Tree) con envı́os y recepciones bloqueantes en el que en cada paso i (desde 1 hasta ⌈log2 (npes)⌉) el proceso pj se comunica con el proceso pj+2i−1 . Por defecto está desactivada (se activarı́a con una variable tipo flag estática). Uno de los problemas que presenta es que no indica ningún fallo en caso de que el número de procesos no sea el adecuado. Aunque la implementación es diferente, el funcionamiento resulta similar al del algoritmo BDE que hemos implementado y que se muestra a continuación. Un algoritmo ampliamente difundido para Allgather, también basado en asumir que el número de procesos es potencia de 2, es el llamado BiDirectional Exchange o BDE. Este algoritmo particiona el comunicador o grupo de procesos en dos mitades y, recursivamente, realiza un Allgather BDE de todos los datos en cada mitad. A continuación, 3.1. Algoritmos Implementados 47 intercambia estos datos entre pares disjuntos de nodos (un nodo de cada mitad). El problema de los algoritmos BDE estriba en que, cuando el número de nodos no es potencia de 2, llegamos a subconjuntos con un número impar de nodos. En este caso, el nodo que sobra no tendrı́a pareja en la otra mitad del subconjunto. La solución pasarı́a porque uno de los nodos enviase dos veces la información (una vez a su pareja y otra al nodo sobrante). Sin embargo, esto duplicarı́a el coste del algoritmo. En la práctica, el resultado no serı́a tan malo porque el proceso que envı́a dos veces no es siempre el mismo, sin embargo, no se suele usar esta variante debido a su complejidad (ver pseudocódigo en la Figura 3.14 y funcionamiento en la Figura 3.15). Procedure BDEAllgather(x,left,right) if left = right then return; size = right-left+1; mid = ⌊(left+right)/2⌋; if me ≤ mid then partner=me+⌊size/2⌋; else partner=me-⌊size/2⌋; if me ≤ mid then BDEAllgather (x,left,mid); else BDEAllgather (x,mid+1,right); if me ≤ mid then Send (xlef t:mid ,partner); Recv (xmid+1:right , partner); else Send (xmid+1:right ,partner); Recv (xlef t:mid , partner); Figura 3.14: Pseudocódigo Allgather BDE Finalmente, la implementación desarrollada de Allgather BDE incluye envı́os no bloqueantes y recepciones bloqueantes (implementación nBDE), al postularse como la opción más eficiente en la evaluación de distintas alternativas existentes. 48 3. Implementación de Primitivas Colectivas MPJ P0 P1 P2 P3 P0 Situación inicial P0 P1 P2 P1 P2 P3 Paso 1 P3 P0 Paso 2 P1 P2 P3 Situación final Figura 3.15: Funcionamiento de Allgather BDE El algoritmo BDE se puede ver como un caso particular de los algoritmos BKT o Bucket. Estos ven el conjunto de nodos como un anillo implementado como un array lineal en el cual se aprovecha la ventaja de que los mensajes que atraviesan un enlace en sentido opuesto no entran en conflicto. En cada paso, todos los procesos envı́an un mensaje al nodo de su derecha. De este modo, los subvectores, que son enviados inicialmente por un nodo de forma individual, acaban finalmente distribuidos por todo el anillo. Este algoritmo también es conocido como Algoritmo cı́clico. Las Figuras 3.16 y 3.17 proporcionan respectivamente el pseudocódigo y un ejemplo de funcionamiento. Procedure BKTAllgather(x ) prev = me - 1; if prev < 0 then prev= npes - 1; next = me + 1; if next = npes then next=0; current i = me; for i=0,...,npes-2 do Send (xcurrent i ,next); current i = current i - 1; if current i < 0 then current i=npes-1; Recv (xcurrent i ,prev); Figura 3.16: Pseudocódigo de Allgather BKT 3.1. Algoritmos Implementados P0 P1 P2 49 P3 P0 Situación inicial P0 P1 P2 P1 P2 P3 Paso 1 P3 P0 P1 P2 P3 Paso 3 Paso 2 P0 P1 P2 P3 Situación final Figura 3.17: Funcionamiento de Allgather BKT Tras realizar una evaluación preliminar de la implementación de este algoritmo ejecutado sobre MPJ Express, se detectó cierta inestabilidad que producı́a errores de ejecución a partir de ciertos tamaños de mensaje, dependiendo del número de procesadores. Para solucionarlo, se introdujeron envı́os no bloqueantes sustituyendo a los que ya habı́a, solventando de este modo la incidencia mencionada, además de mejorar la escalabilidad de esta solución (implementación nbBKT). Finalmente, una opción de implementación adicional es la que parte de la definición de la operación como combinación de Gather y Broadcast. En esta opción se harı́a una llamada inicial a la función Gather, configurando cualquier proceso como raı́z (por ejemplo, el proceso con rango 0) y luego, utilizando la misma raı́z, se llevarı́a a cabo un Broadcast del mensaje recibido. Para implementar el Allgatherv se han versionado los anteriores algoritmos para que tengan en cuenta las particularidades de esta variante. En cuanto a la opción por defecto, lo único que cambia es que se utilizan primitivas no bloqueantes tanto en envı́os como en recepciones en el algoritmo de árbol plano. 50 3. Implementación de Primitivas Colectivas MPJ 3.1.5. Alltoall y Alltoallv Esta función implementa un patrón de comunicación en el que todos los procesos envı́an datos distintos a todos los demás. También es conocida como “intercambio total” (total exchange). En este caso, no se ha implementado ningún algoritmo especı́fico además de los de árbol plano. La versión por defecto en MPJ Express utiliza también este enfoque, combinando envı́os no bloqueantes con recepciones bloqueantes, (nbFT) como se puede apreciar en la Figura 3.18. Procedure Alltoall(x,root)(nbFT) for i=0,...,npes-1 do if me ! = i then sreqi =Isend (xme,i ,pi ); for i=0,...,npes-1 do if me ! = i then Recv (xi,me ,pi ); for i=0,...,npes-1 do if me ! = i then Wait (sreqi ); Figura 3.18: Pseudocódigo Alltoall nbFT Con el objeto de evaluar la eficiencia de las posibles implementaciones de esta primitiva, se implementaron tres versiones más en las que se combinan primitivas bloqueantes y no bloqueantes (ver pseudocódigos en las Figuras 3.19, 3.20 y 3.21): envı́os y recepciones bloqueantes (bFT) envı́os y recepciones no bloqueantes (nb1FT) envı́os bloqueantes y recepciones no bloqueantes (nb2FT) En cuanto a las implementaciones de Alltoallv, se han utilizado los mismos algoritmos que para Alltoall teniendo en cuenta las peculiaridades de esta primitiva colectiva. 3.1. Algoritmos Implementados Procedure Alltoall(x,root)(bFT) for i=0,...,npes-1 do if me ! = i then Send (xme,i ,pi ); for i=0,...,npes-1 do if me ! = i then Recv (xi,me ,pi ); Figura 3.19: Pseudocódigo de Alltoall bFT Procedure Alltoall(x,root)(nb1FT) for i=0,...,npes-1 do if me ! = i then sreqi =Isend (xme,i ,pi ); if me ! = i then rreqi =Irecv (xi,me ,pi ); for i=0,...,npes-1 do if me ! = i then Wait (sreqi ); for i=0,...,npes-1 do if me ! = i then Wait (rreqi ); Figura 3.20: Pseudocódigo de Alltoall nb1FT Procedure Alltoall(x,root)(nb2FT) for i=0,...,npes-1 do if me ! = i then Send (xme,i ,pi ); if me ! = i then rreqi =Irecv (xi,me ,pi ); for i=0,...,npes-1 do if me ! = i then Wait (rreqi ); Figura 3.21: Pseudocódigo de Alltoall nb2FT 51 52 3. Implementación de Primitivas Colectivas MPJ 3.1.6. Reduce La función Reduce lleva a cabo una operación de reducción (aplicación de una operación como puede ser la suma o la multiplicación sobre un determinado conjunto de valores para obtener un único resultado) entre datos de todos los procesos y proporciona el resultado en un proceso raı́z. En MPJ Express se implementa utilizando envı́os no bloqueantes desde todos los procesos, y recepciones bloqueantes en el proceso raı́z conformando un árbol plano (nbFT, ver pseudocódigo en la Figura 3.22). A la inversa (envı́os bloqueantes desde todos los procesos y recepciones no bloqueantes en el proceso raı́z) no tendrı́a sentido, ya que, para hacer la operación, es necesario esperar a haber recibido los operandos, ası́ que no se podrı́a solapar ningún cálculo. Además, de esta forma tampoco serı́a esperable un beneficio tangible, ya que los procesos que envı́an no realizan ninguna otra actividad, con lo cual no hay solapamiento de tareas. Hay que tener en cuenta también que las operaciones definidas por el usuario y las proporcionadas por el API MPJ se tratan de distinta manera. En el primer caso, es preciso realizar una llamada a la función Call que añade al resultado parcial acumulado los nuevos datos y devuelve el resultado final. Esta función recibe como parámetro el resultado acumulado y el valor a incorporar. El nuevo resultado acumulado se sobreescribe sobre el antiguo. En el segundo, es necesaria una llamada a Perform, para realizar las operaciones intermedias e ir acumulando los resultados parciales, y otra a GetResultant para obtener el resultado final. Aquı́, Perform acumula el resultado en un buffer interno temporal, por ello, es necesario llamar a la segunda función cuando queremos obtener el resultado. En el pseudocódigo, estas llamadas no se muestran, en aras de mantener la claridad de la presentación. En el código, se añadirı́a una comprobación para ver el tipo de función utilizada y se realizarı́a el cálculo dependiendo del resultado de dicha comprobación. Para evaluar la eficiencia de la implementación que se acaba de presentar y ver si compensa utilizar primitivas no bloqueantes en este caso, se ha implementado una versión en árbol plano y envı́os y recepciones bloqueantes (bFT, Figura 3.23). Por otro lado, si nos fijamos, el esquema de envı́os es el mismo que en un Scatter. Es decir, cada proceso envı́a a un proceso raı́z un mensaje diferente, con la salvedad 3.1. Algoritmos Implementados 53 Procedure Reduce(x,root) (nbFT) if me ! = root then sreq =Isend ( xme ,root); Wait (sreq); else for i=0,...,npes-1 do if me ! = i then Recv (xi ,i); res = op (res, xi ); Figura 3.22: Pseudocódigo de Reduce nbFT Procedure Reduce(x,root) (bFT) if me ! = root then Send (xme ,root); else for i=0,...,npes-1 do if me ! = i then Recv (xi , i); res = op (res, xi); Figura 3.23: Pseudocódigo de Reduce bFT de que en este caso el proceso raı́z va a usar estos mensajes para llevar a cabo una operación de reducción en lugar de almacenarlos en un array. Por este motivo, se ha implementado el Reduce con un algoritmo MST en que los nodos intermedios van realizando las operaciones con los datos recibidos. En las Figuras 3.24 y 3.25 se muestran el pseudocódigo y un ejemplo de funcionamiento. 54 3. Implementación de Primitivas Colectivas MPJ Procedure MSTReduce(x,root,left,right) if left = right then return; mid = ⌊(left+right)/2⌋; if root ≤ mid then srce=right; else srce = left; if me ≤ mid and root ≤ mid then MSTReduce (x,root,left,mid); else if me ≤ mid and root > mid then MSTReduce (x,dest,left,mid); else if me > mid and root ≤ mid then MSTReduce (x,dest,mid+1,right); else if me > mid and root > mid then MSTReduce (x,root,mid+1,right); if me = srce then Send (x,root); if me = root then Recv (tmp,srce); x = op (x, tmp); Figura 3.24: Pseudocódigo de Reduce con Minimum Spanning Tree (MST) P0 P1 P2 P3 P0 Situación Inicial P0 P1 P2 Paso 2 P1 P2 P3 Paso 1 P3 P0 P1 P2 Situación Final Figura 3.25: Funcionamiento de Reduce MST P3 3.1. Algoritmos Implementados 3.1.7. 55 Allreduce La primitiva Allreduce, al igual que ocurrı́a con Allgather y Gather, puede verse como un Reduce en el cual el resultado final está presente en todos los procesos. La implementación por defecto en MPJ Express utiliza un árbol plano con envı́os no bloqueantes y recepciones bloqueantes (nbFT, ver pseudocódigo en la Figura 3.26). Al igual que en el Allgather, MPJ Express también proporciona una implementación basada en árboles binarios (BT). Procedure Allreduce(x,op) (nbFT) for i=0,...,npes-1 do if me ! = i then sreqi =Isend ( xme ,i); for i=0,...,npes-1 do if me ! = i then Recv (xi ,i); res = op (res, xi ); for i=0,...,npes-1 do if me ! = i then Wait (sreqi ); Figura 3.26: Pseudocódigo de Allreduce nbFT Para esta operación colectiva es posible la utilización del algoritmo BDE, de igual manera que para el Allgather. En este caso, en cada paso se intercambia el vector completo y se opera con el resultado local. En las Figuras 3.27 y 3.28 se pueden observar el pseudocódigo y un ejemplo de funcionamiento. Por motivos de eficiencia, además de la versión del algoritmo con primitivas bloqueantes, se ha implementado una variante con envı́os no bloqueantes (nbBDE). Por último, atendiendo a la definición de esta primitiva, es fácil darse cuenta de que es equivalente a un Reduce seguido de un Broadcast. Nótese que es preciso indicar el mismo proceso raı́z en las llamadas a ambas operaciones colectivas. 56 3. Implementación de Primitivas Colectivas MPJ Procedure BDEAllreduce(x,left,right) if left = right then return; size = right-left+1; mid = ⌊(left+right)/2⌋; if me ≤ mid then partner=me+⌊size/2⌋; else partner=me-⌊size/2⌋; if me ≤ mid then Send (x,partner); Recv (tmp, partner); x = op (x, tmp); else Send (x,partner); Recv (tmp, partner); x = op (x, tmp); if me ≤ mid then BDEAllreduce (x,left,mid); else BDEAllreduce (x,mid+1,right); Figura 3.27: Pseudocódigo de Allreduce BDE P0 P1 P2 p3 P0 Situación Inicial P0 P1 P2 Paso 2 P1 P2 P3 Paso 1 P3 P0 P1 P2 Situación Final Figura 3.28: Funcionamiento de Allreduce BDE P3 3.1. Algoritmos Implementados 3.1.8. 57 Reduce-Scatter El funcionamiento de esta primitiva es equivalente a tomar el resultado del Reduce y distribuirlo como si se hiciese un Scatterv del resultado. Esta implementación es la incluida en MPJ Express , donde inicialmente se elige al proceso con rango 0 como raı́z y se realiza una llamada a una operación de Reduce (bFT). Seguidamente, con el resultado obtenido, se hace un Scatter (nbFT). No obstante, esta implementación no es del todo correcta, ya que se utiliza Scatter en lugar de Scatterv, con lo cual, se limita la funcionalidad obligando a que todos los procesos reciban, al final, mensajes del mismo tamaño. Otra opción es implementarlo directamente sobre comunicaciones punto a punto sin recurrir al uso de otras colectivas. De este modo, es posible implementar esta colectiva haciendo que cada proceso envı́e un mensaje diferente a cada uno de los demás y éstos apliquen una operación de reducción sobre los datos recibidos. El patrón de comunicaciones podrı́a ser un algoritmo BDE, como se ve en las Figuras 3.29 y 3.30. Por las mismas razones expuestas para otras colectivas, además de una implementación con primitivas punto a punto bloqueantes, se proporciona una con envı́os no bloqueantes nbBDE. También es posible implementar esta función es usando un algoritmo cı́clico o BKT. El resultado parcial de la reducción se va acumulando a través del anillo hasta obtener el resultado final (ver Figuras 3.31 y 3.32). Al igual que en Allgather y debido también a motivos de estabilidad del algoritmo, se proporciona una implementación con envı́os no bloqueantes (nbBKT). 58 3. Implementación de Primitivas Colectivas MPJ Procedure BDEReduceScatter(x,left,right) if left = right then return; size = right-left+1; mid = ⌊(left+right)/2⌋; if me ≤ mid then partner=me+⌊size/2⌋; else partner=me-⌊size/2⌋; if me ≤ mid then Send (xmid+1:right ,partner); Recv (tmp, partner); xlef t:mid = op (xlef t:mid , tmp); else Send (xlef t:mid ,partner); Recv (tmp, partner); xmid+1:right = op (xmid+1:right , tmp); if me ≤ mid then BDEReduceScatter (x,left,mid); else BDEReduceScatter (x,mid+1,right); Figura 3.29: Pseudocódigo de Reduce-Scatter BDE 3.1. Algoritmos Implementados 59 P0 P1 P2 P3 P0 P1 P2 P3 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 Situación Inicial P0 P1 P2 Paso 1 P3 0 0 2 2 1 1 3 3 P0 0 Paso 2 P1 1 P2 2 Situación Final Figura 3.30: Funcionamiento de Reduce-Scatter BDE Procedure BKTReduceScatter(x ) prev = me - 1; if prev < 0 then prev= npes - 1; next = me + 1; if next = npes then next=0; current i = next; for i=npes-2,...,0 do Send (xcurrent i ,prev); current i = current i + 1; if current i = npes then current i = 0; xcurrent i = op (xcurrent i , tmp); Figura 3.31: Pseudocódigo de Reduce-Scatter BKT P3 3 60 3. Implementación de Primitivas Colectivas MPJ P0 P1 P1 P2 P3 0 0 0 0 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3 3 P0 P3 P2 0 0 0 0 1 1 1 2 2 3 3 Situación Inicial P0 0 P1 1 P1 P0 P3 P2 P2 P3 0 0 3 0 1 0 2 3 Paso 1 1 2 2 3 1 1 2 3 3 Paso 2 Paso 3 P0 0 P1 1 P2 2 2 P3 3 Situación Final Figura 3.32: Reduce-Scatter BKT 3.1. Algoritmos Implementados 3.1.9. 61 Scan La primitiva Scan es una colectiva de reducción o computacional en la cual cada nodo realiza una operación de reducción con los mensajes enviados por todos los procesos de rango inferior o igual al suyo. Existen dos enfoque básicos para implementar esta operación. En el primero, cada uno de los procesos envı́a su mensaje a todos aquellos que tienen un rango superior. La recepción en cada nodo se realiza para cada uno de los mensajes enviados por los procesos con rango inferior. Ésta es la versión por defecto (nbFT) cuyo pseudocódigo se muestra en la figura 3.33, en la que los envı́os son no bloqueantes. Las recepciones en cambio sı́ son bloqueantes porque es necesario esperar a obtener los datos para realizar el cálculo. Procedure Scan(x ) nbFT for i=npes-1,...,me+1 do sreqi = Isend(xi ,i ); for i=0,...,me-1 do Recv(tmp,i ); res = op (res, tmp); res = op (res, xme ); for i=npes-1,...,me+1 do Wait(sreqi ); Figura 3.33: Pseudocódigo de Scan nbFT Otra alternativa en la implementación del Scan es la utilización de un algoritmo secuencial. En este caso, un proceso envı́a al siguiente su resultado, calculado como resultado de acumular su mensaje al que recibe del proceso anterior, que, a su vez, es el resultado de aplicar la operación de reducción a los mensajes de todos los procesos anteriores. De esta manera, sólo se realiza un envı́o y una recepción en cada nodo (excepto en el primero, que sólo envı́a, y el último, que sólo recibe). El pseudocódigo se puede ver en la Figura 3.34. Procedure Scan(x ) Secuencial if me > 0 then Recv (xme−1 ,me-1); xme = op (xme , xme−1 ); if me<npes-1 then Send (xme ,me+1); Figura 3.34: Pseudocódigo de Scan Secuencial 62 3. Implementación de Primitivas Colectivas MPJ 3.1.10. Barrier Una barrera supone un punto de sincronización para todos los procesos implicados. Su implementación presenta numerosas alternativas. El método tradicional es el uso de envı́os y recepciones bloqueantes de mensajes muy pequeños que impliquen a todos los procesadores. Siguiendo este enfoque, una posible implementación es utilizar un Antibroadcast (todos envı́an a un procesador dado) seguido de un Broadcast (todos reciben de ese mismo proceso). Es decir, que todos los procesos envı́en a uno raı́z y éste les devuelva el mensaje a continuación. Esta implementación se puede realizar con un Gather seguido de un Broadcast. En la versión por defecto de MPJ Express, se utiliza este algoritmo implementado directamente con comunicaciones punto a punto, construyendo un patrón de comunicaciones en árbol de grado cuatro en lugar de hacer llamadas a las primitivas colectivas correspondientes. Otra alternativa implementada es la utilización de un árbol binariopara los envı́os y recepciones (ver ejemplo en la Figura 3.35). En este caso, cuando el número de procesos no es potencia de 2, los que “sobran” se comunican con uno de los nodos hoja. El efecto que se produce es como si el conjunto de procesos se fuese desgranando por la mitad recursivamente y cada nodo se comunicase con otro perteneciente a la mitad opuesta. P0 P1 P2 P3 P4 P5 P6 P7 Paso 1 P0 P1 P2 P3 P4 P5 P6 p1 P7 p2 P5 P6 P7 Paso 2 P0 P1 P2 P3 P4 Paso 3 Figura 3.35: Funcionamiento de la Barrera usando un árbol binario En MPJ Express existe la posibilidad de utilizar una implementación en árbol binario “Exotic”, similar a las que se vieron para otras colectivas como Allgather. Capı́tulo 4 Integración y Optimización en Sistemas Multi-core 4.1. MPJ Express como Marco para la Integración Tal y como se dijo al principio del capı́tulo anterior, la biblioteca de operaciones colectivas desarrollada es portable, con lo que podrá ser integrada en cualquier implementación de MPJ. Sin embargo, para su validación experimental es necesario escoger una biblioteca de referencia en la que integrarla. La biblioteca MPJ más implantada es mpiJava. No obstante, sus colectivas son un wrapper a las de MPI, es decir, delegan en la implementación nativa, por lo que la biblioteca desarrollada no serı́a aplicable. De hecho, la biblioteca que se presenta en este proyecto está orientada a resolver los problemas de escalabilidad de las implementaciones Java puro (100 % Java). Entre estas bibliotecas, la que cuenta con mayor aceptación, con un desarrollo más activo y mayor número de usuarios, es MPJ Express [27] [28]. Aunque son numerosas las implementaciones existentes de MPJ [2] [30], la mayorı́a pertenecen a proyectos cuyo desarrollo ha sido abandonado. Entre las bibliotecas activas en la actualidad, destacamos MPJ/Ibis [29], que no ha sido seleccionado porque tiene problemas de configuración, pocos usuarios y no es totalmente portable. Otro ejemplo es P2P-MPI, orientada a soporte para Grid. Hay también otros proyectos que no implementan el API de MPJ, sino APIs similares, por ejemplo, Parallel Java o Jcluster. Otro proyecto interesante es F-MPJ [30], fruto de la investigación realizada 63 64 4. Integración y Optimización en Sistemas Multi-core en el Grupo de Arquitectura de Computadores de la Universidad de A Coruña, que ya proporcina algunos algoritmos escalables. No obstante, no está todavı́a disponible públicamente. Finalmente, nos hemos decantado por MPJ Express en base a las siguientes razones: es una biblioteca disponible públicamente, tiene cientos de usuarios, y su biblioteca de operaciones colectivas no está optimizada. Para la integración, se ha modificado la clase Intracomm del paquete mpi (en MPJ Express, sustituye “mpj” por “mpi” en los nombres de paquetes y clases, pero esto apenas afecta a la implementación de las colectivas) incluyendo los métodos desarrollados en la biblioteca de operaciones colectivas, además de la documentación de acuerdo a las convenciones vigentes y todo lo necesario para la selección automática de algoritmos1 . 4.1.1. Correcciones sobre MPJ Express A medida que se fue avanzando en la integración y la inclusión de las distintas colectivas en la clase Intracomm de MPJ Express, se fueron detectando pequeños bugs que han sido subsanados para que no afectasen al funcionamiento de las operaciones. La mayorı́a de estos errores se corresponden con problemas en los buffers temporales y tratamiento de desplazamientos iniciales. Esto aparece, por ejemplo, en las operaciones de reducción predefinidas, donde para que el resultado se vaya acumulando, es necesario inicializar el buffer interno de la operación con una llamada a createInitialBuffer(). En esta función se utiliza un solo offset o desplazamiento inicial. Esto obliga a suponer que los datos que se pasan para crear el buffer inicial tienen el mismo desplazamiento que el vector de datos con el que se van a realizar las demás operaciones. En las colectivas de reducción, esto no se puede suponer. En la llamada a Reduce, por ejemplo, podrı́amos tener un offset diferente en el envı́o y en la recepción. Aunque esta situación puede no ser muy habitual, sı́ que se trata de una violación de la especificación de la primitiva, ya que si el offset del envı́o es diferente al de la recepción, podrı́a haber incoherencias al ir actualizando el valor almacenado. El proceso raı́z inicializará el buffer con sus datos para enviar y su offset de envı́o, pero a continuación utilizará el vector de recepción con su offset para actualizarlo. En este caso, una solución sencilla serı́a copiar 1 El código desarrollado asciende a 5.210 SLOC (Source Lines Of Code) [36] 4.1. MPJ Express como Marco para la Integración 65 al inicio el array de envı́o en otro con diferente offset, lo que obligarı́a a copiar dos veces el array (uno en un array temporal y otro al inicializar el buffer de la operación). En otros casos, en que las operaciones de reducción se vayan actualizando de forma parcial utilizando nodos intermedios, la solución se complica e implicarı́a numerosas copias de arrays. La solución por la que se ha optado consiste en crear una función nueva dentro de las operaciones predefinidas, la cual ha de recibir dos offsets, uno para el vector de entrada y otro para el vector que va a almacenar los resultados intermedios. El primero coincidirı́a con el offset del vector a enviar y el segundo con el del vector en que se recibe. En el estándar MPJ no se define la interfaz de una operación de reducción predefinida, con lo cual, en cada biblioteca, habrı́a que comprobar el funcionamiento de estas operaciones. En este punto, lo mejor serı́a poder realizar esta modificación en una superclase sin tener que tratar cada operación predefinida por separado. No obstante, esto no es posible en MPJ Express, lo cual obliga a implementar esta función auxiliar en cada clase. Es importante destacar que también se cambiaron las llamadas en las funciones por defecto incluidas en MPJ Express para asegurar su correcto funcionamiento. Un error similar afecta al Reduce y al Scan. Por lo que se puede deducir de los comentarios que aparecen en el código fuente de la clase Intracomm, este mismo error ha sido subsanado en la primitiva Allreduce gracias a la colaboración de la comunidad. El problema radica en la creación de buffers temporales. Estos se creaban copiando el buffer de envı́o y luego se aplicaban las operaciones de reducción con el offset del buffer de recepción. De nuevo, estos offset no tienen por qué ser iguales. La solución implementada para solventar este problema consiste en crear buffers temporales sin offset inicial y cambiar el tratamiento de los desplazamientos en el resto del método. Para el Scan se creó una función que implementa una versión similar a la que aparece en MPJ Express pero corregida (es decir, tratando buffers y offsets de forma correcta). De esta manera, será posible evaluar si las modificaciones para corregirlo introducen algún problema de eficiencia. Finalmente, en las implementaciones de colectivas de MPJ Express, cuando se pro- 66 4. Integración y Optimización en Sistemas Multi-core ducen condiciones de fallo, se notifica mediante un mensaje por pantalla, pero no se lanza ninguna excepción ni se detiene la ejecución. Con el objeto de tener una implementación más robusta, se han modificado estas notificaciones sustituyéndolas por el lanzamiento de una excepción de tipo MPIException que recibe como parámetro la cadena de caracteres que, antes de la corrección, se mostraba por pantalla. 4.2. Integración y Selección Automática de Algoritmos Tras implementar la biblioteca de operaciones colectivas, pasamos a integrarla en una biblioteca de paso de mensajes para Java (MPJ) con el objeto de que pueda ser utilizada y probada. A pesar de que es portable por estar construida sobre primitivas punto a punto estándar, es necesario realizar una serie de pasos adicionales para llevar a cabo la integración. Tal y como vimos en la sección de diseño, utilizaremos la clase Intracomm, en la que introduciremos los algoritmos implementados (ver Figura 4.1). Serı́a posible crear una subclase con la implementación de la biblioteca desarrollada, pero uno de los objetivos es facilitar su incorporación a cualquier biblioteca MPJ, en particular en MPJ Express, proyecto con el cual se planea la próxima inclusión de esta biblioteca de operaciones colectivas en su distribución oficial, ası́ que no se deberı́a alterar su estructura. Esta integración con MPJ Express se ha realizado con éxito en el marco de este proyecto. mpj MPJ Group Datatype Comm Intracomm Cartcomm Status Intercomm Request Op Prequest Graphcomm Figura 4.1: Clases principales en MPJ UserFunction 4.2. Integración y Selección Automática de Algoritmos 67 Otro de los objetivos del proyecto era el de proporcionar varios algoritmos por primitiva y habilitar un mecanismo de selección. Esta selección del algoritmo se pretende que sea automática y para ello se utilizará un fichero de configuración que indique cuál escoger en función de dos parámetros: número de procesadores y tamaño de mensaje. No obstante, para las colectivas Barrier y Alltoallv, se realizará sólo en función del número de procesadores. En la primera, el tamaño de mensaje es irrelevante y, en la segunda, no tiene por qué ser el mismo para todos y podrı́a causar conflictos de selección entre distintos procesos. En las demás, estos dos parámetros son fáciles de medir en tiempo de ejecución y sirven para caracterizar, en cierto modo, la arquitectura utilizada. Es posible también que haya casos en que se prefiera seleccionar el algoritmo directamente en el código fuente sin utilizar la selección automática. Para ello, como se explica más adelante, se incluyen variables estáticas que permiten activar y desactivar la selección automática y especificar el algoritmo a utilizar en cada colectiva. Para cargar la configuración de selección, se utilizará un inicializador estático en la clase Intracomm que cree una tabla hash con los valores del número de procesos y tamaño de mensajes, junto con el algoritmo a seleccionar. Esto se consigue en Java fácilmente si utilizamos un fichero .properties para la configuración. Para evitar errores en tiempo de ejecución, crearemos un método de acceso que permita obtener cualquiera de estos valores del fichero y que, en caso de que algún campo no esté configurado, nos proporcione un valor por defecto sin provocar una excepción. Ası́, será posible configurar sólo las opciones deseadas. Todas las opciones configurables en tiempo de ejecución se presentan en forma de variables estáticas. Para referirnos a cada algoritmo, se definen unas constantes en la clase Intracomm cuyo nombre tendrá el formato ALGORITMO COLECTIVA, por ejemplo REDUCE MST. Para indicar si la selección es automática o no, se incluirá una variable estática booleana SELECT AUTO. También se define otra variable estática para cada colectiva de nombre optionCollective, configurable por el usuario y que señalará el algoritmo a utilizar en cada operación (por ejemplo, optionBcast) en caso de elegir el algoritmo desde el código y como opción por defecto en la selección automática. Por último, ya que la selección de algoritmo se hace en función del número de nodos y del 68 4. Integración y Optimización en Sistemas Multi-core tamaño del mensaje, para utilizar la función de acceso a las propiedades, se definen dos variables estáticas para los valores por defecto del número de procesos y tamaño de mensaje, respectivamente: PROC DEFAULT y SIZE DEFAULT . Estas variables serán utilizadas cuando no se den valores especı́ficos en el fichero de configuración. En caso de estar activada la selección automática de algoritmos (mediante SELECT AUTO), todos los procesos llaman a una función de configuración automática al inicio de su ejecución (selectAlgorithm). Esta función utilizará el número de procesos y el tamaño de mensaje para seleccionar la opción adecuada del fichero de configuración (o coger la opción por defecto en el caso de encontrarse en una situación no configurada). El número de procesos implicados en la comunicación es sencillo de obtener con la función de MPJ Size() desde cualquiera de los procesos. El tamaño de mensaje, en las colectivas sin sufijo v, es directamente el del mensaje que se va a enviar o recibir en cada procesador. Para operaciones en que el tamaño de mensaje sea variable, por ejemplo Gatherv, se utiliza una función auxiliar que en función del vector de tamaños calcule un valor medio, el cual será utilizado en la selección del algoritmo. En cada colectiva, el método que implementa la firma de la operación estándar, es el que comprueba primero si la selección ha de ser automática. En ese caso, invoca a la función de selección y obtiene el algoritmo a utilizar. Si el algoritmo seleccionado de este modo no cumple los prerrequisitos para ejecutarse con la configuración actual (por ejemplo, si se ha seleccionado un algoritmo BDE y el número de procesadores no es potencia de dos), se sustituye por la opción por defecto. En caso de que no esté activada, se utilizará la opción definida por defecto para la primitiva colectiva que se esté invocando (optionCollective). A continuación, se muestran las firmas y documentación de los tres métodos más significativos en la selección automática. En primer lugar, el que elige el algoritmo adecuado para la operación colectiva indicada en función del tamaño de mensaje y del número de procesos (selectAlgorithm). El siguiente método es el que lee los valores del fichero de configuración (getProp). Recibe un parámetro que le indica qué propiedad leer y un valor por defecto por si falla la lectura, por ejemplo, por no estar configurada esa opción. Finalmente, se muestra la firma del método que calcula el tamaño de mensaje 4.2. Integración y Selección Automática de Algoritmos 69 medio, utilizado en primitivas colectivas en que los mensajes pueden ser de diferente tamaño (mean). En la implementación proporcionada, la media se obtiene como media aritmética de los tamaños de mensaje de cada procesador. Serı́a posible variar esta implementación sin afectar al resto de métodos implicados. protected int selectAlgorithm( int algTag, int proc, int msize ) Usage • It selects the collective algorithm. It uses SELECT AUTO to determine if the selection should be manual (optionCollective) or automatic. algTag collective identifier proc number of processes msize message size returns: algorithm identifier protected int getProp( java.lang.String key, int def ) Usage • It accesses to properties in configuration file. If an exception occurs, it returns the default value. key key in .properties file def property’s default value returns: property’s value protected int mean( int [] vector, int total ) Usage • Auxiliary function to obtain the message size in a collective operation vector size vector total number of sizes returns: size value 70 4. Integración y Optimización en Sistemas Multi-core En el Cuadro 4.2 aparece un ejemplo de configuración. npes representa el número de procesos y tam el tamaño de mensaje. Cuando no es aplicable la diferencia de región por tamaño de mensaje, se muestra un guión. Broadcast Gather Gatherv Scatter Scatterv Allgather Allgatherv Alltoall Alltoallv Reduce Allreduce Reduce-Scatter Scan Barrier npes < 8 tam< 8KB MST nbFT nbFT nbFT nbFT nbFTGather + MSTBcast nbFTGather + MSTBcast nbFT nbFT bFT nbFTReduce +MSTBcast nbBDE Secuencial Árbol binario npes < 8 tam>= 8KB MST nbFT nbFT nbFT nbFT nbFTGather + MSTBcast nbFTGather + MSTBcast nbFT bFT nbFTReduce +MSTBcast nbBKT Secuencial - npes >= 8 tam< 8KB MST MST MST MST MST MSTGather +MSTBcast MSTGather +MSTBcast nbFT nbFT MST MSTReduce +MSTBcast nbBDE Secuencial Árbol binario npes >= 8 tam>= 8KB MST MST MST MST MST MSTGather + MSTBcast MSTGather + MSTBcast nbFT MST MSTReduce +MSTBcast nbBKT Secuencial - Cuadro 4.1: Ejemplo de Configuración de Algoritmos para las Operaciones Colectivas 4.3. Código de Ejemplo Hecha la integración, para poder ejecutar un código MPJ para MPJ Express usando la nueva biblioteca, sólo habrı́a que realizar un fichero de configuración (por ejemplo, con los algoritmos elegidos en el Cuadro 4.2), compilar nuestro código con MPJ Express y ejecutarlo. Un ejemplo podrı́a ser el siguiente: import mpi.*; public class BroadcastEjemplo { public static void main(String[] args) throws MPIException { int my_pe, npes; MPI.Init(args); my_pe = MPI.COMM_WORLD.Rank(); npes = MPI.COMM_WORLD.Size(); 4.3. Código de Ejemplo 71 MPI.COMM_WORLD.Barrier(); if(my_pe == 0){ System.out.println("BEGIN"); } String [] arrStr = new String [1]; arrStr[0]= new String("Hello"); MPI.COMM_WORLD.Bcast(arrStr, 0, 1, MPI.OBJECT, 0); System.out.println("Process["+my_pe+"] Message received:"+arrStr[0]); MPI.COMM_WORLD.Barrier(); if(my_pe == 0){ System.out.println("THE END"); } MPI.Finalize(); } } Este código se corresponderı́a con la clase “BroadcastEjemplo.java”. En ella, se importan los paquetes necesarios de MPJ Express, se inicializan los procesos MPJ (MPI.Init(args)), se realiza la operativa necesaria (en este caso, el Broadcast de una cadena de caracteres) y se finalizan los procesos MPJ (MPI.Finalize()). Las operaciones que llevan a cabo los procesos, además de obtener el número de procesadores total (MPI.Size()) y su rango (MPI.Rank()), son sincronizaciones (Barrier) y un Broadcast. Como se puede ver, estas operaciones se enmarcan en un Comunicador global (MPI.COMM WORLD) que incluye todos los procesos. Ejecutado con cuatro procesos, el resultado serı́a el siguiente: BEGIN Process[0] Process[1] Process[2] Process[3] THE END Message Message Message Message received:Hello received:Hello received:Hello received:Hello 72 4. Integración y Optimización en Sistemas Multi-core 4.4. Incremento de la Escalabilidad en Operaciones Colectivas Una de las principales ventajas de la biblioteca desarrollada es la posibilidad de seleccionar automáticamente los algoritmos que, en función de las caracterı́sticas de cada caso, proporcionen un mejor rendimiento. Lo más habitual es que cada uno de los algoritmos presente ventajas e inconvenientes dependiendo de la configuración experimental. Ası́, aunque los MST reducen las comunicaciones entre nodos distintos, requieren cierto trabajo de inicialización que puede no compensar si el mensaje es pequeño o el número de procesos es reducido y todos forman parte del mismo nodo. Por ello, disponer de diversas implementaciones por colectiva será beneficioso a la hora de paralelizar la ejecución de aplicaciones con MPJ mediante la variación de los parámetros de configuración. En esta biblioteca, para la elección del algoritmo, se utilizan el tamaño de mensaje y el número de procesos. Los lı́mites en cada caso configuran cuatro regiones: mensajes pequeños y pocos procesos, mensajes grandes y pocos procesos, mensajes pequeños y muchos procesos, mensajes grandes y muchos procesos (exceptuando Alltoallv y Barrier en los que, como se ha comentado anteriormente, no tiene sentido separar por tamaño de mensaje). La parametrización de la configuración para obtener rendimientos escalables de las operaciones colectivas, requiere un trabajo previo de benchmarking en la máquina a utilizar. Ası́, se obtendrı́an los parámetros lı́mite en cada colectiva y las implementaciones adecuadas que maximizan el rendimiento para cada cuadrante de parámetros de configuración. Esto es ası́ porque dependiendo de la arquitectura utilizada (el número de cores por núcleo, las interconexiones entre procesadores, etc) los umbrales pueden variar e incluso los algoritmos por cuadrante pueden ser diferentes (sobre todo, en implementaciones con caracterı́sticas similares, como un BT o un BDE). Esta función de inicialización se incluirı́a en el inicializador estático de la clase Intracomm y deberı́a ejecutarse sólo cuando no existiese ya un fichero de configuración. Una de las motivaciones principales de este proyecto era el proporcionar una biblioteca de operaciones colectivas que aprovechase las arquitecturas clúster multi-core. El algoritmo que mejor lo hace es el MST. Esto afecta directamente al Broadcast, Gather, 4.4. Incremento de la Escalabilidad en Operaciones Colectivas 73 Reduce, Scatter, primitivas a las que se aplica el algoritmo MST, y todas las que se hayan implementado como combinación de las mismas (Allgather, Allreduce, Reduce-Scatter), ası́ como sus variantes terminadas en “v”. Asumiendo que el “mapeo” o asignación de procesos MPJ a cores, en un clúster multi-core, se realizará asignando rangos próximos a procesos que estén dentro del mismo procesador, se reducen las comunicaciones entre nodos de diferentes procesadores. Este algoritmo se basa en la subdivisión del espacio de procesos maximizando las comunicaciones internas entre subconjuntos y minimizando aquellas que tienen lugar entre conjuntos diferentes. Por lo tanto, si los procesos de un mismo conjunto están en el mismo nodo, se produce el efecto indicado anteriormente: se reducen las comunicaciones entre procesos de diferentes cores. Otra mejora, que afecta en realidad al uso de MPJ Express, es la reducción de copias de buffers innecesarios en las operaciones de reducción. Tal y como estaban planteadas las operaciones de reducción, se asumı́a que tanto buffer de envı́o como de recepción tenı́an las mismas caracterı́sticas. Esto obligarı́a a hacer copias en vectores temporales para evitar problemas en caso de diferencias entre ellos, como se vio en la sección anterior. En el caso de una implementación en árbol plano, esto sólo conlleva una copia al inicio. Dependiendo de la operación y de quién reciba el resultado, podrı́a ser en todos o sólo en uno de los nodos. No obstante, en un algoritmo algo más complejo, con múltiples envı́os y recepciones en varias etapas, esto supondrı́a hacer copias temporales en cada paso, lo cual afectarı́a de forma importante al rendimiento. Capı́tulo 5 Evaluación del Rendimiento 5.1. Configuración Experimental La evaluación de la biblioteca desarrollada se ha llevado a cabo sobre un clúster multi-core con 8 nodos, cada uno de ellos con 4 GB de RAM y 2 procesadores Intel Xeon 5060 dual-core (4 cores por nodo), interconectados mediante InfiniBand (20 Gbps) y Gigabit Ethernet (1 Gbps). Su sistema operativo es CentOS 5.2 con kernel 2.6.18. La JVM utilizada es la de Sun Microsystems, versión 1.6.0 07. Para esta evaluación se ha utilizado la biblioteca de paso de mensajes en Java más representativa y extendida, MPJ Express versión 0.27. La biblioteca MPI nativa utilizada ha sido Intel MPI 3.2.0.011 configurada para utilizar InfiniBand. El compilador utilizado es el de Intel versión 11.0.074. El micro-benchmarking de la biblioteca de operaciones colectivas se ha realizado utilizando los 32 cores del clúster, a razón de 4 procesos MPJ/MPI por nodo. 5.2. Análisis del Rendimiento de la Biblioteca de Colectivas Implementada Aquı́ se presentan los resultados de rendimiento para las primitivas Broadcast (Figura 5.1), Scatter (Figura 5.2), Allgather (Figura 5.3), Alltoall (Figura 5.4), Reduce (Figura 5.5) y Allreduce (Figura 5.6) para la biblioteca desarrollada, integrada dentro de MPJ Express (resultados etiquetados con “Nueva”), de forma comparativa con las primitivas originales de MPJ Express (resultados etiquetados con “Original”). Esta evaluación ha sido realizada utilizando tanto la red InfiniBand (utilizando el soporte 75 76 5. Evaluación del Rendimiento proporcionado por la emulación TCP/IP sobre InfiniBand, IPoIB), como la red Gigabit Ethernet. Los datos transferidos son arrays de bytes, evitando de este modo la serialización, proceso de transformación de los objetos en arrays de bytes, ya que puede penalizar de forma importante el rendimiento en cualquiera los casos. La métrica utilizada en esta evaluación experimental es el ancho de banda agregado, pues tiene en cuenta la cantidad total de información transferida por cada primitiva, que es el tamaño del mensaje multiplicado por el número de procesos, excepto para Allgather y Alltoall, en donde se multiplica el tamaño de mensaje por el cuadrado del número de procesos. Los resultados experimentales han sido obtenidos con la suite de micro-benchmarks de primitivas colectivas MPJ desarrollada en el Grupo de Arquitectura de Computadores de la UDC [2], debido a la inexistencia de benchmarks adecuados para esta evaluación del rendimiento. Los algoritmos utilizados por MPJ Express son los que aparecen en el Cuadro 3 (ver Capı́tulo 3). Para la nueva biblioteca, se han seleccionado los siguientes algoritmos: MST para el Broadcast, nbFT para el Scatter, Gather (nbFT) + Bcast (MST) para el Allgather, bFT para el Alltoall, MST para el Reduce, y Reduce (MST) + Bcast (MST) para el Allreduce. Para la elección, se han tenido en cuenta resultados experimentales previos. Parte de estos resultados previos se pueden consultar en el Apéndice 2. Como se puede apreciar en las gráficas, los resultados obtenidos verifican que la nueva biblioteca de primitivas colectivas aumenta significativamente el rendimiendo con respecto a las operaciones colectivas originales de MPJ Express. Este incremento es más acusado en operaciones que usan el algoritmo MST, ya que este permite minimizar el coste de comunicaciones en clusters multi-core. Las operaciones que usan este algoritmo son el Broadcast, Allgather (debido a que está implementado como un nbFTGather+MSTBcast), el Reduce, y el Allreduce (al estar implementado como un MSTReduce+MSTBcast). Esta mejora del rendimiento puede alcanzar los dos órdenes de magnitud, como se puede apreciar para Allgather con mensajes entre 4 y 64 KB. En el caso del Scatter y el Alltoall el rendimiento es similar al de la versión original, bien por usar el mismo algoritmo (como en el caso del Scatter), bien por utilizar algoritmos poco escalables (Alltoall usa el bFT). En este último caso, se podrı́a haber utilizado 5.2. Análisis del Rendimiento de la Biblioteca de Colectivas Implementada 77 otro algoritmo, de los proporcionados en la nueva biblioteca, más escalable similar al usado en MPJ Express, con primitivas punto a punto no bloqueantes. No obstante, se utiliza un bFT a modo de comparación. Hay casos como el Scatter en que se podrı́a haber utilizado un algoritmo MST. Sin embargo, en este algoritmo es necesario realizar envı́os a mayores entre nodos intermedios y aunque es beneficioso en caso de tener un comunicador formado por un gran número de procesos, puede no ser lo más efectivo si el número de nodos es pequeño. En este caso, tras unas pruebas preliminares (ver Anexo 2), se ha visto que para el Scatter, Ancho de Banda Agregado [MB/s] con pocos nodos, es más eficiente usar nbFT. Broadcast 1600 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 1400 1200 1000 800 600 400 200 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.1: Resultados experimentales de Broadcast con 32 cores Ancho de Banda Agregado [MB/s] 78 5. Evaluación del Rendimiento Scatter 450 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 400 350 300 250 200 150 100 50 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.2: Resultados experimentales de Scatter con 32 cores Finalmente, es posible apreciar el impacto en el rendimiento del cambio del protocolo de comunicación punto a punto, cuyo umbral está establecido en 512KB para la biblioteca propuesta y en 128KB por defecto en MPJ Express. Este cambio de protocolo afecta a los envı́os, que necesitan que el receptor indique dónde quiere recibir el mensaje cuando éste es de un tamaño considerable, pues en caso contrario, no serı́a posible almacenarlo de forma temporal. Cuando el mensaje es pequeño, se puede utilizar un eager send, es decir, un envı́o no acordado que asume que el receptor tiene suficiente espacio para almacenar el mensaje y no necesita su autorización explı́cita. En MPJ Express se utiliza un valor por defecto reducido (128KB), que se ha decidido incrementar a 512KB, haciendo que sólo se penalicen mensajes de mayor tamaño por necesitar confirmación explı́cita del receptor (protocolo rendezvous). Ancho de Banda Agregado [MB/s] 5.2. Análisis del Rendimiento de la Biblioteca de Colectivas Implementada Allgather 1600 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 1400 1200 1000 800 600 400 200 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Figura 5.3: Resultados experimentales de Allgather con 32 cores Alltoall 1000 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 900 800 700 600 500 400 300 200 100 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.4: Resultados experimentales de Alltoall con 32 cores 79 Ancho de Banda Agregado [MB/s] 80 5. Evaluación del Rendimiento Reduce 800 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 700 600 500 400 300 200 100 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Figura 5.5: Resultados experimentales de Reduce con 32 cores Allreduce 600 Nueva (InfiniBand) Original (InfiniBand) Nueva (Gb. Eth.) Original (Gb. Eth.) 500 400 300 200 100 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.6: Resultados experimentales de Allreduce con 32 cores 5.3. Análisis Comparativo del Rendimiento de Colectivas MPJ vs. MPI 5.3. 81 Análisis Comparativo del Rendimiento de Colectivas MPJ vs. MPI Otro resultado interesante es el de la comparación entre MPJ y MPI. En las Figuras que van desde la 5.7, hasta la 5.12, se pueden ver los resultados obtenidos con MPJ Express, la biblioteca desarrollada y los obtenidos con MPI, sobre InfiniBand. En ellas se aprecia que MPI presenta unos rendimientos muy superiores a los obtenidos por colectivas MPJ. Aunque en MPI se observa un ligero descenso del rendimiento a partir de ciertos tamaños (por el uso de buffers y diferentes protocolos de envı́o), de forma muy acusada en algunas como Scatter e inapreciable en otras como Allgather, este descenso no es suficiente para que MPJ lo alcance. La diferencia de rendimiento entre MPJ y MPI es porcentualmente mucho mayor para mensajes cortos que para mensajes largos, debido a que MPJ presenta latencias mucho mayores que las de MPI con mensajes cortos por utilizar una emulación IP sobre InfiniBand, mientras que MPI utiliza un protocolo de más bajo nivel sobre InfiniBand, IBV (InfiniBand Verbs). Sin embargo, para mensajes largos, MPJ puede llegar a alcanzar anchos de banda comparables a los de MPI (aunque todavı́a inferiores). Estas diferencias son especialmente notables para Alltoall y Reduce Ancho de Banda Agregado [MB/s] 82 5. Evaluación del Rendimiento Broadcast 5000 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 4500 4000 3500 3000 2500 2000 1500 1000 500 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Figura 5.7: Comparación MPI y MPJ para Broadcast con 32 cores Scatter 1200 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 1000 800 600 400 200 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.8: Comparación MPI y MPJ para Scatter con 32 cores Ancho de Banda Agregado [MB/s] 5.3. Análisis Comparativo del Rendimiento de Colectivas MPJ vs. MPI 83 Allgather 7000 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 6000 5000 4000 3000 2000 1000 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Figura 5.9: Comparación MPI y MPJ para Allgather con 32 cores Alltoall 3000 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 2500 2000 1500 1000 500 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.10: Comparación MPI y MPJ para Alltoall con 32 cores Ancho de Banda Agregado [MB/s] 84 5. Evaluación del Rendimiento Reduce 6000 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 5000 4000 3000 2000 1000 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Figura 5.11: Comparación MPI y MPJ para Reduce con 32 cores Allreduce 4000 MPI (InfiniBand) Nueva (InfiniBand) Original (InfiniBand) 3500 3000 2500 2000 1500 1000 500 0 1KB 2KB 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura 5.12: Comparación MPI y MPJ para Allreduce con 32 cores 5.4. Resultados con una Aplicación Java HPC (jGadget) 5.4. 85 Resultados con una Aplicación Java HPC (jGadget) Gadget-2 [37] es una aplicación de simulación bastante extendida en Cosmologı́a ya que permite estudiar una gran variedad de problemas, desde la colisión y fusión de galaxias, hasta la formación de estructuras cosmológicas a gran escala. Esta aplicación se utiliza constantemente para investigación en Cosmologı́a, siendo una buena medida de popularidad el haber sido citado en más de 700 artı́culos de investigación hasta el momento, destacando su uso en la “Millennium Simulation” [38], la mayor simulación Nbody (con más de 1010 partı́culas) del Universo desde sus orı́genes hasta hoy, ejecutada sobre 512 procesadores y que requirió 350000 horas de CPU y 1TB de memoria. Gadget2 está escrito en C y paralelizado con MPI. Además, hace uso de la biblioteca cientı́fica del GNU (GNU Scientific Library, GSL) y la biblioteca FFTW, con soporte opcional para Hierarchical Data Format (HDF5). La estrategia de paralelización es una descomposición del dominio que se realiza de forma irregular y adaptada dinámicamente, con abundantes comunicaciones entre procesos. Este código ha sido portado a Java (jGadget) [39] y paralelizado usando MPJ Express para explorar las capacidades de Java en códigos de simulación real. Esta versión en Java no tiene la posibilidad de usar funciones GSL ni bibliotecas FFTW, pero es portable y facilita enormemente el desarrollo de aplicaciones. En la Figura 5.13 se proporcionan los resultados obtenidos con la aplicación jGadget usando las colectivas originales de MPJ Express y las de la biblioteca desarrollada. Las pruebas han sido realizadas con la misma configuración que las de los apartados anteriores. Se puede ver que la nueva biblioteca de colectivas proporciona una mayor escalabilidad, puesto que, aunque con pocos procesadores los resultados son similares para ambas bibliotecas, cuando el número de procesos aumenta, disminuye el rendimiendo de las primitivas de MPJ Express, mientras que, para la biblioteca desarrollada, se incrementa. Además, la diferencia de rendimiento es considerable. Este problema de escalabilidad se hace patente a partir de 16 procesadores sobre Gigabit Ethernet y a partir 32 para InfiniBand. 86 5. Evaluación del Rendimiento Rendimiento de jGadget 3500 Original (Gig. Eth.) Nueva (Gig. Eth.) Original (InfiniBand) Nueva (InfiniBand) Tiempo (segundos) 3000 2500 2000 1500 1000 500 0 1 2 4 8 Número de Procesos 16 32 Figura 5.13: Resultados obtenidos con la aplicación jGadget Capı́tulo 6 Principales Aportaciones y Conclusiones A pesar de las reticencias iniciales debido a su menor rendimiento, el uso de Java en Computación de Altas Prestaciones está siendo objeto de un creciente interés gracias a que, además de ventajas como seguridad, robustez, orientación a objetos, expresividad y sencillez, entre otras muchas, incluye en el núcleo del lenguaje un completo soporte multithread y comunicaciones de red. Además, la compilación a código nativo en tiempo de ejecución mediante el compilador Just-In-Time hace que el rendimiento de Java hoy dı́a sea competitivo y pueda ser utilizado en entornos en los que el rendimiento es crı́tico. Por otro lado, el paradigma de programación con paso de mensajes es la opción preferida a la hora de paralelizar aplicaciones en las arquitecturas más populares hoy en dı́a, los clúster multi-core. Todo ello, lleva a que haya un gran interés en el desarrollo de proyectos de bibliotecas de paso de mensajes sobre Java (MPJ). Dentro de las bibliotecas de paso de mensajes, las operaciones colectivas juegan un importante papel por representar patrones de comunicación muy habituales y, por tanto, es crucial su rendimiento y escalabilidad. El objetivo de este proyecto es presentar una biblioteca de primitivas colectivas de paso de mensajes en Java que permita obtener rendimientos escalables sobre diferentes configuraciones y que sea portable. Tras una análisis de las soluciones existentes y del estándar MPJ, se desarrolló una biblioteca que permite seleccionar, de forma automática, el algoritmo más conveniente para una determinada configuración, entre el conjunto de los implementados para cada operación colectiva. La biblioteca ha sido integrada 87 88 6. Principales Aportaciones y Conclusiones en MPJ Express y se ha evaluado su rendimiento comparándolo con las primitivas colectivas originales de MPJ Express y con el de una implementación MPI. 6.1. Principales Aportaciones Las principales aportaciones de este trabajo son las siguientes: Se trata de la primera implementación libre y portable de una biblioteca de primitivas colectivas de paso de mensajes en Java. Además se trata de la primera biblioteca de operaciones colectivas que incluye varios algoritmos por cada operación. Diseño y desarrollo de un sistema de selección automática de algoritmos en tiempo de ejecución con el objetivo de maximizar el rendimiento para cada configuración. La biblioteca desarrollada contiene la documentación de las funciones colectivas del estándar MPJ más completa hasta la fecha. La biblioteca desarrollada es totalmente portable, al estar basada en funciones del estándar MPJ. Se ha demostrado integrando esta biblioteca de forma sencilla en la implementación MPJ Express. Como ya se ha mencionado anteriormente, la inclusión de la biblioteca desarrollada en la distribución oficial se realizará en breve. Evaluación del rendimiento de primitivas y de los distintos algoritmos de forma comparativa con respecto a MPJ Express y a MPI. Obtención de una publicación nacional derivada del trabajo del proyecto: S. Ramos, G.L. Taboada,J. Touriño y R. Doallo, “Biblioteca de Primitivas Colectivas en Paso de Mensajes para Java en Sistemas Multi-core”, a presentar en las XX Jornadas de Paralelismo [40]. Finalmente, de forma colateral, se han corregido ciertos bugs en el manejo de buffers en las operaciones de reducción de MPJ Express y en alguna primitiva colectiva como el Reduce. 6.2. Conclusiones 6.2. 89 Conclusiones En este proyecto se presenta una biblioteca de primitivas colectivas de paso de mensajes en Java que proporciona la implementación de varios algoritmos por operación colectiva, permitiendo obtener rendimientos escalables sobre diferentes configuraciones de procesos. La posibilidad de seleccionar dichos algoritmos en tiempo de ejecución de forma automática y configurable facilita el incremento del rendimiento de las aplicaciones Java en computación de altas prestaciones, ya que se incluyen algoritmos que adaptan los patrones de comunicación a distintas arquitecturas, los clúster multi-core entre ellos, con el objeto de maximizar su eficiencia. Un punto importante es que la implementación se ha llevado a cabo sobre primitivas punto a punto del estándar MPJ, lo que hace que la biblioteca sea portable y posibilita su integración en distintas implementaciones MPJ. De hecho, en este proyecto, se ha integrado en MPJ Express de forma inmediata. Además, la evaluación experimental ha mostrado significativas mejoras en el rendimiento en comparación con las operaciones originales. Finalmente, este proyecto plantea nuevas lı́neas de investigación, como la profundización en la configuración automática para proporcionar ficheros de configuración adaptados a un sistema concreto. Otro posible camino es la combinación de algoritmos en dos niveles, pensando en arquitecturas jerárquicas, ya que un algoritmo puede maximizar el rendimiento en una comunicación intranodo, mientras que otro puede ser el algoritmo ideal para comunicaciones internodo. De este modo es posible incrementar el rendimiento y la escalabilidad de Java en sistemas multi-core. Apéndice A Planificación A continuación, se detalla la planificación del trabajo realizado desde comienzos de julio de 2008 hasta julio de 2009. Se ha planificado un trabajo de ocho horas diarias los meses de verano (julio, agosto y septiembre) y de unas dos horas y media durante los meses que abarcan el curso 20082009, siempre contando cinco dı́as laborables por semana. Este cambio de horario es debido a que, durante ese periodo, la realización del proyecto ha tenido que combinarse con las tareas y clases de quinto curso. Las actividades planificadas (ver figura A.1 se dividen en documentación, análisis y diseño, implementación de colectivas, pruebas unitarias, integración, pruebas de integración, benchmarking, redacción de la memoria y preparación de la presentación. Análisis y diseño abarca tanto el análisis y diseño de algoritmos para cada primitiva como el análisis y diseño de la integración. Estas actividades de análisis de algoritmos, implementación y pruebas unitarias, se han ido solapando de manera que, para cada colectiva, se realiza un ciclo completo que abarca: análisis y diseño, implementación y pruebas. Hecho esto, es cuando se lleva a cabo la integración, las pruebas de integración y el benchmarking. El diagrama de Gantt de la figura A.2 muestra todas las actividades, aunque el detalle de las relativas a cada colectiva aparece en la figura A.3. Con esta planificación, el número de horas de trabajo asciende a 1.068 horas, repartidas en 273 dı́as, comenzando el 1 de julio de 2008 y terminando el 16 de julio de 2009. A lo largo de la realización del proyecto aparecen varios hitos que representan con91 92 A. Planificación troles de evaluación del desarrollo del proyecto por parte de los directores. No implican horas de trabajo del realizador del proyecto, con lo cual, no se incluyen como actividades propiamente dichas, sino como puntos de control. Uno de los directores, o ambos, evalúan el trabajo hecho hasta ese punto y comunican su parecer. Esto podrı́a ocasionar una replanificación de ciertas fases en caso de que haya que corregir de forma importante el producto de alguna de las actividades ya realizadas. Estimando un trabajo de 4 horas por control para el director que evalúe el proyecto y teniendo en cuenta que el segundo director sólo participa en el control de la planificación inicial, el de las colectivas y el de la memoria, y que el primer director está presente en todas, tenemos que sumar 24 horas de trabajo del primer director y 12 del segundo. Considerando además un trabajo previo de preparación de cada revisión, se estima un total de 36 horas para el segundo director y 72 horas para el primero. Si suponemos un coste aproximado en función del valor de mercado, el coste estimado del trabajo del proyecto serı́a el que aparece en la tabla A. Realizador del proyecto Primer director Segundo director Horas de trabajo 1.068 h Coste por hora 30 e/h Coste total. 32.040 e 72 h 36 h 45 e/h 60 e/h TOTAL 3.240 e 2.160 e 37.340 e Cuadro A.1: Coste estimado de realización del proyecto. Por otro lado, si contabilizamos el esfuerzo en lı́neas de código desarrolladas, se puede cuantificar como 5.210 SLOC (Source Lines Of Code) [36]. En un estudio del coste de los proyectos de desarrollo software en computación de altas prestaciones [41] se indica que el coste estimado por lı́nea de código es de 75$ por SLOC. Esto resultarı́a en un coste total de 390.750$, con lo que queda patente la elevada productividad alcanzada en el desarrollo del presente proyecto. Finalmente, es necesario remarcar que MPJ Express ha tenido más de 2000 descargas desde la web de su proyecto y cuenta con varios cientos de desarrolladores activos, por lo que hay un amplio número de potenciales usuarios de la biblioteca desarrollada. 93 Figura A.1: Actividades 94 A. Planificación Figura A.2: Diagrama de Gantt 95 Figura A.3: Detalle de planificación de realización de colectivas Apéndice B Resultados Experimentales Adicionales A continuación se proporcionan las gráficas de resultados experimentales obtenidos en el supercomputador Finis Terrae (se presenta su descripción a continuación) para varias colectivas comparando algunos de los algoritmos implementados. Para cada función colectiva, se muestran dos gráficas, una con tamaños de mensaje pequeños, en los que se compara el tiempo de ejecución o latencia, y otra con mensajes mayores, en las que aparece el ancho de banda agregado. La implementación con el nombre “Original”, indica que el algoritmo usado es el proporcionado por MPJ Express (se puede consultar en el Cuadro 3, en el Capı́tulo 3). Esta evaluación adicional del rendimiento de la biblioteca desarrollada se ha realizado en un nodo del supercomputador Finis Terrae [42] ubicado en el Centro de Supercomputación de Galicia (CESGA). Dicho nodo es un HP Integrity rx7640 con 16 cores Intel IA64 Itanium2 Montvale a 1,6 GHz y 128 GB de memoria. Los resultados contenidos en este Anexo presentan una evaluación preliminar del rendimiento de la biblioteca de operaciones colectivas desarrollada. Ası́, pueden servir para seleccionar el algoritmo más adecuado para una configuración en particular, en este caso, un nodo con 16 cores. No obstante, al no ser tan intenso el benchmarking como en el caso del realizado para el capı́tulo de resultados experimentales (en número de iteraciones del programa de prueba), los resultados presentan una mayor variabilidad e inestabilidad. Sin embargo, el objetivo de la evaluación preliminar no es conocer el rendimiento exacto de esta biblioteca, sino cuál es el algoritmo que maximiza el 97 98 B. Resultados Experimentales Adicionales rendimiento en una determinada configuración. Finalmente, se indica que el tamaño del mensaje en esta evaluación preliminar se corresponde con el tamaño global del array procesado, y no el de cada mensaje individual enviado, con el fin de reducir el número de pruebas a realizar. 99 Broadcast (latencia mensajes cortos) 600 Original bFT nbFT MST 550 Tiempo (µs) 500 450 400 350 300 250 200 150 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Broadcast (ancho de banda mensajes largos) 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 4KB Original bFT nbFT MST 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.1: Comparación de algoritmos para Broadcast con 16 cores 4MB 100 B. Resultados Experimentales Adicionales Scatter (latencia mensajes cortos) 540 Original MST 520 Tiempo (µs) 500 480 460 440 420 400 380 360 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Scatter (ancho de banda mensajes largos) 800 Original MST 700 600 500 400 300 200 100 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.2: Comparación de algoritmos para Scatter con 16 cores 4MB 101 Tiempo (µs) Gather (latencia mensajes cortos) 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 16B Original bFT nbFT MST 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Gather (ancho de banda mensajes largos) 500 Original bFT nbFT MST 450 400 350 300 250 200 150 100 50 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.3: Comparación de algoritmos para Gather con 16 cores 4MB 102 B. Resultados Experimentales Adicionales Allgather (latencia mensajes cortos) 4000 Original bBDE nbBKT MSTGather+MSTBcast bBKT nbBDE 3600 Tiempo (µs) 3200 2800 2400 2000 1600 1200 800 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Allgather (ancho de banda mensajes largos) 2400 2200 2000 1800 1600 1400 1200 1000 800 600 400 200 0 4KB Original bBDE nbBKT MSTGather+MSTBcast bBKT nbBDE 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.4: Comparación de algoritmos para Allgather con 16 cores 4MB 103 Alltoall (latencia mensajes cortos) 2800 Original bFT nb1FT nb2FT 2700 Tiempo (µs) 2600 2500 2400 2300 2200 2100 2000 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Alltoall (ancho de banda mensajes largos) 1400 1300 1200 1100 1000 900 800 700 600 500 400 300 200 100 0 4KB Original bFT nb1FT nb2FT 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.5: Comparación de algoritmos para Alltoall con 16 cores 4MB 104 B. Resultados Experimentales Adicionales Reduce (latencia mensajes cortos) 950 Original bFT MST Tiempo (µs) 900 850 800 750 700 650 600 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Reduce (ancho de banda mensajes largos) 350 Original bFT MST 300 250 200 150 100 50 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.6: Comparación de algoritmos para Reduce con 16 cores 4MB 105 Reduce−Scatter (latencia mensajes cortos) 4000 Original nbBKT bBKT nbBDE bBDE Tiempo (µs) 3500 3000 2500 2000 1500 1000 500 16B 32B 64B 128B 256B 512B 1KB 2KB Ancho de Banda Agregado [MB/s] Tamaño del Mensaje Reduce−Scatter (ancho de banda mensajes largos) 120 Original nbBKT bBKT nbBDE bBDE 100 80 60 40 20 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.7: Comparación de algoritmos para Reduce-Scatter con 16 cores 4MB 106 B. Resultados Experimentales Adicionales Allreduce (latencia mensajes cortos) 18000 Original MSTReduce+MSTBcast nbBDE bBDE 16000 12000 10000 8000 6000 4000 2000 0 16B 32B 64B 128B 256B 512B 1KB 2KB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Tiempo (µs) 14000 Allreduce (ancho de banda mensajes largos) 700 Original MSTReduce+MSTBcast nbBDE bBDE 600 500 400 300 200 100 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.8: Comparación de algoritmos para Allreduce con 16 cores 4MB 107 12000 11000 10000 9000 8000 7000 6000 5000 4000 3000 2000 1000 0 16B Original nbFT (Corregido) Secuencial 32B 64B 128B 256B 512B 1KB 2KB Tamaño del Mensaje Ancho de Banda Agregado [MB/s] Tiempo (µs) Scan (latencia mensajes cortos) Scan (ancho de banda mensajes largos) 160 Original nbFT (Corregido) Secuencial 140 120 100 80 60 40 20 0 4KB 8KB 16KB 32KB 64KB 128KB 256KB 512KB 1MB 2MB Tamaño del Mensaje Figura B.9: Comparación de algoritmos para Scan con 16 cores 4MB Bibliografı́a [1] “MPI: A Message-Passing Interface Standard. Message Passing Interface Forum.” Disponible en http://www.mpi-forum.org/docs/mpi-11-html/ mpi-report.html. [2] Taboada, G. L., Touriño, J., and Doallo, R., “Performance Analysis of Java Message-Passing Libraries on Fast Ethernet, Myrinet and SCI Clusters,” Proc. 5th IEEE Intl. Conf. on Cluster Computing (CLUSTER’03), pp. 118–126, 2003. [3] Barchet-Estefanel, L. A. and Mounie, G., “Fast Tuning of Intra-cluster Collective Communications,” in Proc. 11th European PVM/MPI Users’ Group Meeting (EuroPVM/MPI’04), LNCS vol. 3241, (Budapest, Hungary), pp. 28 – 35, 2004. [4] Chan, E., Heimlich, M., Purkayastha, A., and van de Geijn, R. A., “Collective Communication: Theory, Practice, and Experience,” Concurrency and Computation: Practice and Experience, vol. 19, no. 13, pp. 1749–1783, 2007. [5] Thakur, R., Rabenseifner, R., and Gropp, W., “Optimization of Collective Communication Operations in MPICH,” Intl. Journal of High Performance Computing Applications, vol. 19, no. 1, pp. 49–66, 2005. [6] Vadhiyar, S. S., Fagg, G. E., and Dongarra, J. J., “Towards an Accurate Model for Collective Communications,” Intl. Journal of High Performance Computing Applications, vol. 18, no. 1, pp. 159–167, 2004. [7] “mpiJava Home Page.” Disponible en http://www.hpjava.org/mpiJava.html. Última actualización, Mayo 2007. 109 110 BIBLIOGRAFÍA [8] Carpenter, B., Getov, V., Judd, G., Skjellum, A., and Fox, G., “MPI for Java. Position Document and Draft API Specification.” Disponible en http://www.npac. syr.edu/projects/pcrc/reports/MPIposition/position.ps, 1998. [9] Carpenter, B., Getov, V., Judd, G., Skjellum, A., and Fox, G., “MPJ: MPI-like Message Passing for Java,” no. 11, pp. 1019–1038, 2000. [10] Java Grande Forum. http://www.javagrande.org. [11] Lim, S. B., “Platforms for HPJava: Runtime Support for Scalable Programming in Java.” Disponible en http://www.hpjava.org/theses/slim/dissertation/ dissertation/dissertation.html, 2007. [12] “Mpiwiki:Community Portal.” Disponible en http://www.hpjava.org/mpiwiki/ index.php/Mpiwiki:Community_Portal. Última actualización, Abril 2007. [13] Taboada, G. L., Touriño, J., and Doallo, R., “Configuración y Evaluación de Bibliotecas de Paso de Mensajes Java en Clústers,” in XIV Jornadas de Paralelismo, 2003. [14] Baker, M., Carpenter, B., Fox, G., Ko, S., and Lim, S., “mpiJava: an ObjectOriented Java Interface to MPI,” in Proc. 1st Intl. Workshop on Java for Parallel and Distributed Computing (IWJPDC’99), LNCS vol. 1586, (San Juan, Puerto Rico), pp. 748–762, 1999. [15] “JPVM. The Java Parallel Virtual Machine.” http://www.cs.virginia.edu/ ~ajf2j/jpvm.html. Última actualización, Febrero 1999. [16] “P2P-MPI.” http://grid.u-strasbg.fr/p2pmpi/. Última actualización, 2005. [17] Genaud, S. and Rattanapoka, C., “P2P-MPI: A Peer-to-Peer Framework for Robust Execution of Message Passing Parallel Programs,” Journal of Grid Computing, vol. 5, no. 1, pp. 27–42, 2007. [18] “JHPC: List of Research Topics.” http://research.ac.upc.edu/CAP/ SeminariCAP/SEM9899/jordig/main.html. Última actualización, 1998. BIBLIOGRAFÍA 111 [19] Dincer, K., “jmpi and a Performance Instrumentation Analysis and Visualization Tool for jmpi,” in In First UK Workshop on Java for High Performance Network Computing, Europar’98, 1998. [20] Kaminsky, A., “Parallel Java Library.” http://www.cs.rit.edu/~ark/pj.shtml. Última actualización, Julio 2008. [21] A.Kaminsky, “Parallel Java: A Unified API for Shared Memory and Cluster Parallel Programming in 100 % Java,” in Proc. 9th Intl. Workshop on Java and Components for Parallelism, Distribution and Concurrency (IWJacPDC’07), (Long Beach, CA, USA), p. 196a (8 pages), 2007. [22] Zhang, B., “Jcluster. A Java Parallel Environment.” http://vip.6to23.com/ jcluster/. Última actualización, 2004. [23] Zhang, B.-Y., Yang, G.-W., and Zheng, W.-M., “Jcluster: an Efficient Java Parallel Environment on a Large-scale Heterogeneous Cluster,” Concurrency and Computation: Practice and Experience, vol. 18, no. 12, pp. 1541–1557, 2006. [24] Pugh, B. and Spacco, J., “MPJava: High-Performance Message Passing in Java using Java.nio,” 2003. [25] Al-Jaroodi, J., Mohamed, N., Jiang, H., and Swanson, D., “JOPI: A Java ObjectPassing Interface,” in Concurrency and Computation: Practice and Experience, 2005. [26] Al-Jaroodi, J. and Mohamed, N., “JOPI: Java Object-Passing Interface. User’s Guide,” 2004. [27] “Website del Proyecto MPJ Express.” Disponible en http://mpj-express.org/. [28] Shafi, A., Carpenter, B., and Baker, M., “Nested Parallelism for Multi-core HPC Systems using Java,” Journal of Parallel and Distributed Computing, (In press). [29] Bornemann, M., van Nieuwpoort, R. V., and Kielmann, T., “MPJ/Ibis: A Flexible and Efficient Message Passing Platform for Java,” Recent Advances in Parallel Virtual Machine and Message Passing Interface, vol. 3666/2005, pp. 217–224, 2005. 112 BIBLIOGRAFÍA [30] Taboada, G. L., Touriño, J., and Doallo, R., “F-MPJ: Scalable Java Messagepassing Communications on Parallel Systems,” Journal of Supercomputing, 2009 (In press). [31] Taboada, G. L., Touriño, J., and Doallo, R., “Java Fast Sockets: Enabling Highspeed Java Communications on High Performance Clusters,” Computer Communications, vol. 31, no. 17, pp. 4049–4059, 2008. [32] “JavaNow: A Linda Based Parallel Framework.” http://code.google.com/p/ javanow/. Última actualización, 2008. [33] Bhatti, S. A., Dickens, P. M., and Thiruvathukal, G. K., “JavaNow: A Framework for Parallel Computing on Networks of Workstations.” [34] Gray, P. A. and Sunderam, V. S., “IceT: Distributed Computing and Java,” Concurrency: Practice and Experience, vol. 9, no. 11, pp. 1161–1167, 1997. [35] “JavaParty - Java’s Companion for Distributed Computing.” http://svn. ipd.uni-karlsruhe.de/trac/javaparty/wiki/JavaParty?redirectedfrom= WikiStart. [36] Wheeler, D. A., “SLOCCount.” http://www.dwheeler.com/sloccount/. [37] Springel, V., “The Cosmological Simulation Code GADGET-2,” Monthly Notices of the Royal Astronomical Society, vol. 364, no. 4, pp. 1105–1134, 2005. [38] Springel, V., White, S., and Jenkins, A. e. a., “Simulations of the Formation, Evolution and Clustering of Galaxies and Quasars,” Nature, vol. 435, no. 7042, pp. 629–636, 2005. [39] Baker, M., Carpenter, B., and Shafi, A., “MPJ Express Meets Gadget: Towards a Java Code for Cosmological Simulations,” in 13th European PVM/MPI Users’ Group Meeting (EuroPVM/MPI’06), LNCS 4192, Springer-Verlag, (Bonn, Germany), pp. 358–365, 2006. BIBLIOGRAFÍA 113 [40] Ramos, S., Taboada, G. L., , Touriño, J., and Doallo, R., “Biblioteca de Primitivas Colectivas en Paso de Mensajes para Java en Sistemas Multi-core,” in Actas de las XX Jornadas de Paralelismo (JP’09), (A Coruña, Spain), p. (In press), 2009. [41] Reifer, D. J., “Let the Numbers Do the Talking,” March 2002. [42] Centro de Supercomputación de Galicia (CESGA), “Descrición do Supercomputador Finis Terrae.” http://www.cesga.es/content/view/917/115/ [Última actualización, 2009].