Primero, la respuesta exacta depende de: (1) uso, es decir, argumentos de entrada de función, (2) calidad y detalles de implementación de MPI, y (3) el hardware que está utilizando. A menudo, (2) y (3) están relacionados, como cuando el proveedor de hardware optimiza MPI para su red.
En general, fusionar colectivos MPI es mejor para mensajes más pequeños, ya que los costos iniciales pueden no ser triviales y la sincronización que conlleva bloquear colectivos debe minimizarse si hay una variación en el tiempo de cómputo entre llamadas. Para mensajes más grandes, el objetivo debe ser minimizar la cantidad de datos que se envían.
Por ejemplo, en teoría, MPI_Reduce_scatter_block
debería ser mejor que lo MPI_Reduce
seguido MPI_Scatter
, aunque el primero a menudo se implementa en términos del segundo, de modo que no haya una ventaja real. Existe una correlación entre la calidad de la implementación y la frecuencia de uso en la mayoría de las implementaciones de MPI, y los proveedores obviamente optimizan las funciones para las cuales esto es requerido por el contrato de la máquina.
Por otro lado, si uno está en un Blue Gene, el MPI_Reduce_scatter_block
uso MPI_Allreduce
, que hace más comunicación que MPI_Reduce
y MPI_Scatter
combinado, en realidad es bastante más rápido. Esto es algo que descubrí recientemente y es una violación interesante del principio de autoconsistencia de rendimiento en MPI (este principio se describe con más detalle en las "Pautas de rendimiento de MPI autoconsistentes" ).
En el caso específico de dispersión + reunión versus reunión total, considere que en la primera, todos los datos deben ir hacia y desde un solo proceso, lo que lo convierte en el cuello de botella, mientras que en la reunión general, los datos pueden entrar y salir de todos los rangos inmediatamente , porque todos los rangos tienen algunos datos para enviar a todos los demás. Sin embargo, enviar datos desde todos los nodos a la vez no es necesariamente una buena idea en algunas redes.
Finalmente, la mejor manera de responder a esta pregunta es hacer lo siguiente en su código y responder la pregunta por experimento.
#ifdef TWO_MPI_CALLS_ARE_BETTER_THAN_ONE
MPI_Scatter(..)
MPI_Gather(..)
#else
MPI_Allgather(..)
#endif
Una opción aún mejor es hacer que su código lo mida experimentalmente durante las dos primeras iteraciones, luego use el que sea más rápido para las iteraciones restantes:
const int use_allgather = 1;
const int use_scatter_then_gather = 2;
int algorithm = 0;
double t0 = 0.0, t1 = 0.0, dt1 = 0.0, dt2 = 0.0;
while (..)
{
if ( (iteration==0 && algorithm==0) || algorithm==use_scatter_then_gather )
{
t0 = MPI_Wtime();
MPI_Scatter(..);
MPI_Gather(..);
t1 = MPI_Wtime();
dt1 = t1-t0;
}
else if ( (iteration==1 && algorithm==0) || algorithm==use_allgather)
{
t0 = MPI_Wtime();
MPI_Allgather(..);
t1 = MPI_Wtime();
dt2 = t1-t0;
}
if (iteration==1)
{
dt2<dt1 ? algorithm=use_allgather : algorithm=use_scatter_then_gather;
}
}
MPI_Scatter
seguido porMPI_Gather
no proporciona la misma comunicación semántica queMPI_Allgather
. ¿Quizás haya redundancia involucrada cuando expresa la operación de cualquier manera?