Commentaires de réalisation

Localisation des variables globales

  • Une structure globale par thread (existante dans la lib de threads originale réutilisée et augmentée)
  • Depuis le C on accède à la structure du thread courant avec les fonctions POSIX. Lorsqu'on passe du C à l'assembleur (à la naissance du thread ainsi que lors des callbacks) on passe en paramètre la structure locale à l'assembleur. L'assembleur utilise ensuite le registre r14 pour accéder à tout moment par une simple indirection aux données locales.
  • r14 était précédemment utilisé comme cache du pointeur d'exception, celui-ci est placé dans la structure locale au thread
  • Tout amd64.S a été réécrit pour utiliser directement les données locales au thread courant à la place des globales.

Algo de GC

Le GC utilise deux structures distinctes :

  • un ensemble de pages (une page n'est accessible que par un seul thread)
  • un tas partagé accessible à tous les threads

Chaque thread réserve une page pour y allouer ses petits objets. La réservation de page est protégée par un verrou (mais l'accès et la modification du contenu des pages ne l'est pas). Ainsi la plupart du temps, l'allocation d'un petit objet est aussi performante que dans l'implémentation actuelle d'OCaml mais les threads peuvent allouer des petits objets en parallèle. Les gros objets sont alloués directement dans le tas partagé, qui est protégé en écriture par un verrou.

Une page neuve est allouée à un thread lorsqu'une allocation échoue et qu'il reste des pages libres, ainsi qu'à sa naissance et après un GC.

S'il ne reste plus de page libres, on procède à un premier type de GC “partiel” : le tas partagé sert de zone de destination à un Stop & Copy (on vide les pages de tous les threads dans le tas partagé).

Lorsque le tas partagé est plein, on procède au gc “complet” : on utilise Stop & Copy sur l'ensemble tas partagé + pages et on réalloue éventuellement un tas partagé plus gros (on ne dispose pas encore de réduction du tas).

Synchro du GC

  • Stop The World : Dans le cas général, on stoppe tous les threads à l'allocation. Lorsqu'une allocation échoue, le thread qui a échoué lance le GC, il demande alors aux autres de s'arrêter. lors des allocations suivantes des autres threads, si un GC est demandé, ils se mettent en pause (implémentation par conditions POSIX). Le cycle de GC commence lorsque tous les threads sont en pause. Une fois le GC terminé, tout repart.
  • Une primitive stop_the_world_wait attends la fin du GC en cours pour exécuter éventuellement une callback. Ceci permet, en plus de la pause des threads pendant le gc, d'implémenter proprement la mort des threads et les primitives de la lib de thread.
  • Un verrou global est utilisé pour mettre en exclusion mutuelle les parties modifiant les structures de gestion mémoire (exécution d'un GC, naissance du thread, mise en pause par les primitives de la lib de thread sans attendre l'allocation, etc.). On pourrait améliorer la granularité.

Génération de code

  • Ajout du -compact dans le startup.s : Le -compact permet de supprimmer les allocations inline mais l'option n'est pas prise en compte lors de la génération du startup.s (durant la phase de link). Nous avons juste fait en sorte que ce soit le cas, éventuellement on pourrait ajouter une option -supercompact.
  • Modification du emit.ml : Puisqu'on modifie l'utilisation du registre r14 dans l'amd64.S, on fait de même dans le code émit : on remplace simplement les accès à r14 par des accès indirects à r14.exception_pointer. La modification est très locale et on pourrait en faire une option du compilateur facilement.

Miscellaneous facts

  • L'ensemble des primitives de la lib de thread est implémenté.
  • Le module Gc change, les Weaks ne sont pas (encore) implémentés.
  • La version actuelle est sans bug connu mais il reste une bonne phase de nettoyage et de refactorisation.
 
implem.txt · Last modified: 2009/07/03 19:38 by philippe