Ir al contenido

Tipos derivados MPI

MPI describe los mensajes por tipos, no por bytes (sistemas heterogéneos). Un tipo derivado es una lista de (tipo básico, desplazamiento) que describe la data en memoria. Sirve para:

  • enviar data no contigua de un tipo (una columna, una submatriz) o de varios tipos (structs);
  • no hacer una llamada por elemento (múltiples latencias) ni copiar a arrays contiguos.
ConstructorFirmaUso
contiguousMPI_Type_contiguous(count, old, &new)una fila (rowtype), un punto 3D
vectorMPI_Type_vector(count, blocklength, stride, old, &new)columna, bloque de columnas, submatriz
indexedMPI_Type_indexed(count, blocklens[], displs[], old, &new)bloques de largo variable (triangular)
structMPI_Type_create_struct(count, blocklens[], displs[], types[], &new)tipos mezclados

vector: count bloques de blocklength elementos; de inicio a inicio de cada bloque hay stride elementos.

Tipos derivados MPI: ¿qué celdas se envían?

Matriz en memoria row-major. Naranja = elementos del mensaje (el número pequeño es su orden en el mensaje); verde = dónde aterrizan en el receptor.

MPI_Type_vector(10, 1, 10, MPI_FLOAT, &coltype);
MPI_Type_commit(&coltype);
MPI_Send(&A[0][col], 1, coltype, 1, 0, MPI_COMM_WORLD); // col = 6
MPI_Recv(&A[0][col], 1, coltype, 0, 0, MPI_COMM_WORLD, &status);
Rank 0 (emisor) · MPI_Type_vector(10, 1, 10, …)
01234567890123456789012345617890123456278901234563789012345647890123456578901234566789012345677890123456878901234569789012345610789
Rank 1 (receptor) · mismo tipo
01234567890123456789000000610000000006200000000063000000000640000000006500000000066000000000670000000006800000000069000000000610000
Memoria lineal del emisor (índice = i·10 + j)
0102030405060708090
Elementos enviados10
Llamadas MPI necesarias1
Sin tipo derivado10 Send (o copiar a buffer)
count = 10 bloques (uno por fila), blocklength = 1 elemento, stride = 10 (= M, largo de una fila).
MPI_Type_create_struct
ejemplo08-struct.cpp: struct { int i; float f; }
04■ i: MPI_INT, desplazamiento 0 B, 4 B■ f: MPI_FLOAT, desplazamiento 4 B, 4 B
Typemap = {(MPI_INT,0), (MPI_FLOAT,4)}. Desplazamientos con MPI_Get_address relativos al primer campo; los huecos (gris) son padding/datos que no viajan. Pasos: construir → MPI_Type_commit → usar → MPI_Type_free.
U3.4/ejemplo08-struct.cpp
struct buff { int i; float f; } snd_buf, rcv_buf, sum;
int array_of_blocklengths[2];
MPI_Aint array_of_displacements[2], first_var_address, second_var_address;
MPI_Datatype array_of_types[2], send_recv_type;
array_of_blocklengths[0] = 1;
array_of_blocklengths[1] = 1;
MPI_Get_address(&snd_buf.i, &first_var_address);
MPI_Get_address(&snd_buf.f, &second_var_address);
array_of_displacements[0] = (MPI_Aint) 0;
array_of_displacements[1] = MPI_Aint_diff(second_var_address, first_var_address);
array_of_types[0] = MPI_INT;
array_of_types[1] = MPI_FLOAT;
MPI_Type_create_struct(2, array_of_blocklengths, array_of_displacements, array_of_types, &send_recv_type);
MPI_Type_commit(&send_recv_type);
for (i = 0; i < size; i++) { // p vueltas al anillo
MPI_Issend(&snd_buf, 1, send_recv_type, right, 17, MPI_COMM_WORLD, &request);
MPI_Recv(&rcv_buf, 1, send_recv_type, left, 17, MPI_COMM_WORLD, &status);
MPI_Wait(&request, &status);
snd_buf = rcv_buf;
sum.i += rcv_buf.i; sum.f += rcv_buf.f;
}
MPI_Type_free(&send_recv_type);

Por qué los desplazamientos no son la suma de sizeof

Sección titulada «Por qué los desplazamientos no son la suma de sizeof»

El compilador alinea cada campo a un múltiplo de su tamaño e inserta relleno (padding). Con class A2 { int a; double b; char c; }, imprimir &obj, &obj.a, &obj.b y &obj.c muestra que b empieza en el byte 8, no en el 4. Por eso ejemplo08 calcula los desplazamientos con MPI_Get_address + MPI_Aint_diff.

¿Por qué MPI_Get_address? Padding y alineación en structs

