• Aucun résultat trouvé

Gestion mémoire spécifique

Dans le document Université de Reims Champagne-Ardenne (Page 98-101)

Lors de l’optimisation mémoire d’une application séquentielle, les variables souvent accédées par l’algorithme sont regroupées en mémoire afin de minimiser le nombre de chargements depuis la mémoire centrale vers la mémoire cache du micro-processeur.

Dans un contexte multi-cœurs, il en va tout autrement. En effet, soit d une donnée chargée dans la mémoire cache du cœur Ci. Si d est modifiée par le cœur de calcul Cj (j 6=i), alors la mémoire de Ci devra être mise à jour sans savoir si Ci accédera à nouveau àd. Ce phénomène a été mis en lumière dans [JK07], vous y trouverez plus de détails que ceux donnés ici.

5.3.1 Généralités

mtss a donc une gestion mémoire qui n’est pas laissée au hasard. Au lieu de per-mettre au système de placer les variables en mémoire centrale, après avoir lu le fichier du problème à résoudre, mtss détermine quelle quantité de mémoire sera nécessaire pour :

– Objets partagés en lecture seule – Objets partagés en lecture et écriture – Objets privés pour chaque processus

mtss alloue des blocs mémoire assez grands pour contenir tous les objets d’un même ensemble et fait en sorte que ces ensembles ne se chevauchent pas sur la même page mémoire en allouant un peu d’espace supplémentaire et en alignant le début des variables sur une page mémoire. Il faut savoir que la mémoire des ordinateurs est paginée, c’est-à-dire que l’on n’y accède pas octet par octet mais page par page (le nombre d’octets enregistrés par page dépend du matériel utilisé). Donc, si un octet o est requis par un cœur de calcul Ci et que o n’est pas dans sa mémoire cache, alors

Gestion mémoire spécifique 99 le système charge l’ensemble de la page contenanto dans la mémoire cache de Ci. Ce mécanisme est appelé un défaut de cache.

Pour chaque mémoire privée d’un processus, mtss réitère le principe de cloison-nement pour éviter que les variables privées de Ci ne soient sur une page mémoire contenant des données privées deCj.

Figure5.1 – Cloisonnement des types de mémoire

Cette gestion particulière est mise en place car nous souhaitons travailler sur des architectures de type CC-UMA voire CC-NUMA. Le fait que ces architectures soient Cache Coherentfacilite la programmation mais provoque quelques soucis au niveau des défauts de cache, en particulier pour les problèmes combinatoires tels que sat. D’un point de vue calcul pur,sat n’est pas difficile à gérer pour les processeurs, la difficulté est le nombre gigantesque d’accès aléatoires en mémoire. Même si un défaut de cache ne représente finalement pas un long temps d’attente pour les processeurs, ce temps perdu multiplié par le très grand nombre d’accès en mémoire a de lourds impacts sur les performances globales. Un test a été réalisé (section 6.3) dans le but de vérifier cette affirmation.

5.3.2 L’arbre de guidage

L’arbre de guidage est parcouru par les pauvres pour détecter des nœuds pendants dans l’arbre et y calculer des tâches ou le nœud suivant. La partie à l’extrême gauche de cet arbre est développée par le riche. Tous développent simultanément le même arbre, mais chaque nœud est développé par un seul processus. Autrement dit, l’arbre dans sa globalité est un objet parallèle, mais chaque nœud durant son calcul doit être privé pour minimiser les défauts de cache engendrés par la cohérence mémoire. C’est pourquoi chaque nœud de l’arbre dansmtssest isolé des autres. Aucune page mémoire ne contient d’information portant sur plusieurs nœuds. Les pauvres doivent pouvoir se positionner sur un nœud pour en calculer le suivant, mais si il fallait remettre à jour toutes les structures, ce serait trop coûteux en temps. Dansmtss, chaque nœud

de l’arbre est capable de conserver une trace de l’instanciation courante pour que les pauvres ainsi que le riche puissent se mettre à jour en temps constant (contrairement au principe du chemin de guidage qui impose de simplifier la formule avec le chemin reçu).

Pour le riche, cette mise à jour n’est faite que lorsqu’il effectue un retour arrière dans l’arbre après avoir trouvé une branche insatisfaisable et qu’un sous-arbre de guidage a été commencé au niveau du nœudisur lequel il devait remonter. À cet instant, le riche effectue un parcours main gauche du sous-arbre Ai enraciné à la droite de i et copie dans son contexte privé les informations contenues au niveau du nœud j, ce dernier étant le nœud pendant le plus à gauche du sous-arbre Ai. Quant aux pauvres, deux cas s’offrent à eux. Soit ils commencent un sous-arbre de guidage, alors ils appliquent le principe du chemin de guidage sur le chemin du riche. Ils mettent à jour la formule de départ en fonction des choix opérés par le riche puisque le riche n’a pas pris le temps de déposer un contexte dans l’arbre (nous souhaitons que le riche soit le moins concerné possible par le surcoût engendré par le parallélisme). Soit ils travaillent dans un sous-arbre de guidage, et par conséquent, un pauvre a déjà laissé un contexte de calcul dans les nœuds. Dans ce cas, ils commencent toujours leur travail en copiant dans leur contexte privé celui contenu dans le nœud i précédent celui qu’ils doivent calculer. Une fois le travail terminé, ils déposent le nouveau contexte dans le nœud j ajouté à droite ou à gauche deidans l’arbre de guidage.

L’arbre de guidage est donc une structure assez lourde en mémoire. Pour atténuer son importance, le nombre de nœuds développables simultanément par les pauvres est limité empiriquement à 80% du nombre de variables de la formule multiplié par le nombre de pauvres demandés. En réalité, en mémoire centrale, l’arbre de guidage n’est pas un simple tableau, mais une réserve de nœuds pointant vers son père et ses fils.

Il n’est pas rare de rencontrer un sous-arbre de guidage qui ne contienne pas de solution. Dans ce cas, toutes les feuilles ont conduit à un conflit. Le but des processus pauvres est de donner le maximum d’information au processus riche, il serait donc pénalisant que le processus riche ait à explorer entièrement un sous-arbre de guidage pour finalement découvrir que toutes les feuilles sont déjà calculées. Il est plus efficace de mettre l’information le plus tôt possible dans l’arbre. Dans le cas d’un sous-arbre sans solution, l’idéal est de faire remonter l’information jusqu’au nœud appartenant au chemin du riche. Il y a ici un double avantage : le processus riche n’explore pas un arbre en vain et la consommation mémoire est réduite puisque les nœuds du sous-arbre en question seront libérés pour d’autres calculs.mtssgère unbacktrack pouvant être effectué par plusieurs processus à la fois grâce à la gestion d’un verrou spécifique à chaque nœud.

À l’image du chemin de guidage, l’arbre de guidage distribue le travail. Toutefois, l’équilibrage de charge est naturellement géré dans un arbre de guidage, il est inutile de recourir à une politique particulière. Le riche n’a pas besoin d’être équilibré puisque son comportement est celui d’un solveur séquentiel. Les processus pauvres, tant qu’ils sont utilisés pour calculer des tâches parallèles à grain fin sont naturellement équilibrés puisqu’ils travaillent sur des éléments très petits d’un arbre de recherche, il est inutile de devoir stopper certaines tâches pour les subdiviser.

Synchronisation 101

Dans le document Université de Reims Champagne-Ardenne (Page 98-101)