Ein praktischer Leitfaden zur leistungsorientierten Denkweise, die John Carmack zugeschrieben wird: Profiling, Frame‑Time‑Budgets, Trade‑offs und das Ausliefern komplexer Echtzeitsysteme.

John Carmack wird oft wie eine Legende der Spiele‑Engines behandelt, aber das Nützliche ist nicht die Mythologie – es sind die wiederholbaren Gewohnheiten. Es geht nicht darum, den Stil einer Person zu kopieren oder „geniale Züge“ anzunehmen. Es geht um praktische Prinzipien, die zuverlässig zu schnellerer, flüssigerer Software führen, besonders wenn Deadlines und Komplexität wachsen.
Performance‑Engineering bedeutet, Software so zu gestalten, dass sie ein Geschwindigkeitsziel auf realer Hardware unter realen Bedingungen erreicht – ohne die Korrektheit zu brechen. Es ist nicht „mach es schnell um jeden Preis“. Es ist eine disziplinierte Schleife:
Diese Denkweise taucht in Carmacks Arbeit immer wieder auf: mit Daten streiten, Änderungen erklärbar halten und Ansätze bevorzugen, die wartbar sind.
Echtzeitgrafik ist unforgiving, weil sie für jedes Frame eine Deadline hat. Wenn du sie verfehlst, merkt der Nutzer es sofort als Ruckeln, Eingabeverzögerung oder ungleichmäßige Bewegung. Andere Software kann Ineffizienz hinter Queues, Ladebildschirmen oder Hintergrundarbeit verstecken. Ein Renderer kann nicht verhandeln: entweder du bist rechtzeitig fertig, oder nicht.
Deshalb verallgemeinern die Lektionen über Spiele hinaus. Jedes System mit strikten Latenzanforderungen – UI, Audio, AR/VR, Trading, Robotik – profitiert davon, in Budgets zu denken, Engpässe zu verstehen und Überraschungsspitzen zu vermeiden.
Du bekommst Checklisten, Heuristiken und Entscheidungsmuster, die du auf deine Arbeit anwenden kannst: wie man Frame‑Zeit‑/Latenz‑Budgets setzt, wie man vor dem Optimieren profilt, wie man die „eine Sache“ auswählt, die zu fixen ist, und wie man Regressionen verhindert, damit Performance Routine wird – nicht ein panischer Endspurt.
Carmack‑ähnliches Performance‑Denken beginnt mit einem einfachen Schalter: Hör auf, über „FPS“ als primäre Einheit zu reden und fang an, über Frame‑Zeit zu sprechen.
FPS ist reziprok („60 FPS“ klingt gut, „55 FPS“ klingt nah), aber die Nutzererfahrung wird von wie lange jedes Frame dauert bestimmt – und ebenso wichtig, wie konsistent diese Zeiten sind. Ein Sprung von 16,6 ms auf 33,3 ms ist sofort sichtbar, auch wenn dein durchschnittlicher FPS‑Wert noch respektabel aussieht.
Ein Echtzeitprodukt hat mehrere Budgets, nicht nur „rendern schneller“:
Diese Budgets interagieren. GPU‑Zeit durch CPU‑starkes Batching zu sparen kann schiefgehen, und weniger Speicher kann Streaming‑ oder Dekompressionskosten erhöhen.
Wenn dein Ziel 60 FPS ist, ist dein Gesamtbudget 16,6 ms pro Frame. Eine grobe Aufteilung könnte so aussehen:
Wenn entweder CPU oder GPU das Budget überschreitet, verpasst du das Frame. Deshalb reden Teams darüber, „CPU‑bound“ oder „GPU‑bound“ zu sein – nicht als Etikett, sondern als Weg, zu entscheiden, wo das nächste Millisekunden‑Gewinn realistisch herkommen kann.
Der Punkt ist nicht, einer Eitelkeitsmetrik wie „höchste FPS auf einer High‑End‑Kiste“ hinterherzulaufen. Der Punkt ist, zu definieren, was schnell genug für deine Zielgruppe ist – Hardwareziele, Auflösung, Akku‑Limits, Thermik und Eingabereaktivität – und Performance dann als explizite Budgets zu behandeln, die du verwalten und verteidigen kannst.
Carmacks Standardzug ist nicht „optimieren“, sondern „verifizieren“. Echtzeit‑Performanceprobleme strotzen vor einleuchtenden Geschichten – GC‑Pauses, „langsame Shader“, „zu viele Draw Calls“ – und die meisten davon sind in deinem Build auf deiner Hardware falsch. Profiling ersetzt Intuition durch Evidenz.
Behandle Profiling wie eine erstklassige Funktion, nicht als Rettungswerkzeug in letzter Minute. Erfasse Frame‑Zeiten, CPU‑ und GPU‑Timelines und die Zähler, die sie erklären (Dreiecke, Draw Calls, Zustandwechsel, Allokationen, Cache‑Misses, wenn du sie bekommst). Das Ziel ist, eine Frage zu beantworten: Wohin geht die Zeit wirklich?
Ein nützliches Modell: In jedem langsamen Frame ist eine Sache der begrenzende Faktor. Vielleicht ist die GPU in einem schweren Pass blockiert, die CPU in der Animations‑Update‑Phase oder der Main‑Thread an Synchronisationen fest. Finde diese Einschränkung zuerst; alles andere ist Rauschen.
Eine disziplinierte Schleife verhindert Thrash:
Wenn die Verbesserung nicht klar ist, nimm an, dass sie nicht geholfen hat – weil sie wahrscheinlich nicht stabil genug für nächsten Content ist.
Performance‑Arbeit ist besonders anfällig für Selbsttäuschung:
Profiling zuerst hält den Aufwand fokussiert, begründet die Trade‑offs und macht Änderungen in Reviews leichter zu verteidigen.
Echtzeit‑Performanceprobleme wirken chaotisch, weil alles gleichzeitig passiert: Gameplay, Rendering, Streaming, Animation, UI, Physik. Carmacks Instinkt ist, das Rauschen zu durchschneiden und den dominanten Limiter zu identifizieren – die eine Sache, die gerade deine Frame‑Zeit setzt.
Die meisten Verlangsamungen fallen in ein paar Buckets:
Der Punkt ist nicht das Label für einen Report, sondern den richtigen Hebel zu wählen.
Ein paar schnelle Experimente können dir sagen, was wirklich kontrolliert:
Man gewinnt selten, indem man 1% an zehn Systemen abschabt. Finde die größte wiederkehrende Kostenstelle und attackiere sie zuerst. Einen einzelnen 4‑ms‑Verursacher zu entfernen schlägt Wochen an Mikrooptimierungen.
Nachdem du den großen Stein entfernt hast, wird der nächste sichtbare Stein offenbar. Das ist normal. Behandle Performance‑Arbeit als Schleife: messen → ändern → neu messen → neu priorisieren. Das Ziel ist nicht ein perfektes Profil; es ist stetiger Fortschritt zu vorhersehbarer Frame‑Zeit.
Durchschnittliche Frame‑Zeit kann gut aussehen, während die Erfahrung sich trotzdem schlecht anfühlt. Echtzeitgrafik wird an den schlimmsten Momenten gemessen: dem ausgelassenen Frame bei einer großen Explosion, dem Hänger beim Betreten eines neuen Raums, dem plötzlichen Stottern beim Öffnen eines Menüs. Das ist Tail‑Latency – seltene, aber spürbare langsame Frames.
Ein Spiel, das die meiste Zeit bei 16,6 ms (60 FPS) läuft, aber alle paar Sekunden auf 60–120 ms spike't, fühlt sich „kaputt“ an, selbst wenn der Durchschnitt 20 ms bleibt. Menschen sind empfindlich gegenüber Rhythmus. Ein langer Frame zerstört Eingabevorhersagbarkeit, Kamerabewegung und Audio/Video‑Sync.
Spikes entstehen oft durch Arbeit, die nicht gleichmäßig verteilt ist:
Das Ziel ist, teure Arbeit vorhersehbar zu machen:
Plotte nicht nur eine Average‑FPS‑Kurve. Erfasse Per‑Frame‑Timings und visualisiere:
Wenn du die schlimmsten 1% deiner Frames nicht erklären kannst, hast du Leistung nicht wirklich verstanden.
Performance‑Arbeit wird leichter, sobald du aufhörst so zu tun, als könntest du alles gleichzeitig haben. Carmacks Stil drängt Teams dazu, den Tradeoff offen zu benennen: Was kaufen wir, was zahlen wir, und wer merkt den Unterschied?
Die meisten Entscheidungen liegen auf ein paar Achsen:
Wenn eine Änderung eine Achse verbessert, aber drei andere heimlich belastet, dokumentiere das. „Das fügt 0,4 ms GPU und 80 MB VRAM hinzu, um weichere Schatten zu bekommen“ ist eine brauchbare Aussage. „Sieht besser aus“ ist es nicht.
Echtzeitgrafik geht nicht um Perfektion; es geht darum, ein Ziel konsistent zu erreichen. Einigt euch auf Schwellen wie:
Sobald das Team z. B. 16,6 ms bei 1080p auf der Baseline‑GPU als Ziel hat, werden Argumente konkret: Hält dieses Feature uns unter Budget oder erzwingt es Abstriche anderswo?
Wenn du unsicher bist, wähle Optionen, die rückgängig zu machen sind:
Reversibilität schützt den Zeitplan. Du kannst den sicheren Pfad ausliefern und den ambitionierten hinter einem Schalter lassen.
Vermeide Überengineering für unsichtbare Verbesserungen. Eine 1%‑Durchschnittsverbesserung ist selten einen Monat Komplexität wert – es sei denn, sie beseitigt Stottern, reduziert Eingabelatenz oder verhindert einen harten Speichercrash. Priorisiere Änderungen, die Spieler sofort merken, und lass den Rest warten.
Performance‑Arbeit wird dramatisch einfacher, wenn das Programm richtig ist. Eine überraschende Menge „Optimierungszeit“ wird tatsächlich damit verbracht, Korrektheitsfehler nachzujagen, die nur wie Performance‑Probleme aussehen: eine versehentliche O(N²)‑Schleife durch duplizierte Arbeit, ein Renderpass, der zweimal läuft weil ein Flag nicht zurückgesetzt wurde, ein Memory‑Leak, das die Frame‑Zeit langsam erhöht, oder eine Race‑Condition, die zufälliges Stottern erzeugt.
Eine stabile, vorhersehbare Engine liefert saubere Messungen. Wenn sich das Verhalten zwischen Läufen ändert, kannst du Profiling nicht vertrauen und optimierst Rauschen.
Disziplinierte Engineering‑Praktiken helfen bei der Geschwindigkeit:
Viele Frame‑Time‑Spikes sind „Heisenbugs“: sie verschwinden, wenn du Logging einbaust oder im Debugger stepst. Das Gegenmittel ist deterministische Reproduktion.
Baue ein kleines, kontrolliertes Test‑Harness:
Wenn ein Hänger auftaucht, willst du einen Button, der ihn 100 Mal abspielt – nicht einen vagen Report, dass er „manchmal nach 10 Minuten“ passiert.
Speed‑Arbeit profitiert von kleinen, reviewbaren Änderungen. Große Refactors schaffen mehrere Fehlerquellen auf einmal: Regressionen, neue Allokationen und versteckte Mehrarbeit. Enge Diffs machen es einfacher, die einzige Frage zu beantworten, die zählt: Was hat sich an der Frame‑Zeit geändert und warum?
Disziplin ist hier keine Bürokratie – sie ist, wie du Messungen vertrauenswürdig hältst, sodass Optimierung klar statt abergläubisch wird.
Echtzeit‑Performance ist nicht nur „schneller Code“. Es geht darum, Arbeit so zu arrangieren, dass CPU und GPU sie effizient erledigen können. Carmack betonte immer wieder eine einfache Wahrheit: die Maschine ist literal. Sie liebt vorhersehbare Daten und hasst vermeidbaren Overhead.
Moderne CPUs sind unglaublich schnell – bis sie auf Speicher warten. Wenn deine Daten über viele kleine Objekte verstreut sind, rennt die CPU Zeiger nach, statt Rechnungen zu machen.
Ein nützliches Bild: Mach nicht zehn kleine Einkaufstouren für zehn Artikel. Pack sie in einen Wagen und geh einmal durch die Gänge. Im Code heißt das, häufig genutzte Werte nahe beieinander zu halten (oft in Arrays oder dicht gepackten Structs), damit jede Cache‑Line nützliche Daten bringt.
Häufige Allokationen erzeugen versteckte Kosten: Allocator‑Overhead, Speicherfragmentierung und unvorhersehbare Pausen, wenn das System aufräumt. Auch wenn jede Allokation „klein“ ist, kann ein stetiger Strom von ihnen zu einer Steuer werden, die du jedes Frame zahlst.
Gängige Fixes sind absichtlich langweilig: Puffer wiederverwenden, Objekte poolen und langfristige Allokationen für heiße Pfade bevorzugen. Das Ziel ist Konsistenz, nicht Cleverness.
Eine überraschende Menge Frame‑Zeit verschwindet in Buchhaltung: Zustandwechsel, Draw Calls, Treiberarbeit, Syscalls und Thread‑Koordination.
Batching ist die „große Einkaufstasche“ des Renderings und der Simulation. Statt vieler kleiner Operationen gruppiere ähnliche Arbeit, damit du teure Grenzen seltener überschreitest. Oft schlägt das Reduzieren von Overhead das Mikro‑Optimieren eines Shaders oder einer inneren Schleife – denn die Maschine verbringt weniger Zeit damit, sich vorzubereiten, und mehr Zeit damit, zu arbeiten.
Performance‑Arbeit ist nicht nur schneller Code – es geht auch darum, weniger Code zu haben. Komplexität kostet jeden Tag: Bugs brauchen länger zum Isolieren, Fixes erfordern mehr Tests, Iteration verlangsamt sich, weil jede Änderung mehr Teile berührt, und Regressionen schleichen sich über selten genutzte Pfade ein. Diese Komplexität verschwendet nicht nur Entwicklerzeit; sie fügt oft Laufzeit‑Overhead hinzu (zusätzliche Branches, Allokationen, Cache‑Misses, Synchronisation), der schwer sichtbar ist, bis es zu spät ist.
Ein „cleveres“ System kann elegant wirken, bis du auf Deadline ein Frame‑Spike hast, das nur auf einer Map, einer GPU oder einer Einstellungen‑Kombination auftritt. Jedes zusätzliche Feature‑Flag, jeder Fallback und jeder Spezialfall vervielfacht die Verhaltensweisen, die du verstehen und messen musst. Diese Komplexität verschwendet nicht nur Entwicklungszeit; sie fügt oft Laufzeitkosten hinzu, die schwer zu sehen sind.
Eine gute Regel: Wenn du das Performance‑Modell nicht in ein paar Sätzen einem Teamkollegen erklären kannst, kannst du es wahrscheinlich nicht zuverlässig optimieren.
Einfache Lösungen haben zwei Vorteile:
Manchmal ist der schnellste Weg, ein Feature zu entfernen, eine Option zu streichen oder Varianten zu konsolidieren. Weniger Features heißt weniger Codepfade, weniger Zustandskombinationen und weniger Orte, an denen Performance stillschweigend schlechter werden kann.
Code zu löschen ist auch eine Qualitätsmaßnahme: der beste Bug ist der, den du gar nicht mehr haben kannst, weil das Modul entfernt wurde.
Patch (surgische Korrektur), wenn:
Refactor (Struktur vereinfachen), wenn:
Einfachheit ist nicht „weniger ambitioniert“. Es ist die Wahl von Designs, die unter Druck verständlich bleiben – wenn Performance am wichtigsten ist.
Performance‑Arbeit bleibt nur, wenn du merkst, wann sie nachlässt. Genau das ist Performance‑Regressionstesting: eine reproduzierbare Methode, um zu erkennen, wann eine Änderung das Produkt langsamer, weniger flüssig oder speicherhungriger macht. Im Gegensatz zu Funktionstests („funktioniert es?“) beantworten Regressionstests „fühlt es sich noch gleich schnell an?" Ein Build kann 100% korrekt sein und trotzdem ein schlechter Release, wenn er 4 ms Frame‑Zeit hinzufügt oder Ladezeiten verdoppelt.
Du brauchst kein Labor, um anzufangen – nur Konsistenz.
Wähle einen kleinen Satz Baseline‑Szenen, die reale Nutzung repräsentieren: eine GPU‑schwere Ansicht, eine CPU‑schwere Ansicht und eine „Worst‑Case“‑Stressszene. Halte sie stabil und skriptiert, sodass Kamerapfad und Eingaben bei jedem Lauf identisch sind.
Führe Tests auf fester Hardware (bekannter PC/Console/Devkit) aus. Wenn du Treiber, OS oder Takteinstellungen änderst, notiere es. Behandle die Hardware/Software‑Kombination als Teil des Test‑Fixtures.
Speichere Ergebnisse in einer versionierten Historie: Commit‑Hash, Build‑Config, Maschinen‑ID und gemessene Metriken. Das Ziel ist keine perfekte Zahl, sondern eine vertrauenswürdige Trendlinie.
Bevorzuge schwer zu bestreitende Metriken:
Definiere einfache Schwellen (z. B. p95 Frame‑Time darf nicht um mehr als 5% regressieren).
Behandle Regressionen wie Bugs mit Owner und Deadline.
Zuerst bisecten, um die Änderung zu finden, die sie eingeführt hat. Wenn die Regression einen Release blockiert, reverte schnell und lande die Änderung mit Fix neu.
Wenn du sie behoben hast, füge Guardrails hinzu: behalte den Test, füge eine Notiz im Code hinzu und dokumentiere das erwartete Budget. Die Gewohnheit ist der Gewinn – Performance wird etwas, das du erhältst, nicht etwas, das du „später machst."
„Shippen“ ist kein Kalenderevent – es ist eine technische Anforderung. Ein System, das nur im Labor gut läuft oder erst nach einer Woche manuellen Tweakings die Frame‑Zeit erreicht, ist nicht fertig. Carmacks Denkweise behandelt reale Beschränkungen (Hardware‑Vielfalt, unordentlicher Content, unvorhersehbares Spieler‑Verhalten) von Tag eins als Teil der Spezifikation.
Wenn du nah an Release bist, ist Vorhersehbarkeit wertvoller als Perfektion. Definiere die Non‑Negotiables klar: Ziel‑FPS, Worst‑Case‑Frame‑Time‑Spikes, Speicherlimits, Ladezeiten. Behandle alles, was sie verletzt, als Bug, nicht als „Polish“. Das rückt Performance‑Arbeit von optionaler Optimierung zu Zuverlässigkeitsarbeit.
Nicht alle Verlangsamungen sind gleich wichtig. Behebe zuerst die sichtbarsten Probleme:
Profiling‑Disziplin zahlt sich aus: du rätst nicht, welches Problem „groß“ scheint, sondern wählst basierend auf gemessenem Impact.
Spätere Performance‑Arbeit ist riskant, weil „Fixes" neue Kosten einführen können. Nutze gestufte Rollouts: land Instrumentation zuerst, dann die Änderung hinter einem Toggle, dann weite Exposure. Bevorzuge performance‑sichere Defaults – Einstellungen, die Frame‑Zeit schützen, auch wenn sie die visuelle Qualität leicht reduzieren – besonders bei autodetektierten Konfigurationen.
Wenn du mehrere Plattformen/Tiers auslieferst, betrachte Defaults als Produktentscheidung: Es ist besser, etwas weniger fancy auszusehen, als instabil zu wirken.
Übersetze Tradeoffs in Outcomes: „Dieser Effekt kostet 2 ms jedes Frame auf Mittelklasse‑GPUs, was uns in Kämpfen unter 60 FPS bringen könnte.“ Biete Optionen, keine Vorträge: Auflösung reduzieren, Shader vereinfachen, Spawn‑Rate limitieren oder ein niedrigeres Ziel akzeptieren. Beschränkungen sind leichter akzeptierbar, wenn sie als konkrete Entscheidungen mit klarem Nutzerimpact präsentiert werden.
Du brauchst keine neue Engine oder einen Rewrite, um Carmack‑ähnliches Performance‑Denken zu übernehmen. Du brauchst eine wiederholbare Schleife, die Performance sichtbar, testbar und schwer versehentlich zu brechen macht.
Messen: erfasse eine Basislinie (Durchschnitt, p95, schlimmster Spike) für Frame‑Zeit und Schlüssel‑Subsysteme.
Budgetieren: setze ein Per‑Frame‑Budget für CPU und GPU (und Speicher, wenn knapp). Schreibe das Budget neben das Feature‑Ziel.
Isolieren: reproduziere die Kosten in einer minimalen Szene oder einem Test. Wenn du es nicht reproduzieren kannst, kannst du es nicht zuverlässig fixen.
Optimieren: ändere eine Sache zur Zeit. Bevorzuge Änderungen, die Arbeit reduzieren, nicht nur „schneller machen".
Validieren: profiliere erneut, vergleiche Deltas und prüfe auf Qualitäts‑ oder Korrektheitsregressionen.
Dokumentieren: notiere, was sich geändert hat, warum es geholfen hat und worauf in Zukunft zu achten ist.
Frame-Zeit ist die Zeit pro Frame in Millisekunden (ms) und bildet direkt ab, wie viel Arbeit CPU/GPU erledigt haben.
Wähle ein Ziel (z. B. 60 FPS) und wandle es in eine harte Deadline um (16,6 ms). Teile diese Deadline dann in explizite Budgets auf.
Beispiel als Ausgangspunkt:
Behandle diese Zahlen als Produktanforderungen und passe sie für Plattform, Auflösung, Thermik und Eingabeverzögerungsziele an.
Fang damit an, deine Tests reproduzierbar zu machen und messe, bevor du etwas änderst.
Erst wenn du weißt, wohin die Zeit geht, solltest du entscheiden, was zu optimieren ist.
Führe schnelle, gezielte Experimente durch, die den limitierenden Faktor isolieren:
Weil Nutzer die schlechtesten Frames wahrnehmen, nicht den Durchschnitt.
Verfolge:
Ein Build, der im Mittel 16,6 ms läuft, aber auf 80 ms spike't, fühlt sich trotzdem kaputt an.
Mach teure Arbeit vorhersehbar und planbar:
Außerdem: logge Spikes, damit du sie reproduzieren und beheben kannst, statt zu hoffen, dass sie verschwinden.
Mach den Tradeoff zahlenbasiert und zeige den Nutzwert.
Nutze Aussagen wie:
Entscheide anhand vereinbarter Schwellenwerte:
Weil instabile Korrektheit Performance‑Daten unzuverlässig macht.
Praktische Schritte:
Wenn sich Verhalten von Lauf zu Lauf ändert, optimierst du Rauschen statt Engpässe.
Die meisten „schnellen“ Verbesserungen sind eigentlich Speicher‑ und Overhead‑Arbeit.
Konzentriere dich auf:
Oft bringt das Reduzieren von Overhead größere Gewinne als das Feintuning einer inneren Berechnung.
Mach Performance messbar, reproduzierbar und schwer versehentlich zu brechen.
Vermeide System‑Rewrites, bis du den dominanten Kostenfaktor in Millisekunden benennen kannst.
Wenn du unsicher bist, bevorzuge reversible Entscheidungen (Feature‑Flags, skalierbare Qualitätsstufen).
Wenn eine Regression auftritt: bisect, vergebe einen Owner und reverte schnell, falls sie Release‑blockierend ist.