Photographie de Thierry FournierInterview de Thierry Fournier, auteur du livre "Go avancé – Au cœur du runtime : architecture, concurrence et optimisations [13/09/2026].

Retrouvez-le sur son site ou son compte linkedIn.

Bonjour Thierry. Pour commencer, pourriez-vous dire quelques mots sur vous, quel est votre profil ?

Je suis passionné par la conception de systèmes performants et utiles. Je travaille sur toute la chaîne, du code jusqu'au système d'exploitation et au réseau, avec une pratique de la sécurité, de la résilience et de la répartition de charge.

Depuis plus de vingt ans, je développe et j'exploite des systèmes critiques, en C et en Go. Ma première expérience professionnelle, à la Société Générale, a porté sur l'infrastructure d'hébergement, puis sur le reverse proxy frontal qui assurait la sécurité de la banque en ligne. Chez HAProxy Technologies, j'ai participé à la conception de l'appliance embarquée de load balancing et contribué au développement de HAProxy. Chez OZON, j'ai développé un WAF en C et des outils d'agrégation de données temps réel en Go. Depuis deux ans, j'assure le lead technique d'une équipe de développement Go, avec une attention particulière portée aux performances et à la mise en production.

Cofonder OZON avec des moyens limités m'a appris à faire des choix techniques cohérents avec les enjeux business : décider ce qui doit être solide tout de suite et ce qui peut attendre, choisir la dette technique acceptable, distinguer l'optimisation qui fait économiser des serveurs de celle qui ne fait que consommer du temps de développement. J'en ai retenu qu'une optimisation n'a de valeur que si elle répond à un besoin.

Vous venez d'écrire un livre assez pointu dédié aux mécanismes internes du langage Go. Plus de 600 pages sur le fonctionnement du runtime, comment avez-vous géré un projet éditorial aussi dense ?

Au départ, il y a de la curiosité et le besoin de comprendre. Go occupe une place particulière parmi les langages disponibles : il compile en code natif, comme C, mais embarque un runtime qui gère la mémoire et la concurrence, comme le ferait une machine virtuelle. Certains de ses comportements ne sont pas évidents. J'ai commencé à creuser des questions précises, comme le coût réel d'une closure ou la façon dont une map se traduit en code machine, et j'ai publié mes résultats dans des articles de blog. J'ai ensuite constaté que chaque réponse renvoyait à un autre mécanisme : l'allocateur, le GC, le scheduler. L'idée d'un ouvrage complet est venue de là.

Pour la conduite du projet, j'ai travaillé chapitre par chapitre : huit mois d'écriture, puis sept mois de relecture. J'écris en Markdown, et j'ai développé un outil qui convertit le texte en LaTeX et produit un PDF au format du livre final. Il me permettait de mesurer la taille réelle de l'ouvrage et de vérifier la mise en page au fur et à mesure. Voir le livre prendre forme aide aussi à tenir sur la durée.

La relecture a pris presque autant de temps que l'écriture, et l'éditeur, D-BookeR, y a eu un rôle central. Au-delà de conseils de rédaction précieux, il a relevé chaque passage qui n'était pas clair, mal organisé, répétitif ou contradictoire.

Quelles sont les principales difficultés que vous avez rencontrées dans sa réalisation ? Y a-t-il des points qui vous ont particulièrement posé problème ?

La difficulté la plus visible, c'est que le sujet évolue pendant qu'on écrit. Go 1.26 a activé par défaut Green Tea, une nouvelle façon de conduire la phase de marquage du garbage collector : les petits objets sont traités par page plutôt qu'un par un, pour améliorer la localité mémoire. Le chapitre sur le GC était déjà écrit, et il a fallu le reprendre pour intégrer ce changement.

