2008-09-17 9 views
11

Estoy escribiendo una aplicación financiera C# que recibe mensajes de la red, los traduce a objetos diferentes según el tipo de mensaje y finalmente aplica la lógica de negocios de la aplicación en ellos.¿Cómo evitar la recolección de basura en la aplicación .NET en tiempo real?

El punto es que después de aplicar la lógica de negocios, estoy muy seguro de que nunca necesitaré esta instancia nuevamente. En lugar de esperar a que el recolector de basura los libere, me gustaría explícitamente "eliminarlos".

¿Hay alguna manera mejor de hacerlo en C#? ¿Debería usar un conjunto de objetos para reutilizar siempre el mismo conjunto de instancias o existe una estrategia mejor?

El objetivo es evitar que la recolección de basura use cualquier CPU durante un proceso de tiempo crítico.

+0

En .Net, la agrupación es útil cuando (a) crear un objeto determinado es lento, o (b) en una aplicación de 32 bits, para disminuir la fragmentación de memoria, de una matriz/lista/dict etc. que utiliza datos bloque> 64 KB, por ejemplo > 8K elementos x 8 B por elemento, o 16K elementos x 4 B por elemento. .Net ** nunca reubica (mueve) bloques tan grandes **, lo que puede llevar a una memoria fragmentada, si solicita diferentes tamaños cada vez. Más seguro para asignar un número de elementos suficientemente grande por adelantado, y retenerlos. (Pero borre sus campos cuando no estén en uso, para que GC pueda reclamar lo que sea que señale). Si supera el tamaño asignado, duplique los # elementos. – ToolmakerSteve

+0

En el caso extremo, tengo una aplicación de 32 bits (debido a dependencias que no funcionan en 64 bits), así que aunque estoy en un Windows de 64 bits con mucha RAM, para evitar la fragmentación de memoria que tenía para mantener las listas de listas, de forma que cada sublista encaje en 64 KB, de modo que .Net no asigne en "montón de objetos grandes". Por lo tanto, podría "compactar" la memoria durante el GC, en lugar de crear grandes "agujeros" en el espacio de direcciones de 4 GB de la aplicación. Esta aplicación en particular fue problemática porque incluía una gran cantidad de asignación de DirectX desde la memoria nativa (no .Net). La combinación de asignaciones de .Net y .Net condujo a la fragmentación. – ToolmakerSteve

Respuesta

21

No los elimine de inmediato. Llamar al recolector de basura por cada objeto es una mala idea. Normalmente usted realmente no quiere meterse con el recolector de basura, e incluso los procesos críticos en el tiempo son solo condiciones de carrera esperando que ocurran si son tan sensibles.

Pero si sabe que tendrá períodos de carga ocupados vs cargas livianas para su aplicación, puede intentar un GC.Collect() más general cuando llegue a un período de luz para alentar la limpieza antes del próximo período ocupado.

+8

Esto está bien siempre y cuando comprenda que GC.Collect no proporciona una recolección de basura determinista. Solo le está diciendo al GC que ejecute su ciclo, en cuyo caso se determina si se va a recolectar la memoria. La llamada a GC.Collect no garantiza la recopilación de memoria. – JeremiahClark

+1

Reformulado para mostrar no determinismo. –

+1

Tenga en cuenta que ** llamar a GC.Collect() con demasiada frecuencia puede hacer más daño que bien **: cualquier objeto que aún tenga referencias se moverá de la generación 0 al gen 1, o del gen 1 al gen 2. Cualquier objeto de este tipo se mantendrá por más tiempo/requerirá más trabajo para que el GC lo elimine. SOLAMENTE llame a GC.Collect() cuando aún NO se estén trabajando en los mensajes, de modo que no haya referencias a ningún dato temporal. – ToolmakerSteve

18

vistazo aquí: http://msdn.microsoft.com/en-us/library/bb384202.aspx

Se puede decir que el recolector de basura que estás haciendo algo importante en este momento, y va a tratar de ser amable con usted.

+2

Buena entrada de blog en http://blogs.microsoft.co.il/blogs/sasha/archive/2008/08/10/low-latency-gc-in-net-3-5.aspx –

1

