Een praktische gids voor de performance-eerst denkwijze van John Carmack: profileren, frame-tijdbudgetten, afwegingen en het uitbrengen van complexe realtime systemen.

John Carmack wordt vaak als een legende van game-engines gezien, maar het nuttige deel is niet de mythologie—het zijn de herhaalbare gewoontes. Het gaat niet om iemands stijl kopiëren of aannemen dat alles zijn ‘geniale zet’ was. Het gaat om praktische principes die betrouwbaar leiden tot snellere, soepelere software, vooral als deadlines en complexiteit toenemen.
Prestatie-engineering betekent software zo bouwen dat het een snelheidsdoel haalt op echte hardware, onder echte omstandigheden—zonder correctheid te breken. Het is niet “maak het zo snel mogelijk ongeacht de kosten.” Het is een gedisciplineerde lus:
Die denkwijze komt keer op keer terug in Carmacks werk: discussieer met data, houd veranderingen verklaarbaar en geef de voorkeur aan benaderingen die je kunt onderhouden.
Realtime graphics is genadeloos omdat er elk frame een deadline is. Als je die mist, merkt de gebruiker het meteen als hapering, input-lag of ongelijkheid in beweging. Andere software kan inefficiëntie verbergen achter wachtrijen, laadschermen of achtergrondwerk. Een renderer kan niet onderhandelen: je bent ofwel op tijd klaar, of je bent het niet.
Daarom generaliseren de lessen buiten games. Elk systeem met strakke latentie-eisen—UI, audio, AR/VR, trading, robotica—profiteert van denken in budgetten, het begrijpen van knelpunten en het vermijden van onverwachte pieken.
Je krijgt checklists, vuistregels en beslispatronen die je op je eigen werk kunt toepassen: hoe je frame-tijd (of latentie) budgetten instelt, hoe je profileert voordat je optimaliseert, hoe je het “één ding” kiest om te repareren, en hoe je regressies voorkomt zodat performance routine wordt—niet een paniek in een latere fase.
Carmack-achtige prestatiegedachten beginnen met een simpele omschakeling: stop met praten over “FPS” als primaire eenheid en ga praten over frame-tijd.
FPS is een reciproke (“60 FPS” klinkt goed, “55 FPS” klinkt dichtbij), maar de gebruikerservaring wordt bepaald door hoe lang elk frame duurt—en, even belangrijk, hoe consistent die tijden zijn. Een sprong van 16,6 ms naar 33,3 ms is meteen zichtbaar, ook al ziet je gemiddelde FPS er nog redelijk uit.
Een realtime product heeft meerdere budgetten, niet alleen “render sneller”:
Deze budgetten beïnvloeden elkaar. GPU-tijd besparen door CPU-zware batching toe te voegen kan tegen je werken, en geheugen verminderen kan streaming- of decompressiekosten verhogen.
Als je target 60 FPS is, is je totale budget 16,6 ms per frame. Een ruwe verdeling kan eruitzien als:
Als CPU of GPU het budget overschrijdt, mis je het frame. Daarom praten teams over “CPU-bound” of “GPU-bound”—niet als labels, maar als manier om te beslissen waar de volgende milliseconde realistisch vandaan kan komen.
Het punt is niet het najagen van een vanity-metriek zoals “hoogste FPS op een high-end pc.” Het punt is definiëren wat snel genoeg betekent voor je doelgroep—hardwaredoelen, resolutie, batterij, thermiek en input-responsiviteit—en performance daarna als expliciete budgetten te behandelen die je kunt beheren en verdedigen.
Carmacks standaardactie is niet “optimaliseer,” maar “verifieer.” Realtime performanceproblemen zitten vol plausibele verhalen—GC-pauses, “trage shaders,” “te veel draw calls”—en de meeste daarvan zijn niet waar in jouw build op jouw hardware. Profiling vervangt intuïtie door bewijs.
Behandel profiling als een eersteklas feature, niet als een reddingsmiddel op het laatste moment. Leg frame-tijden vast, CPU- en GPU-timelines, en de tellingen die ze verklaren (triangles, draw calls, state changes, allocaties, cache-misses als je die kunt krijgen). Het doel is één vraag te beantwoorden: waar gaat de tijd werkelijk heen?
Een nuttig model: in elk traag frame is één ding de beperkende factor. Misschien zit de GPU vast op een zware pass, de CPU in animatie-updates, of de main thread in synchronisatie. Vind die beperking eerst; alles anders is ruis.
Een gedisciplineerde lus voorkomt dat je blijft zwalken:
Als de verbetering niet duidelijk is, ga ervan uit dat het niet hielp—want het overleeft waarschijnlijk de volgende contentwijziging niet.
Performancewerk is extra vatbaar voor zelfmisleiding:
Profiling eerst houdt je inspanning gefocust, je afwegingen gerechtvaardigd en je wijzigingen makkelijker te verdedigen in reviews.
Realtime performanceproblemen voelen rommelig omdat alles tegelijkertijd gebeurt: gameplay, rendering, streaming, animatie, UI, physics. Carmacks instinct is door de ruis heen te snijden en de dominante limiter te identificeren—het ene ding dat op dit moment je frame-tijd bepaalt.
De meeste vertragingen vallen in een paar bakken:
Het doel is niet om het te labelen voor een rapport—het is om de juiste hefboom te kiezen.
Enkele snelle experimenten kunnen je vertellen wat echt de limiter is:
Je wint zelden door 1% van tien systemen af te schaven. Vind de grootste kostenpost die elk frame terugkeert en pak die als eerste aan. Het verwijderen van één enkele 4 ms-offender verslaat weken van micro-optimalisaties.
Nadat je de grote steen hebt gefikst, wordt de volgende grootste zichtbaar. Dat is normaal. Behandel performancewerk als een lus: meet → verander → meet opnieuw → herprioriteer. Het doel is geen perfecte profile; het is gestage vooruitgang naar voorspelbare frame-tijd.
Het gemiddelde frame-tijd kan prima lijken terwijl de ervaring toch slecht is. Realtime graphics wordt beoordeeld op de slechtste momenten: het drop-frame tijdens een explosie, de hitch bij het betreden van een nieuwe kamer, de plotselinge stutter als een menu opent. Dat is tail-latency—zeldzame maar voelbare langzame frames.
Een game die meestal 16,6 ms draait (60 FPS) maar elke paar seconden naar 60–120 ms schiet, voelt “kapot” aan, ook al ziet het gemiddelde er nog redelijk uit. Mensen zijn gevoelig voor ritme. Eén lang frame breekt input-voorspelbaarheid, camerabeweging en audio/visuele synchronisatie.
Spikes komen vaak van werk dat niet gelijkmatig verdeeld is:
Het doel is duur werk voorspelbaar te maken:
Plot niet alleen een gemiddelde FPS-lijn. Leg per-frame timings vast en visualiseer:
Als je je slechtste 1% frames niet kunt uitleggen, heb je performance nog niet echt uitgelegd.
Performancewerk wordt makkelijker zodra je stopt met doen alsof je alles tegelijk kunt hebben. Carmacks stijl dwingt teams om de trade-off hardop te benoemen: wat kopen we, wat betalen we, en wie voelt het verschil?
De meeste beslissingen zitten op een paar assen:
Als een wijziging één as verbetert maar stilletjes drie anderen belast, documenteer het. “Dit voegt 0,4 ms GPU en 80 MB VRAM toe om zachtere schaduwen te krijgen” is een bruikbare uitspraak. “Het ziet er beter uit” is dat niet.
Realtime graphics gaat niet om perfectie; het gaat om consistent onder een target blijven. Spreek drempels af zoals:
Zodra het team het bijvoorbeeld eens is dat 16,6 ms op 1080p op de baseline GPU het doel is, worden discussies concreet: houdt deze feature ons onder het budget, of dwingt het een downgrade elders af?
Als je onzeker bent, kies opties die je kunt terugdraaien:
Omkeerbaarheid beschermt de planning. Je kunt het veilige pad uitrollen en het ambitieuze achter een toggle houden.
Vermijd over-engineering voor onzichtbare winst. Een 1% gemiddelde verbetering is zelden een maand werk waard—tenzij het stutter wegneemt, input-latency verbetert of een harde geheugencrash voorkomt. Geef prioriteit aan veranderingen die spelers direct merken en laat de rest wachten.
Performancewerk wordt veel makkelijker als het programma juist is. Een verrassende hoeveelheid “optimalisatietijd” gaat naar het achtervolgen van correctheidsbugs die er alleen uitzien als performanceproblemen: een onbedoelde O(N²)-lus door gedupliceerd werk, een renderpass die twee keer draait omdat een vlag niet reset, een geheugenlek dat langzaam frame-tijd verhoogt, of een race-condition die in willekeurige haperingen verandert.
Een stabiele, voorspelbare engine geeft schone metingen. Als gedrag tussen runs verandert, kun je profielen niet vertrouwen en beland je met optimaliseren van ruis.
Gedisciplineerde engineeringpraktijken helpen snelheid:
Veel frame-tijd spikes zijn “Heisenbugs”: ze verdwijnen als je logging toevoegt of door een debugger stapt. Het tegengif is deterministische reproductie.
Bouw een kleine, gecontroleerde test-harness:
Als een hitch verschijnt, wil je een knop die het 100 keer afspeelt—niet een vaag rapport dat het “soms gebeurt na 10 minuten.”
Performancewerk profiteert van kleine, reviewbare wijzigingen. Grote refactors creëren meerdere faalmodi tegelijk: regressies, nieuwe allocaties en verborgen extra kosten. Strakke diffs maken het makkelijker de enige vraag te beantwoorden die telt: wat veranderde in frame-tijd en waarom?
Discipline is hier geen bureaucratie—het is hoe je metingen betrouwbaar houdt zodat optimalisatie duidelijk wordt in plaats van bijgeloof.
Realtime performance draait niet alleen om “snellere code.” Het gaat om werk zo ordenen dat CPU en GPU er efficiënt mee kunnen omgaan. Carmack benadrukte herhaaldelijk een eenvoudige waarheid: de machine is letterlijk. Hij houdt van voorspelbare data en haat vermijdbare overhead.
Moderne CPU's zijn ongelooflijk snel—totdat ze wachten op geheugen. Als je data verspreid is over veel kleine objecten, besteedt de CPU tijd aan pointers volgen in plaats van rekenen.
Een bruikbaar mentaal model: ga niet tien keer naar de winkel voor tien items. Doe ze in één kar en loop de gangen één keer. In code betekent dat veelgebruikte waarden dicht bij elkaar houden (vaak in arrays of strak verpakte structs) zodat elke cacheline-fetch data binnenbrengt die je daadwerkelijk gebruikt.
Frequent alloceren creëert verborgen kosten: allocator-overhead, geheugenfragmentatie en onvoorspelbare pauzes als het systeem moet opruimen. Zelfs als elke allocatie “klein” is, kan een gestage stroom ervan een belasting worden die je elke frame betaalt.
Gebruikelijke fixes zijn opzettelijk saai: hergebruik buffers, pool objecten en geef de voorkeur aan langlevende allocaties voor hot paths. Het doel is niet slimheid—het is consistentie.
Vanzelfsprekend kan veel frame-tijd verdwijnen in administratieve taken: state-changes, draw calls, driver-werk, syscalls en thread-coördinatie.
Batching is de “grote kar” versie van rendering en simulatie. In plaats van veel kleine operaties, groepeer gelijkaardig werk zodat je minder vaak dure grenzen overgaat. Vaak verslaan overheadreducties micro-optimalisaties van shaders—omdat de machine minder tijd besteedt aan voorbereiden en meer tijd aan daadwerkelijk werken.
Performancewerk gaat niet alleen over sneller code—het gaat ook over minder code. Complexiteit kost je dagelijks: bugs zijn moeilijker te isoleren, fixes vereisen meer testen, itereren vertraagt omdat elke wijziging meer bewegende delen raakt, en regressies sluipen binnen via zelden gebruikte paden.
Een “slimme” oplossing kan elegant lijken totdat je op een deadline zit en een frame-spike alleen op één kaart, één GPU of één instellingencombinatie verschijnt. Elke extra feature-flag, fallback-pad en special-case vermenigvuldigt het aantal gedragingen dat je moet begrijpen en meten. Die complexiteit verspilt niet alleen ontwikkeltijd; het voegt vaak runtime-overhead toe (extra branches, allocaties, cache-misses, synchronisatie) die je pas ziet als het te laat is.
Een goede regel: als je het prestatiedeelmodel niet in een paar zinnen aan een collega kunt uitleggen, kun je het waarschijnlijk niet betrouwbaar optimaliseren.
Eenvoudige oplossingen hebben twee voordelen:
Soms is het snelste pad een feature verwijderen, een optie schrappen of meerdere varianten samenvoegen. Minder features betekent minder codepaden, minder state-combinaties en minder plekken waar performance stilletjes kan verslechteren.
Code verwijderen is ook een kwaliteitsstap: de beste bug is degene die je wegneemt door de module die hem kon genereren te verwijderen.
Patch (surgische fix) wanneer:
Refactor (structuur vereenvoudigen) wanneer:
Eenvoud is geen “minder ambitieus.” Het is kiezen voor ontwerpen die begrijpelijk blijven onder druk—wanneer performance het meest telt.
Performancewerk blijft alleen zitten als je kunt zien wanneer het verslapt. Dat is wat performance regression testing doet: een herhaalbare manier om te detecteren wanneer een nieuwe wijziging het product trager, minder soepel of zwaarder in geheugen maakt.
In tegenstelling tot functionele tests (die antwoord geven op “werkt het?”) beantwoorden regressietests “voelt het nog steeds even snel?” Een build kan 100% correct zijn en toch een slechte release als hij 4 ms extra frame-tijd toevoegt of laadtijden verdubbelt.
Je hebt geen lab nodig om te beginnen—alleen consistentie.
Kies een kleine set baseline-scenes die echt gebruik representeren: één GPU-zware view, één CPU-zware view en één “worst case” stress-scène. Houd ze stabiel en gescript zodat camerapad en input identiek zijn per run.
Draai tests op vaste hardware (een bekende PC/console/devkit). Als je drivers, OS of klok-instellingen verandert, leg dat vast. Behandel de hardware/software-combinatie als onderdeel van de test-fixture.
Bewaar resultaten in een geversioneerde geschiedenis: commit-hash, build-config, machine-ID en gemeten metrics. Het doel is niet een perfect getal—het is een betrouwbare trendlijn.
Geef de voorkeur aan metrics die moeilijk te betwisten zijn:
Definieer eenvoudige drempels (bijv. p95 frame-tijd mag niet meer dan 5% verslechteren).
Behandel regressies als bugs met een eigenaar en een deadline.
Eerst, bisect om de wijziging te vinden die het veroorzaakte. Als de regressie een release blokkeert, revert snel en land opnieuw met een fix.
Wanneer je het oplost, voeg guardrails toe: behoud de test, zet een opmerking in de code en documenteer het verwachte budget. De gewoonte is de winst—performance wordt iets dat je onderhoudt, niet iets dat je “later doet.”
“Uitbrengen” is geen datum in de kalender—het is een engineeringvereiste. Een systeem dat alleen goed draait in het lab, of alleen binnenkomt op tijd na een week handmatig fiddlen, is niet klaar. Carmacks denkwijze behandelt echte-wereld constraints (hardwarevariatie, rommelige content, onvoorspelbaar spelersgedrag) als onderdeel van de specificatie vanaf dag één.
Als je dichtbij release zit, is perfectie minder waardevol dan voorspelbaarheid. Definieer de niet-onderhandelbare zaken in duidelijke termen: target FPS, worst-case frame-time spikes, geheugenlimieten en laadtijden. Behandel alles wat die overschrijdt als een bug, niet als “polish.” Dit herkadert performancewerk van optionele optimalisatie naar betrouwbaarheid.
Niet alle vertragingen zijn even belangrijk. Los de meest zichtbare problemen eerst op:
Profilingdiscipline betaalt zich hier uit: je raadt niet welke kwestie “groot lijkt,” je kiest op basis van gemeten impact.
Late-cycle performancewerk is riskant omdat “fixes” nieuwe kosten kunnen introduceren. Gebruik gefaseerde rollouts: land eerst instrumentatie, zet de wijziging daarna achter een toggle, en vergroot geleidelijk de exposure. Geef voorkeur aan performance-veilige defaults—instellingen die frame-tijd beschermen zelfs als ze visuele kwaliteit iets verlagen—vooral bij auto-detect instellingen.
Als je meerdere platforms of tiers uitbrengt, behandel defaults als productkeuze: het is beter er iets minder fancy uit te zien dan onstabiel te voelen.
Vertaal trade-offs naar uitkomsten: “Dit effect kost 2 ms per frame op mid-tier GPU's, wat risico geeft om onder 60 FPS te zakken tijdens gevechten.” Bied opties, geen colleges: resolutie verlagen, shader vereenvoudigen, spawn-rate limiteren of een lager target accepteren. Constraints zijn makkelijker te accepteren als ze concrete keuzes zijn met duidelijke gebruikersimpact.
Je hebt geen nieuwe engine of rewrite nodig om Carmack-achtige performancegedachten te adopteren. Je hebt een herhaalbare lus nodig die performance zichtbaar, testbaar en moeilijk te breken maakt.
Meet: leg een basislijn vast (gemiddelde, p95, slechtste spike) voor frame-tijd en sleutel-subsystemen.
Budget: stel een per-frame budget vast voor CPU en GPU (en geheugen als je krap zit). Schrijf het budget naast het feature-doel.
Isoleer: reproduceer de kosten in een minimale scene of test. Als je het niet kunt reproduceren, kun je het niet betrouwbaar fixen.
Optimaliseer: verander één ding tegelijk. Geef voorkeur aan veranderingen die werk verminderen, niet alleen “maken het sneller.”
Valideer: profileer opnieuw, vergelijk deltas en controleer op kwaliteit- en correctheidsregressies.
Documenteer: noteer wat veranderde, waarom het hielp en waar je later op moet letten.
Als je deze gewoontes operationeel wilt maken in een team, is de sleutel het verlagen van frictie: snelle experimenten, herhaalbare harnesses en makkelijke rollbacks.
Koder.ai kan hier helpen wanneer je de omringende tooling bouwt—niet de engine zelf. Omdat het een vibe-coding platform is dat echte, exporteerbare broncode genereert (webapps in React; backends in Go met PostgreSQL; mobiel in Flutter), kun je snel interne dashboards opzetten voor frame-tijd percentielen, regressiegeschiedenis en “performance review” checklists, en vervolgens itereren via chat terwijl eisen evolueren. Snapshots en rollback passen praktisch bij de “verander één ding, meet opnieuw”-lus.
Als je meer praktische begeleiding wilt, bekijk /blog of zie hoe teams dit operationaliseren op /pricing.
Frame-tijd is de tijd per frame in milliseconden (ms) en geeft direct aan hoeveel werk de CPU/GPU deed.
Kies een target (bijv. 60 FPS) en zet dat om in een harde deadline (16,6 ms). Verdeel die deadline vervolgens in expliciete budgetten.
Voorbeeld startpunt:
Behandel dit als producteisen en pas aan op basis van platform, resolutie, thermiek en input-latency doelen.
Begin met herhaalbare tests en meet voordat je iets verandert.
Alleen als je weet waar de tijd naartoe gaat kun je bepalen wat je moet optimaliseren.
Voer snelle, gerichte experimenten uit die de limiter isoleren:
Schrijf geen systemen over voordat je de dominante kost in milliseconden kunt benoemen.
Omdat gebruikers de slechtste frames voelen, niet het gemiddelde.
Volg:
Een build die gemiddeld 16,6 ms draait maar op pieken naar 80 ms gaat, voelt nog steeds kapot aan.
Maak duur werk voorspelbaar en planbaar:
Log spikes zodat je ze kunt reproduceren en oplossen, niet alleen hopen dat ze verdwijnen.
Maak de afweging expliciet in cijfers en gebruikersimpact.
Gebruik uitspraken zoals:
Beslis op basis van afgesproken drempels:
Onstabiele correctheid maakt performance-data onbetrouwbaar.
Praktische stappen:
Als gedrag per run verandert, optimaliseer je ruis in plaats van echte knelpunten.
Het meeste ‘snelle code’-werk is in feite ‘geheugen en overhead’-werk.
Richt je op:
Vaak levert het verminderen van overhead grotere winst op dan het tunen van een rekencyclus.
Maak performance meetbaar, herhaalbaar en moeilijk om per ongeluk te breken.
Als je onzeker bent, kies voor omkeerbare beslissingen (feature flags, schaalbare instellingen).
Wanneer een regressie verschijnt: bisect, wijs een eigenaar toe en revert snel als het een release blokkeert.