« Cette application utilise 900 Mo de RAM. »

Sur notre test Chromium, cette phrase peut être vraie. Elle peut aussi être très trompeuse.

Nous avons lancé un Chromium headless avec cinq pages simples, attendu cinq secondes, figé son arbre de processus puis lu la mémoire de chacun. En additionnant les RSS de onze processus, on obtient environ 899 MiB au médian sur trois captures. En utilisant le PSS du même arbre, on tombe à environ 278 MiB. L'USS est encore plus bas, autour de 194 MiB.

Même logiciel. Même instant. Même noyau. Presque un facteur 3,2 entre deux nombres que l'on pourrait tous les deux appeler « mémoire utilisée ».

Le problème n'est pas qu'un compteur mente. Le problème est qu'ils ne répondent pas à la même question.12

Un petit projet Linux nommé memftprt donne une bonne excuse pour remettre ces questions à plat. Il ne cherche pas seulement la mémoire d'un PID. Il suit l'arbre complet d'une application, attend un état comparable, fige les processus, puis produit RSS, PSS, USS, mémoire anonyme, fichiers mappés, mémoire partagée et swap.1

Nous l'avons compilé et utilisé sur trois cas locaux : le serveur de développement Astro d'IRZ, OpenCode en mode serveur, et Chromium avec cinq pages. Pas pour établir un classement. Pour voir à quel point le choix du périmètre change l'histoire.

Un logiciel moderne est rarement un seul processus

Quand on lance npm run dev, le PID que le shell affiche n'est pas forcément « l'application ».

Dans notre capture Astro, le processus racine était npm. Il utilisait environ 68 MiB de RSS. Si l'on s'arrêtait là, on pourrait annoncer qu'un serveur Astro de développement consomme autour de 70 Mo.

L'arbre capturé contenait quatre processus. Le gros du travail vivait dans un enfant Node, accompagné notamment d'esbuild. Une fois l'arbre complet pris en compte, le total montait à environ 473 MiB de RSS et 397 MiB de PSS.

Le PID racine ne représentait donc qu'une petite partie du service que nous avions réellement démarré.

C'est banal dans les outils de développement. Un wrapper npm lance un shell. Le shell lance Node. Node peut lancer un compilateur, un langage server, un browser worker ou d'autres processus. Les IDE et les navigateurs vont encore plus loin : renderer par onglet ou site, processus GPU, utilitaires réseau, extensions, crash handlers et services annexes.

Chromium assume explicitement une architecture multi-processus pour isoler et paralléliser différentes tâches.4

Demander « combien consomme Chrome ? » en regardant un PID arbitraire est donc un peu comme estimer la consommation électrique d'un atelier en mesurant seulement la multiprise posée près de la porte.

La première question n'est pas RSS ou PSS.

C'est : qu'est-ce que l'on appelle l'application ?

RSS : les pages visibles par un processus

RSS signifie Resident Set Size.

À gros traits, c'est la quantité de pages mémoire actuellement résidentes en RAM et mappées dans l'espace d'adressage du processus. C'est le chiffre que l'on retrouve très souvent dans ps, top, /proc/PID/status et une quantité d'outils de monitoring.2

RSS est utile. Si un seul processus possède principalement des pages privées, il peut donner une intuition très correcte de son empreinte résidente.

OpenCode nous a fourni ce cas presque idéal.

Nous avons lancé :

opencode serve --hostname 127.0.0.1 --port ... --pure

Dans trois captures, memftprt n'a vu qu'un processus. Le RSS médian était autour de 292 MiB, le PSS autour de 255 MiB et l'USS autour de 221 MiB. Comme il n'y avait aucun descendant, « processus racine » et « arbre complet » étaient exactement le même périmètre.

La différence entre RSS et PSS existait encore, mais elle était beaucoup moins théâtrale que dans Chromium.

Le problème du RSS commence surtout lorsqu'on additionne des processus qui partagent des pages.

Additionner les RSS peut compter la même page plusieurs fois

Prenons une bibliothèque partagée de 30 MiB utilisée par trois processus.

Chaque processus peut avoir ces pages dans son RSS. Si l'on additionne naïvement les trois RSS, la bibliothèque peut contribuer près de 90 MiB au total alors qu'une partie de ses pages physiques est commune.

Cela n'est pas un bug du RSS. Du point de vue de chaque processus, les pages sont bien mappées et résidentes.

C'est l'addition qui répond à une autre question que celle que l'on croyait poser.

Linux expose justement le PSS, Proportional Set Size, dans smaps et smaps_rollup. Pour une page partagée entre plusieurs processus, le coût est réparti entre les processus qui la mappent.2