Vient ensuite la mesure. Un benchmark mal écrit mesure autre chose que ce qu'on croit : le compilateur supprime du code dont le résultat n'est pas utilisé, la machine a du bruit, et le résultat change d'un processeur à l'autre. Je privilégie donc toujours les comparaisons : deux variantes mesurées sur la même machine, où c'est l'écart qui compte et non la valeur absolue. C'est ce qui a permis de changer de machine en cours de projet, d'un Mac à processeur Intel à un Mac équipé d'une puce Apple M4 Pro, sans que cela remette en cause les mesures déjà faites.

Restait à savoir où s'arrêter, et donc quoi inclure. Ma liste de sujets initiale était bien plus longue que le sommaire final. En voyant le livre grossir, j'ai dû trier et renoncer à des sujets entiers. Le critère était l'objectif du livre : il ne s'agit pas de former des développeurs du compilateur, mais de donner les éléments pour prendre des décisions.

Comment avez-vous vérifié vos affirmations ? Sur quoi se fondent vos préconisations ?

Sur les sources primaires. Les descriptions du runtime et du compilateur s'appuient sur la documentation de go.dev et sur le code source du dépôt github.com/golang/go. Quand je décris une structure interne, elle vient du code, pas d'un article de seconde main.

L'outillage fourni avec Go permet d'observer directement les choix du compilateur : ce qui est alloué sur le heap, ce qui est inliné. Quand un comportement peut être mesuré ou mis en évidence, un benchmark ou une démonstration l'accompagne, avec un code en annexe que le lecteur peut rejouer sur sa machine et sa version de Go.

« Désassembler pour voir
ce qui est réellement exécuté. »

J'ai fait six ans d'études en électronique, et l'assembleur a été mon premier langage. Chez HAProxy Technologies, chaque modification du cœur de HAProxy donnait lieu à un désassemblage pour vérifier que l'assembleur généré était optimal : au million de requêtes par seconde, chaque instruction compte. J'en ai retenu un réflexe que j'applique à Go : désassembler pour voir ce qui est réellement exécuté.

Les préconisations découlent de ces mécanismes, mais aussi de mon expérience professionnelle. Je privilégie le pragmatisme : une solution simple qui répond au besoin plutôt qu'une construction pensée pour des cas qui n'arriveront pas. Quand un choix dépend du contexte, je le dis plutôt que d'en faire une règle. Quand un choix de conception de Go me paraît discutable, je le dis aussi, comme c'est le cas de la variable déclarée dans l'init d'une boucle for. La syntaxe reste ambiguë, et plutôt que de lever cette ambiguïté, les concepteurs ont changé le comportement en Go 1.22 pour adopter ce que la majorité comprenait. Cela supprime beaucoup de bugs, mais un langage qui s'aligne sur la lecture majoritaire laisse les autres se tromper.

Go est généralement apprécié pour sa simplicité et ses performances. Pourquoi encourager ses utilisateurs à s'intéresser au runtime ?

Parce qu'un langage simple à écrire cache généralement une complexité à l'exécution, et Go n'échappe pas à la règle. Son compilateur et son runtime prennent des décisions à la place du développeur : si une variable va sur la stack ou sur le heap, combien de threads utiliser, quand lancer le garbage collector, quand préempter une goroutine. Tant que ces décisions conviennent, on n'a pas besoin de les connaître.

Quand elles ne conviennent plus, les symptômes sont indirects : une latence qui augmente sous charge, une mémoire qui ne redescend pas après un pic, du CPU consommé par le GC. Sans modèle du runtime, on essaie des réglages au hasard. Avec, on sait quelle métrique regarder et quels leviers existent.

Plus simplement, le runtime est le matériau du développeur Go, et connaître son matériau permet de travailler mieux et plus efficacement.

« Le runtime est le matériau
du développeur Go »

Avez-vous un exemple personnel d'un bug ou d'un problème de performance que vous avez résolu grâce à cette compréhension du runtime ?

