Gestión de la memoria de C++ para la distribución de texturas en videojuegos
memory-management defragmentation (5)
Esta es una pregunta "difícil". No he encontrado nada interesante en la web.
Estoy desarrollando un módulo de gestión de memoria para mi empresa. Desarrollamos juegos para consolas de próxima generación (Xbox 360, PS3 y PC ... ¡consideramos PC como una consola!).
Necesitaremos en el futuro, para nuestros próximos juegos, manejar la transmisión de texturas para grandes mundos de juegos que no se pueden cargar en la memoria de la consola principal (por ahora no se habla de PC).
Vamos a transmitir al principio mipmaps de alta resolución de texturas (es decir, aproximadamente el 70% del tamaño de los datos mundiales). Quizás en el futuro tendremos que transmitir también geometría, mipmaps más pequeños, audio, etc.
Estoy desarrollando un Administrador de memoria para ese problema, centrado en X360 (porque en PS3 podemos usar la memoria del host y el asignador GMM asociado de desfragmentación automática).
El problema al que me enfrento es el siguiente: hemos decidido reservar un Área de memoria específica para la transmisión de texturas (por ejemplo, 64 Megabytes) y queremos manejar todas las asignaciones y desasignaciones en esa área. Hemos asignado el área al comienzo de la aplicación y el área tiene la garantía física de que es contigua (no solo de forma virtual, ya que necesitamos almacenar texturas allí).
He implementado un asignador de desfragmentación automática, utilizando identificadores en lugar de punteros. El tiempo no es un problema, el problema es la fragmentación de la memoria. En el juego, continuamente cargamos y descargamos objetivos de transmisión, por lo que nos gustaría usar la cantidad máxima de nuestro búfer (64 Megabytes).
Con este asignador podemos usar todo el espacio asignado, pero la rutina de desfragmentación funciona en un tiempo inaccesible (¡a veces 60 milisegundos, más que un cuadro!) Mientras que el algoritmo no es tan malo ... ¡hay demasiados memes!
Estoy buscando una solución para resolver este problema. Me gustaría encontrar al menos un buen documento, o un post-mortem, o alguien que haya enfrentado el mismo problema mío.
Ahora estoy eligiendo entre dos estrategias: 1) mover la rutina de desfragmentación en un hilo dedicado (bueno para X360 con 6 hilos de hw, malo para PS3 con solo un hilo de hw ... ¡y no me digas que use SPU!) con todos los problemas de subprocesos múltiples de las regiones de bloqueo, de acceder a una región que se está moviendo, ... 2) encuentre una solución "incremental" al problema de desfragmentación: podemos asignar a cada cuadro un tiempo (por ejemplo, hasta 1 milisegundo) para la desfragmentación y el Administrador de memoria hará lo que pueda hacer en el presupuesto de cada fotograma.
¿Alguien puede contarme su experiencia?
¿Por qué no usar múltiples áreas de memoria para las texturas transmitidas y agrupar por tamaño de textura?
Insomniac tiene un artículo sobre su implementación de reproducción de texturas en PS3. Supongo que podría ser útil: link .
Para las estrategias de asignación general para minimizar la fragmentación, tal vez Doug Lea pueda ayudar.
Pero a partir de la lectura de su pregunta, parece que la está pensando demasiado y le recomiendo un enfoque combinado. (También ejecutar un pase de desfragmentación en la memoria combinada de escritura no suena particularmente seguro o divertido).
Hice muchos estudios recientemente sobre el manejo de la memoria y este es el artículo más informativo y útil que encontré en la red.
http://www.ibm.com/developerworks/linux/library/l-memory/
Basado en ese papel, el mejor y más rápido resultado que obtendrá es dividir sus 64 MB en partes del mismo tamaño. El tamaño de los trozos dependerá del tamaño de su objeto. Y asigne o desasigne una porción completa a la vez. Sus
- Más rápido que la recolección de basura incremental.
- Mas simple
- Y resuelve ese problema de "demasiada fragmación" en cierta cantidad.
Léalo, encontrará información excelente sobre cada solución posible que hay y méritos y deméritos para cada uno.
Tenemos casi exactamente el sistema que describió, excepto que asignamos en ranuras de tamaño fijo (256x256, 512x512, 1024x1024 y 2048x2048), en dos formatos cada una (DXT1 y DXT5), precisamente para evitar la administración de la memoria.
Yo recomendaría un enfoque incremental. Cada cuadro encuentra un bloque contiguo de memoria en uso que tiene espacio libre en ambos lados y lo mueve en la dirección que lo permita. o simplemente puede mover todos los bloques en una dirección, encontrar un espacio y un bloque de inuse que sea el mejor ajuste y moverlo. En la 360 probablemente deberías usar un hilo para hacer el movimiento, mientras que en la PS3 sería mejor usar la GPU para mover los datos por ti.
Ya que estás usando las asas, tienes mucha libertad para mover la memoria. Creo que usar un subproceso separado probablemente no sea la mejor (más segura o más rápida); supongo que sería mejor usar un tipo de asignador de copia incremental, donde en cada malloc()
o free()
compacta ( copie hacia adelante o hacia atrás en la memoria) un cierto número de bloques asignados, con el número de bytes que copia agotando un "presupuesto" que se restablece periódicamente a su valor inicial (por ejemplo, en cada actualización de pantalla). (Por supuesto solo se copian bloques enteros).
La idea es que copiar un número dado de bytes lleva un tiempo bastante predecible, por lo que puede estimar cuántos bytes de copia puede realizar de manera segura por cada actualización de pantalla, y limitarse a eso. Si hay suficiente tiempo en el presupuesto, una llamada a malloc()
o free()
desfragmentará completamente la memoria, de lo contrario, la desfragmentará tanto como sea posible en las restricciones de tiempo dadas.
Hay algunas preguntas que estoy dejando sin resolver aquí, por ejemplo, exactamente cómo compactar la memoria. Un asignador de copia no incremental estándar puede comenzar a asignar desde el frente, luego copiar todo hacia atrás (liberar memoria en el frente) cuando se agote la memoria, pero aquí no tiene esa libertad. Es posible que necesite algunas heurísticas para decidir si mover los bloques hacia adelante o hacia atrás. Lo importante es evitar las oscilaciones (el mismo bloque se mueve hacia adelante y luego hacia atrás en llamadas sucesivas a malloc()
o free()
).