Si trois processus partagent réellement une page dans les mêmes conditions, chacun n'en porte qu'une fraction dans son PSS.

C'est ce mécanisme qui explique une grande partie de notre différence Chromium.

La somme RSS de l'arbre tournait autour de 898 à 900 MiB sur les trois captures. Le PSS de l'arbre variait davantage, de 260 à 333 MiB, avec une médiane à environ 278 MiB. L'USS se situait entre 193 et 211 MiB.

Dire « Chromium utilisait 900 Mo » sans préciser que l'on additionne les RSS de onze processus donne donc une image très différente de « l'arbre Chromium avait environ 278 MiB de PSS ».

Les deux phrases utilisent des nombres réels. Une seule dit vraiment ce qu'elle mesure.

USS : ce qui disparaîtrait avec ce processus

USS, Unique Set Size, retire encore la mémoire partagée.

Il ne conserve que les pages résidentes privées du processus. memftprt le décrit comme la mémoire qui serait libérée si ce processus seul disparaissait, en laissant les autres utilisateurs des pages partagées continuer à vivre.1

Dans notre capture Chromium médiane, le PSS total de l'arbre était environ 278 MiB et l'USS autour de 194 MiB.

L'écart, environ 83 MiB, représente l'ordre de grandeur de la part proportionnelle des mappings partagés attribuée à l'arbre dans cette capture. Ce n'est pas « de la fausse RAM ». Ce sont des pages qui existent, mais dont la propriété est partagée avec d'autres mappings.

PSS et USS répondent donc à deux questions légèrement différentes :

  • combien de mémoire physique peut-on raisonnablement attribuer à cette application en partageant le coût commun ?
  • combien de pages résidentes lui sont vraiment privées ?

Pour diagnostiquer un gros processus, les deux sont utiles.

Pour écrire un tweet disant « mon outil consomme X Go », aucun n'est magique sans la méthode autour.

Même le RSS rapide n'est pas toujours une photographie parfaite

Le noyau Linux ajoute une autre petite joie administrative.

Sa documentation prévient que certains compteurs rapides tels que VmRSS dans /proc/PID/status peuvent être imprécis parce que l'accounting est mis à jour de manière asynchrone. Pour une mesure plus détaillée, smaps parcourt les mappings mémoire ; smaps_rollup fournit une agrégation du même type de statistiques à l'échelle du processus.2

Cette différence explique pourquoi deux outils système peuvent parfois afficher des nombres proches mais pas identiques au même moment.

Il y a aussi le temps.

Un programme alloue, libère, remplit des caches, compile du code, ouvre un onglet, termine un worker. Si l'on lit le père puis ses enfants l'un après l'autre, l'état peut changer pendant la collecte.

memftprt essaie de traiter ce problème comme un problème de mesure, pas uniquement de parsing de /proc.1

Prendre la photo au même instant

Par défaut, memftprt échantillonne l'arbre toutes les 500 ms et attend qu'il soit stable pendant trois secondes. Parmi ses critères : aucun processus en train d'exécuter activement du CPU ou bloqué en sommeil non interruptible, très peu d'évolution CPU, RSS et taille virtuelle quasi stables, et même ensemble d'identités PID/heure de démarrage.1

Une fois cette fenêtre atteinte, il fige l'arbre.

Quand un cgroup v2 délégué et propre est disponible, l'outil peut utiliser son mécanisme de freeze. Sinon il retombe sur un mécanisme basé sur ptrace/SIGSTOP, en prenant soin de reprendre uniquement les identités qu'il avait arrêtées.1

Le noyau expose bien cgroup.freeze pour arrêter un cgroup et ses descendants.3

Dans nos essais sur MJ/WSL, memftprt a utilisé le backend signals. Nous avons choisi un délai fixe de cinq secondes plutôt que la détection de quiescence, afin que les trois familles d'applications soient photographiées au même moment relatif après leur lancement.

Ce n'est pas la seule méthode valable. C'est simplement la méthode de notre expérience.

Et cette phrase devrait accompagner beaucoup plus de benchmarks mémoire.

Ce que nous avons réellement mesuré

Nos tests du 12 août 2026 n'essaient pas de comparer Astro, OpenCode et Chromium entre eux. Leurs workloads n'ont rien à voir.

Ils servent à montrer trois formes d'application.

Astro : le wrapper qui cache le vrai processus

Commande : serveur de développement IRZ sur un port dédié.

Capture exploitable : 4 processus.