Je me rappelle d'un service dont la consommation mémoire montait trop. J'ai commencé par régler le GC pour qu'il se déclenche plus tôt : cela contenait suffisamment la mémoire pour éviter l'OOM kill, au prix d'un peu de CPU, le temps de comprendre la cause. La cause, c'était quelques structures à la durée de vie éphémère, mais très manipulées au cœur du logiciel, qui vivaient sur le heap et circulaient par pointeur. Les repasser sur la stack, avec des copies, a supprimé le problème : une copie coûte moins cher qu'une allocation sur le heap suivie du travail du GC. Le réglage du GC est alors redevenu inutile.

C'est un cas où les deux niveaux servent : le réglage pour tenir en attendant, et l'escape analysis pour traiter la cause.

On pourrait penser que cette connaissance ne sert qu'aux systèmes critiques ou très sollicités. Est-ce le cas, et si non, pouvez-vous donner un exemple du quotidien où elle fait une vraie différence ?

En fait, non. Quand on connaît le runtime et le comportement du compilateur, on se méfie plus facilement de certaines formes de code. On reconnaît des schémas, on connaît les limites de certaines techniques, et cela vaut pour n'importe quel programme.

Une concaténation de chaînes dans une boucle a par exemple une limite d'efficacité, du fait des contraintes du type string. Une variable capturée par une closure pour être modifiée, et qui survit à la fonction appelante, passe de la pile au tas et est accédée comme un pointeur, sans que la syntaxe l'indique. Une slice passée à une fonction ne protège pas les données : c'est lié au fonctionnement fondamental des slices. Un switch à dix cas peut être plus rapide qu'un switch à cinq cas : c'est lié à la façon dont le compilateur les traduit.

Au quotidien, la différence se fait sur la vitesse de compréhension : quand on sait ce qui se passe en dessous, on reconnaît plus vite les schémas de bugs. Sur la durée de vie du projet, la différence se fait sur les choix faits dès l'écriture.

Bien sûr, tout ceci n'est qu'une brique dans la conception générale. Pour avoir une vision complète, le développeur doit aussi connaître d'autres ordres de grandeur : le coût d'évaluation d'une expression régulière, le parsing d'une requête HTTP, le temps d'accès à une base de données. C'est aussi ce qui permet de trancher : savoir dans quels cas Go convient, et dans quels cas il vaut mieux l'éviter.

Les assistants IA qui génèrent du code Go tiennent-ils compte de ces subtilités d'implémentation ? Et cela change-t-il, selon vous, l'intérêt de creuser soi-même la connaissance d'un langage ?

Ils produisent en général du code Go correct et idiomatique pour les cas courants. Mais ils reproduisent ce qu'ils ont vu : si un motif a été répandu, ils le proposent, qu'il soit encore utile ou non. C'est paradoxal : l'assistant peut restituer le fonctionnement du runtime, mais tant que le développeur ne sollicite pas cette connaissance, c'est le code le plus répandu qui prime. Par ailleurs, si on ne le leur précise pas, ils ne connaissent pas le contexte de déploiement : la charge, les limites mémoire et CPU, la latence acceptable, qui déterminent souvent le bon choix.

« Les assistants renforcent l'intérêt de connaître le langage,
plutôt qu'ils ne le réduisent. »

Une part croissante du code est déjà produite par des assistants, et le rôle du développeur se déplace vers la spécification : décrire le besoin, les contraintes, et les formes à privilégier ou à éviter. Une bonne spécification ne s'arrête pas à la fonctionnalité. Elle intègre les contraintes, comme le volume de données, la latence attendue ou la mémoire disponible, et elle liste les pièges à éviter dans les fonctions très sollicitées. Un assistant n'aura pas forcément ces réflexes. Pour les lui signaler, il faut savoir que ces pièges existent. Les assistants renforcent donc l'intérêt de connaître le langage, plutôt qu'ils ne le réduisent.

Connaître le runtime du langage qu'on utilise, c'est un premier pas pour passer de programmeur à développeur. C'est probablement là que se situera la valeur ajoutée d'un humain face aux assistants.

