[LINUX] Coordination de chaque processus dans MPI et mise en mémoire tampon de la sortie standard

introduction

Contexte

Regardez [ce tweet] de M. Robota (https://twitter.com/kaityo256/status/1049669976745861121) concernant MPI, et comment chaque processus fonctionne-t-il ensemble (principalement en ce qui concerne la compilation de la sortie standard)? J'étais curieux de connaître le contrôle du tampon et le point, alors j'ai fait une enquête approximative.

Mise en garde

Cette fois, je n'ai pas écrit en détail sur l'environnement, alors assurez-vous de prendre le dos et de l'utiliser par vous-même sans le prendre. De plus, veuillez noter que les termes utilisés sont assez attachés au texte.

De plus, j'ai essayé l'environnement Linux et il existe trois types de MPI: Intel, SGI-MPT et OpenMPI. De plus, il n'y a aucune mention de Fortran. Pour C / C ++.

Qu'est-ce que la mise en mémoire tampon de sortie standard?

Qu'est-ce que la mise en mémoire tampon?

Tout d'abord, à propos de la "mise en mémoire tampon".

Lors de la sortie de données à partir de la sortie standard, cela se fait via stdout pour C et std :: cout pour C ++, mais même si vous appelez printf ou std :: ostream :: operator <<, la sortie sera immédiatement reflétée. Ce n'est pas toujours fait. En effet, l'appel des API OS (écriture et envoi) qui sont réellement en charge de la sortie en petits morceaux est généralement désavantageux en termes de performances, et dans la bibliothèque standard, il est stocké dans une mémoire tampon dans une certaine mesure et regroupé. C'est parce que j'essaye de tout cracher en même temps. Ce comportement est appelé ** mise en mémoire tampon **.

Mise en mémoire tampon dans la bibliothèque standard

Il existe trois modes de mise en mémoire tampon:

Pour C stdio, le contrôle se fait avec setbuf / setvbuf. Le contrôle par défaut dépend du fichier / périphérique auquel la sortie standard est connectée, de la ligne tamponnée pour TTY / PTY, et entièrement tamponnée dans le cas contraire. L'opération de rinçage est effectuée avec la fonction fflush.

Pour iostream C ++, le contrôle est effectué en définissant l'indicateur std :: ios_base :: unitbuf sur std :: cout (std :: cout.setf ou unsetf). S'il est défini, il n'est pas mis en tampon, s'il n'est pas défini, il est entièrement mis en mémoire tampon et il n'y a pas de ligne en mémoire tampon. La valeur par défaut est pleine mémoire tampon. L'opération de vidage est effectuée avec le manipulateur d'E / S std :: flush ou std :: endl.

Comme vous pouvez le voir, les contrôles sont séparés pour C et C ++, mais dans le cas d'un code mixte C / C ++, c'est généralement un problème si l'ordre de sortie est perturbé, donc par défaut les deux sont synchronisés. Je vais. En d'autres termes, même s'il est entièrement mis en mémoire tampon du côté C ++, si le côté C ne l'est pas, il sera tiré du côté C. Cependant, ce comportement peut être remplacé par std :: ios_base :: sync_with_stdio (false).

Comment fonctionne MPI

Aperçu du mécanisme

En gros, ce que MPI est, c'est que si vous spécifiez les nœuds qui peuvent être mobilisés et le nombre de processus à exécuter, plusieurs nœuds peuvent démarrer le même programme (voire hétérogène) et effectuer des calculs coopératifs. C'est une bibliothèque et un groupe d'outils.

Par conséquent, il existe divers réseaux à haut débit tels qu'InfiniBand en tant que coopération entre les programmes démarrés, mais cette fois je pense au "flux de sortie standard". Par conséquent, il suffit de considérer les trois facteurs indiqués dans la figure suivante.

image.png

** Ceci est un terme arbitraire **,

Il sera classé comme. Dans la figure ci-dessus, l'avant et les autres sont dessinés comme s'ils s'exécutaient sur des nœuds différents, mais ils peuvent être identiques.

Différences par MPI

Intel MPI Le premier est Intel MPI. Cela ressemble à la figure suivante.

image.png

Les rôles et les réponses sont les suivants.

Après le démarrage, le gestionnaire établit une connexion TCP / IP vers l'avant, agrège la sortie acheminée par le worker et l'envoie vers l'avant.

SGI MPT Vient ensuite SGI MPT.

image.png

Les rôles et les réponses sont les suivants.

La division des rôles est similaire à l'Intel MPI mentionné précédemment, mais le démarrage du gestionnaire par l'avant nécessite un démon appelé arrayd (même s'il s'agit d'un nœud local) fourni avec le SGI MPT.

OpenMPI Enfin, OpenMPI.

image.png

Les rôles et les réponses sont les suivants.

La grande différence avec les deux MPI ci-dessus est que la communication entre les gestionnaires et les travailleurs devient PTY, comme c'est le cas avec la gestion des nœuds locaux.

Tampon de sortie standard en MPI

Réorganisation du chemin de sortie

Si vous effectuez une sortie avec un programme MPI, nous examinerons ce qui se passe lorsqu'il est finalement agrégé et sorti vers l'avant.

Comme organisé ci-dessus, trois types de programmes, front manager et worker, travaillent ensemble lors de l'exécution de MPI. Et ** la sortie des travailleurs est agrégée au début via le gestionnaire **. Par conséquent, il est également nécessaire d'organiser la mise en mémoire tampon pour chaque route. C'est,

Il y a trois endroits.

Différences de mise en mémoire tampon pour chaque MPI

Intel MPI Dans le cas d'Intel MPI, la sortie de chaque worker est mélangée même au milieu de la ligne. Il semble donc que la mise en mémoire tampon est désactivée.

Cela est dû au fait que la bibliothèque MPI appelle en interne setbuf / setvbuf pendant MPI_Init pour mettre le worker dans un état sans tampon **. En d'autres termes, la partie worker-> manager est désactivée pour la mise en mémoire tampon, et la destination de sortie manager-> front, front-> final est renversée telle quelle sans aucun contrôle particulier, il semble donc que la mise en mémoire tampon est désactivée dans son ensemble.

Par conséquent, après MPI_Init, vous pouvez activer la mise en mémoire tampon en appelant setbuf / setvbuf et en reconfigurant la mise en mémoire tampon. De plus, il semble que l'indicateur de std :: cout ne soit pas modifié dans MPI_Init ou MPI: Init, donc s'il s'agit d'une application C ++ pure, la mise en mémoire tampon sera activée en désactivant la synchronisation C, C ++. ..

-ordered-output Use this option to avoid intermingling of data output from the MPI processes. This option affects both the standard output and the standard error streams. NOTE When using this option, end the last output line of each process with the end-of-line '\n' character. Otherwise the application may stop responding.

SGI MPT Dans le cas de SGI MPT, la sortie est organisée ligne par ligne, ce qui équivaut au comportement de mise en mémoire tampon de ligne.

Le mécanisme derrière cela est un peu compliqué.

En d'autres termes, il est tamponné par les efforts de la réception. À l'inverse, il se peut que vous ne souhaitiez pas que l'application MPI (worker) contrôle le tampon sans autorisation.

OpenMPI Comme SGI MPT, OpenMPI se comporte comme une ligne tamponnée.

Ce mécanisme est très simple, car la sortie entre le travailleur et le gestionnaire est PTY, et le contrôle stdout correspondant est mis en mémoire tampon de lignes par défaut. Autres Manager-> Front, Front-> Destination de sortie finale ne semble pas contrôler quelque chose de spécial. En d'autres termes, OpenMPI lui-même ne traite pas spécifiquement du contrôle de la mise en mémoire tampon, il est laissé à la bibliothèque standard.

Résumé

Donc, j'ai vu la différence de contrôle dans chaque MPI.

Bien qu'il existe des différences dans chaque MPI, si vous voulez vous assurer que la mise en mémoire tampon est efficace, je pense qu'il est préférable d'appeler setbuf / setvbuf immédiatement après MPI_Init.

référence

Ci-dessous, la source et le journal des opérations lors de la tentative de comportement avec Intel MPI sont répertoriés à titre de référence.

Journal des opérations


$ cat /etc/centos-release
CentOS Linux release 7.4.1708 (Core) 
$ mpirun --version
Intel(R) MPI Library for Linux* OS, Version 2018 Update 1 Build 20171011 (id: 17941)
Copyright (C) 2003-2017, Intel Corporation. All rights reserved.
$ icpc --version
icpc (ICC) 18.0.1 20171018
Copyright (C) 1985-2017 Intel Corporation.  All rights reserved.

$ mpiicpc -std=gnu++11 -o test test.cpp
$ mpirun -np 2 ./test
abababababababababbababababababababababa

abababababababababababababababababababab
a
bbabaababababababababababababababababab
a
bababbaababababbaababababababababababab

babaabababababababababababababababababab

$ mpirun -np 2 ./test --nosync
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test --setvbuf
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test --nosync --unitbuf
abababbaabababababababababababababababab
a
bababababababababababababababababababab

babababababababababababababababababababa

ababababbabababababababababababababababa

abababababababababababababababababababab

$ mpiicpc -std=gnu++11 -o test2 test2.cpp
$ mpirun -np 2 ./test2
abababababbaababababababbaababababababab

babaabababbaabababababababababababababab

babababababababababababababababababababa

ababababbaabababababbabaabababababababab
a
bababababababababababababababababababab

$ mpirun -np 2 ./test2 -f
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbb
$ mpirun -np 2 ./test2 -l
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
$ 

test.cpp


#include <mpi.h>
#include <iostream>
#include <thread>
#include <string>
#include <cstdio>

static char stdoutbuf[8192];

int main(int argc, char **argv) {
  MPI::Init(argc,argv);
  MPI::COMM_WORLD.Set_errhandler(MPI::ERRORS_THROW_EXCEPTIONS);
  int rank = MPI::COMM_WORLD.Get_rank();

  for ( int i=1; i<argc; i++ ) {
    std::string opt(argv[i]);
    if ( opt == "--nosync" ) {
      // detach C++-iostream from C-stdio
      std::ios_base::sync_with_stdio(false);
    }
    else if ( opt == "--setvbuf" ) {
      // re-setvbuf for C-stdio
      std::setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf));
    }
    else if ( opt == "--unitbuf" ) {
      // disable buffering on C++-iostream
      std::cout.setf(std::ios_base::unitbuf);
    }
    else if ( rank == 0 ) {
      std::cerr << "invalid option: " << opt << std::endl;
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
  }

  char c='a'+rank;
  for ( int i=0; i<5; i++ ) {
    MPI::COMM_WORLD.Barrier();
    for ( int j=0; j<20; j++ ) {
      std::cout << c;
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    std::cout << std::endl;
  }
  MPI::Finalize();
}

test2.cpp


#include <mpi.h>
#include <iostream>
#include <thread>
#include <string>
#include <cstdio>

static char stdoutbuf[8192];

int main(int argc, char **argv) {
  MPI::Init(argc,argv);
  MPI::COMM_WORLD.Set_errhandler(MPI::ERRORS_THROW_EXCEPTIONS);
  int rank = MPI::COMM_WORLD.Get_rank();

  if ( argc > 1 ) {
    std::string opt(argv[1]);
    if ( opt == "-f" ) {
      // full buffered
      std::setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf));
    }
    else if ( opt == "-l" ) {
      // line buffered
      std::setvbuf(stdout,stdoutbuf,_IOLBF,sizeof(stdoutbuf));
    }
  }

  char c='a'+rank;
  for ( int i=0; i<5; i++ ) {
    MPI::COMM_WORLD.Barrier();
    for ( int j=0; j<20; j++ ) {
      std::cout << c;
      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    std::cout << '\n';
  }
  std::cout << std::flush;
  MPI::Finalize();
}

Recommended Posts

Coordination de chaque processus dans MPI et mise en mémoire tampon de la sortie standard
Calcul de l'écart type et du coefficient de corrélation en Python
Reçoit et génère la sortie standard des implémentations Python 2 et Python 3> C
Vérifiez le temps de traitement et le nombre d'appels pour chaque processus avec python (cProfile)
Explication du CSV et exemple d'implémentation dans chaque langage de programmation
Lisez l'image du jeu de puzzle et sortez la séquence de chaque bloc
Lire la sortie standard d'un sous-processus ligne par ligne en Python
Comment entrer / sortir des valeurs à partir d'une entrée standard dans la programmation de compétition, etc.
Rendre la sortie standard non bloquante en Python
Exporter et exporter des fichiers en Python
Même dans le processus de conversion de CSV en délimiteur d'espace, essayez sérieusement de séparer les entrées / sorties et les règles
Implémenter une partie du processus en C ++
Remarques sur l'entrée / sortie standard de Go
UnicodeEncodeError lutte avec la sortie standard de python3
Captures d'écran de la pêche sur le Web avec du sélénium et Chrome.
Séparation de la conception et des données dans matplotlib
Différence de sortie de la fonction de fenêtre de longueur paire
Résumé des modules et des classes dans Python-TensorFlow2-
Statut de chaque système de traitement Python en 2020
Projet Euler # 1 "Multiple de 3 et 5" en Python
Comment compter le nombre d'éléments dans Django et sortir dans le modèle