2008-10-24 13 views
109

En mi experiencia, parece que la mayoría de la gente le dirá que no es prudente forzar una recolección de basura pero en algunos casos está trabajando con objetos grandes que no siempre se recolectan en la generación 0 pero donde la memoria es un problema, ¿está bien forzar la recopilación? ¿Existe una mejor práctica para hacerlo?Mejores prácticas para forzar la recolección de basura en C#

Respuesta

107

La mejor práctica es no forzar una recolección de basura.

De acuerdo con MSDN:

"Es posible forzar la basura colección llamando por cobrar, pero mayoría de las veces, esto se debe evitarse, ya que puede crear problemas de rendimiento."

sin embargo, si se puede probar de forma fiable su código para confirmar que llamadas por cobrar() no tendrá un impacto negativo y luego seguir adelante ...

Solo trata de asegurarte de que los objetos se limpien cuando ya no los necesites. Si tiene objetos personalizados, mire usando la "declaración de uso" y la interfaz IDisposable.

Este enlace tiene algunos buenos consejos prácticos en lo que respecta a la liberación de memoria/recogida de basuras, etc:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

+3

Además, puede configurar los diferentes http://msdn.microsoft.com/en-us/library/bb384202.aspx –

+4

de LatencyMode si sus objetos apuntan a la memoria no administrada, puede dejar que el el recolector de basura lo sabe a través de GC.AddMemoryPressure Api (http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx). Esto le da al recolector de basura más información sobre su sistema sin interferir con los algoritmos de recolección. – Govert

+0

+1: * mira el uso de la "declaración de uso" y la interfaz IDisposable. * Ni siquiera consideraría forzar excepto como último recurso: un buen consejo (leer como 'descargo de responsabilidad'). Sin embargo, estoy forzando la recopilación en una prueba unitaria para simular la pérdida de una referencia activa en una operación de back-end, lanzando en última instancia una 'TargetOfInvocationNullException'. – IAbstract

13

He aprendido a no intentar ser más astuto que la recolección de basura. Dicho esto, me limito a utilizar using palabra clave cuando se trata de recursos no administrados como archivos de E/S o conexiones de bases de datos.

+27

compilador?¿Qué tiene que ver el compilador con GC? :) – KristoferA

+1

Nada, solo compila y optimiza el código. Y esto definitivamente no tiene nada que ver con el CLR ... o .NET incluso. – Kon

+1

Los objetos que ocupan gran cantidad de memoria, como imágenes muy grandes, pueden terminar siendo basura, a menos que los recopile basura de forma explícita. Creo que esto (objetos grandes) fue el problema del OP, más o menos. – code4life

8

No estoy seguro de si es una buena práctica, pero cuando trabajo con grandes cantidades de imágenes en un bucle (es decir, creando y eliminando muchos objetos de Gráficos/Imagen/Mapa de bits), dejo el GC.Collect regularmente.

Creo que he leído en alguna parte que el GC solo se ejecuta cuando el programa está (principalmente) inactivo, y no en el medio de un bucle intensivo, por lo que podría parecer un área donde el GC manual podría tener sentido.

+0

¿Estás seguro de que necesitas esto? El GC * * recogerá si necesita memoria, incluso si su código no está inactivo. –

+0

No estoy seguro de cómo está en .NET 3.5 SP1 ahora, pero anteriormente (1.1 y creo que probé en comparación con 2.0) hizo una diferencia en el uso de la memoria. Por supuesto, el GC siempre se acumulará cuando sea necesario, pero aún así podrías desperdiciar 100 Megas de RAM cuando solo necesites 20. Necesitaría unas cuantas pruebas más. –

+1

El GC se activa en la asignación de memoria cuando la generación 0 alcanza un determinado umbral (por ejemplo, 1 MB), no cuando "algo está inactivo". De lo contrario, podría terminar con OutOfMemoryException en un bucle simplemente asignando e inmediatamente descartando objetos. – liggett78

7

Creo que ya ha enumerado las mejores prácticas y que NO es utilizarlas a menos que REALMENTE sea necesario. Recomiendo encarecidamente examinar su código con más detalle, utilizando potencialmente herramientas de creación de perfiles si es necesario para responder estas preguntas primero.

  1. ¿Tiene algo en su código que se declara artículos en un ámbito mayor que la necesaria
  2. es el uso de memoria realmente demasiado alta
  3. comparar el rendimiento antes y después de usar GC.Collect() para ver si realmente ayuda
4

Los objetos grandes se asignan a la LOH (montón de objetos grandes), no en la generación 0. Si Están diciendo que no se recogen basura con gen 0, tienes razón. Creo que solo se recopilan cuando ocurre el ciclo completo del GC (generaciones 0, 1 y 2).

