• Aucun résultat trouvé

elle-même. Ceci du fait des politiques de pagination paresseuses utilisées par les OS mo- dernes.

Compromis économie/performances : Nous discuterons la problématique de choix entre une

politique d’économie mémoire et d’orientation vers la performance. Nous discuterons éga- lement la possibilité de rendre ce choix dynamique afin de s’adapter aux différentes phases des programmes.

Segments utilisateurs : Nous décrirons la méthode annexe retenue par notre allocateur afin

d’inclure nativement la gestion de segments spécialement préparés par l’utilisateur. Cette intégration devra, si possible, être intégrée de manière compatible avec les routines d’al- location standards.

4.3

Aspects génériques des allocateurs

Au-delà des caractéristiques listées ci-dessus, un allocateur mémoire doit répondre à cer- taines contraintes techniques nécessaires à leur bon fonctionnement. Nous discutons ici les quatre points centraux dans la construction d’un allocateur mémoire : la gestion des blocs libres, la fusion et scission de blocs, les alignements et le placement des métadonnées.

4.3.1 Gestion des blocs libres

Un allocateur mémoire remplit essentiellement trois fonctions internes : 1. Demander et rendre de la mémoire au système d’exploitation sous-jacent. 2. Maintenir un suivi des blocs libres non rendus à l’OS pour réutilisation futur. 3. Trouver le meilleur choix de réutilisation parmi les blocs libres disponibles.

Au titre des points 2 et 3, l’allocateur consiste essentiellement à fournir une liste de blocs libres. L’organisation de cette dernière vise alors à optimiser la capacité à trouver rapidement un bloc réutilisable en réponse à une requête de l’utilisateur. La méthode retenue doit égale- ment minimiser les problèmes de fragmentation afin de limiter la consommation mémoire. On distingue habituellement les algorithmes suivants pour cela :

Meilleur ajustement (best-fit) : L’algorithme cherche le bloc libre ayant la taille la plus proche

de la requête. Appliquée de manière stricte cette méthode peut nécessiter un parcours de l’ensemble des blocs libres. Il est généralement appliqué de manière partielle en donnant une borne sur le nombre d’éléments parcouru.

Premier suffisant (first-fit) : L’algorithme retient le premier élément de taille suffisante dans

la liste parcourue. Cette méthode est l’une des plus employées. Certaines études montrent d’ailleurs qu’elle peut tendre vers l’efficacité de l’algorithme best-fit[WJNB95]. Pour ce type d’algorithme, on a le choix d’ordre de la liste de sorte à favoriser la réutilisation des blocs récents (LIFO : Last In, Last Out) ou plus anciens (FIFO : First In, First Out). Une alternative intéressante peut également consister à trier les éléments par adresse de sorte à favoriser la localité spatiale.

Prochain suffisant (next-fit) : Simple évolution de l’algorithme first-fit, ce mode se rappel de

la dernière position dans la liste et reprend la recherche à cette position. Cette approche tend toutefois à générer plus de fragmentation et une moins bonne localité du fait de l’étalement des blocs dans l’espace d’adressage.

Plus grand disponible (worst-fit) : A l’opposé de l’algorithme best-fit, l’algorithme cherche le

bloc le plus grand disponible, ceci afin de maintenir des résidus de taille suffisante lors des scissions.

Une amélioration importante peut s’obtenir en construisant des listes distinctes (ségrégation) pour différentes tailles de blocs. On obtient alors un algorithme plus proche d’un algorithme de type best-fit sans avoir le surcoût d’un parcours complet. L’allocateur peut alors fonctionner en imposant des tailles précises, impliquant toutefois une augmentation de la fragmentation interne, ou bien, préférer travailler par classes de tailles approximatives.

4.3.2 Fusion et scission de blocs

Au-delà de cette sélection d’éléments libres, il importe de remarquer qu’un bloc trop grand peut être scindé pour satisfaire une requête plus petite. De la même manière, deux blocs libres voisins peuvent être fusionnés pour donner un bloc plus grand. Sur ce point, les allocateurs peuvent décider d’appliquer les fusions et scissions de manière immédiate, différée ou de ne pas en effectuer. L’approche immédiate permet d’ajuster au plus près la consommation mémoire du programme, mais induit un surcoût si les blocs fusionnés doivent être à nouveau scindés pour répondre aux requêtes suivantes. De nombreux allocateurs introduisent donc un cache et main- tiennent ainsi un certain nombre de blocs non fusionnés accessibles rapidement[Eva06,SG].

Les fusions de blocs nécessitent la connaissance de taille des blocs voisins. Ce critère impor- tant doit être pris en compte dans la construction des métadonnées de l’allocateur de manière à permettre une implémentation efficace de ces actions. À ce titre, dans le cadre d’une approche sur base de liste chaînée, l’utilisation d’algorithme first-fit avec un tri par adresse peut permettre de mettre en commun les métadonnées de fusion et recherche de blocs libres.

Une autre approche introduite par Knuth[Kno65,PN77] dite buddy allocator consiste à n’au- toriser qu’un nombre réduit de tailles construites sur la base d’une suite numérique. Cette struc- ture permet des fusions et scissions rapides. On utilise habituellement des puissances de deux (binary buddies) permettant des calculs rapides sur la base d’opérations binaires. Il est aussi pos- sible d’utiliser des suites plus complexes (Fibonnacci...). Cette approche permet des algorithmes performants et réduit la fragmentation externe. Il augmente toutefois significativement la frag- mentation interne pour les grandes tailles.

La méthode extrême consiste à mettre en place une ségrégation au niveau du stockage lui- même en interdisant le mélange de blocs de différentes tailles. Différentes régions sont alors mises en place pour générer un nombre prédéterminé de tailles de blocs. Dans ce cas de figure, l’allocateur n’effectue que les fusions et découpages par grands ensembles. Cette approche est retenue dans un certain nombre d’allocateurs récents, dont les trois principaux que nous allons étudier.

4.3.3 Contraintes d’alignements

Sur les architectures x86 (et de nombreuses autres), tout élément doit être aligné sur sa propre taille jusqu’à 8 octets. Un allocateur permettant le voisinage de blocs de tailles diffé- rentes doit donc allouer des blocs d’un minium de 8 octets afin d’assurer que le voisin suivant

En-tête 16o Données 4o Données 8o Padding 4o (a) (b) 0x0000

FIGURE4.1 – Illustration du remplissage rendue obligatoire par la règle d’alignement mémoire. On