Cada campo se alinea a un múltiplo de su tamaño ⇒ el compilador inserta relleno. Los desplazamientos reales no son la suma de los sizeof.

a
b
c
a4bc20
campotipo MPIrelleno antesdesplazamiento realdirección (&obj + d)suma de sizeof
int aMPI_INT0 B00x7ffd5a3c1b200 ✓
double bMPI_DOUBLE4 B80x7ffd5a3c1b284 ✖
char cMPI_CHAR0 B160x7ffd5a3c1b3012 ✖
sizeof(struct)24 B
suma de campos13 B
relleno total11 B
relleno final (cola)7 B
int          bl[3] = {1, 1, 1};
MPI_Aint     d[3], base;
MPI_Datatype ty[3] = {MPI_INT, MPI_DOUBLE, MPI_CHAR};
MPI_Get_address(&obj, &base);
MPI_Get_address(&obj.a, &d[0]);  d[0] = MPI_Aint_diff(d[0], base);   // = 0
MPI_Get_address(&obj.b, &d[1]);  d[1] = MPI_Aint_diff(d[1], base);   // = 8
MPI_Get_address(&obj.c, &d[2]);  d[2] = MPI_Aint_diff(d[2], base);   // = 16
MPI_Type_create_struct(3, bl, d, ty, &tipo);
MPI_Type_commit(&tipo);
Hay 11 B de relleno: si calculas los desplazamientos sumando sizeof, MPI leería bytes equivocados. Por eso se usan MPI_Get_address + MPI_Aint_diff. Ordenar los campos de mayor a menor tamaño reduce el relleno.

Alineación típica en x86-64 (char 1, int/float 4, double 8). Imprimir &obj, &obj.a, &obj.b, &obj.c muestra exactamente estos saltos.

MPI_Type_indexed(count, blocklens[], displs[], …) generaliza a vector: cada bloque tiene su propio largo y desplazamiento. En miejemplo.cpp se envía la diagonal de una matriz 6×66\times6 con bloques de largo 1 en i*M + i = 35. Elige el preset «miejemplo — diagonal» en el visualizador de arriba.

  • MPI_Pack/MPI_Unpack (tipo MPI_PACKED): empaquetar variables en un buffer de bytes. Muy dinámico (estructura que cambia en ejecución, uso único), pero con costo de copia. Si la misma estructura se envía en un bucle, conviene MPI_Type_create_struct.
  • MPI_Sendrecv(&sbuf, scnt, stype, dest, stag, &rbuf, rcnt, rtype, src, rtag, comm, &status): envío y recibo en una llamada — MPI evita el bloqueo mutuo de un anillo.

Complete el anillo que suma dos enteros {i, j} por proceso usando MPI_Type_contiguous y comunicación no bloqueada (como en PD3).

Ver solución
MPI_Datatype pairtype;
MPI_Type_contiguous(2, MPI_INT, &pairtype); // struct {int i; int j;} = 2 ints contiguos
MPI_Type_commit(&pairtype);
sum.i = 0; sum.j = 0; snd_buf.i = my_rank; snd_buf.j = 10 * my_rank;
for (i = 0; i < size; i++) {
MPI_Issend(&snd_buf, 1, pairtype, right, 17, MPI_COMM_WORLD, &request);
MPI_Recv(&rcv_buf, 1, pairtype, left, 17, MPI_COMM_WORLD, &status);
MPI_Wait(&request, &status);
snd_buf = rcv_buf;
sum.i += rcv_buf.i; sum.j += rcv_buf.j;
}
printf("PE%i:\tSum = %i\t%i\n", my_rank, sum.i, sum.j);
MPI_Type_free(&pairtype);

Cada proceso imprime ∑r=p(p−1)2\sum r=\frac{p(p-1)}{2} y 10⋅p(p−1)210\cdot\frac{p(p-1)}{2}. Como ambos campos son int y no hay padding, contiguous basta; con tipos mezclados (int + float) se necesita create_struct.

Autoevaluación

0/4 correctas · 0 respondidas
1. Columna 3 de float A[8][5]:
2. MPI_Type_vector(2, 3, 6, MPI_DOUBLE, &t) enviado desde A (3×63\times6) manda…
3. ¿Qué hay que llamar antes de usar un tipo nuevo?
4. Para struct {int; float} los desplazamientos se obtienen con…

Fuente ·U3.4 Paralelismo Distribuido-tiposMPI.pdf · U3.4 Comunicacion-tiposMPI-codigo/ejemplo01–08 · U3.4 Comunicacion-tiposMPI-codigo/miejemplo.cpp · U3.4 Comunicacion-tiposMPI-codigo/test.cpp · practica_3_ofi/ejercicio02_col.cpp