Dicho esto, creo que en el otro lado, el GC se ajustará y recogerá la memoria de forma más agresiva cuando trabaje con objetos grandes y la presión de la memoria aumenta.

Es difícil decir si recopilar o no y en qué circunstancias. Solía ​​hacer GC.Collect() después de eliminar ventanas/formularios de diálogo con numerosos controles, etc. (porque para cuando el formulario y sus controles terminan en gen 2 debido a la creación de muchas instancias de objetos comerciales/carga de muchos datos, no Objetos grandes, obviamente), pero en realidad no notaron ningún efecto positivo o negativo a largo plazo al hacerlo.

5

Supongamos que su programa no tiene pérdida de memoria, los objetos se acumulan y no pueden ser GC-ed en Gen 0 porque: 1) Se hace referencia durante mucho tiempo, así que entre en Gen1 & Gen2; 2) Son objetos grandes (> 80K) así que entra en LOH (Large Object Heap). Y LOH no hace compactación como en Gen0, Gen1 & Gen2.

Compruebe el contador de rendimiento de ".NET Memory" puede ver que el 1) problema realmente no es un problema. En general, cada 10 Gen0 GC disparará 1 Gen1 GC, y cada 10 Gen1 GC activará 1 Gen2 GC. Teóricamente, GC1 & GC2 nunca puede ser GC-ed si no hay presión en GC0 (si el uso de la memoria del programa está realmente cableado). Nunca me pasa a mí.

Para el problema 2), puede verificar el contador de rendimiento ".NET Memory" para verificar si LOH se está hinchando. Si realmente es un problema para su problema, quizás pueda crear un grupo de objetos grandes ya que este blog sugiere http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx.

2

Una cosa más, activar GC Collect explícitamente puede NO mejorar el rendimiento de su programa. Es bastante posible empeorar las cosas.

.NET GC está bien diseñado y ajustado para ser adaptable, lo que significa que puede ajustar el umbral GC0/1/2 de acuerdo con el "hábito" del uso de la memoria de su programa. Por lo tanto, se adaptará a su programa después de algún tiempo de ejecución. Una vez que invoque GC.Collect explícitamente, los umbrales se restablecerán. Y .NET tiene que pasar tiempo para adaptarse al "hábito" de su programa nuevamente.

Mi sugerencia es siempre confiar .NET GC. Cualquier superficie con problemas de memoria, verifique el contador de rendimiento ".NET Memory" y diagnostique mi propio código.

+6

Creo que es mejor que combine esta respuesta con su respuesta anterior. – Salamander2007

27

Mírelo de esta manera: ¿es más eficaz tirar la basura de la cocina cuando el bote de basura está al 10% o dejar que se llene antes de sacarlo?

Al no dejar que se llene, usted está perdiendo el tiempo caminando hacia y desde el cubo de basura afuera. Esto es análogo a lo que sucede cuando se ejecuta el subproceso del GC: todos los subprocesos administrados se suspenden mientras se está ejecutando. Y si no me equivoco, el hilo del GC se puede compartir entre varios AppDomains, por lo que la recolección de elementos no utilizados los afecta a todos.

Por supuesto, es posible que encuentre una situación en la que no vaya a agregar nada al bote de basura pronto, por ejemplo, si va a tomar unas vacaciones. Entonces, sería una buena idea tirar la basura antes de salir.

Esto PODRÍA ser una vez que forzar un GC puede ayudar: si su programa está inactivo, la memoria en uso no se recoge basura porque no hay asignaciones.

+5

Si tuvieras un bebé que moriría si lo dejaras por más de un minuto, y solo tuvieras un minuto para manejar la basura, entonces querrías hacer un poco cada vez en vez de todos a la vez. Desafortunadamente, el método GC :: Collect() no es más rápido cuanto más lo llame. Entonces, para un motor en tiempo real, si no puede usar el mecanismo de eliminación y dejar que el GC agrupe sus datos, entonces no debe usar un sistema administrado, según mi respuesta (probablemente por debajo de este, lol). – Jin

+1

En mi caso, estoy ejecutando un algoritmo A * (ruta más corta) REPETIDAMENTE para ajustarlo al rendimiento ... que, en producción, solo se ejecutará una vez (en cada "mapa"). Así que quiero que el GC se haga antes de cada iteración, fuera de mi "bloque medido de rendimiento", porque creo que modela más de cerca la situación en producción, en la que el GC podría/debería forzarse después de navegar cada "mapa". – corlettk

+0

