Nous avons été formés à programmer en mode sériel et passer à la programmation parallèle n'a rien d'évident, mais plusieurs technologies existent pour vous faciliter la vie : Intel Threading Building Blocks ou le standard OpenMP par exemple.
Une fois les concepts de base assimilés, implémenter un programme parallèle qui utilise à peu près efficacement un processeur à deux ou quatre coeurs va assez vite si votre algorithme s'y prête. Mais il est surtout très facile de transformer un logiciel sériel sans bogue en un logiciel multithreadé fortement bogué sans que ce soit évident, ni à détecter ni à corriger. Pour répondre à ce problème, Intel a compris très tôt qu'il fallait aider les développeurs avec des outils logiciels. Pour les développeurs sous Visual Studio en C/C++ cet outil est Intel Parallel Studio, plus spécifiquement Parallel Inspector pour la partie isolation et correction de bogues parallèles que nous allons étudier aujourd'hui.
UN EXEMPLE SIMPLE POUR VISUAL STUDIO
Partons d’un exemple concret : un logiciel qui trouve les nombres premiers dans un intervalle donné. Partons du code parallélisé par OpenMP, après ajout d'une simple ligne de "pragma" au code sériel. Visual studio (2005 ou 2008) doit être installé, puis téléchargez et installez Intel Parallel Studio dont la version bêta est disponible gratuitement en ligne sur intel.com/go/parallel et sur le CD du mois dernier de Programmez !. Créez un nouveau projet nommé "premiers" d'application en ligne de commande et décochez les en-têtes précompilés. Remplacez le contenu de “premiers.cpp” par le contenu du fichier téléchargé et sauvez, ensuite cliquez droit dans l'explorateur de solution sur "premiers". Dans le menu vous avez un sous-menu "Intel Parallel Composer" en plus, dans lequel vous pouvez choisir d'utiliser le compilateur Intel. Cliquez et observez la nouvelle icône bleue "C++" utilisée pour votre solution. Nous avons un code qui fait appel à OpenMP, mais encore faut il activer la bonne option de compilation : demandez les propriétés de votre projet, allez dans “C/C++ / Langage / General”. Vous avez un item OpenMP support que vous devez mettre a /OpenMP. Maintenant, compilez en mode debug et exécutez-le plusieurs fois sur une machine parallèle (Multi-Core, HyperThreading, …) en surveillant dans le gestionnaire de processus Windows que vous utilisez plusieurs coeurs.
EXEMPLE SIMPLE MAIS BOGUÉ
Le décompte de nombre premiers est différent pour des exécutions successives, ce qui est bien sûr impossible, nous avons donc un bogue. La version sérielle n'avait pas ce problème, il s'agit donc d'un problème dû à l'exécution parallèle. En rendant notre logiciel parallèle avec OpenMP nous avons probablement omis de protéger certaines parties, mais lesquelles ? Il est parfois facile comme ici de détecter manuellement un bogue parallèle, dans certaines conditions expérimentales c'est presque impossible. Le bogue parallèle est un animal sournois et dur à chasser, il peut se montrer très discret sur votre station de développement et n'apparaître que sur votre serveur de production. Intel Parallel Inspector est capable de détecter des problèmes potentiels dans l'exécution parallèle même s’ils n'arrivent pas dans les conditions de votre exécution. Il suffit juste d'exécuter la partie de votre logiciel que vous souhaitez analyser compilée en parallèle dans Parallel Inspector et il trouvera à coup sur les problèmes dans votre code, magique ! Il faut qu'Inspector ait accès aux symboles pour remonter jusqu'au code source à l’endroit précis du problème, donc compilez en mode debug.
ANALYSE DES ERREURS DE THREADING
Lancez maintenant une analyse directement depuis Visual Studio par le menu “Outils / Intel Parallel Inspector / Check threading errors”. Vous pouvez demander différents niveaux d'analyse : Parallel Inspector instrumente les accès mémoire, ce qui est assez lent en soi mais qui ralentira aussi l'exécution. Il faut donc soumettre une charge de travail réduite pour que l'exécution ne dure pas trop longtemps. Par contre cela n'influe pas sur la fiabilité des résultats, nous cherchons ici à détecter des bogues, pas à augmenter ou mesurer la performance. Au début de l'exécution, vous voyez défiler dans les logs les librairies utilisées et celles qui sont compilées en debug, elles sont instrumentées, ce qui pour un gros logiciel est une opération intensive, mais qui sera réutilisée pour de futures exécutions.
UN RÉSULTAT CLAIR ET CONCIS RELIÉ À VOTRE SOURCE
Voici le rapport qui apparaît : vous avez une liste des problèmes en haut, P1, P2, P3 qui sont dans les 3 cas des “data race”, soit des interactions de différents threads vers les mêmes variables. Pour chaque problème vous avez dans la liste du bas un détail des interactions. En effet, pour un problème donné exécuté par différents threads vous aurez une liste d'interactions entre différents couples de threads. Ici c'est l'exécution de la même ligne 73 du source qui interagit entre différents threads, double-cliquez sur la ligne et vous verrez la vue complète de 2 threads exécutant deux morceaux de code en même temps (ici le même code, mais pas toujours). La ligne 73 est une variable partagée (gProgress) qui est incrémentée, tout simplement. Une incrémentation est une lecture puis une écriture, selon que la lecture dans le thread du haut arrive avant ou après l'écriture du thread du bas, le résultat est différent. Et bien sûr, deux écritures simultanées sont aussi un problème. C'est donc une erreur de programmation parallèle ! Toute variable partagée entre différents threads et accédée en écriture doit être protégée. Double-cliquez sur le source pour aller dans l'édition du source à la bonne ligne. La solution est déjà dans le code fourni, ligne 72 qu'il suffit de décommenter en laissant le dièse en début de ligne (la solution proposée n'est pas optimale mais fonctionne). Une section critique s'assure que la région protégée (ici la ligne qui suit) ne sera exécutée que par un seul thread à la fois. Retour au rapport, allez a l'erreur P2 ligne 103 et décommentez la ligne 102 qui est la solution. Puis P3, qui est aussi ligne 102. Ce n'est pas un bogue de l'outil, mais simplement que nous avons deux bogues bien distincts sur la même ligne, une concerne le tableau, l'autre la variable incrémentée. L’outil raisonne en termes d’accès mémoire, de variables et vous indique les lignes fautives. Sauvez le source, fermez le rapport et recompilez. Vous pouvez maintenant relancer l'analyse d’erreurs de threads. Un bogue peut en cacher un autre. Cette fois nous avons un bogue un peu plus complexe, une interaction entre 2 threads mais qui exécutent un morceau différent du code, lignes 75 et 73. La ligne 75 lit la variable gProgress qui est incrémentée juste avant, ligne 73. L'incrémentation en elle-même est maintenant protégée grâce a la section critique, mais si le thread du bas incrémente la variable alors que le thread du haut passe de la ligne 73 à 75 nous avons un bogue. Il faut donc s'assurer que tout le bloc 73 à 78 est exécuté par un seul thread à la fois d’une seule traite, en pratique étendre la section critique avec des accolades, ouverte ligne 73 et fermée ligne 78. Recompilons et exécutons à nouveau dans Parallel Inspector: cette fois 0 erreur ! Nous avons maintenant un logiciel parallèle sans bogues de parallélisme !
LES LIBRAIRIES AUSSI !
Vous avez corrigé les bogues que vous avez créés, mais en tant que développeur vous êtes responsables de tout ce qui est appelé par votre application. En effet, toute librairie lancée depuis une région parallèle doit aussi être garantie sans bogues parallèles. Bonne nouvelle, Parallel Inspector trouvera aussi ces bogues, par contre, faute de version debug de ces librairies et du code source vous ne pourrez les corriger, mais au moins vous saurez qu’il ne faut pas utiliser ces librairies en parallèle.
CONCLUSION
Vous avez pu voir dans cet exemple que les bogues parallèles sont complexes à isoler et corriger mais qu’Intel Parallel Inspector facilite et accélère grandement le travail. Il ne se substitue pas à l’apprentissage des concepts de base du parallélisme, mais vous accompagne dans votre projet en garantissant la qualité du code et des librairies. Si nous exécutons plusieurs fois le nouveau binaire, le résultat est cette fois le même, ce qui est bon signe, mais le temps d'exécution est plus long, C'est une des vérités de la programmation parallèle, un logiciel bogué va souvent plus vite ! Mais ne vous inquiétez pas, pour augmenter la vitesse, nous explorerons Intel Parallel Amplifier très bientôt.
Paul Guermonprez
Ingénieur logiciel – Intel.
Actualités
Intel Parallel Studio 2011 Getting Started
Pratique
Supplément C++
Livre blanc
Intel Parallel Studio 2011
Intel propose une nouvelle gamme d'outils de développement d'applications pour profiter du multicore via une programmation parallèle. Il s'agit de l'Intel® Parallel Studio qui d'adresse aux développeurs Windows qui possèdent Visual Studio* C/C++2008. Ce nouvel outil est en fait un ensemble qui réunit , Parallel Advisor pour diagnostiquer le code quant aux possibilités de parallélisation, Parallel Composer pour incorporer du parallélisme à l'aide d'un compilateur C++ Intel et des bibliothèques thread safe, Parallel Inspector pour découvrir des conflits de threading éventuels et Parallel Amplifier pour diagnostiquer le comportement des threads.
Liens
Actu
Pratique
Actu
Distributeurs INTEL