460 likes | 580 Views
Les Modèles de Développement Parallèle pour développer efficacement. Code Session : TCP301. Bruno BOUCARD Architecte SGCIB Programmation Parallèle http://msmvps.com/blogs/brunoboucard Développement Parallèle http://blogs.msdn.com/devpara/default.aspx. Agenda. Introduction Modélisation
E N D
Les Modèles de Développement Parallèle pour développer efficacement • Code Session : TCP301 Bruno BOUCARD Architecte SGCIB Programmation Parallèle http://msmvps.com/blogs/brunoboucard Développement Parallèle http://blogs.msdn.com/devpara/default.aspx
Agenda • Introduction • Modélisation • Conclusion
IntroductionLes lois de la physique changent les règles ! • Pourquoi les fondeurs ont-ils changé de paradigme ? • Les systèmes d'exploitation sont-ils disponibles ? 256 Cœurs • « The Free Lunch Is Over » (2005) HerbSutter - Architecte Microsoft
IntroductionQue faut-il pour retrouver le « Free Lunch » ? • Offres parallèles plus simples • Par exemple Visual Studio 2010 • Méthode et Patterns sans équivoque • 30 ans d’expérience dans le parallélisme • Penser Parallèle • Nous devons pratiquer le parallélisme dans notre quotidien
IntroductionLa concurrence est parmi nous : à table ! Piler l'ail Découper en dés la tomate Faire bouillir de l'eau Mélanger avec du gros Sel Hacher le basilic Recouvrir d'huile d'olive Cuire les pâtes Mélanger Laisser reposer Egoutter les pâtes Mélanger et servir
Agenda • Introduction • Modélisation • Conclusion
Choisir une stratégie et un algorithme adaptés • Trouver la concurrence ModéliserMéthode itérative
Mesurer les performances de la solution ModéliserEtape 1
ModéliserAnalyser les performances • Analyser les coûts une fois la solution parallèle implémentée • Analyser les coûts de la solution séquentielle
Trouver la concurrence ModéliserEtape 2
ModéliserTrouver la concurrence • Commencer avec un cahier des charges qui décrit le problème • Quel que soit le contexte fonctionnel ou technique, vous serez guidé naturellement par l’une des décompositions • Terminer avec une décomposition de tâches ordonnées en fonction des dépendances techniques et fonctionnelles, compléter par les données partagées que vous aurez identifiées • Il arrive qu’on passe d’une décomposition orientée tâches à une décomposition orientée données ou bien flux de données en fonction du type de traitement Commencer Analyse des dépendances Décomposition Grouper les tâches Orientée données Ordonner les tâches Evaluer le design Orientée tâches Partager les données • Analyser les dépendances • Visual Studio 2010 : Architecture -> GenerateDependency Graph • NDepend: http://www.ndepend.com/ • CppDepend: http://www.cppdepend.com/
ModéliserDécomposition et granularité • Votre décomposition en tâches doit tenir compte de leur granularité et de leur surcoût Cœur 0 Cœur 3 Cœur 1 Cœur 2 Cœur 0 Cœur 1 Cœur 2 Cœur 3 tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche Configuration 2 Quelle est la meilleure configuration ? Configuration 1 surcoût charge
ModéliserDécomposition et répartition de charge • Votre groupement de tâches doit tenir compte de leur charge Cœur 1 tâche Cœur 0 Cœur 2 Cœur 3 Cœur 0 Cœur 1 Cœur 2 Cœur 3 tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche tâche Configuration 2 Quelle est la meilleure configuration ? Configuration 1 surcoût charge
ModéliserEvaluer votre design • Flexibilité • Préférer l’abstraction pour faciliter l’adaptation à différents scénarios d’exécution • Nombre de cœurs sollicités • Partitionnement des données • Efficacité • Le temps dépensé à gérer le parallélisme vs le temps gagné à tirer parti des cœurs • Amélioration des performances en fonction du nombre de processeurs • Simplicité • Le code peut être facilement diagnostiqué • La solution technique choisie est facile à maintenir
Choisir un algorithme en fonction de votre stratégie ModéliserEtape 2
ModéliserChoisir un algorithme en fonction de votre stratégie
ModéliserAlgorithmes données • Décomposition géométrique Taille des morceaux => Trop grand – sous utilisation => Trop petit – sur consommation Format des morceaux => Attention au false sharing Traitement d’une collection d’images, addition de matrices …
ModéliserAlgorithmes données – Attention au False Sharing • Pour des raisons de performances, les systèmes utilisent des lignes de cache • Lorsque des threads sur différents processeurs modifient en parallèle les variables qui résident sur la même ligne de cache, le False Sharing n’est pas loin Coeur 0 Coeur 1 T0 T1 Cache Cache Ligne de cache Ligne de cache • Éviter l'allocation de mémoire contiguë • Deux champs d'instance dans la même instance de classe sont proches dans leurs emplacement de mémoire • Deux champs statiques dans le même type sont proches en mémoire • Deux éléments avec des index adjacents dans un tableau sont proches en mémoire • Les objets alloués consécutivement sont probablement proches en mémoire • Les variables locales utilisées ensemble dans une fermeture sont probablement capturées dans les champs d'instance, et ainsi, d'après le premier commentaire ci-dessus, sont également proches en mémoire Mémoire
ModéliserDécomposition géométrique for (int i = 0; i < size; i++) { parallel_for (0, size, 5, [&result](int j) { double t = 0; for (int k = 0; k < size; k++) { t += (m1[i][k] * m2[k][j]); } result[i][j] = t; }); }
ModéliserAlgorithmes données • Décomposition récursive Le choix de la profondeur détermine la performance - Arbre profond => contention processeurs - Arbre de profondeur limitée => sous utilisation des processeurs 1 tâche 2 tâches 3 tâches 3 tâches Parcours de graphe ou parcours d’arbre …
ModéliserRécursivité sur des données staticvoidWalk<T>(Tree<T> root, Action<T> Action) { if (root == null) return; var t1 = Task.Factory.StartNew(() => action(root.Data)); var t2 = Task.Factory.StartNew(() => Walk(root.Left, action)); var t3 = Task.Factory.StartNew(() => Walk(root.Rigth, action)); Task.WaitAll(t1, t2, t3); }
ModéliserAlgorithmes tâches • Parallélisme de tâches linéaires Nombre de tâches => Trop peu: les cœurs sont sous utilisés => Trop élevé: contention des tâches Dépendances =>Retirables => Séparables => Lecture seule ou lecture/écriture sous opération 1 sous opération 2 sous opération 3 sous opération 4 sous opération 1 sous opération 4 sous opération 2 Paralléliser des opérations décomposables sous opération 3
ModéliserAlgorithmes tâches Arbres profond => contention processeurs Arbres de profondeur limitée => sous utilisation des processeur • Diviser pour régner Problème Séquentiel Split Sous - problème Sous - problème 2 chemins parallèles Split Split Sous - problème Sous - problème Sous - problème Sous - problème 4 chemins parallèles Résoudre Résoudre Résoudre Résoudre Sous - problème Sous - problème Sous - problème Sous - problème Fusionner Fusionner 2 chemins parallèles Sous - solution Sous - solution Fusionner QuickSort Solution Séquentiel
ModéliserDiviser pour régner voidquicksort(int* a, int n) { if (n <= 1) return; int s = partition(a, n); task_group g; g.run([&]{ quicksort(a, s); }); g.run([&]{ quicksort(a + s, n + s); }); g.wait(); }
ModéliserAlgorithmes flux de données • Pipeline Les charges de travail des étapes => Egales – pipeline linéaire => Inégales – pipeline non-linéaire Chaîne de montage automobile
ModéliserAlgorithmes flux de données • Coordination orientée événements Traitement d’une dépêche sur un desk journalistique …
ModéliserPipeline agent_a a; agent_b b; agent_c c; // a -> b -> c b.set_input(a.get_output()); c.set_input(b.get_output()); c.start(); b.start(); a.start(), a.wait(); b.wait(); c.wait();
Choisir un pattern de structure ModéliserEtape 3
ModéliserChoisir un pattern de structure • Le passage à l’implémentation se décompose en deux types de patterns de structure Patterns de structures Programme Données
ModéliserChoisir un pattern de structures de programme • Après avoir sélectionné votre algorithme parallèle, il faut maintenant le supporter dans votre programme Structures de programme SPMD Master/Worker Boucle parallèle Fork/Join
ModéliserLes Patterns de structures de programme • SPMD, Master/Worker, Boucle parallèle et Fork/Join partagent les mêmes idiomes Partitionner Exécuter Fusionner
ModéliserFork / Join Parallel.Invoke( () => ComputeMean(), () => ComputeMedian() ); parallel_invoke( [&] { ComputeMean(); }, [&] { ComputeMedian(); } );
ModéliserBoucle parallèle Parallel.For(0, 1000, (i) => _treatments.Update(i); ); parallel_for (0, 1000, 1, [&](int i) { _treatments.Update(i); });
ModéliserStructures de programme parallèle • Appliquées dans différents contextes • SPMD – Systèmes distribués • MPI, SOA, GridComputing, • Fork / Join – Orienté Tâches • TPL ou PPL • Master/Worker – Orienté Tâches • TPL ou PPL • Boucle Parallèle – Orienté Données • OpenMP, TPL, PLINQ ou PPL
ModéliserChoisir un pattern de structures de données partagées Structures de données partagées Données partagées Queue partagée
Modéliser Données Partagées • Définir une abstraction qui vous affranchira d’une complexité sujette à des erreurs • Cacher la complexité de la protection des données à travers des interfaces simples et non ambigües • Privilégier une stratégie de verrouillage performante • Si possible via des techniques peu couteuses (éviter les transitions mode noyau/ mode utilisateur) • Pour une boucle parallèle, utiliser les données privées des threads Thread Local Storage (TLS) pour réduire le coût du verrouillage excessif
ModéliserDonnées Partagées combinable<list<TypeBitmap>> bitmaps; parallel_for_each(m_filenames.begin(), m_filenames.end(), [&](std::wstring filename) { bitmaps.local().push_back(LoadImage(filename)); }); bitmaps.combine_each([&](list<TypeBitmap>& localList) { images.splice(images.begin(), localList); });
Modéliser Pattern Décorateur • Encapsuler le parallélisme • Permet un usage agnostique dans un contexte parallèle • Par exemple ajouter le support du parallélisme à une structure existante • Par exemple: rendre le type array<T> compatible parallèle • Encapsuler le type d’origine en protégeant les codes sensibles à une exécution simultanée : concurrent_array<T> concurrent_array<T> array<T> IArray IArray
Modéliser Queue Partagée • ConcurrentQueue<T> • Cache le verrouillage interne qui protège l’objet interne Queue<T> • Implémente IProducerConsumerCollection<T> • Facilite le pattern Producteur / Consommateur • T1 -> theQueue.Enqueue • T2 -> theQueue.Dequeue
ModéliserQueue Partagée var results = new ConcurrentQueue<Result>(); Parallel.For(0, 1000, (i) => { Resultresult = ComputeResult(i); results.Enqueue(result); });
Agenda • Introduction • Modélisation • Conclusion
ConclusionQuelques suggestions pour l’implémentation • Préférer les nouveaux outils de haut niveau d’abstraction • VS 2010: TPL, PLINQ, PPL et Asynchronous Agents Library • Tester Axum • Privilégier les solutions simples à maintenir • La granularité fine est souvent synonyme de complexité • Utiliser systématiquement des librairies thread-safe • Fiabilité == Gain de temps • Ne jamais présumer d’un ordre d’exécution • La justesse du code est à ce prix
ConclusionPour retrouver le « Free Lunch » • Respecter les 4 grandes étapes • Notamment les « analyses des dépendances » et « analyses des performances » • Avancer de manière itérative • Difficile de trouver « la solution » du premier coup • Si possible privilégier les capacités de monter en charge ainsi que la simplicité • Mesurer régulièrement les performances de vos choix • Visual Studio 2010 est votre ami • Si vos choix ne vous semblent pas satisfaisants • Oser changer votre algorithme pour augmenter ses chances de parallélisation • Penser Parallèle • C’est en pratiquant régulièrement la méthode présentée que vous gagnerez en réflexes sur l’usage des Patterns parallèles les mieux adaptés à vos besoins
Vous n’êtes pas seul !Livres et blogues • Mes ouvrages préférés • Programmation Parallèle • http://msmvps.com/blogs/brunoboucard • Portail Microsoft ParallelComputing • http://msdn.microsoft.com/en-us/concurrency • Patterns for Parallel Programming de Stephen Toub • http://www.microsoft.com/downloads/details.aspx?FamilyID=86b3d32b-ad26-4bb8-a3ae-c1637026c3ee&displaylang=en