Llamar a GC antes del bloque medido en realidad no modela la situación en producción, ya que en producción el GC se realizará en tiempos impredecibles. Para mitigar eso, debe tomar una medición larga que incluirá varias ejecuciones de GC, y factorizar los picos durante GC en sus análisis y estadísticas. – Ran

0

Sin embargo, si se puede probar de forma fiable su código para confirmar que llamadas por cobrar() no tendrá un impacto negativo y luego seguir adelante ...

en mi humilde opinión, esto es similar a decir " Si puede probar que su programa nunca tendrá errores en el futuro, continúe ..."

Con toda seriedad, forzar el GC es útil para la depuración/pruebas. Si sientes que necesitas hacerlo en cualquier otro momento, entonces estás equivocado o tu programa ha sido creado incorrectamente. manera, la solución no está forzando el GC ...

+0

"entonces o bien está equivocado, o su programa ha sido creado incorrectamente. De cualquier forma, la solución no está forzando al GC ..." Los absolutos casi nunca son verdad. Hay algunas circunstancias excepcionales que tiene sentido. – rolls

20

Creo que el ejemplo dado por Rico Mariani fue bueno: puede ser apropiado activar un GC si hay un cambio significativo en el estado de la aplicación. Por ejemplo, en un editor de documentos puede estar bien activar un GC cuando se cierra un documento.

+2

O justo antes de abrir un objeto contiguo grande que ha mostrado un historial de falla y no presenta una resolución eficiente para aumentar su granularidad. – crokusek

15

Hay pocas pautas generales en la programación que sean absolutas. La mitad del tiempo, cuando alguien dice 'lo estás haciendo mal', solo están emitiendo una cierta cantidad de dogma. En C, solía haber miedo a cosas como el código de modificación automática o los hilos, en los lenguajes del GC está forzando al GC o, alternativamente, impidiendo que el GC se ejecute.

Como es el caso con la mayoría de las pautas y buenas reglas prácticas (y buenas prácticas de diseño), hay raras ocasiones en las que tiene sentido trabajar en torno a la norma establecida. Tienes que estar seguro de que entiendes el caso, que tu caso realmente requiere la abrogación de la práctica común, y que entiendes los riesgos y efectos secundarios que puedes causar. Pero hay tales casos.

Los problemas de programación son muy variados y requieren un enfoque flexible. He visto casos en los que tiene sentido bloquear el GC en los lenguajes y lugares recogidos de basura donde tiene sentido desencadenarlo en lugar de esperar que ocurra de forma natural. El 95% del tiempo, cualquiera de estos sería una señal de no haber abordado correctamente el problema. Pero 1 vez en 20, probablemente haya un caso válido para eso.

29

La mejor práctica es no forzar una recolección de basura en la mayoría de los casos. (Cada sistema que he trabajado en que había obligado colecciones de basura, había subrayado los problemas que si resuelve habría eliminado la necesidad obligó a la recolección de basura y acelerar el sistema en gran medida.)

Hay un pocos casos cuando sabe sabe más sobre el uso de la memoria que el recolector de basura. Es poco probable que esto sea cierto en una aplicación multiusuario o en un servicio que responde a más de una solicitud a la vez.

Sin embargo, en algunos procesamiento de tipo de lote usted sabe más que el GC. P.ej. considerar una aplicación que.

  • se da una lista de nombres de archivo en la línea de comandos
  • Procesa un solo archivo a continuación, escribir el resultado en un archivo de resultados.
  • Al procesar el archivo, crea una gran cantidad de objetos relacionados entre sí que no pueden ser recogidos hasta que el procesamiento del archivo tiene completa (por ejemplo, un árbol de análisis sintáctico)
  • No mantener el estado coincidencia entre los archivos que hayan tratado.

Usted puede ser capaz de hacer un caso (después de una cuidadosa) las pruebas que se debe forzar una recolección completa después de que haya proceso de cada archivo.

Otros casos es un servicio que se activa cada pocos minutos para procesar algunos elementos, y no mantiene ningún estado mientras está dormido.Luego forzar una colección completa justo antes de ir a dormir puede valer la pena.

La única vez que yo consideraría forzando una colección es cuando sé que un montón de objeto había sido creado recientemente y muy pocos objetos son actualmente hace referencia.

Preferiría tener una API de recolección de basura cuando podría darle pistas sobre este tipo de cosas sin tener que forzar una GC.

Véase también "Rico Mariani's Performance Tidbits"

+1