Le livre s'appuie sur des mécanismes très récents du runtime, certains liés à Go 1.26. Comment avez-vous pensé sa pérennité face à un langage qui continue d'évoluer ?

Les principes généraux changent peu : le modèle G-M-P du scheduler, le GC concurrent à trois couleurs, l'escape analysis, le modèle mémoire et ses relations happens-before, la représentation des slices, des strings et des interfaces. Une grande partie du livre porte sur ces principes, et ils restent valables d'une version à l'autre. Ce sont les implémentations qui évoluent à chaque version.

Le livre décrit donc une version de référence, Go 1.23, celle qui était encore couramment utilisée en production quand j'ai commencé à écrire. Quand un mécanisme appartient à une version plus récente, le texte le précise. C'est le cas, par exemple, de Green Tea, le nouveau mode de marquage du GC, activé par défaut dans Go 1.26.

Le livre montre aussi comment observer le runtime soi-même : GODEBUG, pprof, sortie du compilateur, assembleur. Ces outils restent quand la version change. Le lecteur qui les a pratiqués au fil du livre est capable de comprendre par lui-même pourquoi un comportement diffère de ce qui est écrit.

Les évolutions d'implémentation seront intégrées dans une future édition, basée sur une version de référence plus récente.

Comment lire votre livre ? Du début à la fin ?

Pas nécessairement. Les deux premières parties, Fondements du runtime et Types et structures de données, se lisent indépendamment l'une de l'autre et chacune est utile seule. Les parties sur les mécanismes avancés du langage et sur la concurrence s'appuient sur elles : il vaut mieux connaître le scheduler avant d'aborder les channels, et la représentation des types avant les optimisations du compilateur.

Les annexes sont facultatives. Elles contiennent le code des démonstrations, les benchmarks et le détail de quelques algorithmes utilisés par le runtime, comme les tas binaires ou la division par une constante.

Après une première lecture, le livre peut servir de référence : on revient au chapitre sur le GC quand un service consomme trop de mémoire, ou aux primitives de synchronisation quand un profil montre de la contention.

« Les principes généraux changent peu.
Ce sont les implémentations qui évoluent à chaque version. »

Faut-il déjà bien maîtriser Go pour tirer parti du livre, ou y a-t-il des sections utiles même à des développeurs moins expérimentés ?

Le livre suppose une pratique régulière de Go : la syntaxe, les types, les goroutines et les channels utilisés dans des programmes réels. Ce n'est pas un tutoriel, et je conseille le tutoriel officiel à ceux qui débutent. Des notions d'assembleur aident pour quelques passages sur les optimisations du compilateur, mais elles ne sont pas nécessaires pour suivre les explications.

Un développeur moins expérimenté trouvera toutefois des chapitres utiles rapidement : la structure exacte des types complexes comme les slices, les strings ou les interfaces, le comportement exact de defer, panic et recover et les raisons de ce comportement, ainsi que le chapitre sur les outils de la programmation concurrente, avec le race detector et testing/synctest. Ce sont des sujets qu'on rencontre dès les premiers mois de Go, et dont les bugs sont difficiles à comprendre sans connaître leur fonctionnement interne.

Ce livre est-il celui que vous auriez aimé lire il y a quelques années ?

Oui. Je suis de nature curieuse, et mon expérience est proche du processeur. J'ai besoin de comprendre ce qui se cache derrière des structures comme les channels ou les maps. Rien de tout cela n'existe dans un CPU : ce sont des abstractions construites par le runtime, avec des algorithmes élaborés. Quand j'ai cherché à comprendre leur fonctionnement, j'ai trouvé peu de choses, et j'ai fini par creuser moi-même dans le code.

Avec ce livre, j'aurais sans doute creusé quand même, pour vérifier par moi-même. Mais cela aurait été beaucoup plus rapide.

Merci, Thierry, d'avoir répondu à cette interview.

Mascotte de Go montant une échelle

Voir aussi


Go, un langage adapté aux besoins actuels