Je n'affirme pas que le Hurd apportera plus de performances que ne pourrait l'apporter Unix, ou Linux. D'abord parce que ça dépend de comment le Hurd avancera, et surtout de comment Linux évoluera avec les années. Mais reprenons en deux points, pourquoi je pense que les pertes de performances inhérents au système multi-serveurs à micro-noyau peuvent devenir négligeables, et ouvrir sur quelques optimisations non négligeables que le Hurd peut permettre.
Les problèmes de performances des systèmes multi-serveurs à micro-noyau (c'est à dire, implémentés avec un petit noyau et un grand nombre d'applications par dessus implémentant ce qu'habituellement le noyau implémente) sont liés à la nécessité qu'ont les serveurs de communiquer entre eux. Ces communications entraînent un surcoût, lié principalement à deux choses : elles s'accompagnent souvent de copies de données d'une part, et elles nécessitent des appels systèmes et context switches d'autre part :
1) Imaginons le cas où une tâche A, mettons le driver IDE, récupère des données d'un backing store quelconque (en l'occurrence probablement un disque dur). Elle doit les communiquer à une tâche B (le système de fichiers par exemple). Elle envoie donc un message avec ces données à B. Mais B est en train de faire autre chose : il est certes multithreadé, mais il peut pas aller plus vite que le scheduler ne lui permet. Le driver IDE, lui, a envoyé son message, et continue son boulot. Il récupère d'autres données, pour quelqu'un d'autre. Comme il va pas s'amuser à garder les anciennes en mémoire éternellement, il va réécrire celles d'avant. Et là, pouf : le noyau, qui pour l'instant n'avait rien copié, va être obligé de copier dans sa propre mémoire les premières données pour pouvoir les restituer juste ensuite à B. Copies bien inutiles, liées au caractère asynchrone des messages. Avec L4, l'ensemble des messages sont synchrones. De telles copies sont totalement évitées. Le COW (Copy on Write, cette technique qui permet de ne répliquer les données que quand elles sont modifiées, et de les faire apparaître virtuellement aux deux endroits tant que c'est en lecture seule) est bien plus efficace, l'objectif « zero copy » bien plus facile à réaliser.
2) Le sur-coût lié aux changements de tâches fréquents (tâche A -> noyau -> tâche B, et ce très rapidement et très souvent du fait des très nombreuses communications) est lié à ce qu'on appelle les "context switch". En effet, lorsqu'une tâche est en cours d'exécution, un certain nombre d'informations la concernant (son "contexte") sont chargés dans des registres. Si ce n'est que ça, à la vitesse où vont nos processeurs, aucun soucis, on peut assez rapidement charger/décharger sans voir grand chose. Mais à chaque changement de contexte, on effectue généralement ce qu'on appelle un "TLB flush", qui consiste à vider le Translation Look-Aside Buffer (TLB). Ce TLB est un cache qui garde en mémoire les correspondances "adresse virtuelle" <-> "adresse physique". Et cette étape de conversion "adresse virtuelle" -> "adresse physique" (ce qui arrive quand on a un "TLB miss") est lourde, très lourde. Or, à chaque changement de contexte, on perd tout ce qu'on avait déjà "traduit". Si on multiplie par 10 les changements de contexte, imaginez le temps perdu ! Le TLB deviendrait presque inutile.
Pour éviter cela, certaines architectures disposent d'un TLB taggé, c'est à dire qu'ils associent à chaque paire un identifiant représentant l'espace d'adressage auquel la traduction correspond. Ainsi, le TLB est partagé à beaucoup, mais au moins on ne perd pas son contenu. C'est notamment le cas de certains MIPS (le R6000, crois-je me souvenir). L'idée des développeurs de L4 est d'arriver à faire la même chose sur l'ensemble des architectures. Et il s'avère que celà est faisable sur x86, PowerPC, Sparc, Alpha, au moins. Sur x86, plus de 80% des changements de contexte ne nécessitent plus de TLB flush. Les pertes liées aux changements de contexte sont donc minimes. Et a fortiori encore plus sur les autres, où on a du 100%.
J'évoquais dans un autre journal que les pertes liées à l'exécution de Linux sur L4 plutôt qu'en natif étaient d'environ 5 à 10%. C'est la traduction de la volonté de l'équipe de L4Ka (et de TU-Dresden) de mettre les performances comme une des priorités du développement de L4.
Le titre du cinquième lien de la dépêche contient un terme tout à fait intéressant : « self-paging tasks ». Le concept est assez simple : sous Unix, toutes les tâches se partagent la mémoire physique, chacune en réclamant de la mémoire (par mmap, malloc, ...) à chaque fois qu'elle pense en avoir besoin. Le noyau lui en donne systématiquement, sauf quand il n'en a plus (ENOMEM), ou quand ses quotas sont dépassés, etc. Quand il n'y a plus de mémoire principale (RAM) disponible, le système décide de swapper selon ses envies du moment. La VM étant centralisée dans le noyau, c'est lui qui décidera unilatéralement qui reste et qui s'en va, selon ses propres critères, parfois intelligents, souvent moins. Quand il n'y a plus de mémoire disponible du tout (RAM + swap épuisées), le système tue généralement les prochaines tâches qui essayent d'allouer de la mémoire (il est vraisemblable qu'elles soient responsables du problème, et de toutes façons elles ne marcheraient probablement plus si on leur disait ENOMEM), ou une tâche ciblée (principe de l'OOM Killer sous Linux), ou le système crash (principe du zalloc panic sous GNU Mach. ;-))
Pour le port du Hurd sur L4, l'idée est d'allouer à chaque tâche une portion de mémoire physique au démarrage. Elle gère ensuite cette mémoire comme bon lui semble : elle décide de passer des pages en swap quand elle pense que c'est nécessaire (soit parce qu'il n'y a plus d'espace disponible, soit parce qu'elle a de bonnes raisons classées secret défense), d'aller rechercher telle ou telle page elle-même, etc., etc. Évidemment, par défaut, chaque tâche dispose d'un gestionnaire de mémoire (libhurd-mm) qui lui est "greffé" au démarrage. Mais chaque application peut avoir son propre gestionnaire de mémoires, plus intelligent, plus adapté à son cas. Et Dieu sait que dans le cas du multimédia, c'est quelque chose de fondamental, et un problème récurrent qui explique que beaucoup de matériel spécialisé utilise des systèmes d'exploitations particuliers (par exemple, http://www.cl.cam.ac.uk/Research/SRG/netos/old-projects/nemesis(...) : Nemesis).
La chose devient encore plus intéressante quand on observe le cas où une tâche estime avoir besoin de plus de mémoire physique qu'elle n'en a déjà, ou quand le système se retrouve en danger de manque de mémoire physique. L'idée là est qu'une opération de re-négociation est lancée. Cette négociation implique un gestionnaire de QoS (l'intendant du système) et la tâche elle-même (son thread gestionnaire de mémoire, bien entendu). Les deux se lancent dans une opération de négociation régie par les règles décidées par le système (qui n'ont pas été fixées). Un modèle simple et efficace est le modèle du marché : chaque tâche dispose d'une certaine somme au démarrage (somme calculée en fonction de la priorité (nice), des quotas, de ce que l'administrateur a décidé, et -surtout- de la mémoire déjà allouée et de l'utilisation CPU déjà faite). Pour cette somme, elle reçoit tant de temps CPU, et tant de mémoire physique. Selon la rareté des ressources (l'offre) et les demandes des tâches, les prix varient. Ainsi, une tâche qui sait n'avoir besoin que très peu de mémoire pendant quelques minutes peut décider au début de l'opération de vendre une partie de sa mémoire, et avec l'argent reçu, de racheter du temps CPU supplémentaire. Un tel modèle économique a déjà été implémenté : il a apporté plus de 40% de performances dans certains cas, pour des bases de données particulièrement (qui ont une utilisation très spécifique de la mémoire et du temps CPU - un SGDB, c'est un peu un cache géant qui s'emballe de temps en temps ;-)).
[NB: les économistes, en herbe ou non, remarqueront que si le marché marche aussi bien dans ce cas, c'est parce que tout passe par le commissaire-QoS manager. Le Hurd, dernier hériter du GOSPLAN ? ;-)]
Toutes ces choses, une gestion des ressources totalement centralisée (scheduler et VM uniques dans le noyau) ne le permet en aucun cas. Au mieux, elle permet de lui spécifier des "conseils" (hints) d'allocation mémoire, comme celà a existé sous Solaris ou VMS. Et je pense que c'est qu'un exemple de ce que le Hurd permet, et qu'avec les années et le développement, on se rendra compte plus clairement de la magie de la chose.
[^] # Re: Que m'apporte le hurd ?
Posté par Manuel Menal . En réponse à la dépêche Hurd : nouvelle version de Debian GNU/Hurd et avancée du port sur L4. Évalué à 10.
Les problèmes de performances des systèmes multi-serveurs à micro-noyau (c'est à dire, implémentés avec un petit noyau et un grand nombre d'applications par dessus implémentant ce qu'habituellement le noyau implémente) sont liés à la nécessité qu'ont les serveurs de communiquer entre eux. Ces communications entraînent un surcoût, lié principalement à deux choses : elles s'accompagnent souvent de copies de données d'une part, et elles nécessitent des appels systèmes et context switches d'autre part :
1) Imaginons le cas où une tâche A, mettons le driver IDE, récupère des données d'un backing store quelconque (en l'occurrence probablement un disque dur). Elle doit les communiquer à une tâche B (le système de fichiers par exemple). Elle envoie donc un message avec ces données à B. Mais B est en train de faire autre chose : il est certes multithreadé, mais il peut pas aller plus vite que le scheduler ne lui permet. Le driver IDE, lui, a envoyé son message, et continue son boulot. Il récupère d'autres données, pour quelqu'un d'autre. Comme il va pas s'amuser à garder les anciennes en mémoire éternellement, il va réécrire celles d'avant. Et là, pouf : le noyau, qui pour l'instant n'avait rien copié, va être obligé de copier dans sa propre mémoire les premières données pour pouvoir les restituer juste ensuite à B. Copies bien inutiles, liées au caractère asynchrone des messages. Avec L4, l'ensemble des messages sont synchrones. De telles copies sont totalement évitées. Le COW (Copy on Write, cette technique qui permet de ne répliquer les données que quand elles sont modifiées, et de les faire apparaître virtuellement aux deux endroits tant que c'est en lecture seule) est bien plus efficace, l'objectif « zero copy » bien plus facile à réaliser.
2) Le sur-coût lié aux changements de tâches fréquents (tâche A -> noyau -> tâche B, et ce très rapidement et très souvent du fait des très nombreuses communications) est lié à ce qu'on appelle les "context switch". En effet, lorsqu'une tâche est en cours d'exécution, un certain nombre d'informations la concernant (son "contexte") sont chargés dans des registres. Si ce n'est que ça, à la vitesse où vont nos processeurs, aucun soucis, on peut assez rapidement charger/décharger sans voir grand chose. Mais à chaque changement de contexte, on effectue généralement ce qu'on appelle un "TLB flush", qui consiste à vider le Translation Look-Aside Buffer (TLB). Ce TLB est un cache qui garde en mémoire les correspondances "adresse virtuelle" <-> "adresse physique". Et cette étape de conversion "adresse virtuelle" -> "adresse physique" (ce qui arrive quand on a un "TLB miss") est lourde, très lourde. Or, à chaque changement de contexte, on perd tout ce qu'on avait déjà "traduit". Si on multiplie par 10 les changements de contexte, imaginez le temps perdu ! Le TLB deviendrait presque inutile.
Pour éviter cela, certaines architectures disposent d'un TLB taggé, c'est à dire qu'ils associent à chaque paire un identifiant représentant l'espace d'adressage auquel la traduction correspond. Ainsi, le TLB est partagé à beaucoup, mais au moins on ne perd pas son contenu. C'est notamment le cas de certains MIPS (le R6000, crois-je me souvenir). L'idée des développeurs de L4 est d'arriver à faire la même chose sur l'ensemble des architectures. Et il s'avère que celà est faisable sur x86, PowerPC, Sparc, Alpha, au moins. Sur x86, plus de 80% des changements de contexte ne nécessitent plus de TLB flush. Les pertes liées aux changements de contexte sont donc minimes. Et a fortiori encore plus sur les autres, où on a du 100%.
J'évoquais dans un autre journal que les pertes liées à l'exécution de Linux sur L4 plutôt qu'en natif étaient d'environ 5 à 10%. C'est la traduction de la volonté de l'équipe de L4Ka (et de TU-Dresden) de mettre les performances comme une des priorités du développement de L4.
Le titre du cinquième lien de la dépêche contient un terme tout à fait intéressant : « self-paging tasks ». Le concept est assez simple : sous Unix, toutes les tâches se partagent la mémoire physique, chacune en réclamant de la mémoire (par mmap, malloc, ...) à chaque fois qu'elle pense en avoir besoin. Le noyau lui en donne systématiquement, sauf quand il n'en a plus (ENOMEM), ou quand ses quotas sont dépassés, etc. Quand il n'y a plus de mémoire principale (RAM) disponible, le système décide de swapper selon ses envies du moment. La VM étant centralisée dans le noyau, c'est lui qui décidera unilatéralement qui reste et qui s'en va, selon ses propres critères, parfois intelligents, souvent moins. Quand il n'y a plus de mémoire disponible du tout (RAM + swap épuisées), le système tue généralement les prochaines tâches qui essayent d'allouer de la mémoire (il est vraisemblable qu'elles soient responsables du problème, et de toutes façons elles ne marcheraient probablement plus si on leur disait ENOMEM), ou une tâche ciblée (principe de l'OOM Killer sous Linux), ou le système crash (principe du zalloc panic sous GNU Mach. ;-))
Pour le port du Hurd sur L4, l'idée est d'allouer à chaque tâche une portion de mémoire physique au démarrage. Elle gère ensuite cette mémoire comme bon lui semble : elle décide de passer des pages en swap quand elle pense que c'est nécessaire (soit parce qu'il n'y a plus d'espace disponible, soit parce qu'elle a de bonnes raisons classées secret défense), d'aller rechercher telle ou telle page elle-même, etc., etc. Évidemment, par défaut, chaque tâche dispose d'un gestionnaire de mémoire (libhurd-mm) qui lui est "greffé" au démarrage. Mais chaque application peut avoir son propre gestionnaire de mémoires, plus intelligent, plus adapté à son cas. Et Dieu sait que dans le cas du multimédia, c'est quelque chose de fondamental, et un problème récurrent qui explique que beaucoup de matériel spécialisé utilise des systèmes d'exploitations particuliers (par exemple, http://www.cl.cam.ac.uk/Research/SRG/netos/old-projects/nemesis(...) : Nemesis).
La chose devient encore plus intéressante quand on observe le cas où une tâche estime avoir besoin de plus de mémoire physique qu'elle n'en a déjà, ou quand le système se retrouve en danger de manque de mémoire physique. L'idée là est qu'une opération de re-négociation est lancée. Cette négociation implique un gestionnaire de QoS (l'intendant du système) et la tâche elle-même (son thread gestionnaire de mémoire, bien entendu). Les deux se lancent dans une opération de négociation régie par les règles décidées par le système (qui n'ont pas été fixées). Un modèle simple et efficace est le modèle du marché : chaque tâche dispose d'une certaine somme au démarrage (somme calculée en fonction de la priorité (nice), des quotas, de ce que l'administrateur a décidé, et -surtout- de la mémoire déjà allouée et de l'utilisation CPU déjà faite). Pour cette somme, elle reçoit tant de temps CPU, et tant de mémoire physique. Selon la rareté des ressources (l'offre) et les demandes des tâches, les prix varient. Ainsi, une tâche qui sait n'avoir besoin que très peu de mémoire pendant quelques minutes peut décider au début de l'opération de vendre une partie de sa mémoire, et avec l'argent reçu, de racheter du temps CPU supplémentaire. Un tel modèle économique a déjà été implémenté : il a apporté plus de 40% de performances dans certains cas, pour des bases de données particulièrement (qui ont une utilisation très spécifique de la mémoire et du temps CPU - un SGDB, c'est un peu un cache géant qui s'emballe de temps en temps ;-)).
[NB: les économistes, en herbe ou non, remarqueront que si le marché marche aussi bien dans ce cas, c'est parce que tout passe par le commissaire-QoS manager. Le Hurd, dernier hériter du GOSPLAN ? ;-)]
Toutes ces choses, une gestion des ressources totalement centralisée (scheduler et VM uniques dans le noyau) ne le permet en aucun cas. Au mieux, elle permet de lui spécifier des "conseils" (hints) d'allocation mémoire, comme celà a existé sous Solaris ou VMS. Et je pense que c'est qu'un exemple de ce que le Hurd permet, et qu'avec les années et le développement, on se rendra compte plus clairement de la magie de la chose.