Message Passing Interface (MPI) INTRODUCCIÓN MPI (Message Passing Interface) como es un interfaz estandarizada para la realización de aplicaciones paralelas basadas en pasaje de mensajes. El modelo de programación que subyace tras MPI es MIMD (Multiple Instruction streams, Multiple Data streams) aunque se dan especiales facilidades para la utilización del modelo SPMD (Single Program Multiple Data), un caso particular de MIMD en el que todos los procesos ejecutan el mismo programa, aunque no necesariamente la misma instrucción al mismo tiempo. El pasaje de mensajes es un paradigma ampliamente usado en determinadas clases de máquinas paralelas, especialmente aquellas con memoria distribuida. Aún habiendo bastantes variaciones, los conceptos básicos de las comunicaciones entre procesos por medio de mensajes están bien estudiados, en los últimos 15 años se han hecho progresos sustanciales en lo que hace a las aplicaciones de este paradigma y cada distribuidor ha implementado su propia variante, así es como recientemente muchos sistemas han demostrado ser eficientes y portables. De esta manera y con el tiempo es como se ha definido la sintaxis y la semántica de un núcleo de librerías de rutinas que hace del pasaje de mensajes una herramienta absolutamente útil para un amplio rango de usuarios e implementable sobre variedad de computadoras. De esta manera, el estándar incluye: • Comunicaciones punto a punto. • Operaciones colectivas • Grupos de procesos • Contextos de comunicación • Topologías de procesos • Interfaces para para Fortran 77 y C • Manejo y análisis del ambiente de desarrollo • Perfiles de interfaz Pero no se especifica: • Operaciones explícitas de memoria compartida • Operaciones que requieren de un soporte del sistema mayor que el estándar corriente, por ejemplo, manejo de interrupciones, ejecución remota o mensajes activos. • Herramientas de construcción de programas • Facilidades de depuración • Soporte explícito para cadenas (threads) • Soporte para administración de tareas • Funciones de Entrada/Salida (I/O) 1 DEFINICIONES Proceso (Process) Es una subparte paralelizable o ejecutable de un problema o algoritmo, si bien puede estar asociada a un procesador por cuestiones de eficiencia, puede presentarse también de manera que haya más de un proceso por procesador o CPU. Todos los procesos se comunican con otros a través de mensajes, aún corriendo sobre el mismo procesador. Comunicador (Communicator) Un comunicador es un dominio de comunicación que define un grupo de procesos que les permite comunicarse entre ellos, de manera que puedan separarse los contextos en los que se producen las comunicaciones, es decir un mecanismo que permite al programador definir módulos que encapsulen estructuras de comunicación. Cada proceso tiene una identidad (rank) dentro del comunicador, que va de 0 a N-1, donde hay N procesos. Actualmente se puede disponer de dos tipos de comunicadores , un intracomunicador para comunicaciones dentro de un grupo o un intercomunicador para comunicaciones entre grupos. Para programas simples solo se usan intracomunicadores pues no suele ser necesario más que un grupo y generalmente se usa el comunicador por defecto MPI_COMM_WORLD que engloba a todos los procesos dentro de una aplicación. Rango o identidad (Rank) Cada proceso tiene su propia y única identidad, identificada por un número entero que le es asignado por el sistema cuando el proceso se inicializa. Algunas veces el rank es llamado “process ID”. Los ranks son contiguos y empiezan desde cero. Es usado por el programador para identificar la fuente y destino de los mensajes y con frecuencia se utiliza con un condicional. Ejemplo: If (rank == 0) /* …. */ else /* ..... */ Grupo (Group) Un grupo es un conjunto ordenado de N procesos. Cada proceso en un grupo sigue asociado con un único rank entero. Buffer de aplicación Es el espacio de direcciones de memoria que mantiene los datos para mandar o recibir. Buffer del sistema Espacio de memoria del sistema para el almacenamiento de mensajes. Datos del buffer de aplicación pueden ser copiados a/de el espacio de sistema para operaciones de enviar/recibir pudiendo mantener una comunicación asíncrona. COMPLETADO DE UNA TAREA Son varias las versiones de funciones para enviar y recibir, y para describir estas variaciones suelen verse términos como “localmente completo” o “globalmente completo”. Entonces una rutina es localmente completa cuando ha completado su parte en la operación. Una rutina es globalmente completa cuando todas las partes involucradas en 2 una operación han concluido, esto es que todas las rutinas deben ser localmente completas para que la operación sea globalmente completa. Rutinas bloqueantes En MPI, una rutina bloqueante (enviar o recibir) retorna cuando es localmente completa. La condición de localmente completa para una rutina de envío bloqueante es que la locación usada para mantener el mensaje pueda ser usada otra vez o alterada sin afectar el mensaje que ha sido enviado. Esencialmente, el proceso fuente es bloqueado, el mínimo tiempo que es requerido para acceder al mensaje. Rutinas no-bloqueantes Una rutina no-bloqueante retorna inmediatamente, esto es que permite ejecutar la próxima sentencia, tanto si está localmente completa como si no lo está, es decir que retornará aún antes de que la locación fuente sea segura para ser modificada. FUNCIONES BÁSICAS MPI es un sistema complejo, por lo tanto es mucho lo que se puede decir, en su totalidad comprende más de 130 funciones, muchas de las cuales tienen numerosos parámetros o variantes. Nuestro objetivo es presentar conceptos esenciales de la programación con pasaje de mensajes, con lo que la utilización de 24 funciones básicas es más que suficiente para un amplio rango de aplicaciones y aún con sólo 6 se pueden elaborar muchas de ellas. Estas 6 funciones básicas son: MPI_INIT que inicializa el ambiente MPI, necesaria en todos los programas. int MPI_INIT(int *argc, char argv[ ]); MPI_FINALIZE, termina la ejecución del ambiente MPI, al igual que la función anterior debe estar presente en todos los programas MPI. int MPI_Finalize(void) 3 MPI_COMM_RANK, determina el rango o identificación del proceso en el comunicador. int MPI_Comm_rank(MPI_Comm comm.,int *rank) MPI_COMM_SIZE, determina el número de procesos corriendo asociados con comunicador. int MPI_Comm_size(MPI_Comm comm, int *size) MPI_SEND , envía un mensaje a otro proceso. int MPI_Send(void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm) MPI_RECV, recibe un mensaje de otro proceso. int MPI_Recv(void *buf, int count, MPI_Datatype datatype, int source, int tag,MPI_Comm comm, MPI_Status *status) INTRODUCCIÓN A LOS COMUNICADORES • Comunicador = variable de tipo MPI_Comm = Grupo_procs + Contexto – Grupo de procesos: Subconjunto de procesos – Contexto de comunicación: Ámbito de paso de mensajes en el que se comunican dichos procesos. • Argumento en funciones de transferencia. • MPI_COMM_WORLD : Comunicador MPI por defecto que incluye todos los procesos en ejecución • Identificación unívoca de procesos participantes en comunicador – Un proceso puede pertenecer a diferentes comunicadores – Cada proceso tiene un identificador: desde 0 a size_comm-1 – Mensajes destinados a diferentes contextos de comunicación no interfieren entre sí. 4 OBTENIENDO INFORMACIÓN Ejemplo de obtención del rango (rank) y tamaño (size) #include <stdio.h> #include "mpi.h" int main(int argc, char *argv[ ] ) { int rank, size; MPI_Init( &argc, &argv ); MPI_Comm_size( MPI_COMM_WORLD, &size ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); printf( "Hello world from process %d of %d\n", rank, size ); MPI_Finalize( ); return 0; } Envío y recepción de mensajes int MPI_Send ( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm ) – Envía datos almacenados en buffer apuntado por buf al proc. dest con la etiqueta tag (entero >0) dentro del comunicador comm. – Existen implementaciones bloqueantes y no bloqueantes int MPI_Recv ( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status ) – Recibe mensaje de proceso source dentro del comunicador comm. – Sólo se recibe un mensaje enviado desde source con etiqueta tag pero existen agumentos comodín: MPI_ANY_SOURCE, MPI_ANY_TAG. – Mensaje es almacenado en posiciones continuas desde buf. – Argumentos count y datatype: especifican la longitud del buffer. – Objeto status : Estructura con campos MPI_SOURCE y MPI_TAG. – Permite obtener información sobre el mensaje recibido – Obtener tamaño del mensaje recibido: Función MPI_Get_count 5 – int MPI_Get_count( MPI_Status *status, MPI_Datatype dtype, int *count ) Ejemplo: Programa para dos procesos MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size ); if (rank == 0) { value=100; MPI_Send (&value, 1, MPI_INT, 1, 0, MPI_COMM_WORLD ); else MPI_Recv ( &value, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status ); MPI_Finalize( ); Envío y recepción de mensajes. Ejemplo #include <stdio.h> #include "mpi.h" int main(int argc, char *argv[ ]); { int rank, value, size; MPI_Status status; MPI_Init( &argc, &argv ); MPI_Comm_rank( MPI_COMM_WORLD, &rank ); MPI_Comm_size( MPI_COMM_WORLD, &size ); do { if (rank == 0) { scanf( "%d", &value ); MPI_Send(&value, 1, MPI_INT, rank + 1, 0, MPI_COMM_WORLD );} else { MPI_Recv( &value, 1, MPI_INT, rank - 1, 0, MPI_COMM_WORLD, &status ); if (rank < size - 1) MPI_Send( &value, 1, MPI_INT, rank + 1, 0, MPI_COMM_WORLD ); } printf( "Process %d got %d\n", rank, value ); } while (value >= 0); MPI_Finalize( ); return 0; } 6