Un juego que funciona no es lo mismo que un juego que funciona bien. Hoy aprendemos a medir dónde se va el tiempo de CPU, y a recuperarlo con cambios concretos.
En la lección anterior nos enfocamos en que el juego multijugador funcionara correctamente. Ahora toca la pregunta de rendimiento — y la regla más importante de toda esta lección es: nunca optimices a ciegas. La intuición sobre "qué es lento" en C++ falla sorprendentemente seguido. Necesitas medir con datos reales antes de cambiar una sola línea.
Para mediciones más profundas, herramientas como `perf` en Linux o Valgrind/Callgrind te muestran exactamente qué función consume más tiempo de CPU, sin adivinar.
Antes de tocar una sola línea de tu código, asegúrate de estar compilando en modo release con optimizaciones activadas. La diferencia entre compilar sin optimizar y con `-O2` puede ser de varias veces en rendimiento, sin cambiar nada de tu lógica.
En CMake, esto se controla con `CMAKE_BUILD_TYPE`:
Un error común de principiante es medir el rendimiento de su juego compilado en modo debug y sacar conclusiones equivocadas — código sin optimizar puede ser 5 a 10 veces más lento que el mismo código optimizado.
La CPU no lee memoria de a un byte; lee en bloques (líneas de caché, típicamente 64 bytes). Si tus datos están dispersos en memoria (por ejemplo, un `std::vector<Enemigo*>` con punteros a objetos alocados en cualquier lugar del heap), cada acceso puede ser un "cache miss" costoso. Si en cambio tienes un `std::vector<Enemigo>` con los objetos contiguos en memoria, recorrerlos es mucho más rápido.
Este patrón de organizar datos por cómo se acceden (en vez de por conveniencia orientada a objetos) se conoce como "diseño orientado a datos" (data-oriented design), y es una de las técnicas de mayor impacto en rendimiento para juegos con muchas entidades.
Llamar a `new`, `malloc`, o incluso hacer `push_back` en un `std::vector` que necesita crecer, son operaciones relativamente costosas si ocurren muchas veces por segundo. La técnica estándar es reservar la memoria una sola vez, por adelantado.
Un "object pool" como este evita por completo las asignaciones dinámicas durante el juego: reutilizas espacios ya reservados en vez de crear y destruir objetos constantemente, algo especialmente notorio en juegos con muchos proyectiles o partículas.
En la lección 3 usamos `delta` variable para mover objetos de forma proporcional al tiempo real. Esto es correcto para renderizado, pero puede introducir inconsistencias físicas sutiles (colisiones que se comportan distinto según el framerate). La solución estándar en motores serios es un paso de tiempo fijo para la física, desacoplado del framerate de dibujado.
La forma más efectiva de usar un asistente de IA en esta lección no es pedirle "optimiza mi juego" en abstracto — sin datos de perfilado, cualquier sugerencia es una adivinanza. En cambio, corre `perf` o Valgrind primero, y pégale a tu asistente el reporte real: qué función consume más tiempo, cuántas veces se llama, y el código fuente de esa función específica. Con esos datos concretos, un asistente de IA puede sugerir cambios específicos y explicarte el porqué — por ejemplo, detectar que estás copiando un `struct` grande por valor en cada llamada en vez de pasarlo por referencia constante.
Con el rendimiento bajo control, en la próxima lección vamos a abordar una pregunta que probablemente te has hecho durante todo el curso: ¿cuándo conviene realmente dar el salto de C puro a C++, y qué se gana (y se pierde) al hacerlo?
Carlos Montiel es arquitecto de soluciones IA empresarial. Implementa LLMs, Agentes, RAG y orquestadores en empresas de Guatemala y Latinoamérica. Contáctalo para una consultoría.
Contactar a Carlos Montiel