Un guide pratique sur la mentalité « performance d’abord » associée à John Carmack : profilage, budgets de temps par frame, arbitrages et mise en production de systèmes temps réel complexes.

John Carmack est souvent traité comme une légende des moteurs de jeu, mais la partie utile n’est pas la mythologie — ce sont les habitudes répétables. Il ne s’agit pas d’imiter le style d’une personne ou de supposer des « coups de génie ». Il s’agit de principes pratiques qui mènent de façon fiable à des logiciels plus rapides et plus fluides, surtout quand les délais et la complexité s’accumulent.
L'ingénierie de la performance consiste à faire en sorte que le logiciel atteigne une cible de vitesse sur du matériel réel, dans des conditions réelles — sans casser la correction. Ce n’est pas « le rendre vite à tout prix ». C’est une boucle disciplinée :
Cet état d’esprit revient sans cesse dans le travail de Carmack : discuter avec des données, garder les changements explicables et préférer des approches maintenables.
Les graphismes temps réel ne pardonnent pas parce qu’il y a une deadline à chaque frame. Si vous la manquez, l’utilisateur le ressent immédiatement sous forme de saccades, latence d’entrée ou mouvement irrégulier. D’autres logiciels peuvent cacher l’inefficacité derrière des files d’attente, écrans de chargement ou tâches en arrière-plan. Un renderer ne peut pas négocier : soit vous finissez à temps, soit vous ne finissez pas.
C’est pourquoi les leçons généralisent au-delà des jeux. Tout système avec des contraintes de latence serrées — UI, audio, AR/VR, trading, robotique — bénéficie d’une pensée en budgets, d’une compréhension des goulots et d’une prévention des pics surprises.
Vous obtiendrez des checklists, heuristiques et schémas de décision applicables à votre travail : comment définir des budgets de temps par frame (ou de latence), comment profiler avant d’optimiser, comment choisir la « seule chose » à corriger, et comment prévenir les régressions pour que la performance devienne une routine — pas une panique de dernière minute.
La pensée de performance à la Carmack commence par un simple changement : arrêtez de parler du « FPS » comme unité primaire et commencez à parler du temps par frame.
Le FPS est réciproque (« 60 FPS » sonne bien, « 55 FPS » paraît proche), mais l’expérience utilisateur est guidée par la durée de chaque frame — et, tout aussi important, par la constance de ces durées. Un saut de 16,6 ms à 33,3 ms est immédiatement visible, même si votre FPS moyen semble encore respectable.
Un produit temps réel a plusieurs budgets, pas seulement « rendre plus vite » :
Ces budgets interagissent. Économiser du temps GPU en ajoutant du batching gourmand en CPU peut se retourner contre vous, et réduire la mémoire peut augmenter les coûts de streaming ou de décompression.
Si votre cible est 60 FPS, votre budget total est 16,6 ms par frame. Une répartition approximative pourrait ressembler à :
Si le CPU ou le GPU dépasse le budget, vous ratez la frame. C’est pourquoi les équipes parlent d’être « CPU-bound » ou « GPU-bound » — pas comme des étiquettes, mais comme un moyen de décider d’où proviendra le prochain milliseconde réaliste.
Le but n’est pas de courir après un metric de vanité comme « le plus haut FPS sur une machine haut de gamme ». Il s’agit de définir ce qui est assez rapide pour votre audience — cibles matérielles, résolution, autonomie, contraintes thermiques et réactivité d’entrée — puis de traiter la performance comme des budgets explicites que vous pouvez gérer et défendre.
Le mouvement par défaut de Carmack n’est pas « optimiser », c’est « vérifier ». Les problèmes de performance temps réel foisonnent d’explications plausibles — pauses GC, « shaders lents », « trop de draw calls » — et la plupart sont fausses dans votre build sur votre matériel. Le profilage est la façon de remplacer l’intuition par des preuves.
Traitez le profilage comme une fonctionnalité de première classe, pas comme un outil de sauvetage de dernière minute. Capturez les temps par frame, les timelines CPU et GPU, et les compteurs qui les expliquent (triangles, draw calls, changements d’état, allocations, cache misses si vous pouvez les obtenir). L’objectif est de répondre à une question : où le temps est-il réellement passé ?
Un modèle utile : dans chaque frame lente, une chose est le facteur limitant. Peut-être le GPU bloqué sur une passe lourde, le CPU bloqué sur la mise à jour d’animations, ou le thread principal en attente d’une synchronisation. Trouvez cette contrainte d’abord ; le reste n’est que bruit.
Une boucle disciplinée vous empêche de vous emballer :
Si l’amélioration n’est pas claire, supposez qu’elle n’a pas aidé — car elle ne survivra probablement pas aux prochains ajouts de contenu.
Le travail de performance est particulièrement vulnérable à l’auto-illusion :
Le profilage en premier garde vos efforts ciblés, vos arbitrages justifiés et vos changements plus faciles à défendre en revue.
Les problèmes de performance temps réel semblent désordonnés parce que tout se passe en même temps : gameplay, rendu, streaming, animation, UI, physique. L’instinct de Carmack est de couper à travers le bruit et d’identifier le limitant dominant — la seule chose qui fixe actuellement votre temps par frame.
La plupart des ralentissements tombent dans quelques bacs :
L’objectif n’est pas d’étiqueter pour un rapport — c’est de choisir le bon levier.
Quelques expériences rapides peuvent indiquer ce qui est réellement en contrôle :
Vous gagnez rarement en enlevant 1 % à dix systèmes. Trouvez le coût le plus important qui se répète chaque frame et attaquez-le en premier. Supprimer une seule offense de 4 ms bat des semaines de micro-optimisations.
Après avoir corrigé le gros caillou, le prochain gros caillou devient visible. C’est normal. Traitez le travail de performance comme une boucle : mesurer → changer → re-mesurer → re-prioriser. Le but n’est pas un profil parfait ; c’est un progrès régulier vers un temps par frame prévisible.
Le temps par frame moyen peut sembler correct alors que l’expérience reste mauvaise. Les graphismes temps réel sont jugés par les pires moments : la frame manquée pendant une grosse explosion, le hitch en entrant dans une nouvelle salle, la saccade soudaine à l’ouverture d’un menu. C’est la latence en queue — des frames rares mais suffisamment fréquentes pour que les utilisateurs les remarquent.
Un jeu qui tourne à 16,6 ms la plupart du temps (60 FPS) mais pique à 60–120 ms toutes les quelques secondes paraîtra « cassé », même si la moyenne s’affiche encore à 20 ms. Les humains sont sensibles au rythme. Une seule longue frame casse la prédictibilité des entrées, le mouvement de la caméra et la synchro audio/vidéo.
Les pics viennent souvent de travaux qui ne sont pas répartis de façon homogène :
L’objectif est de rendre le travail coûteux prévisible :
Ne vous contentez pas d’un graphique de FPS moyen. Enregistrez les timings par frame et visualisez :
Si vous ne pouvez pas expliquer vos 1 % pires frames, vous n’avez pas vraiment expliqué la performance.
Le travail de performance devient plus simple dès que vous cessez de prétendre tout pouvoir avoir. Le style de Carmack pousse les équipes à nommer l’arbitrage à voix haute : qu’achetons-nous, qu’en payons-nous et qui en ressent la différence ?
La plupart des décisions se situent sur quelques axes :
Si un changement améliore un axe mais en taxe trois autres silencieusement, documentez-le. « Ceci ajoute 0,4 ms GPU et 80 Mo VRAM pour des ombres plus douces » est une phrase utile. « Ça a l’air mieux » ne l’est pas.
Les graphismes temps réel ne cherchent pas la perfection ; ils visent à toucher une cible de manière cohérente. Mettez-vous d’accord sur des seuils comme :
Une fois l’équipe d’accord que, disons, 16,6 ms à 1080p sur le GPU de base est l’objectif, les discussions deviennent concrètes : cette fonctionnalité nous garde-t-elle sous le budget, ou force-t-elle une dégradation ailleurs ?
Quand vous n’êtes pas sûr, choisissez des options que vous pouvez annuler :
La réversibilité protège le planning. Vous pouvez livrer le chemin sûr et garder l’ambitieux derrière un toggle.
Évitez de sur-ingéniéer des gains invisibles. Une amélioration de 1 % de la moyenne vaut rarement un mois de complexité — sauf si elle élimine des saccades, corrige la latence d’entrée ou prévient un crash mémoire dur. Priorisez les changements que les joueurs remarquent immédiatement, et laissez le reste attendre.
Le travail de performance devient beaucoup plus simple quand le programme est juste. Une grande part du temps passé à « optimiser » sert en réalité à traquer des bugs de correction qui ressemblent à des problèmes de performance : une boucle O(N²) accidentelle à cause d’un travail dupliqué, une passe de rendu qui s’exécute deux fois parce qu’un flag ne se réinitialise pas, une fuite mémoire qui augmente doucement le temps par frame, ou une condition de course qui tourne en saccade aléatoire.
Un moteur stable et prévisible vous donne des mesures propres. Si le comportement change entre les runs, vous ne pouvez pas faire confiance aux profils, et vous finirez par optimiser du bruit.
Les pratiques d’ingénierie disciplinées aident la vitesse :
Beaucoup de pics de temps par frame sont des « Heisenbugs » : ils disparaissent quand vous ajoutez du logging ou que vous attachez un débogueur. L’antidote est la reproduction déterministe.
Construisez un petit banc d’essai contrôlé :
Quand un hitch apparaît, vous voulez un bouton qui le rejoue 100 fois — pas un rapport vague disant que ça « arrive parfois après 10 minutes ».
Le travail de performance bénéficie de changements petits et revus. Les gros refactors créent plusieurs modes de défaillance à la fois : régressions, nouvelles allocations et travail caché en plus. Des diffs serrés facilitent la réponse à la seule question qui compte : qu’est-ce qui a changé dans le temps par frame, et pourquoi ?
La discipline n’est pas de la bureaucratie ici — c’est la façon de garder les mesures fiables pour que l’optimisation devienne directe au lieu d’être superstitieuse.
La performance temps réel ne se résume pas à du « code plus rapide ». Il s’agit d’organiser le travail pour que le CPU et le GPU puissent l’exécuter efficacement. Carmack a souvent souligné une vérité simple : la machine est littérale. Elle adore les données prévisibles et déteste les surcoûts évitables.
Les CPUs modernes sont incroyablement rapides — jusqu’à ce qu’ils attendent la mémoire. Si vos données sont dispersées en beaucoup de petits objets, le CPU passe du temps à suivre des pointeurs au lieu de faire des calculs.
Un modèle mental utile : ne faites pas dix petits trajets pour dix articles. Mettez-les dans un seul chariot et parcourez les rayons une fois. En code, cela signifie garder les valeurs fréquemment utilisées proches (souvent en tableaux ou structs compactes) pour que chaque fetch de cache line apporte des données que vous allez vraiment utiliser.
Les allocations fréquentes créent des coûts cachés : overhead de l’allocateur, fragmentation mémoire et pauses imprévisibles quand le système doit ranger. Même si chaque allocation est « petite », un flux régulier peut devenir une taxe que vous payez chaque frame.
Les corrections courantes sont volontairement ennuyeuses : réutiliser les buffers, pooler les objets et préférer des allocations longue durée pour les chemins chauds. L’objectif n’est pas la ruse — c’est la cohérence.
Une quantité surprenante de temps par frame peut s’évaporer dans la paperasserie : changements d’état, draw calls, travail du driver, syscalls et coordination de threads.
Le batching est la version « gros chariot » du rendu et de la simulation. Au lieu d’émettre beaucoup de petites opérations, groupez le travail similaire pour franchir moins souvent des frontières coûteuses. Souvent, réduire l’overhead bat l’optimisation micro d’un shader ou d’une boucle interne — parce que la machine passe moins de temps à se préparer et plus de temps à travailler.
Le travail de performance n’est pas seulement du code plus rapide — c’est aussi avoir moins de code. La complexité a un coût que vous payez chaque jour : les bugs prennent plus de temps à isoler, les correctifs exigent plus de tests, l’itération ralentit parce que chaque changement touche plus de pièces, et les régressions s’infiltrent via des chemins peu utilisés. Cette complexité ne gaspille pas seulement le temps des développeurs ; elle ajoute souvent un overhead runtime (branches supplémentaires, allocations, cache misses, synchronisations) difficile à voir avant qu’il ne soit trop tard.
Un système « clever » peut sembler élégant jusqu’à ce que vous soyez sous pression et qu’un pic n’apparaisse que sur une carte, un GPU ou une combinaison de paramètres. Chaque drapeau de fonctionnalité, chemin de repli et cas spécial multiplie le nombre de comportements à comprendre et mesurer. Cette complexité n’est pas seulement du temps dev ; elle ajoute souvent un surcoût runtime difficile à débusquer.
Bonne règle : si vous ne pouvez pas expliquer le modèle de performance à un coéquipier en quelques phrases, vous ne pouvez probablement pas l’optimiser de manière fiable.
Les solutions simples ont deux avantages :
Parfois le chemin le plus rapide est de supprimer une fonctionnalité, couper une option ou fusionner plusieurs variantes en une seule. Moins de fonctionnalités signifie moins de chemins de code, moins de combinaisons d’état et moins d’endroits où la performance peut se dégrader en silence.
Supprimer du code est aussi un geste de qualité : le meilleur bug est celui que vous empêchez en supprimant le module qui pouvait le générer.
Patch (correctif chirurgical) quand :
Refactorer (simplifier la structure) quand :
La simplicité n’est pas « moins ambitieux ». C’est choisir des designs qui restent compréhensibles sous pression — quand la performance compte le plus.
Le travail de performance ne tient que si vous pouvez détecter quand il glisse. Voilà ce qu’est un test de régression de performance : un moyen reproductible de détecter quand un changement rend le produit plus lent, moins fluide ou plus gourmand en mémoire. Contrairement aux tests fonctionnels (qui répondent à « ça marche ? »), les tests de régression répondent à « est-ce que ça reste aussi rapide ? » Un build peut être 100 % correct et rester une mauvaise release s’il ajoute 4 ms de temps par frame ou double les temps de chargement.
Vous n’avez pas besoin d’un labo pour commencer — juste de la constance.
Choisissez un petit ensemble de scènes de référence représentant un usage réel : une vue GPU-heavy, une vue CPU-heavy et une scène de stress "pire cas". Gardez-les stables et scriptées pour que le chemin de caméra et les entrées soient identiques à chaque run.
Exécutez les tests sur matériel fixe (PC/console/devkit connu). Si vous changez drivers, OS ou réglages d’horloge, enregistrez-le. Traitez la combinaison matériel/logiciel comme faisant partie du dispositif de test.
Stockez les résultats dans un historique versionné : hash de commit, config de build, ID machine et métriques mesurées. Le but n’est pas un nombre parfait — c’est une courbe de tendance fiable.
Privilégiez des métriques difficiles à contester :
Définissez des seuils simples (par ex. : p95 ne doit pas régresser de plus de 5 %).
Traitez les régressions comme des bugs avec un propriétaire et une deadline.
D’abord, bisectez pour trouver le changement qui l’a introduite. Si la régression bloque une release, revertissez rapidement et relevez avec un correctif.
Quand vous corrigez, ajoutez des garde-fous : gardez le test, ajoutez une note dans le code et documentez le budget attendu. L’habitude est la victoire — la performance devient quelque chose que vous maintenez, pas que vous « ferez plus tard ».
« Livrer » n’est pas un événement calendaire — c’est une exigence d’ingénierie. Un système qui ne marche bien qu’en labo, ou qui n’atteint le temps par frame qu’après une semaine d’ajustements manuels, n’est pas prêt. L’état d’esprit de Carmack traite les contraintes du monde réel (variété matérielle, contenu désordonné, comportement joueur imprévisible) comme faisant partie du cahier des charges dès le départ.
Quand vous êtes proche de la release, la prévisibilité vaut mieux que la perfection. Définissez les non-négociables en termes clairs : FPS cible, pics de temps par frame acceptables, limites mémoire et temps de chargement. Traitez tout ce qui les viole comme un bug, pas comme du « polish ». Cela re-cadre le travail de performance de l’optimisation optionnelle en travail de fiabilité.
Toutes les lenteurs ne se valent pas. Corrigez d’abord les problèmes visibles par l’utilisateur :
La discipline du profilage paye ici : vous ne devinez pas quel problème « semble gros », vous choisissez sur la base de l’impact mesuré.
Le travail de performance en fin de cycle est risqué car les « fixes » peuvent introduire de nouveaux coûts. Utilisez des déploiements par étapes : landez l’instrumentation d’abord, puis le changement derrière un toggle, puis étendez l’exposition. Préférez des valeurs par défaut sûres — réglages qui protègent le temps par frame même si elles réduisent un peu la qualité visuelle — surtout pour les configurations auto-détectées.
Si vous expédiez sur plusieurs plateformes ou niveaux, traitez les defaults comme une décision produit : mieux vaut sembler un peu moins fancy que d’être instable.
Traduisez les arbitrages en résultats : « Cet effet coûte 2 ms par frame sur les GPU milieu de gamme, ce qui risque de nous faire tomber sous 60 FPS pendant les combats. » Proposez des options, pas des leçons : réduire la résolution, simplifier le shader, limiter le taux d’apparition, ou accepter une cible plus basse. Les contraintes sont plus faciles à accepter quand elles sont présentées comme des choix concrets avec un impact utilisateur clair.
Vous n’avez pas besoin d’un nouveau moteur ni d’une réécriture pour adopter la pensée performance à la Carmack. Il vous faut une boucle reproductible qui rend la performance visible, testable et difficile à casser accidentellement.
Mesurer : capturez une baseline (moyenne, p95, pire spike) pour le temps par frame et les sous-systèmes clés.
Budgéter : fixez un budget par frame pour le CPU et le GPU (et la mémoire si vous êtes serrés). Écrivez le budget à côté de l’objectif fonctionnalité.
Isoler : reproduisez le coût dans une scène minimale ou un test. Si vous ne pouvez pas le reproduire, vous ne pouvez pas le corriger.
Optimiser : changez une chose à la fois. Préférez des changements qui réduisent le travail, pas seulement « le rendre plus rapide ».
Valider : re-profilez, comparez les deltas et vérifiez les régressions qualité et les problèmes de correction.
Documenter : notez ce qui a changé, pourquoi cela a aidé et quoi surveiller à l’avenir.
Si vous voulez opérationnaliser ces habitudes au sein d’une équipe, la clé est de réduire la friction : expériences rapides, harnesses reproductibles et rollbacks faciles.
Koder.ai peut aider quand vous construisez les outils autour — pas le moteur lui-même. Étant une plateforme vibe-coding qui génère du code réel et exportable (apps web en React ; backends en Go avec PostgreSQL ; mobile en Flutter), vous pouvez rapidement monter des dashboards internes pour percentiles de temps par frame, historique de régressions et checklists de « revue performance », puis itérer via chat au fur et à mesure que les exigences évoluent. Les snapshots et rollback s’accordent aussi bien avec la boucle « changer une chose, re-mesurer ».
Si vous voulez plus de conseils pratiques, parcourez /blog ou voyez comment les équipes opérationnalisent cela sur /pricing.
Le temps par frame est le temps par image en millisecondes (ms) et correspond directement à la quantité de travail faite par le CPU/GPU.
Choisissez une cible (par ex. 60 FPS) et convertissez-la en un délai strict (16,6 ms). Ensuite, répartissez ce délai en budgets explicites.
Exemple de point de départ :
Considérez ces valeurs comme des exigences produit et ajustez-les selon la plateforme, la résolution, les contraintes thermiques et les objectifs de latence d'entrée.
Commencez par rendre vos tests reproductibles, puis mesurez avant de changer quoi que ce soit.
Ce n'est qu'après avoir su où le temps est dépensé que vous pouvez décider quoi optimiser.
Exécutez des tests courts et ciblés qui isolent le limiteur :
Parce que les utilisateurs ressentent les pires frames, pas la moyenne.
Suivez :
Un build qui moyenne 16,6 ms mais pique à 80 ms donnera quand même une sensation de mauvaise qualité.
Rendez le travail coûteux prévisible et planifié :
Consignez aussi les spikes pour pouvoir les reproduire et les corriger, plutôt que d’espérer qu’ils disparaissent.
Formulez l'arbitrage explicitement en chiffres et en impact utilisateur.
Utilisez des énoncés comme :
Puis décidez selon des seuils convenus :
Parce qu'une correction instable rend les données de performance peu fiables.
Mesures pratiques :
La plupart du « code rapide » consiste en réalité à réduire la mémoire et les surcoûts.
Concentrez-vous sur :
Souvent, couper les frais généraux donne de plus grands gains que d'ajuster une petite boucle mathématique.
Rendez la performance mesurable, reproductible et difficile à casser par accident.
Évitez de réécrire des systèmes avant de pouvoir nommer le coût dominant en millisecondes.
Si vous hésitez, favorisez des décisions réversibles (feature flags, paliers de qualité).
Si le comportement varie d'une exécution à l'autre, vous optimisez du bruit plutôt que des goulots réels.
Quand une régression apparaît : bisect, assignez un propriétaire, et revert rapidement si ça bloque la release.