J'ai remis un coup sur mon travail FABULOUS pour avancer un peu sur le parallélisme dans Metafor. Depuis un bout de temps, j'étais arrivé à la conclusion que l'utilisation d'OpenMP n'était vraiment pas intéressante pour faire du parallélisme de type SMP en C++:
for(int i,…
). Même le type size_t
(généralement un unsigned long int
codé sur 8 octets), utilisé pour tirer parti du 64 bits et éviter une belle chiée de warnings, n'est pas supporté par OpenMP… Un petit espoir vient de OpenMP 3.0, qui est proposé par le compilateur Intel 11.1 et gcc 4.4, qui supporte la notion de “tâches parallèles”.clifton
ou gaston
ne supporte pas du tout OpenMP. De plus, Microsoft vient d'annoncer qu'il ne compte pas supporter OpenMP 3.0 à court terme… Bref, il faudrait idéalement se limiter à OpenMP 2.0 pour garder le compilateur Microsoft que tout le monde utilise par défaut.dans ce contexte bien décevant, j'avais vu quelques infos sur la bibliothèque Threading Building Blocks (TBB) dans les pubs que je reçois d'Intel et ça me semblait plutôt (très) bien ficelé. Je m'étais dit que j'allais essayer, ou plutôt que j'allais faire essayer ça à un TFiste si j'en trouvais un. Finalement, à défaut d'étudiant, j'ai décidé de me lancer moi-même là dedans et le résultat est vraiment très intéressant. Tous les points mentionnés ci-dessous sont résolus:
mtGeo
. Ouf!parallel_for
, parallel_reduce
), TBB supporte un système de “tâches” (tasks
) extrêmement sophistiqué et efficace. Il est ainsi possible de créer des arbres de tâches très complexes sur plusieurs niveaux (je ne dis pas que c'est simple à faire mais c'est faisable d'après la doc - je ne vois pas actuellement de limitations dans ce qui est envisageable).tbbmalloc
) et des conteneurs “parallèles” type STL (je ne les ai pas encore essayés mais ça me semble très bien fait et très utile). Considérons ça comme le coup de grâce pour OpenMP. Quels sont les défauts de TBB?
#ifdef/#endif
partout). En pratique, ce n'est pas un gros problème. Intel TBB est très facile à installer (sous Windows, il suffit de dézipper les binaires précompilés et sous Linux, un simple gmake
suffit).
Bilan très positif donc! Metafor a été mis à jour et nécessite maintenant TBB. Le nombre de threads est géré en ligne de commande par l'option “-j
” ou, dans le jeu de données, par une classe IntelTBB
similaire aux classes Blas
et OpenMP
. Par défaut, si on ne spécifie rien, TBB alloue un nombre de threads qui dépend de la charge machine (et qui peut varier en cours de calcul!). Je me suis donc arrangé pour que TBB n'alloue qu'un seul thread par défaut. Ca permet d'avoir une batterie de test reproductible.
J'ai parallélisé le mailleur Gen4 avec Intel TBB. J'ai pris ça comme un exercice “réduit” avant de m'attaquer à Metafor et sa boucle d'assemblage élémentaire. Deux routines ont été parallélisées:
std::list
; autrement dit, impossible à faire avec OpenMP. J'utilise un “parallel_do
” et un nouvel itérateur customisé qui encapsule les deux itérateurs sur les listes. La mise à jour du minimum de la fonction objectif se fait à l'aide d'un “spin_mutex
”. J'obtiens de très bonnes perfs (speedup de x3.25 avec 4 threads sur ma machine quad-core et même x4.87 avec 8 threads et l'hyperthreading!).parallel_do
ne marche pas bien et j'ai du utiliser un “parallel_for
” (j'ai testé aussi un “parallel_reduce
” qui n'apporte rien mais qui est plus joli).La visu permet de voir l'état du maillage pendant de sa construction. L'affichage est mis à jour tous les 0.3 sec. Évidemment, cet affichage ralentit légèrement le mailleur (le thread graphique n'est pas un thread séparé comme dans Metafor - le mailleur est donc arrêté pendant cette mise à jour de l'affichage). Pour obtenir les meilleures perfs, il faut donc désactiver la visu. Par contre, si on l'utilise pour débuguer, on voit enfin ce qui se passe.
Je me suis attaqué à la parallélisation de Metafor. Le solveur linéaire étant déjà parallèle, il fallait paralléliser le calcul des forces et le calcul de la matrice de raideur tangente. Il restera ensuite la détection du contact pour que tout soit parallèle.
Dans Metafor, le calcul des forces est effectué par la classe StrVector
et plus particulièrement sa fonction membre generalForce
. J'ai modifié la boucle sur les éléments pour qu'elle soit compatible avec TBB. A ce niveau, deux choix étaient possibles:
parallel_for
: la boucle est alors divisée en un nombre de tâches relativement grosses correspondant à l'assemblage de plusieurs éléments. Néanmoins, dans ce cas, il faut avoir accès à un itérateur aléatoire; il n'est donc pas possible d'utiliser l'itérateur ElementIterator
qui permet de “sauter” les éléments cassés. De plus, il y a de fortes probabilités pour que la charge de travail soit mal répartie (certains éléments peuvent se calculer plus vite que d'autres). Enfin, si on veut que ça soit réellement efficace, il faut effectuer un assemblage dans des vecteurs distincts pour chaque thread, suivi d'une “réduction”.parallel_do
: chaque itération de la boucle est une seule tâche (évidemment plus petite que dans le cas précédent). L'intérêt est de pouvoir utiliser l'itérateur ElementIterator
. Par contre, comme chaque tâche est petite, on risque de subir de grosses pertes de performances dans la gestion des tâches en elle-même. L'assemblage est effectué dans une section critique protégée par un mutex.
Actuellement, je me suis orienté vers un parallel_do
mais ce choix n'est peut-être pas définitif.
Au niveau des exceptions qui peuvent se produire pendant le calcul des forces (plasticité non convergée, jacobien négatif, etc.), l'exception est interceptée par TBB et convertie en une tbb::exception
. On perd donc le type de l'exception (du moins, dans la version actuelle - Intel corrigera ça dans les versions futures). En pratique, ce n'est pas trop grave. L'important est de savoir que ça a merdouillé pour récupérer la sauce.
Une fois que la boucle d'assemblage de StrVector
est parallélisée, il faut être certain que les éléments peuvent calculer leurs forces indépendamment les uns des autres. Bien sûr, ca n'a pas marché du premier coup:
PlasticCriterion
, YieldStress
, etc).EvpIsoHHypoMaterial
a.k.a. “stupid material” et ses dizaines de clones - no comment) pour qu'il fonctionne en parallèle (dans le cas sans thermique - pour la thermique, on verra plus tard). Modification au niveau des éléments:
ElementIterator
. L'itérateur se comporte maintenant comme un itérateur de std::vector<Element*>
et non plus std::vector<Element>
Co
(positions nodales), To
(températures nodales) et assimilés. La matrice des masses “par défaut” (inutilisée) a été supprimée.normalAndTangents
de TractionElement
.Modifications au niveau des matériaux:
EvpIsoHHypoMaterial
, PlasticCriterion
(SijE
, deuxG
, normSijE
, res
, sig
, K
, etc.). Toutes ces variables empêchent le calcul de 2 éléments simultanément.Pour le calcul de la raideur, il n'y a pas de problème particulier mis à part dans le cas de la matrice de raideur tangente numérique qui ne peut pas être parallélisée actuellement puisque chaque perturbation est directement effectuée dans la base de données, commune à tous les éléments.
C'est l'objet MatrixStrBase
qui se charge du calcul de la matrice. La boucle a été modifiée selon la syntaxe imposée par Intel TBB, avec un spin_mutex
pour l'assemblage. Le parallel_do
est actuellement désactivé et remplacé par une boucle série qui utilise néanmoins le nouveau contenu de boucle.
EulerianReZoner
est parallélisé avec TBB au lieu d'OpenMP (à titre d'exercice car le gain de perfs est nul, voire négatif).-j 2
). VectorStr
et MatrixStrBase
(ce sera fait dans mon prochain commit).metafor.exe
bouffe tout le CPU). Ca correspond à un spin_mutex
qui spinne dans le vide (un “busy wait”). Si vous observez ce phénomène vous aussi, prévenez moi. Je ne sais pas encore si c'est dû au code où à mes libs. Ce n'est de toutes façons pas reproductible.toolbox.utilities
(saveFac
& convertFac
ne fonctionnaient plus).contactHertzGen4.py
(la valeur utilisée était trop sensible au maillage qui n'était plus le même avec les modifs de Gen4)clifton
pour supprimer la visu qui n'est plus à jour. Autrement dit, plus personne n'a compilé sur clifton depuis que j'ai mis à jour Qt (ca fait un bail).BPointReZoner
pour les tests de la thèse de Roxane: ajout d'une fonction setLineConf
qui permet de déplacer les frontières eulériennes au cours du calcul.arcelor.tests.belval.refroid
(test de C. Bouffioux)— Romain BOMAN 2011/04/05 11:35