Patrones de asignación de memoria utilizados en el desarrollo del juego.

20

He estado investigando la creación de mis propios métodos de asignación (que admitirán cosas como un grupo de memoria y creación de perfiles), sin embargo, a medida que continúo mi investigación, he estado buscando cómo se hizo esto en el desarrollo de juegos.

¿Qué técnica de asignación de memoria podría usar y por qué es una buena técnica?

chadb
fuente
1
¿realmente lo necesitas? es solo una de las cosas más complicadas que un equipo puede implementar, si pueden implementarlo.
Ali1S232
44
Es un campo de interés para mí, así que me gustaría aprenderlo e implementarlo
Chadb
Debo decir que el tema es realmente interesante ... Hay casos en los que eso puede significar mucho, pero en tu juego promedio de PC preferiría preocuparme por el juego real ...
Rioki
¿Ha investigado el código fuente de la biblioteca estándar moderna malloc y gratuita o nueva y eliminar? Pregunto porque parece que proporcionaría una base muy útil contra la cual comparar cualquier estrategia de asignación alternativa con algoritmo o prácticamente. Parece que también proporcionaría una idea real de lo que se va a meter.
Louis Langholtz

Respuestas:

25

Game Engine Architecture tiene información sobre este tema. Lo básico es que necesita hacer un análisis para comprender cuáles son sus requisitos de memoria por nivel / marco / etc. son como, pero hay algunos patrones que el autor menciona haber visto varias veces:

  • Asignadores basados ​​en pilas: asignan un gran segmento de memoria una vez, y luego asignan punteros dentro de ese bloque de memoria en respuesta a solicitudes de otras partes del juego. Esto es útil para evitar cambios de contexto requeridos por la asignación de memoria, y también porque puede usar sus propias técnicas para imponer contigüidad o alineación específica para operaciones SIMD. Algunos motores también usan una pila de doble extremo donde un tipo de recurso se carga desde la parte superior y el otro se carga desde la parte inferior. Tal vez LSR (Load and Stay Resident, el tipo de cosa que se necesitará a lo largo de todo el juego) desde la parte superior, y datos por nivel desde la parte inferior.
  • Memoria de cuadro único o memoria de cuadro con doble búfer: memoria para operaciones que ocurren dentro de uno o dos ciclos de cuadro. Esto es útil porque en lugar de tener que asignar / desasignar cada fotograma, simplemente puede eliminar los datos del último fotograma restableciendo el puntero que usa para realizar un seguimiento de la memoria al comienzo del bloque.
  • Grupos de objetos: un bloque de memoria para muchos objetos del mismo tamaño, como partículas, enemigos, proyectiles. Estos son útiles porque puede lograr fácilmente la contigüidad al encontrar el primer segmento no utilizado en su grupo. También facilitan la iteración, porque cada objeto está en un desplazamiento conocido del último.

Lo más importante que el autor menciona es la fragmentación de la memoria. Esto es un problema menor si está desarrollando para, por ejemplo, una PC donde tiene algún tipo de copia de seguridad de paginación de memoria con la que puede contar, pero en un contexto de memoria fija como una consola, existe el riesgo de estar "sin memoria" cuando intente asignar un objeto grande porque su memoria está fragmentada de tal manera que solo están disponibles pequeños bloques contiguos. Con ese fin, recomienda que un asignador basado en pila como el anterior también incluya un método para desfragmentar periódicamente su contenido.

Para obtener más información sobre el código real involucrado en esto, recomiendo el artículo de Christian Gyrling, "¿Nos hemos quedado sin memoria?" , que cubre técnicas para asignadores personalizados, principalmente desde una perspectiva de análisis de patrones de uso de memoria, pero esto también es aplicable para diseñar una solución personalizada para la administración de memoria.


fuente
1

Por lo que he visto (pero no hecho), cada juego tiende a heredar los mecanismos de asignación de un marco, de un motor de juego, de la versión anterior (2010 -> 2011) o obtiene un conjunto de nuevos escritos específicamente para su estructura (ya sea cuando las estructuras de datos son reutilizables y de tamaño fijo o de numerosos tipos y tamaños variables).

También teníamos diferentes asignadores para archivos / componentes de sonido que para niveles y otros objetos del juego en el mismo proyecto. En otros proyectos, los asignadores se heredan de bibliotecas externas solo para los componentes administrados por esa biblioteca.

La optimización realmente depende de sus necesidades. Pero generalmente la asignación se realiza antes de ingresar a la escena del juego y luego se reutiliza la memoria. Algunos juegos pueden salirse sin dar asignadores personalizados. Pero para los juegos de acción en los que se presupuestan los recursos del procesador, la memoria y los datos, no puede permitirse perder el tiempo de procesamiento en asignaciones grandes, no puede desperdiciar la memoria en la fragmentación y otros problemas.

Con respecto a los ejemplos, simplemente debe comenzar por echar un vistazo al motor de juego OGRE 3D , que tiene algunas opciones para configurar los asignadores de memoria.

Coyote
fuente
0

El error que a menudo se comete es escribir sus propios asignadores para que pueda tener más control sobre la cantidad de memoria utilizada por cada sistema y tener más visibilidad sobre lo que está sucediendo. Una forma mucho mejor de lograr esto es usar un generador de perfiles de memoria. Hay muchos perfiladores de memoria, mi perfilador MemPro es un ejemplo. Esta es una forma totalmente no invasiva de realizar un seguimiento de todo el uso de la memoria, y puede dividirlo automáticamente en subsistemas utilizando filtros comodín callstack. Idealmente, es mejor mantener su asignación de memoria y seguimiento de memoria totalmente separados, tienen requisitos totalmente diferentes.

La división arbitraria de su memoria en grupos a menudo puede ser perjudicial porque cada grupo tendrá una sobrecarga. Puedes terminar usando mucha más memoria de la que necesitas sin darte cuenta. Para reducir el desperdicio siempre es mejor agrupar todo, la holgura es compartida por todo el sistema.

Las únicas razones para usar asignadores personalizados son el rendimiento de la CPU (principalmente para la coherencia de la memoria caché) y para limitar la fragmentación. Un ejemplo perfecto de esto es un sistema de partículas. Desea que todas las partículas sean contiguas en la memoria y no quiere sazonar la memoria principal con muchas asignaciones de corta duración. Otro buen ejemplo para la partición es un lenguaje de script.

Si desea un ejemplo de reemplazo de malloc de uso general, puede echar un vistazo a mi asignador VMem . Se ha utilizado en varios juegos AAA enviados. Tiene técnicas que limitan la fragmentación y mantienen baja la huella de memoria, algo crítico para los juegos de consola. También es muy rápido bajo alta contención de hilos. Mi sitio web tiene una amplia documentación sobre estas técnicas.

Stewart Lynch
fuente