Processus racine npm :

  • RSS : ~68,0 MiB
  • PSS : ~31,8 MiB
  • USS : ~18,4 MiB

Arbre complet :

  • RSS : ~472,8 MiB
  • PSS : ~396,9 MiB
  • USS : ~368,8 MiB

Une capture antérieure du même run donnait environ 472 MiB RSS et 396 MiB PSS pour l'arbre, ce qui recoupe cet ordre de grandeur. Nous n'avons toutefois pas obtenu trois captures répétées propres pour Astro, donc il serait malhonnête d'appeler ces nombres une médiane à trois essais.

La leçon est uniquement le périmètre : regarder npm seul rate la grande majorité de l'application lancée.

OpenCode : un seul processus, au moins dans ce mode

Commande : opencode serve --pure, sans tâche agent active.

Trois captures, un processus à chaque fois.

Médianes :

  • RSS : ~292,0 MiB
  • PSS : ~254,7 MiB
  • USS : ~220,5 MiB

Ici, suivre tout l'arbre n'ajoute rien parce qu'il n'y a pas d'enfant au moment des captures.

C'est un bon contre-exemple à notre propre article. « Il faut toujours sommer l'arbre » serait une nouvelle recette idiote. Il faut d'abord regarder comment le logiciel est organisé.

Et surtout, ce chiffre ne décrit pas « combien OpenCode utilise quand il code ». Le serveur était au repos. Une session avec modèles locaux, outils, navigateurs ou sous-processus peut produire une structure différente.

Chromium : quand RSS devient une addition spectaculaire

Nous avons lancé Chromium headless via Playwright, créé cinq pages contenant chacune un document synthétique simple, puis gardé le navigateur ouvert.

Trois captures, onze processus à chaque fois.

Médiane du processus racine Node/launcher :

  • RSS : ~130,4 MiB
  • PSS : ~92,2 MiB
  • USS : ~84,5 MiB

Médiane de l'arbre :

  • RSS : ~898,6 MiB
  • PSS : ~277,6 MiB
  • USS : ~194,4 MiB

La somme RSS raconte donc un navigateur proche du gigaoctet. Le PSS raconte environ 278 MiB de mémoire physique proportionnellement attribuable à l'arbre dans ce snapshot. L'USS dit qu'environ 194 MiB étaient réellement privés.

Ce n'est pas un benchmark de Chrome. Les pages étaient artificielles, le navigateur headless, et l'environnement WSL. C'est une démonstration contrôlée du problème de comptabilité.

Et elle fonctionne presque trop bien.

Un cgroup peut parfois être une meilleure réponse que l'arbre de processus

Suivre parent et enfants n'est pas la seule manière de définir le périmètre.

Les cgroups Linux offrent une frontière différente : mettre le workload dans un groupe, puis mesurer ce groupe. En cgroup v2, memory.current expose la quantité totale de mémoire actuellement utilisée par le cgroup et ses descendants.3

Pour un conteneur, un service systemd ou un workload que l'on peut isoler proprement, cette unité peut être plus naturelle qu'une reconstruction des relations PPID.

Elle résout aussi certains cas où un processus daemonise, se re-parente ou partage des ressources d'une façon qui rend l'arbre logique moins évident.

memftprt lui-même peut s'appuyer sur un cgroup délégué pour figer le workload lorsqu'il en dispose.1

Cela souligne une idée importante : il n'existe pas un compteur « RAM application » caché quelque part dans Linux. On choisit d'abord une frontière, puis une métrique adaptée à la question.

PID, arbre de processus, cgroup, conteneur, machine entière : chacun est un périmètre différent.

La JVM ajoute encore une seconde langue

memftprt possède aussi un adaptateur pour HotSpot Native Memory Tracking.1

La JVM peut parler de heap, classes, threads, code cache, garbage collector, mémoire réservée et mémoire committed. Ces catégories sont extrêmement utiles pour comprendre ce que fait le runtime.

Mais elles ne doivent pas être additionnées à PSS ou RSS.

Une partie des mêmes pages apparaît simplement décrite sous deux points de vue. Le système d'exploitation parle de résidence et de propriété de pages. La JVM parle d'organisation interne du runtime.1

PSS + NMT committed n'est donc pas « la vraie mémoire totale ». C'est souvent du double comptage avec une addition proprement imprimée.

On retrouve exactement le même piège conceptuel : deux nombres peuvent être corrects séparément et devenir faux une fois combinés sans comprendre ce qu'ils représentent.

Pourquoi les comparaisons d'applications tournent vite au concours de pommes et de radiateurs

