c# compact-framework garbage-collection iteration

c# - Permitiendo la iteración sin generar basura.



compact-framework garbage-collection (7)

¿Tiene que ser IEnumerable? ¿Ayudará la refactorización a una matriz con buenos datos indexados antiguos?

Tengo el siguiente código en un grupo de objetos que implementa la interfaz IEnumerable.

public IEnumerable<T> ActiveNodes { get { for (int i = 0; i < _pool.Count; i++) { if (_pool[i].AvailableInPool) { yield return _pool[i]; } } } }

Por lo que sé (de acuerdo con this pregunta), esto generará basura, ya que el objeto IEnumerable deberá ser recolectado. Ninguno de los elementos en _pool se recopilará, ya que el propósito de la agrupación es mantener referencias a todos ellos para evitar la creación de basura.

¿Alguien puede sugerir una forma de permitir la iteración sobre _pool para que no se genere basura?

Al iterar sobre el grupo, todos los elementos en el grupo que tienen AvailableInPool == true deben iterar. La orden no importa.


Además, puede tener un grupo de enumeradores preasignados. Piensa en cuántas enumeraciones simultáneas quieres apoyar.

La sobrecarga de recolección de basura irá, a expensas del consumo de memoria adicional. Optimización de la velocidad frente a la memoria en su forma más pura.


En primer lugar, varias personas están rechazando a Olhovsky para sugerir que esto no se preocupa por nada. Evitar la presión de recolección es realmente muy importante en algunas aplicaciones en algunos entornos.

El recolector de basura de marco compacto tiene una política poco sofisticada; activa una colección cada vez que se han asignado 1000 KB de memoria. Ahora suponga que está escribiendo un juego que se ejecuta en el marco compacto, y el motor de física genera 1 KB de basura cada vez que se ejecuta. Los motores de física normalmente se ejecutan en el orden de 20 veces por segundo. Así que eso es 1200KB de presión por minuto, y bueno, eso ya es más de una colección por minuto solo del motor de física . Si la colección causa un tartamudeo notable en el juego, entonces eso podría ser inaceptable. En tal escenario, cualquier cosa que pueda hacer para disminuir la presión de recolección ayuda.

Estoy aprendiendo esto de la manera más difícil, aunque trabajo en el CLR de escritorio. Tenemos escenarios en el compilador donde debemos evitar la presión de recolección, y estamos saltando a través de todo tipo de aros de agrupación de objetos para hacerlo. Olhovsky, siento tu dolor.

Entonces, para responder a su pregunta, ¿cómo puede recorrer la colección de objetos agrupados sin crear una presión de colección?

Primero, pensemos por qué ocurre la presión de recolección en el escenario típico. Supongamos que tienes

foreach(var node in ActiveNodes) { ... }

Lógicamente esto asigna dos objetos. Primero, asigna lo enumerable, la secuencia, que representa la secuencia de nodos. En segundo lugar, asigna el enumerador, el cursor, que representa la posición actual en la secuencia.

En la práctica, a veces puede hacer trampa un poco y tener un objeto que representa tanto la secuencia como el enumerador, pero aún tiene un objeto asignado.

¿Cómo podemos evitar esta presión de recogida? Tres cosas vienen a la mente.

1) No haga un método ActiveNodes en primer lugar. Haga que la persona que llama itere sobre el grupo por índice, y verifique si el nodo está disponible. La secuencia es entonces el conjunto, que ya está asignado, y el cursor es un entero, ninguno de los cuales está creando una nueva presión de recolección. El precio que pagas es código duplicado.

2) Como Steven sugiere, el compilador tomará cualquier tipo que tenga los métodos y propiedades públicos correctos; no tienen que ser IEnumerable e IEnumerator. Puede crear su propia secuencia de estructura mutable y objetos de cursor, pasarlos por valor y evitar la presión de recolección. Es peligroso tener estructuras mutables, pero es posible. Tenga en cuenta que la List<T> utiliza esta estrategia para su enumerador; Estudia su implementación para ideas.

3) ¡Asigne la secuencia y los enumeradores en el montón normalmente y agrúpelos también! Ya va con una estrategia de agrupación, por lo que no hay razón por la que no pueda agrupar un enumerador también. Los enumeradores incluso tienen un método conveniente de "Restablecer" que generalmente solo lanza una excepción, pero podría escribir un objeto de enumerador personalizado que lo usó para restablecer el enumerador al principio de la secuencia cuando vuelve al grupo.

La mayoría de los objetos solo se enumeran una vez a la vez, por lo que la agrupación puede ser pequeña en casos típicos.

(Ahora, por supuesto, puede que tenga un problema con el huevo y la gallina aquí; ¿cómo va a enumerar el grupo de enumeradores?)


