Guía práctica sobre la mentalidad orientada al rendimiento asociada a John Carmack: perfilado, presupuestos de tiempo por frame, tradeoffs y cómo enviar sistemas complejos en tiempo real.

John Carmack a menudo se trata como una leyenda de los motores de juego, pero la parte útil no es la mitología: son los hábitos repetibles. No se trata de copiar el estilo de una persona ni de asumir "movidas de genio". Se trata de principios prácticos que conducen de forma fiable a software más rápido y fluido, especialmente cuando se acumulan plazos y complejidad.
La ingeniería de rendimiento significa hacer que el software cumpla un objetivo de velocidad en hardware real, bajo condiciones reales, sin romper la corrección. No es “hacerlo rápido a cualquier precio”. Es un bucle disciplinado:
Esa mentalidad aparece en el trabajo de Carmack una y otra vez: discutir con datos, mantener los cambios explicables y preferir enfoques que puedas mantener.
Los gráficos en tiempo real son implacables porque tienen una fecha límite cada frame. Si la fallas, el usuario la siente inmediatamente como stutter, latencia de entrada o movimiento irregular. Otro software puede ocultar ineficiencias con colas, pantallas de carga o trabajo en segundo plano. Un renderizador no puede negociar: o terminas a tiempo, o no.
Por eso las lecciones se generalizan más allá de los juegos. Cualquier sistema con requisitos estrictos de latencia—UI, audio, AR/VR, trading, robótica—se beneficia de pensar en presupuestos, entender los cuellos de botella y evitar picos sorpresa.
Obtendrás listas de verificación, heurísticas y patrones de decisión que puedes aplicar a tu trabajo: cómo fijar presupuestos de tiempo por frame (o latencia), cómo perfilar antes de optimizar, cómo elegir la “una cosa” a arreglar y cómo prevenir regresiones para que el rendimiento se convierta en rutina, no en pánico de última hora.
El pensamiento de rendimiento al estilo Carmack empieza por un cambio simple: deja de hablar de “FPS” como la unidad principal y empieza a hablar de tiempo por frame.
El FPS es recíproco ("60 FPS" suena bien, "55 FPS" parece cercano), pero la experiencia del usuario la determina cuánto tarda cada frame—y, igual de importante, cuán consistentes son esos tiempos. Un salto de 16,6 ms a 33,3 ms se ve al instante incluso si tu FPS medio todavía parece respetable.
Un producto en tiempo real tiene múltiples presupuestos, no solo “renderizar más rápido”:
Estos presupuestos interactúan. Ahorrar tiempo de GPU añadiendo batching pesado en CPU puede salir mal, y reducir memoria puede aumentar costos de streaming o descompresión.
Si tu objetivo es 60 FPS, tu presupuesto total es 16,6 ms por frame. Una distribución aproximada podría ser:
Si CPU o GPU exceden el presupuesto, fallas el frame. Por eso los equipos hablan de estar “limitados por CPU” o “limitados por GPU”—no como etiquetas, sino como forma de decidir de dónde puede venir el siguiente milisegundo.
El objetivo no es perseguir una métrica de vanidad como “el FPS más alto en un PC de gama alta”. El objetivo es definir qué es lo suficientemente rápido para tu audiencia—objetivos de hardware, resolución, batería, térmicas y capacidad de respuesta—y luego tratar el rendimiento como presupuestos explícitos que puedas gestionar y defender.
El movimiento por defecto de Carmack no es “optimizar”, es “verificar”. Los problemas de rendimiento en tiempo real están llenos de historias plausibles—pausas del GC, “shaders lentos”, “demasiados draw calls”—y la mayoría de ellas están equivocadas en tu build sobre tu hardware. El perfilado es cómo reemplazas la intuición por evidencia.
Trata el perfilado como una característica de primera clase, no como una herramienta de rescate de última hora. Captura tiempos por frame, líneas de tiempo de CPU y GPU, y las cuentas que los explican (triángulos, draw calls, cambios de estado, asignaciones, fallos de caché si puedes obtenerlos). El objetivo es responder una pregunta: ¿a dónde va realmente el tiempo?
Un modelo útil: en cada frame lento, una cosa es el factor limitante. Puede ser la GPU atascada en un pase pesado, la CPU atascada en la actualización de animaciones o el hilo principal bloqueado en sincronización. Encuentra esa restricción primero; todo lo demás es ruido.
Un bucle disciplinado te evita marearte:
Si la mejora no es clara, asume que no ayudó—porque probablemente no sobrevivirá al próximo contenido.
El trabajo de rendimiento es especialmente vulnerable al autoengaño:
Perfilando primero mantienes el esfuerzo enfocado, los tradeoffs justificados y los cambios más fáciles de defender en revisión.
Los problemas de rendimiento en tiempo real se sienten desordenados porque todo ocurre a la vez: gameplay, renderizado, streaming, animación, UI, física. El instinto de Carmack es cortar el ruido e identificar el limitador dominante—la única cosa que actualmente fija tu tiempo por frame.
La mayoría de las ralentizaciones caen en unos pocos cubos:
El punto no es etiquetarlo para un informe—es tirar de la palanca correcta.
Unos pocos experimentos rápidos pueden decirte qué está realmente en control:
Rara vez ganas rebajando un 1% en diez sistemas. Encuentra el mayor coste que se repite cada frame y ataca eso primero. Eliminar un único culpable de 4 ms vence semanas de microoptimizaciones.
Tras arreglar la gran roca, la siguiente gran roca se hace visible. Eso es normal. Trata el trabajo de rendimiento como un bucle: medir → cambiar → volver a medir → re-priorizar. El objetivo no es un perfil perfecto; es progreso constante hacia un tiempo por frame predecible.
El tiempo medio por frame puede verse bien mientras la experiencia sigue siendo mala. Los gráficos en tiempo real se juzgan por los peores momentos: el frame perdido durante una gran explosión, el enganche al entrar en una sala nueva, el stutter repentino cuando se abre un menú. Eso es latencia en la cola—frames lentos raros pero lo suficientemente frecuentes como para que los usuarios lo noten.
Un juego que corre a 16,6 ms la mayor parte del tiempo (60 FPS) pero que hace picos a 60–120 ms cada pocos segundos se sentirá “roto”, incluso si la media aún sale como 20 ms. Los humanos son sensibles al ritmo. Un solo frame largo rompe la predictibilidad de la entrada, el movimiento de cámara y la sincronía audio/visual.
Los picos suelen venir de trabajo que no está distribuido homogéneamente:
El objetivo es hacer el trabajo caro predecible:
No solo traces una línea de FPS promedio. Registra tiempos por frame y visualiza:
Si no puedes explicar tus peores 1% de frames, realmente no has explicado el rendimiento.
El trabajo de rendimiento se facilita en el momento en que dejas de fingir que puedes tenerlo todo. El estilo de Carmack empuja a los equipos a nombrar la compensación en voz alta: ¿qué compramos, qué pagamos y quién nota la diferencia?
La mayoría de decisiones se sitúan en unos pocos ejes:
Si un cambio mejora un eje pero afecta silenciosamente a tres más, documéntalo. “Esto añade 0,4 ms GPU y 80 MB VRAM para obtener sombras más suaves” es una declaración usable. “Se ve mejor” no lo es.
Los gráficos en tiempo real no van de perfección; van de alcanzar un objetivo de forma consistente. Acordad umbrales como:
Una vez que el equipo acuerde que, por ejemplo, 16,6 ms a 1080p en la GPU base es la meta, las discusiones se vuelven concretas: ¿esta característica nos mantiene dentro del presupuesto o obliga a degradar otra cosa?
Cuando no estés seguro, elige opciones que puedas deshacer:
La reversibilidad protege el calendario. Puedes enviar la vía segura y mantener la ambiciosa detrás de un interruptor.
Evita sobreingenierizar ganancias invisibles. Una mejora del 1% de la media rara vez merece un mes de complejidad—a menos que elimine stutter, arregle latencia de entrada o prevenga un crash por memoria. Prioriza los cambios que los jugadores notan inmediatamente y deja el resto para después.
El trabajo de rendimiento se vuelve mucho más fácil cuando el programa está correcto. Una cantidad sorprendente de tiempo de “optimización” en realidad se dedica a perseguir bugs de corrección que solo parecen problemas de rendimiento: un bucle O(N²) accidental por trabajo duplicado, un pase de render que se ejecuta dos veces porque una bandera no se reseteó, una fuga de memoria que aumenta lentamente el tiempo por frame o una condición de carrera que se convierte en stutter aleatorio.
Un motor estable y predecible te da mediciones limpias. Si el comportamiento cambia entre ejecuciones, no puedes confiar en perfiles y acabarás optimizando ruido.
Las prácticas de ingeniería disciplinadas ayudan al rendimiento:
Muchos picos de tiempo por frame son “Heisenbugs”: desaparecen cuando añades logging o depuras paso a paso. El antídoto es la reproducción determinista.
Construye un pequeño harness de prueba controlado:
Cuando aparece un enganche, quieres un botón que lo reproduzca 100 veces—no un informe vago de que “a veces pasa después de 10 minutos”.
El trabajo de rendimiento se beneficia de cambios pequeños y revisables. Refactors grandes crean múltiples modos de fallo a la vez: regresiones, nuevas asignaciones y trabajo extra oculto. Diffs ajustados hacen más fácil responder la única pregunta que importa: ¿qué cambió en el tiempo por frame y por qué?
La disciplina no es burocracia aquí—es cómo mantienes las mediciones confiables para que la optimización sea directa en lugar de supersticiosa.
El rendimiento en tiempo real no es solo “código más rápido”. Es organizar el trabajo para que la CPU y la GPU puedan hacerlo eficientemente. Carmack enfatizó repetidamente una verdad simple: la máquina es literal. Ama datos predecibles y odia overhead evitable.
Las CPU modernas son increíblemente rápidas—hasta que esperan por memoria. Si tus datos están dispersos en muchos objetos pequeños, la CPU pasa tiempo siguiendo punteros en lugar de hacer matemáticas.
Un modelo mental útil: no hagas diez viajes de compras por diez artículos. Ponlos en un carro y recorre los pasillos una vez. En código, eso significa mantener valores usados frecuentemente juntos (a menudo en arrays o structs empaquetados) para que cada fetch de caché traiga datos que realmente usarás.
Las asignaciones frecuentes crean costes ocultos: overhead del allocator, fragmentación de memoria y pausas impredecibles cuando el sistema tiene que ordenar. Incluso si cada asignación es “pequeña”, un flujo constante de ellas puede convertirse en un impuesto que pagas cada frame.
Arreglos comunes son intencionalmente aburridos: reutiliza buffers, usa pools de objetos y prefiere asignaciones de larga vida en caminos calientes. El objetivo no es la astucia—es la consistencia.
Una cantidad sorprendente de tiempo por frame puede desaparecer en labores administrativas: cambios de estado, draw calls, trabajo del driver, syscalls y coordinación de hilos.
El batching es la versión de “un gran carro” para renderizado y simulación. En vez de emitir muchas operaciones pequeñas, agrupa trabajo similar para cruzar fronteras costosas menos veces. A menudo, cortar overhead vence a micro-optimizar un shader o un bucle interior—porque la máquina pasa menos tiempo preparándose y más tiempo trabajando.
El trabajo de rendimiento no es solo código más rápido—es también tener menos código. La complejidad tiene un coste que pagas cada día: los bugs tardan más en aislarse, las correcciones requieren pruebas más cuidadosas, la iteración se ralentiza porque cada cambio toca más piezas y las regresiones aparecen por caminos raramente usados.
Un sistema “ingenioso” puede parecer elegante hasta que estás contra el plazo y un pico aparece solo en un mapa, una GPU o una combinación de ajustes. Cada flag extra, camino de fallback y caso especial multiplica el número de comportamientos que necesitas entender y medir. Esa complejidad no solo desperdicia tiempo de desarrollador; a menudo añade overhead en tiempo de ejecución (branches extra, asignaciones, fallos de caché, sincronización) que es difícil de ver hasta que es demasiado tarde.
Una buena regla: si no puedes explicar el modelo de rendimiento a un compañero en unas pocas frases, probablemente no puedas optimizarlo de forma fiable.
Las soluciones simples tienen dos ventajas:
A veces el camino más rápido es eliminar una característica, cortar una opción o colapsar varias variantes en una. Menos features significa menos paths de código, menos combinaciones de estado y menos lugares donde el rendimiento puede degradarse silenciosamente.
Borrar código también es una jugada de calidad: el mejor bug es el que eliminas borrando el módulo que podría generarlo.
Parche (fix quirúrgico) cuando:
Refactor cuando:
La simplicidad no es “menos ambicioso”. Es elegir diseños que sigan siendo comprensibles bajo presión—cuando el rendimiento importa más.
El trabajo de rendimiento solo perdura si puedes detectar cuándo se desvía. Eso es lo que es la prueba de regresión de rendimiento: una forma repetible de detectar cuando un cambio hace el producto más lento, menos fluido o más pesado en memoria.
A diferencia de las pruebas funcionales (que responden “¿funciona?”), las pruebas de regresión responden “¿sigue pareciendo igual de rápido?” Una build puede ser 100% correcta y aun así ser una mala release si añade 4 ms de tiempo por frame o duplica los tiempos de carga.
No necesitas un laboratorio para empezar—solo consistencia.
Elige un pequeño conjunto de escenas base que representen uso real: una vista pesada en GPU, una pesada en CPU y una escena de estrés “peor caso”. Mantenlas estables y guioniza la ruta de cámara y la entrada para que cada ejecución sea idéntica.
Ejecuta tests en hardware fijo (un PC/consola/devkit conocido). Si cambias drivers, SO o ajustes de reloj, regístralo. Trata la combinación hardware/software como parte del fixture de prueba.
Almacena resultados en un historial versionado: hash de commit, config de build, ID de máquina y métricas medidas. El objetivo no es un número perfecto—es una línea de tendencia confiable.
Prefiere métricas difíciles de discutir:
Define umbrales simples (por ejemplo: p95 no debe degradarse más del 5%).
Trata las regresiones como bugs con dueño y fecha límite.
Primero, bisecta para encontrar el cambio que la introdujo. Si la regresión bloquea una release, revierte rápido y re-landa con la corrección.
Cuando lo arregles, añade guardarraíles: mantiene la prueba, añade una nota en el código y documenta el presupuesto esperado. El hábito es la victoria—el rendimiento pasa a ser algo que mantienes, no algo que “harás más tarde”.
“Enviar” no es un evento en el calendario—es un requisito de ingeniería. Un sistema que solo funciona bien en el laboratorio, o que solo alcanza el tiempo por frame tras una semana de ajustes manuales, no está listo. La mentalidad de Carmack trata las limitaciones del mundo real (variedad de hardware, contenido desordenado, comportamiento impredecible de jugadores) como parte de la especificación desde el día uno.
Cuando te acercas al lanzamiento, la perfección vale menos que la predictibilidad. Define lo no negociable en términos simples: FPS objetivo, picos máximo de tiempo por frame, límites de memoria y tiempos de carga. Luego trata todo lo que los viole como un bug, no como “pulido”. Esto recuadra el trabajo de rendimiento de optimización opcional a trabajo de fiabilidad.
No todas las ralentizaciones importan por igual. Arregla primero los problemas visibles para el usuario:
La disciplina del perfilado paga aquí: no estás adivinando qué problema “parece grande”, eliges según impacto medido.
El trabajo de rendimiento en ciclo tardío es arriesgado porque las “soluciones” pueden introducir nuevos costes. Usa despliegues por etapas: primero instrumenta, luego coloca el cambio detrás de un toggle, y después amplía la exposición. Prefiere valores por defecto que protejan el tiempo por frame aunque reduzcan ligeramente la calidad visual—especialmente para configuraciones auto-detectadas.
Si envías múltiples plataformas o tiers, trata los defaults como decisión de producto: mejor verse un poco menos espectacular que sentirse inestable.
Traduce los tradeoffs en resultados: “Este efecto cuesta 2 ms cada frame en GPUs de gama media, lo que arriesga bajar de 60 FPS durante los combates.” Ofrece opciones, no lecciones: reducir resolución, simplificar el shader, limitar la tasa de spawn o aceptar un objetivo inferior. Las limitaciones son más fáciles de aceptar cuando se enmarcan como elecciones concretas con impacto claro para el usuario.
No necesitas un motor nuevo ni reescrituras para adoptar el pensamiento de rendimiento al estilo Carmack. Necesitas un bucle repetible que haga el rendimiento visible, verificable y difícil de romper accidentalmente.
Medir: captura una línea base (media, p95, peor pico) del tiempo por frame y subsistemas clave.
Presupuestar: fija un presupuesto por frame para CPU y GPU (y memoria si estás apretado). Escribe el presupuesto junto al objetivo de la feature.
Aislar: reproduce el coste en una escena mínima o test. Si no puedes reproducirlo, no puedes arreglarlo de forma fiable.
Optimizar: cambia una cosa a la vez. Prefiere cambios que reduzcan trabajo, no solo “hacerlo más rápido”.
Validar: vuelve a perfilar, compara deltas y comprueba regresiones de calidad y corrección.
Documentar: registra qué cambió, por qué ayudó y qué vigilar en el futuro.
Si quieres operacionalizar estos hábitos a través de un equipo, la clave es reducir fricción: experimentos rápidos, harnesses repetibles y rollbacks sencillos.
Koder.ai puede ayudar aquí cuando estés construyendo la herramienta de soporte—no el motor en sí. Como plataforma vibe-coding que genera código real y exportable (apps web en React; backends en Go con PostgreSQL; móvil en Flutter), puedes crear rápidamente dashboards internos para percentiles de tiempo por frame, historial de regresiones y plantillas de "revisión de rendimiento", y luego iterar vía chat según evolucionen los requisitos. Las snapshots y el rollback también encajan con el bucle de “cambia una cosa, vuelve a medir”.
Si quieres más orientación práctica, visita /blog o consulta cómo equipos lo operacionalizan en /pricing.
El tiempo por frame es el tiempo por fotograma en milisegundos (ms), y se corresponde directamente con la cantidad de trabajo que hizo la CPU/GPU.
Elige un objetivo (por ejemplo, 60 FPS) y conviértelo en un plazo rígido (16,6 ms). Luego divide ese plazo en presupuestos explícitos.
Ejemplo de punto de partida:
Trata estos valores como requisitos de producto y ajústalos según la plataforma, la resolución, las limitaciones térmicas y los objetivos de latencia de entrada.
Empieza por hacer tus pruebas reproducibles y luego mide antes de cambiar nada.
Solo después de saber a dónde va el tiempo deberías decidir qué optimizar.
Haz experimentos rápidos y dirigidos que aíslen el limitador:
Porque los usuarios sienten los peores fotogramas, no la media.
Rastrea:
Una build que promedia 16,6 ms pero tiene picos de 80 ms seguirá sintiéndose rota.
Haz que el trabajo caro sea predecible y programado:
También registra los picos para poder reproducirlos y arreglarlos, no solo esperar que desaparezcan.
Haz explícitas las compensaciones en números y en impacto para el usuario.
Usa afirmaciones como:
Luego decide según los umbrales acordados:
Porque la corrección inestable hace que los datos de rendimiento no sean fiables.
Pasos prácticos:
Si el comportamiento cambia entre ejecuciones, acabarás optimizando ruido en lugar de cuellos de botella.
El trabajo de “código rápido” suele ser, en realidad, trabajo sobre memoria y overhead.
Enfócate en:
A menudo, cortar overhead produce ganancias mayores que retocar un bucle interior.
Haz que el rendimiento sea medible, reproducible y difícil de romper sin querer.
Evita reescribir sistemas hasta que puedas nombrar el coste dominante en milisegundos.
Si dudas, prefiere decisiones reversibles (feature flags, niveles de calidad escalables).
Cuando aparezca una regresión: bisecta, asigna propietario y revierte rápido si bloquea la salida.