Puede tener una cantidad limitada de instancias de cada tipo en un grupo, y reutilizar lo ya hecho con las instancias. El tamaño de la agrupación dependerá de la cantidad de mensajes que procesará.

5

Obligar a GC.Collect() es generalmente una mala idea, deje el GC para hacer lo que mejor sabe hacer. Parece que la mejor solución sería usar un grupo de objetos que pueda cultivar si es necesario. He utilizado este patrón con éxito.

De esta forma evitará no solo la recolección de basura sino también el costo de asignación regular.

Finalmente, ¿está seguro de que el GC le está causando un problema? Probablemente deberías medirlo y probarlo antes de implementar cualquier solución de ahorro de rendimiento: ¡podrías estar causando un trabajo innecesario!

0

En teoría, el GC no debería funcionar si su CPU tiene una carga pesada o si realmente es necesario. Pero si es necesario, puede guardar todos sus objetos en la memoria, quizás una instancia singleton, y nunca limpiarlos a menos que esté listo. Esa es probablemente la única forma de garantizar cuando se ejecuta el GC.

2

Si es absolutamente crítico en el tiempo, entonces debe usar una plataforma determinista como C/C++. Incluso al llamar a GC.Collect() generará ciclos de CPU.

Su pregunta comienza con la sugerencia de que desea guardar la memoria pero deshacerse de los objetos. Esta es una optimización de espacio crítico. Debe decidir lo que realmente quiere porque el GC es mejor para optimizar esta situación que un ser humano.

1

En lugar de crear una nueva instancia de un objeto cada vez que reciba un mensaje, ¿por qué no reutilizar los objetos que ya han sido utilizados? De esta forma no estará luchando contra el recolector de basura y su memoria de montón no se fragmentará. **

Para cada tipo de mensaje, puede crear un grupo para contener las instancias que no están en uso. Cada vez que recibe un mensaje de red, observa el tipo de mensaje, saca una instancia en espera del grupo apropiado y aplica su lógica comercial. Después de eso, vuelve a colocar esa instancia del objeto de mensaje en su grupo.

Lo más probable es que desee cargar su grupo de forma "perezosa" con instancias para que su código se pueda escalar fácilmente. Por lo tanto, su clase de grupo necesitará detectar cuándo se ha retirado una instancia nula y llenarla antes de distribuirla. Luego, cuando el código de llamada lo vuelve a poner en el grupo, es una instancia real.

** "La agrupación de objetos es un patrón que permite reutilizar los objetos en lugar de asignarlos y desasignarlos, lo que ayuda a evitar la fragmentación del montón y las costosas compactaciones GC".

http://geekswithblogs.net/robp/archive/2008/08/07/speedy-c-part-2-optimizing-memory-allocations---pooling-and.aspx

7

Intentando adivinar el recolector de basura es en general una muy mala idea. En Windows, el recolector de basura es a generational one y se puede confiar en que será bastante eficiente. Hay algunas excepciones notables a esta regla general: la más común es que la ocurrencia de un evento de una sola vez que usted conozca de hecho habrá causado la muerte de muchos objetos antiguos, una vez que los objetos se hayan promovido a Gen2 (el más longevo) tienden a pasar el rato.

En el caso que mencione, parecerá que está generando una cantidad de objetos efímeros; estos generarán colecciones Gen0. Estos ocurren con relativa frecuencia de todos modos, y son los más eficientes. Podría evitarlos teniendo un grupo de objetos reutilizables, si lo prefiere, pero es mejor cerciorarse con certeza si el GC es un problema de rendimiento antes de realizar dicha acción: el perfilador CLR es la herramienta para hacerlo.

Cabe señalar que el recolector de basura es diferente en diferentes marcos .NET en el marco compacto (que se ejecuta en la Xbox 360 y en las plataformas móviles) es un GC no generacional y como tal debe ser mucho más cuidado con qué basura genera tu programa.

11

Usted pega en usted mismo - use un grupo de objetos y reutilice esos objetos. La semántica de las llamadas a esos objetos debería ocultarse detrás de una fachada de fábrica. Necesitarás hacer crecer el grupo de una manera predefinida. Tal vez el doble de tamaño cada vez que llega al límite: un algoritmo de alta concentración de agua o un porcentaje fijo. Realmente le recomiendo encarecidamente que no llame a GC.Collect().