Analogía: Playschool (sistema) mantiene crayones (recursos). Según el número de niños (tareas) y la escasez de colores, el docente (.Net) decide cómo asignar y compartir entre los niños. Cuando se solicita un color raro, el docente puede asignar desde el grupo o buscar uno que no se esté utilizando. El maestro tiene la discreción de recolectar periódicamente lápices de colores sin usar (recolección de basura) para mantener las cosas ordenadas (optimizar el uso de los recursos). En general, uno de los padres (programador) no puede predeterminar la mejor política de limpieza de crayones en el aula. La siesta programada de un niño es poco probable que sea un buen momento para interferir con el color de los otros niños. – AlanK

+0

@ Alan, me gusta esto, ya que cuando los niños se van a casa por el día, es un muy buen momento para que los maestros ayuden a hacer una buena limpieza sin que los niños se pongan en el camino.(Los sistemas recientes en los que he trabajado han reiniciado el proceso de servicio en esos momentos para forzar la GC.) –

1

No está seguro de si se trata de una buena práctica ...

Sugerencia: no implementan esto o nada cuando no esté seguro. Reevaluar cuando se conocen los hechos, luego realizar pruebas de rendimiento antes/después para verificar.

8

Un caso Encontré recientemente que las llamadas manuales requeridas al GC.Collect() era cuando trabajaba con objetos grandes de C++ que estaban envueltos en pequeños objetos administrados de C++, que a su vez se accedían desde C#.

El recolector de basura nunca se llamó porque la cantidad de memoria administrada utilizada era insignificante, pero la cantidad de memoria no administrada utilizada era enorme. Llamar manualmente al Dispose() en los objetos requeriría que haga un seguimiento de cuándo los objetos ya no son necesarios, mientras que llamar al GC.Collect() limpiará cualquier objeto que ya no se refiera .....

+6

Una mejor forma de resolver esto es llamando a 'GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)' en el constructor y luego 'GC.RemoveMemoryPressure (addedSize) 'en el finalizador. De esta forma, el recolector de basura se ejecutará automáticamente, teniendo en cuenta el tamaño de las estructuras no administradas que se pueden recopilar. http://stackoverflow.com/questions/1149181/what-is-the-point-of-using-gc-addmemorypressure-with-un-unmanaged-resource – HugoRune

+0

Y una forma aún mejor de resolver el problema es realmente llamar a Dispose(), que se supone que debes hacer de todos modos. – fabspro

+2

La mejor manera es usar la estructura de Uso. Try/Finally .Dispose es una molestia – TamusJRoyce

4

Me gustaría agregar que: Llamar a GC.Collect() (+ WaitForPendingFinalizers()) es una parte de la historia. Como lo mencionaron correctamente otros, GC.COllect() es una recopilación no determinista y se deja a discreción del propio GC (CLR). Incluso si agrega una llamada a WaitForPendingFinalizers, puede que no sea determinista. Tome el código de este msdn link y ejecute el código con la iteración del bucle del objeto como 1 o 2. Encontrará lo que significa no determinista (establecer un punto de ruptura en el destructor del objeto). Precisamente, el destructor no se llama cuando había solo 1 (o 2) objetos persistentes por Espera ...(). [Requerimiento de cita]

Si el código trata con recursos no administrados (por ejemplo, identificadores de archivos externos) , debe implementar destructores (o finalizadores).

Aquí es un ejemplo interesante:

Nota: Si ya ha probado el ejemplo anterior de MSDN, el siguiente código va a limpiar el aire.

class Program 
{  
    static void Main(string[] args) 
     { 
      SomePublisher publisher = new SomePublisher(); 

      for (int i = 0; i < 10; i++) 
      { 
       SomeSubscriber subscriber = new SomeSubscriber(publisher); 
       subscriber = null; 
      } 

      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 

      Console.WriteLine(SomeSubscriber.Count.ToString()); 


      Console.ReadLine(); 
     } 
    } 

    public class SomePublisher 
    { 
     public event EventHandler SomeEvent; 
    } 

    public class SomeSubscriber 
    { 
     public static int Count; 

     public SomeSubscriber(SomePublisher publisher) 
     { 
      publisher.SomeEvent += new EventHandler(publisher_SomeEvent); 
     } 

     ~SomeSubscriber() 
     { 
      SomeSubscriber.Count++; 
     } 

     private void publisher_SomeEvent(object sender, EventArgs e) 
     { 
      // TODO: something 
      string stub = ""; 
     } 
    } 

Sugiero, en primer lugar analizar lo que la salida podría ser a continuación, ejecutar y después lee el motivo a continuación:

{El destructor sólo se le llama de forma implícita una vez que termine el programa. } Para determinar determinísticamente el objeto, uno debe implementar IDisposable y hacer una llamada explícita a Dispose(). Esa es la esencia! :)

Cuestiones relacionadas