La iteración de elementos en cualquier diseño "normal" siempre dará como resultado la creación de un nuevo objeto enumerable. Crear y desechar objetos es muy rápido, por lo que solo en escenarios muy especiales (donde la latencia más baja es la máxima prioridad) las recolecciones de basura (yo digo ''podría'') podrían ser un problema.

Un diseño sin basura es posible devolviendo estructuras que no implementan IEnumerable . El compilador de C # todavía puede iterar tales objetos, porque la instrucción foreach utiliza la escritura duck. Pero una vez más, es poco probable que esto te ayude.

ACTUALIZAR

Creo que hay mejores maneras de aumentar el rendimiento de su aplicación, que resultando en este engaño de bajo nivel, pero es su decisión. Aquí hay una estructura enumerable y un enumerador de estructura. Cuando devuelves lo enumerable, el compilador de C # puede buscar sobre él:

public struct StructEnumerable<T> { private readonly List<T> pool; public StructEnumerable(List<T> pool) { this.pool = pool; } public StructEnumerator<T> GetEnumerator() { return new StructEnumerator<T>(this.pool); } }

Aquí está el StructEnumerator :

public struct StructEnumerator<T> { private readonly List<T> pool; private int index; public StructEnumerator(List<T> pool) { this.pool = pool; this.index = 0; } public T Current { get { if (this.pool == null || this.index == 0) throw new InvalidOperationException(); return this.pool[this.index - 1]; } } public bool MoveNext() { this.index++; return this.pool != null && this.pool.Count >= this.index; } public void Reset() { this.index = 0; } }

Simplemente puede devolver el StructEnumerable<T> siguiente manera:

public StructEnumerable<T> Items { get { return new StructEnumerable<T>(this.pool); } }

Y C # puede iterar sobre esto con un foreach normal:

foreach (var item in pool.Items) { Console.WriteLine(item); }

Tenga en cuenta que no puede LINQ sobre el elemento. Necesitas la interfaz IEnumerable para eso, y eso involucra boxeo y recolección de basura.

Buena suerte.

ACTUALIZAR:

Tenga en cuenta que al usar foreach sobre una matriz y la List<T> no se generará basura. Cuando se usa foreach en una matriz, C # transformará la operación en una instrucción for , mientras que List<T> ya implementa un enumerador de struct , lo que hace que foreach no produzca basura.


No tengas miedo de esos pequeños objetos de basura. Los ActiveNodes en el grupo serán (deberían) ser mucho más costosos. Por lo tanto, si te deshaces de recrearlos debería ser suficiente.

Edición: si está diseñado para usar una plataforma administrada y realmente desea archivar un estado sin basura, rechace el uso del grupo en un bucle foreach y repita sobre él de otra manera, posiblemente utilizando un indexador. O considere crear una lista de nodos potenciales y devuélvala en su lugar.

@ Edit2: Por supuesto, la implementación de IEnumerator y el uso de Current (), Next (), etc., también funcionaría.


Podría implementar su propia clase IEnumerator que enumera sobre estos nodos activos.

Ahora, si puede garantizar que solo un cliente estará enumerando sobre los nodos activos a la vez, su clase puede almacenar en caché esta clase, por lo que solo existe una instancia. Entonces no habrá que recoger basura. La llamada a ActiveNodes llamará reset para iniciar la enumeración desde el principio.

Esta es una suposición peligrosa, pero si está optimizando puede ser capaz de considerarlo

Si tiene varios clientes que se enumeran en estos nodos en cualquier momento, cada uno necesitará su propia instancia de IEnumerator para poder almacenar su posición actual del cursor en la colección. En cuyo caso, será necesario crearlos y recopilarlos con cada llamada, y también puede seguir con su diseño original.


Ya que XNA para XBox también funciona con Compact Framework (y sospecho que es en lo que estás trabajando dadas las sugerencias que has dado (1)), podemos confiar en que los desarrolladores de XNA nos enseñarán exactamente cuándo foreach crea basura.

Para citar el punto más relevante (aunque vale la pena leer todo el artículo):

Al hacer un análisis sobre una Collection<T> se asignará un enumerador.

Al hacer un análisis de la mayoría de las otras colecciones, incluidas matrices, listas, colas, listas enlazadas y otros:

  • Si las colecciones se utilizan explícitamente, NO se asignará un enumerador.
  • si se convierten en interfaces, se asignará un enumerador.

Por lo tanto, si _pool es una List , matriz o similar y puede permitírselo, puede devolver ese tipo directamente o convertir IEnumerable<T> en el tipo correspondiente para evitar la basura durante el análisis foreach.

Como una lectura adicional, Shawn Hargreaves puede tener información adicional útil.

(1) 60 llamadas por segundo, Compact Framework, no se puede pasar al código nativo, 1 MB de asignación antes de que se active un GC.