Les comparaisons « éditeur A utilise 300 Mo, éditeur B 1,2 Go » circulent régulièrement.

Elles peuvent être utiles lorsque le protocole est bon. Elles deviennent du théâtre quand personne ne précise :

  • version du logiciel ;
  • système et noyau ;
  • workload ;
  • délai après lancement ;
  • nombre de documents, onglets ou projets ;
  • extensions et plugins ;
  • caches froids ou chauds ;
  • processus inclus ;
  • métrique utilisée ;
  • swap ;
  • nombre de répétitions.

Il suffit d'un changement de périmètre pour déplacer le résultat de plusieurs centaines de mégaoctets.

Notre test Astro en est une caricature. Mesurer seulement le processus npm donne 68 MiB RSS. Mesurer tout ce qu'il a lancé donne près de 473 MiB de RSS. Aucune optimisation n'a eu lieu entre les deux mesures. Nous avons simplement regardé davantage de processus.

La comparaison la plus rigoureuse n'est donc pas celle qui affiche le plus de décimales. C'est celle qui rend le protocole suffisamment clair pour que quelqu'un puisse reproduire la même question.

Pour mesurer un outil agentique, il faut décider où finit l'agent

Cette question devient encore plus amusante avec les agents de code.

Un CLI agent peut être un seul processus au repos, comme OpenCode dans notre test. Pendant une vraie tâche, il peut lancer git, npm, un type checker, un navigateur, des tests, un serveur de développement, voire un serveur local de modèle.

Doit-on compter tout cela comme « mémoire de l'agent » ?

Si l'objectif est de mesurer le poids du programme agent lui-même, probablement pas.

Si l'objectif est de savoir combien de RAM il faut pour exécuter le workflow que l'agent déclenche, probablement oui.

Et si le modèle tourne dans LM Studio sur une autre machine, faut-il l'inclure ? La réponse dépend encore de la question.

Le terme « agent » décrit une responsabilité logique. Le noyau, lui, voit des processus, pages, sockets et cgroups. Il ne sait pas que Chromium a été ouvert parce qu'un modèle a décidé de vérifier une page.

Mesurer un système agentique demande donc de documenter non seulement la métrique, mais la frontière fonctionnelle choisie.

C'est un sujet que nous voulons pousser plus loin avec des workloads réels. Le petit test OpenCode ici sert surtout de témoin : un agent peut être très simple au repos et devenir un orchestrateur de dizaines de processus dès qu'il travaille.

memftprt est intéressant parce qu'il refuse la fausse simplicité

Le dépôt de memftprt est jeune. Il a été publié début août 2026 et ne possède pas encore le recul d'un outil système installé partout. Il ne faut pas transformer trois commits et une suite de tests en standard de mesure universel.1

Son idée est néanmoins saine.

Il produit plusieurs chiffres au lieu d'en choisir un mystérieusement. Il garde le détail par processus. Il enregistre comment l'arbre a été figé. Il distingue un champ absent d'un zéro dans le JSON. Il conserve les statuts de ses probes runtime. Il explique explicitement que RSS, PSS, USS et NMT ne doivent pas être mélangés comme des Lego compatibles.1

Le logiciel ne résout pas la question « combien de RAM utilise mon application ? ».

Il force plutôt à la reformuler correctement.

Quelle application ? À quel instant ? Sous quelle charge ? Quelles pages veut-on attribuer à qui ? Cherche-t-on la mémoire privée, la pression physique imputable au workload, la somme des mappings résidents, la limite d'un conteneur ou la structure interne d'un runtime ?

Une fois ces questions posées, le chiffre devient beaucoup moins magique.

La prochaine fois qu'un logiciel « prend 1 Go », demander quel gigaoctet

Notre Chromium de test avait presque 900 MiB de RSS additionnée. Il avait environ 278 MiB de PSS sur l'arbre. Son USS était autour de 194 MiB.

Ces nombres ne s'annulent pas. Ils décrivent trois vues du même instant.

Le bon réflexe n'est donc pas de remplacer partout RSS par PSS et déclarer le problème réglé. RSS reste utile. USS reste utile. memory.current d'un cgroup peut être plus pertinent encore dans d'autres situations.

Le réflexe utile est plus petit : ne jamais accepter un chiffre mémoire sans demander son périmètre.

C'est moins satisfaisant qu'un classement d'applications du plus léger au plus obèse. Ça demande parfois de lire /proc. Et il y a toujours un processus Chromium supplémentaire quelque part pour compliquer le tableau.

Mais au moins, quand on dira qu'un logiciel utilise 900 Mo, on saura enfin de quels 900 Mo on parle.