Cuando la carga en su grupo sea lo suficientemente baja, podría reducir el conjunto y eso eventualmente desencadenará una recolección de basura. Deje que el CLR se preocupe por ello.

2

Por lo que parece, parece que estás hablando de la finalización determinista (destructores en C++), que no existe en C#. Lo más parecido que encontrará en C# es el patrón Desechable. Básicamente, implementa la interfaz IDisposable.

El patrón básico es el siguiente:

public class MyClass: IDisposable 
{ 
    private bool _disposed; 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if(_disposed)  
      return; 

     if(disposing) 
     { 
      // Dispose managed resources here 
     } 

     _disposed = true; 
    } 
} 
+0

¿Es esto peligroso si su clase tiene otros tipos de referencia como propiedades? ¿Todavía se quedarán atrás para la recolección de basura o se tratarán de inmediato cuando se eliminen? – jocull

+1

@jocull 'Dispose' en realidad no tiene nada que ver con la recolección de basura. Lo mejor que puede hacer allí es disponer de manera determinista de los recursos * un * managed (por supuesto, también debe tener un finalizador en ese caso), o establecer algunas referencias internas a 'null' (por lo tanto, es posible que sean elegibles para la recopilación). La única parte que es determinista son los recursos no administrados. La memoria administrada es * siempre * administrada: esto evita que se borre accidentalmente algo que todavía tiene referencias, por ejemplo. Incluso la pila no está * garantizada * para estar allí. Es un detalle de implementación, no parte de la especificación. – Luaan

2

¿Cómo es la aplicación intensiva? Escribí una aplicación que captura 3 tarjetas de sonido (Managed DirectX, 44.1KHz, Stereo, 16 bits), en bloques de 8KB, y envía 2 de las 3 transmisiones a otra computadora a través de TCP/IP. La interfaz de usuario representa un medidor de nivel de audio y un título/artista de desplazamiento (suave) para cada uno de los 3 canales. Esto funciona en PC con XP, 1.8GHz, 512MB, etc. La aplicación usa aproximadamente el 5% de la CPU.

Me mantuve alejado de llamar manualmente a los métodos de GC. Pero tuve que sintonizar algunas cosas que fueron un desperdicio. Utilicé el perfilador Ant de RedGate para perfeccionar las partes derrochadoras. Una herramienta increíble!

Quería utilizar un conjunto de matrices de bytes preasignados, pero el ensamblaje DX administrado asigna los almacenamientos intermedios de bytes internamente, luego los devuelve a la aplicación. Resultó que no era necesario.

+0

Respuesta completamente no astroturf, pero estoy leyendo esta pregunta porque estamos enfrentando un problema similar al del póster original, y una de las primeras cosas mencionadas fue Hormigas. –

+0

Un buffer de 8kB para streams de muestra estéreo de 16 bits a 44.1kHz hace que el retraso sea de unos 45ms. Eso está lejos de tiempo real. Tendrás muchos más problemas para obtener menos de 5 ms a menos que te apegues a la programación nativa no asignada. –

5

"El objetivo es evitar la recolección de basura de usar cualquier CPU durante un proceso de tiempo crítico"

Q: Si por el momento crítico, quiere decir que estás escuchando alguna pieza de hardware esotérica , y no puede permitirse perder la interrupción?

A: Si es así, entonces C# no es el idioma que debe usar, desea Assembler, C o C++ para eso.

Q: Si por tiempo crítico, quiere decir que hay muchos mensajes en la tubería, y no quiere que el recolector de basura reduzca la velocidad?

A: Si es así, se está preocupando innecesariamente. Por el sonido de las cosas, sus objetos tienen una vida muy corta, esto significa que el recolector de basura los reciclará de manera muy eficiente, sin ningún retraso aparente en el rendimiento.

Sin embargo, la única manera de saberlo con certeza es probarlo, configurarlo para que se ejecute durante la noche procesando un flujo constante de mensajes de prueba. Me sorprenderá si sus estadísticas de rendimiento se pueden detectar cuando el GC se active (y incluso si puedes verlo, estaré aún más sorprendido si realmente importa).

Cuestiones relacionadas