Comment le coût de calcul d'une opération mpi_allgather se compare-t-il à une opération de collecte / diffusion?

11

Je travaille sur un problème qui peut être mis en parallèle en utilisant une seule opération mpi_allgather ou une opération mpi_scatter et une opération mpi_gather. Ces opérations sont appelées dans une boucle while, elles peuvent donc être appelées plusieurs fois.

Dans l'implémentation avec un schéma MPI_allgather, je rassemble un vecteur distribué sur tous les processus de résolution de matrice en double. Dans l'autre implémentation, je rassemble le vecteur distribué sur un seul processeur (le nœud racine), résout le système linéaire sur ce processeur, puis diffuse le vecteur de solution sur tous les processus.

Je suis curieux de savoir si le coût d'une opération de rassemblement est bien plus élevé que les opérations de dispersion et de collecte combinées. La longueur du message joue-t-elle un rôle important dans sa complexité? Cela varie-t-il entre les implémentations de mpi?

Éditer:

Paul
la source
Veuillez décrire la structure de la communication et les tailles concernées. Un MPI_Scattersuivi MPI_Gatherne fournit pas la même sémantique de communication que MPI_Allgather. Peut-être y a-t-il une redondance lorsque vous exprimez l'opération d'une manière ou d'une autre?
Jed Brown
Paul, Jed a raison, tu voulais dire un MPI_Gathersuivi d'un MPI_Bcast?
Aron Ahmadia
@JedBrown: J'ai ajouté un peu plus d'informations.
Paul
@AronAhmadia: Je ne pense pas que je devrais utiliser un MPI_Bcast parce que j'envoie une partie du vecteur, à chaque processus, pas le vecteur entier. Ma justification est qu'un message plus court sera plus rapide à envoyer qu'un message plus gros, en général. Est-ce que ça a du sens?
Paul
La matrice est-elle déjà distribuée de manière redondante? Est-il déjà pris en compte? Plusieurs processus partagent-ils les mêmes caches et bus de mémoire? (Cela affecterait la vitesse de résolution des systèmes redondants.) Quelle est la taille / le coût des systèmes? Pourquoi résoudre en série?
Jed Brown

Réponses:

9

Tout d'abord, la réponse exacte dépend: (1) de l'utilisation, c'est-à-dire des arguments d'entrée de fonction, (2) de la qualité et des détails de l'implémentation MPI, et (3) du matériel que vous utilisez. Souvent, (2) et (3) sont liés, par exemple lorsque le fournisseur de matériel optimise MPI pour son réseau.

En général, la fusion des collectifs MPI est meilleure pour les petits messages, car les coûts de démarrage peuvent être non triviaux et la synchronisation entraînée par le blocage des collectifs doit être minimisée en cas de variation du temps de calcul entre les appels. Pour les messages plus volumineux, l'objectif doit être de minimiser la quantité de données envoyées.

Par exemple, en théorie, MPI_Reduce_scatter_blockdevrait être meilleur que MPI_Reducesuivi MPI_Scatter, bien que le premier soit souvent mis en œuvre en fonction du second, de sorte qu'il n'y a pas de réel avantage. Il existe une corrélation entre la qualité de la mise en œuvre et la fréquence d'utilisation dans la plupart des mises en œuvre de MPI, et les fournisseurs optimisent évidemment les fonctions pour lesquelles cela est requis par le contrat de la machine.

D'un autre côté, si l'on est sur un Blue Gene, le fait d' MPI_Reduce_scatter_blockutiliser MPI_Allreduce, qui fait plus de communication que MPI_Reduceet MPI_Scattercombiné, est en fait un peu plus rapide. C'est quelque chose que j'ai récemment découvert et qui constitue une violation intéressante du principe d'auto-cohérence des performances dans MPI (ce principe est décrit plus en détail dans les "Directives de performances MPI auto-cohérentes" ).

Dans le cas spécifique de scatter + rassembler contre allgather, considérez que dans le premier, toutes les données doivent aller vers et depuis un seul processus, ce qui en fait le goulot d'étranglement, tandis que dans l'allgather, les données peuvent entrer et sortir de tous les rangs immédiatement , car tous les rangs ont des données à envoyer à tous les autres rangs. Cependant, l'envoi de données de tous les nœuds à la fois n'est pas nécessairement une bonne idée sur certains réseaux.

Enfin, la meilleure façon de répondre à cette question est de procéder comme suit dans votre code et de répondre à la question par expérience.

#ifdef TWO_MPI_CALLS_ARE_BETTER_THAN_ONE
  MPI_Scatter(..)
  MPI_Gather(..)
#else
  MPI_Allgather(..)
#endif

