Exécution et débogage de programmes - LabUnix
Transcription
Exécution et débogage de programmes - LabUnix
Exécution et débogage de programmes parallèles MPI avec MPICH2 Table des matières 1 Introduction : Caractéristiques d'un cluster et de trex_cluster 2 2 Étapes pour l'exécution d'un programme MPI 3 3 Stratégie pour déboguer un programme parallèle 5 4 Erreurs typiques dans des programmes MPI 4.1 Erreurs conduisant à des interblocages (deadlock ) . . . . . . . . . . . . . 7 5 Options de compilation avec MPICH2 9 4.2 Erreurs conduisant à des résultats erronnés . . . . . . . . . . . . . . . . . 4.3 Comportements non spéciés de MPI . . . . . . . . . . . . . . . . . . . . 6 Options d'exécution avec MPICH2 7 7 7 12 1 Introduction : Caractéristiques d'un cluster et de trex_cluster Voici deux dénitions de ce qu'est un cluster : Co-located collection of mass-produced computers and switches dedicated to running parallel jobs. The computers typically do not have displays or keyboards [and] some of the computers may not allow users to login [except head node]. All computers run the same OS and have identical disk images. [They also] use a faster, [generally] switched networked, e.g., gigabit Ethernet. [Qui03] Any collection of distinct computers that are connected and used as a parallel computer, or to form a redundant system for higher availability. The computers in a cluster are not specialized to cluster computing. In other words, the computer making up the cluster [. . . ] are not custom-built for use in the cluster. [MSM05]. Un cluster une grappe de processeurs, une grappe de calcul est donc une machine parallèle MIMD de type multi-ordinateurs ayant les caractéristiques suivantes : • Intégration des diverses machines en un système unique, généralement avec un réseau dédié. • Les divers processeurs travaillent sur un même problème. • Les divers noeuds sont des machines de faible coût, où chaque noeud pourrait en théorie être utilisé comme une machine/station de travail normale. Toutefois, en pratique, généralement un seul des noeuds le head node est accessible directement aux usagers. La machine trex_cluster La machine que nous allons utiliser pour la partie du cours sur MPI est la machine trex_cluster.labunix.uqam.ca, qui possède les caractéristiques suivantes : • Machine Ciara Tech. • 14 processeurs Intel Xeon.1 • Réseau Gigabit Ethernet (full duplex ). • Système d'exploitation Linux CentOS 5.6. 1 Il y avait initialement 16 processeurs, mais 2 sont maintenant hors-service. 2 2 Étapes pour l'exécution d'un programme MPI Pour compiler et exécuter un programme MPI/C avec MPICH 2.0 (version MPI installée sur trex_cluster, plusieurs étapes sont nécessaires, décrites plus bas. Notez toutefois que des cibles make appropriées ont été dénies pour les laboratoires, donc il sut généralement d'exécuter make compile puis make run. 1. Il faut compiler le programme sur le noeud maître (trex_cluster). Par exemple : $ mpicc -o hello -std=c99 hello.c 2. Il faut copier l'exécutable sur les diérents noeuds. Un script cp-sur-blades.csh a été déni pour ce faire, lequel script fait des appels à scp et copie l'exécutable à la racine du répertoire. Par exemple : $ cp hello ~ $ ./cp-sur-blades.sh ~/hello *** On copie 'hello' sur les differentes blades 'hello' copie sur 14 machines 3. Il faut activer, si ce n'est pas déjà fait, le daemon MPI. Par exemple : $ cat mpd.hosts trex_cluster blade03 blade04 blade05 blade06 blade07 blade09 blade10 blade11 blade12 blade13 blade14 blade15 blade16 $ mpdboot -n 4 -f mpd.hosts $ mpdtrace trex_cluster blade05 blade04 blade03 Note : Un script activer-mpdboot.sh a aussi été déni pour faire ce travail. Par exemple, pour utiliser huit (8) processeurs parmi tous les processeurs disponibles (spéciés dans mpd.hosts) : $ ./activer-mpdboot.sh 8 mpd.hosts 3 4. On lance l'exécution du programme en spéciant le nombre de processeurs qu'on désire utiliser, et ce à partir de la racine du répertoire avec la commande mpirun. Par exemple : $ cd ~ $ mpirun -np 6 hello trex_cluster numProc = 0, nbProcs = blade05 6 numProc = 1, nbProcs = 6 numProc = 2, nbProcs = 6 numProc = 3, nbProcs = 6 trex_cluster numProc = 4, nbProcs = 6 blade04 blade03 blade05 numProc = 5, nbProcs = 6 --------------------------------------- Signalons que n'importe quel programme ou commande accessible sur toutes les machines peut être exécuté avec mpirun : $ mpirun Mon Mar Mon Mar Mon Mar -np 3 date 4 20:45:08 EST 2013 4 18:44:59 EST 2013 4 18:36:33 EST 2013 $ mpirun -np 2 ls -l hello -rwxrwxr-x 1 tremblay_g tremblay_g 798799 Mar -rwxrwxr-x 1 tremblay_g tremblay_g 798799 Mar 4 18:35 hello 4 20:43 hello $ mpirun -np 2 uname -a Linux blade05 2.6.18-92.el5 #1 SMP Tue Jun 10 18:49:47 EDT 2008 i686 i686 i386 GNU/Linux Linux trex_cluster 2.6.18-238.el5 #1 SMP Thu Jan 13 16:24:47 EST 2011 i686 i686 i386 GNU/Li 5. Lorsqu'on a dénitivement terminé, on tue les démons permettant l'exécution des programmes MPI. Par exemple : $ mpdallexit 4 3 Stratégie pour déboguer un programme parallèle This brings up one of the most important points to keep up in mind when you're debugging a parallel program: Many (if not most) parallel program bugs have nothing to do with the fact that the program is a parallel program. Many (if not most) parallel program bugs are caused by the same mistakes that cause serial program bugs. [Pac97] Pour déboguer un programme parallèle, il est généralement préférable de procéder, en gros, comme suit : a. On vérie tout d'abord le bon fonctionnement du programme pour un unique processus s'exécutant sur un unique processeur. b. On vérie le bon fonctionnement pour deux ou plusieurs processus mais s'exécutant sur un seul et unique processeur voir plus bas. c. On vérie le bon fonctionnement du programme pour deux ou plusieurs processus s'exécutant sur deux processeurs. d. On vérie le bon fonctionnement avec plusieurs processus s'exécutant sur plusieurs processeurs. Autres trucs : • Lorsqu'on utilise des printf pour générer une trace d'exécution, il est préférable de mettre une instruction fflush immédiatement après le printf, pour assurer que les impressions se fassent dans un ordre qui reète le plus possible l'exécution réelle : fflush( stdout ); Il faut aussi savoir que les programmes parallèles peuvent parfois contenir des Heisenbug. Plus précisément, il peut arriver qu'un programme parallèle ne fonctionne pas par exemple, à cause d'erreurs de synchronisation entre processus mais que le programme fonctionne sans problème quand on rajoute des instructions printf pour tenter de le déboguer!2 Lorsqu'on utilise des traces d'exécution, il faut aussi tenir compte du fait que la quantité totale d'information générée sera multipliée par le nombre de processeurs ce qui peut parfois rendre dicile de comprendre les informations ainsi produites. 2 On parle de Heisenbug car l'eet d'observer le programme modie son comportement, dans l'esprit du principe d'incertitude d'Heisenberg en physique. 5 • Toujours pour la génération de traces d'exécution avec printf, il est préférable d'indiquer explicitement le numéro du processus générant un élément de la trace. Si ce numéro est mis (en préxe) au début de la ligne, alors on peut ensuite utiliser l'outil Unix sort pour produire une liste exacte de ce qui est imprimé par chaque processus voir aussi plus bas pour une façon rapide d'indiquer les numéros de processus avec l'option d'exécution -l. • Lorsqu'une erreur d'exécution est générée par MPI (avec MPICH2), le message contient, au début de la ligne, le numéro du processus (virtuel) et autres informations. Par exemple, le message suivant a été produit par le processus virtuel 0 : rank 0 in job 1 trex_cluster_55002 caused collective abort of all ranks exit status of rank 0: killed by signal 11 6 4 Erreurs typiques dans des programmes MPI Note : Les deux premières sous-sections sont une traduction (partielle) de l'appendice C du livre de M.J. Quinn [Qui03]. 4.1 Erreurs conduisant à des interblocages (deadlock ) • Un seul processus exécute un opération de communication collective (e.g., MPI_Reduce, MPI_Bcast, MPI_Scatter). Une solution à ce problème est de ne jamais mettre des opérations de communication collective dans du code s'exécutant conditionnellement. • Deux ou plusieurs processus essaient d'échanger de l'information, mais en utilisant MPI_Recv, et ce avant que des appels appropriés à MPI_Send aient été eectués. Diérentes solutions sont possibles, par exemple : toujours s'assurer d'eectuer les MPI_Send avant les MPI_Recv ; utiliser plutôt des appels à l'opération MPI_Sendrecv ; utiliser des appels à MPI_Irecv. • Un processus essaie de recevoir des données d'un processeur, qui n'eectue jamais d'envoi approprié. Ceci est souvent causé par l'utilisation d'un mauvais numéro de processus. Lorsque possible, il est préférable d'utiliser les opérations collectives de communication. Si ce n'est pas possible, il faut s'assurer que le patron des échanges entre les processus soit le plus simple possible. • Un processus essaie de recevoir des données de lui-même. 4.2 Erreurs conduisant à des résultats erronnés • Incohérence au niveau des types utilisés dans l'opération Send vs. Recv. Pour éviter ce problème, il faut s'assurer au niveau du code source que chaque opération d'envoi possède une unique opération de réception correspondante, et vérier (et re-vérier) le code source. • Arguments d'un appel à une opération de communication transmis dans le mauvais ordre. 4.3 Comportements non spéciés de MPI • The MPI specication purposefully does not mandate whether or not collective communication operations have the side eect of synchronizing the processes over which they operate. Donc, des opérations telles que MPI_Bcast, MPI_Reduce, etc., peuvent, ou non, créer une barrière de synchronisation entre les processus impliqués. 7 • According to the MPI standard, message buering may or may not occur when processes communicate with each other using MPI_Send. Cette question sera abordée plus en détail ultérieurement. 8 5 Options de compilation avec MPICH2 Deux options de compilation permettent d'aider au débogage de programmes MPI (avec la mise en oeuvre MPICH2) : • -mpe=mpitrace : Lorsque cette option est spéciée au moment de la compilation, divers messages liés à l'exécution d'opérations MPI sont achés lors de l'exécution du programme. Un exemple, pour deux processeurs, est illustré à la gure 1. • -mpe=mpicheck : Lorsque cette option est spéciée à la compilation, diverses vérications sont eectuées sur les communications collectives au moment de l'exécution. Si des communications sont eectuées incorrectement par exemple, types invalides, numéros de processus racine incohérents, etc. alors des messages d'erreur sont achés, comme l'illustre la gure 2. Signalons que pour cet exemple, si les vérications des communications collectives n'avaient pas été activées, le programme aurait terminé sans générer d'erreur au niveau des communications, mais avec un résultat incorrect :( 9 $ make EXEC=min NP=2 run *** On copie '/home/tremblay_g/min' sur les differentes blades '/home/tremblay_g/min' copie sur 16 machines Starting MPI_Init... Starting MPI_Init... [0] Ending MPI_Init [0] Starting MPI_Comm_rank... [0] Ending MPI_Comm_rank [0] Starting MPI_Comm_size... [0] Ending MPI_Comm_size [0] Starting MPI_Barrier... [1] Ending MPI_Init [1] Starting MPI_Comm_rank... [1] Ending MPI_Comm_rank [1] Starting MPI_Comm_size... [1] Ending MPI_Comm_size [1] Starting MPI_Barrier... [0] Ending MPI_Barrier [0] Starting MPI_Scatter... [1] Ending MPI_Barrier [1] Starting MPI_Scatter... [0] Ending MPI_Scatter [1] Ending MPI_Scatter [0] Starting MPI_Reduce... [1] Starting MPI_Reduce... [1] Ending MPI_Reduce [1] Starting MPI_Finalize... [0] Ending MPI_Reduce [0] Starting MPI_Finalize... [0] Ending MPI_Finalize 10 calcul(s) du min pour 16000 element(s) avec 2 processeur(s) en [1] Ending MPI_Finalize 0.002 secondes => 1 Figure 1: Trace d'exécution produite avec l'option de compilation -mpe=mpitrace. 10 $ make EXEC=min NP=2 run Starting MPI Collective and Datatype Checking! Backtrace of the callstack at rank 1: At [0]: min(CollChk_err_han+0xb9)[0x8050cf9] At [1]: min(CollChk_same_root+0x5c)[0x8052d8c] At [2]: min(MPI_Reduce+0x52)[0x8051092] At [3]: min(main+0x20e)[0x8050b4b] At [4]: /lib/libc.so.6(__libc_start_main+0xdc)[0x95adec] At [5]: min[0x8050741] Fatal error in MPI_Comm_call_errhandler: Collective Checking: REDUCE (Rank 1) --> Root Parameter (0) is inconsistent with rank 0 (1) [cli_1]: aborting job: Fatal error in MPI_Comm_call_errhandler: Collective Checking: REDUCE (Rank 1) --> Root Parameter (0) is inconsistent with rank 0 (1) rank 1 in job 1 trex_cluster_55002 caused collective abort of all ranks exit status of rank 1: return code 1 Figure 2: Message d'erreur produit par la vérication des communications (option de compilation -mpe=mpicheck). 11 6 Options d'exécution avec MPICH2 Divers options peuvent être spéciés à mpirun pour aider à déboguer un programme : • On peut demander, avec l'option -l, que chaque impression sur stdout soit automatiquement préxée du numéro du processus : voir gure 3. • On peut utiliser explicitement le débogueur gdb : $ mpirun -gdb pgm • On peut exécuter le programme en créant plusieurs processus, mais sur un noeud unique, ou sur un petit nombre de noeuds. Pour ce faire, il faut lancer de mpdboot avec une liste de machines ne contenant que les noeuds à utiliser, tel qu'illustré à la gure 4. Remarque : Dans les chiers makefile qui vous sont fournis, deux cibles utilisant cette façon de faire sont dénies : debug_un : Lance les NP processus sur un unique noeud. debug_deux : Lance les NP processus sur deux processeurs. Voir gure 5. Références [MSM05] T.G. Mattson, B.A. Sanders, and B.L. Massingill. Patterns for Parallel Programming. Addison-Wesley, 2005. [Pac97] P.S. Pacheco. Parallel Programming with MPI. Morgan Kaufman Publ., 1997. [Qui03] M.J. Quinn. Parallel Programming in C with MPI and OpenMP. McGrawHill, 2003. 12 Sans l'option -l : ================== $ make EXEC=hello NP=3 run trex_cluster numProc = 0, nbProcs = blade05 blade12 3 numProc = 1, nbProcs = 3 numProc = 2, nbProcs = 3 --------------------------------------Avec l'option -l : ================== $ make EXEC=hello NP=3 run 0: trex_cluster 0: numProc = 0, nbProcs = 3 0: 1: blade05 1: numProc = 1, nbProcs = 3 1: 2: blade12 2: numProc = 2, nbProcs = 3 2: 0: --------------------------------------- Figure 3: Utilisation de l'option -l pour indiquer le numéro du processus sur les lignes de sortie. 13 $ cat unemachine.txt trex_cluster $ mpdboot -f unemachine.txt $ mpdtrace trex_cluster $ mpirun -l -np 5 hostname 0: trex_cluster 4: trex_cluster 2: trex_cluster 3: trex_cluster 1: trex_cluster $ mpdallexit $ cat deuxmachines.txt trex_cluster blade02 $ mpdboot -n 2 -f deuxmachines.txt $ mpdtrace trex_cluster blade02 $ mpirun -l -n 5 hostname 0: trex_cluster 4: trex_cluster 2: trex_cluster 1: blade02 3: blade02 Figure 4: Création de plusieurs processus sur un noeud unique ou sur un groupe limité de noeuds. Cet exemple illustre aussi l'utilisation de l'option -l et le fait que le programme exécuté par mpirun peut aussi être une simple commande Unix. Quant à la commande mpdtrace, elle permet d'obtenir les noms des noeuds qui font partie du groupe de noeuds activés lors du dernier appel à mpdboot. 14 $ make EXEC=hello NP=3 debug_un *** On active mpdboot avec un seul processeur trex_cluster numProc = 0, nbProcs = 3 trex_cluster numProc = 1, nbProcs = 3 trex_cluster numProc = 2, nbProcs = 3 --------------------------------------$ make EXEC=hello NP=3 debug_deux *** On active mpdboot avec 2 processeurs trex_cluster numProc = 0, nbProcs = 3 blade02 numProc = 1, nbProcs = 3 trex_cluster numProc = 2, nbProcs = 3 --------------------------------------$ make EXEC=hello NP=3 run *** On active mpdboot avec 16 processeurs trex_cluster numProc = 0, nbProcs = 3 blade05 blade13 numProc = 1, nbProcs = 3 numProc = 2, nbProcs = 3 --------------------------------------- Figure 5: Exemples d'exécution avec les cibles debug_un, debug_deux vs. run. 15