MPI básico
Message passing: procesos con memoria local que se comunican enviando/recibiendo mensajes. MPI es el API estándar (MPI Forum, 1.0 en 1994) para C, C++ y Fortran; implementaciones MPICH y OpenMPI. Paralelismo explícito; funciona en CPU, nodo, NUMA y cluster, y admite híbrido (MPI + OpenMP).
Estructura de un programa
Sección titulada «Estructura de un programa»#include <mpi.h>#include <iostream>using namespace std;
int main(int argc, char *argv[]) { int ierr, rank, size, dato = 10; ierr = MPI_Init(&argc, &argv); // una sola vez, al inicio MPI_Comm_size(MPI_COMM_WORLD, &size); // número total de procesos MPI_Comm_rank(MPI_COMM_WORLD, &rank); // mi id: 0..size-1 if (ierr == MPI_SUCCESS) cout << "Hola mundo desde el proceso " << rank << " de " << size << endl;
if (rank == 2) dato = 20; // cada proceso tiene SU copia de dato cout << "Proceso " << rank << " tiene dato = " << dato << endl;
double time = MPI_Wtime(); // reloj en segundos (double) cout << "Tiempo del proceso " << rank << " = " << time << endl; MPI_Finalize();}mpic++ -o ejemplo01 ejemplo01.cpp # C: mpicc · Fortran: mpif90mpirun -np 4 ./ejemplo01Punto a punto
Sección titulada «Punto a punto»MPI_Send(&buffer, count, datatype, dest, tag, comm)MPI_Recv(&buffer, count, datatype, source, tag, comm, &status)- El mensaje es un array de
countelementos dedatatype(MPI_INT, MPI_FLOAT, MPI_DOUBLE, MPI_CHAR, MPI_BYTE, MPI_PACKED…). El tipo enviado debe coincidir con el recibido (soporte para arquitecturas heterogéneas). - El recibo se confirma si source, dest y tag corresponden (count y datatype no garantizan el emparejamiento). Comodines:
MPI_ANY_SOURCE,MPI_ANY_TAG. status.MPI_SOURCE,status.MPI_TAG,MPI_Get_count(&status, type, &count).
if (rank == 0) { numero = 10; MPI_Send(&numero, 1, MPI_INT, 1, 0, MPI_COMM_WORLD); for (int i = 0; i < 10; i++) arr_numero[i] = i; MPI_Send(arr_numero, 10, MPI_INT, 1, 0, MPI_COMM_WORLD); // un array: count = 10} else if (rank == 1) { MPI_Recv(&numero, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &estado); MPI_Recv(arr_numero, 10, MPI_INT, 0, 0, MPI_COMM_WORLD, &estado);}Modos de envío (bloqueantes)
Sección titulada «Modos de envío (bloqueantes)»| Rutina | Se completa cuando… |
|---|---|
MPI_Ssend (síncrono) | el receptor empezó a recibir (acuse de recibo) |
MPI_Bsend (buffered) | siempre de inmediato: copia a un buffer del usuario (MPI_Buffer_attach/detach) |
MPI_Send (standard) | síncrono o buffered según la implementación (mensaje chico ⇒ buffer; grande ⇒ síncrono) |
MPI_Rsend (ready) | de inmediato; si el receptor no estaba listo ⇒ error. Solo si el performance es crítico |
MPI_Recv | el mensaje llegó al buffer |
Ejemplos del curso
Sección titulada «Ejemplos del curso»if (rank == 0) { numero = 4; for (int i = 1; i < size; i++) MPI_Send(&numero, 1, MPI_INT, i, 0, MPI_COMM_WORLD); // p-1 envíos secuenciales} else { MPI_Recv(&numero, 1, MPI_INT, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &estado);}Costo : lineal en . MPI_Bcast lo hace en árbol.
if (my_rank == 0) { int res = 5; // ¡incluir el buf del maestro! for (int i = 1; i < size; i++) { MPI_Recv(&rbuf, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &stat); res += rbuf; } cout << "res = " << res << endl;} else { MPI_Send(&buf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);}Medir tiempos: separar cómputo y comunicación
Sección titulada «Medir tiempos: separar cómputo y comunicación»Patrón de la práctica 3: una barrera antes de cada fase, para que todos arranquen juntos y el reloj mida la fase y no la espera por procesos atrasados.
// --- Comunicación: Bcast de v y Scatter de A por filas ---MPI_Barrier(MPI_COMM_WORLD);t_aux = MPI_Wtime();MPI_Bcast(v, N, MPI_LONG_LONG, 0, MPI_COMM_WORLD);MPI_Scatter(A, elem_proc, MPI_INT, local_A, elem_proc, MPI_INT, 0, MPI_COMM_WORLD);t_comm += MPI_Wtime() - t_aux;
// --- Cómputo local: cada proceso calcula sus fil_proc filas ---MPI_Barrier(MPI_COMM_WORLD);t_aux = MPI_Wtime();for (i = 0; i < fil_proc; i++) { local_x[i] = 0; for (j = 0; j < N; j++) local_x[i] += local_A[i * N + j] * v[j];}t_comp = MPI_Wtime() - t_aux;
// --- Comunicación: Gather de local_x hacia x en P0 ---MPI_Barrier(MPI_COMM_WORLD);t_aux = MPI_Wtime();MPI_Gather(local_x, fil_proc, MPI_LONG_LONG, x, fil_proc, MPI_LONG_LONG, 0, MPI_COMM_WORLD);t_comm += MPI_Wtime() - t_aux;
// --- el tiempo que cuenta es el del proceso más lento ---MPI_Reduce(&t_comp, &total_comp, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);MPI_Reduce(&t_comm, &total_comm, 1, MPI_DOUBLE, MPI_MAX, 0, MPI_COMM_WORLD);Correr en un clúster (Slurm)
Sección titulada «Correr en un clúster (Slurm)»En un clúster no se ejecuta mpirun en el nodo de login: se compila ahí y se encola un job que reserva procesos.
- Compilar en el nodo de login, con los módulos del compilador y de MPI cargados:
module load gnu12/12.4.0 mpich/3.4.3-ucxy luegompic++ -O2 -DN=600 -o row_N600 ejercicio02_row.cpp.-DN=...fija el tamaño en tiempo de compilación. - Escribir el script del job:
#!/bin/bash#SBATCH --job-name=pd03_ej2_3#SBATCH --ntasks=10 # p máximo que se usará#SBATCH --time=05:00:00 # si se queda corto, Slurm mata el job a la mitad#SBATCH --partition=standard#SBATCH --output=slurm_%j.outmodule purgemodule load gnu12/12.4.0 mpich/3.4.3-ucxfor p in 1 2 4 6 8 10; do mpirun -np $p ./row_N600; done
- Encolar con
sbatch job.slurmy revisarslurm_<id>.out/.err. Dentro de la reserva,mpirun -np pconntasksusa un subconjunto de los procesos.
Ejercicio
Sección titulada «Ejercicio»¿Qué imprime ejemplo02.cpp con mpirun -np 3? ¿Por qué el fprintf después de MPI_Abort puede no aparecer?
Ver solución
Con size > 2, el rank 0 llama a MPI_Abort(MPI_COMM_WORLD, -1), que termina todos los procesos del comunicador inmediatamente; el fprintf está después del abort, así que normalmente no se imprime (debería ir antes). Los ranks 1 y 2 no hacen nada (el else solo se ejecuta si size ≤ 2) y son abortados.
Autoevaluación
0/4 correctas · 0 respondidasMPI_Comm_rank devuelve…Fuente ·U3.1 Paralelismo Distribuido-MPI.pdf · ejemplo01.cpp · ejemplo02.cpp · ejemplo03.cpp · ejemplo04.cpp · mpi_getting_started.cpp · practica_3_ofi/ejercicio02_row.cpp · practica_3_ofi/ejercicio2_3_eficiencia/khipu_eficiencia.slurm · compile_eficiencia.sh