Afortunadamente, como usted señaló, el COMPACTO compilaciones Mono usan un GC generacional (en marcado contraste con los de Microsoft, como WinMo / WinPhone / XBox, que solo mantienen una lista plana).
Si su juego es simple, el GC debería manejarlo bien, pero aquí hay algunos consejos que puede considerar.
Optimización prematura
Primero asegúrese de que esto sea realmente un problema para usted antes de intentar solucionarlo.
Agrupación de tipos de referencia caros
Debe agrupar los tipos de referencia que crea con frecuencia o que tienen estructuras profundas. Un ejemplo de cada uno sería:
- Creado a menudo: Un
Bullet
objeto en un juego de infierno de balas .
- Estructura profunda: árbol de decisión para una implementación de IA.
Debe usar a Stack
como su grupo (a diferencia de la mayoría de las implementaciones que usan a Queue
). La razón de esto es porque con unStack
si devuelve un objeto al grupo y algo más lo agarra inmediatamente; Tendrá muchas más posibilidades de estar en una página activa, o incluso en el caché de la CPU si tiene suerte. Es un poquito más rápido. Además, siempre limite el tamaño de sus piscinas (solo ignore los 'registros' si se ha excedido su límite).
Evite crear nuevas listas para borrarlas
No cree uno nuevo List
cuando realmente lo haya querido Clear()
. Puede reutilizar la matriz de back-end y guardar una gran cantidad de asignaciones y copias de la matriz. De manera similar a este, intente y cree listas con una capacidad inicial significativa (recuerde, esto no es un límite, solo una capacidad inicial), no necesita ser precisa, solo una estimación. Esto debería aplicarse básicamente a cualquier tipo de colección, excepto a LinkedList
.
Utilice matrices de estructura (o listas) donde sea posible
Obtiene pocos beneficios del uso de estructuras (o tipos de valor en general) si los pasa entre objetos. Por ejemplo, en la mayoría de los sistemas de partículas "buenas", las partículas individuales se almacenan en una matriz masiva: la matriz y el índice se pasan en lugar de la partícula misma. La razón por la que esto funciona tan bien es porque cuando el GC necesita recopilar la matriz, puede omitir el contenido por completo (es una matriz primitiva, no hay nada que hacer aquí). Entonces, en lugar de mirar 10 000 objetos, el GC simplemente necesita mirar 1 matriz: ¡gran ganancia! Nuevamente, esto solo funcionará con tipos de valor .
Después de RoyT. proporcionó algunos comentarios viables y constructivos. Siento que necesito ampliar más esto. Solo debe usar esta técnica cuando se trata de grandes cantidades de entidades (de miles a decenas de miles). Además, debe ser una estructura que no debe tener ningún campo de tipo de referencia y debe vivir en una matriz tipada explícitamente. Contrariamente a sus comentarios, lo estamos colocando en una matriz que es muy probable que sea un campo en una clase, lo que significa que va a aterrizar el montón (no estamos tratando de evitar una asignación de montón, simplemente evitando el trabajo de GC). Lo que realmente nos importa es el hecho de que es una porción contigua de memoria con muchos valores que el GC simplemente puede ver en una O(1)
operación en lugar de una O(n)
operación.
También debe asignar estos arreglos lo más cerca posible al inicio de su aplicación para mitigar las posibilidades de que ocurra fragmentación o trabajo excesivo a medida que el GC intenta mover estos fragmentos (y considere usar una lista enlazada híbrida en lugar del List
tipo incorporado) )
GC.Collect ()
Esta es definitivamente LA MEJOR forma de dispararse en el pie (ver: "Consideraciones de rendimiento") con un GC generacional. Solo debe llamarlo cuando haya creado una cantidad EXTREMA de basura, y la única instancia en la que eso podría ser un problema es justo después de haber cargado el contenido para un nivel, e incluso entonces probablemente solo deba recolectar la primera generación ( GC.Collect(0);
) con suerte para evitar la promoción de objetos a la tercera generación.
IDisposable y anulación de campo
Vale la pena anular los campos cuando ya no necesita un objeto (más aún en objetos restringidos). La razón está en los detalles de cómo funciona el GC: solo elimina objetos que no están enraizados (es decir, referenciados) incluso si ese objeto se hubiera desrooteado debido a que otros objetos se eliminaron en la colección actual ( nota: esto depende del GC sabor en uso - algunos realmente limpian las cadenas). Además, si un objeto sobrevive a una colección, se promociona inmediatamente a la próxima generación; esto significa que cualquier objeto que quede en los campos se promocionará durante una colección. Cada generación sucesiva es exponencialmente más costosa de recolectar (y ocurre con poca frecuencia).
Tome el siguiente ejemplo:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
Si MyFurtherNestedObject
contiene un objeto de varios megabytes, puede estar seguro de que el GC no lo mirará durante mucho tiempo, porque lo ha promovido inadvertidamente a G3. Contrasta eso con este ejemplo:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
El patrón Disposer te ayuda a configurar una forma predecible de pedirle a los objetos que borren sus campos privados. Por ejemplo:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}