Une option encore meilleure consiste à demander à votre code de le mesurer expérimentalement pendant les deux premières itérations, puis à utiliser la plus rapide pour les itérations 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;
    }
}
Jeff
la source
Ce n'est pas une mauvaise idée ... chronométrez les deux et déterminez laquelle est la plus rapide.
Paul
La plupart des environnements HPC modernes optimisent de nombreux appels MPI. Parfois, cela entraîne des accélérations incroyables, d'autres fois, des comportements extrêmement opaques. Faites attention!
meawoppl
@Jeff: Je viens de me rendre compte que j'ai omis un détail important ... Je travaille avec un cluster au Texas Advanced Computing Center, où ils utilisent un réseau de topologie en arbre gras. Est-ce que cela affecterait la différence de performance entre les approches tout-rassemblement et rassemblement-diffusion?
Paul
La topologie @Paul n'est pas le facteur dominant ici, mais un fat-tree a une bande passante de bissection substantielle, ce qui devrait rendre le tout bon marché. Cependant, rassembler devrait toujours être moins cher qu'allgather. Cependant, pour les messages plus volumineux, il peut être inférieur à un facteur de 2.
Jeff
5

Jeff a tout à fait raison sur la seule façon d'être sûr de mesurer - nous sommes des scientifiques, après tout, et c'est une question empirique - et donne d'excellents conseils sur la façon de mettre en œuvre de telles mesures. Permettez-moi maintenant de proposer une opinion contraire (ou peut-être complémentaire).

Il y a une distinction à faire entre écrire un code qui sera largement utilisé et le régler à une fin spécifique. En général, nous faisons le premier - construire notre code afin que a) nous puissions l'utiliser sur une grande variété de plates-formes, et b) le code soit maintenable et extensible pour les années à venir. Mais parfois, nous faisons l'autre - nous avons l'équivalent d'un an d'allocation sur une grosse machine, et nous progressons vers un ensemble requis de grandes simulations et nous avons besoin d'une certaine base de performances pour obtenir ce dont nous avons besoin pendant le moment de l'attribution accordée.

Lorsque nous écrivons du code, le rendre largement utilisable et maintenable est beaucoup plus important que de réduire de quelques pour cent le temps d'exécution sur une machine particulière. Dans ce cas, la bonne chose à faire est presque toujours d'utiliser la routine qui décrit le mieux ce que vous voulez faire - c'est généralement l'appel le plus spécifique que vous pouvez faire qui fait ce que vous voulez. Par exemple, si un allgather direct ou allgatherv fait ce que vous voulez, vous devriez l'utiliser plutôt que de lancer vos propres opérations de dispersion / gatter. Les raisons sont les suivantes:

  • Le code représente maintenant plus clairement ce que vous essayez de faire, le rendant plus compréhensible pour la prochaine personne qui viendra à votre code l'année suivante sans avoir la moindre idée de ce que le code est censé faire (cette personne pourrait bien être vous);
  • Des optimisations sont disponibles au niveau MPI pour ce cas plus spécifique qui ne sont pas dans le cas plus général, donc votre bibliothèque MPI peut vous aider; et
  • Essayer de lancer le vôtre se retournera probablement contre vous; même s'il fonctionne mieux sur la machine X avec l'implémentation MPI Y.ZZ, il peut très bien fonctionner moins bien lorsque vous passez à une autre machine ou mettez à niveau votre implémentation MPI.

Dans ce cas assez courant, si vous découvrez que certains collectifs MPI fonctionnent de manière déraisonnablement lente sur votre machine, la meilleure chose à faire est de déposer un rapport de bogue auprès du fournisseur mpi; vous ne voulez pas compliquer votre propre logiciel en essayant de contourner dans le code d'application ce qui devrait être correctement corrigé au niveau de la bibliothèque MPI.

Cependant . Si vous êtes en mode "tuning" - vous avez un code qui fonctionne, vous devez monter à très grande échelle en peu de temps (par exemple, une allocation d'un an), et vous avez profilé votre code et découvert que cette partie particulière de votre code est un goulot d'étranglement, alors il est logique de commencer à effectuer ces réglages très spécifiques. Espérons qu'ils ne feront pas partie à long terme de votre code - idéalement, ces modifications resteront dans une branche spécifique au projet de votre référentiel - mais vous devrez peut-être les faire. Dans ce cas, le codage de deux approches différentes distinguées par des directives de préprocesseur, ou une approche "d'autotuning" pour un modèle de communication spécifique - peut avoir beaucoup de sens.

Je ne suis donc pas en désaccord avec Jeff, je veux juste ajouter un peu de contexte sur le moment où vous devriez vous préoccuper suffisamment de ces questions de performances relatives pour modifier votre code pour y faire face.


la source
Je pense que je suis plus intéressé par la portabilité que par l'optimisation à ce stade, mais je suis toujours curieux de savoir s'il existe une autre implémentation qui est tout aussi portable mais plus rapide :)
Paul