Práctica recomendada para forzar la recolección de basura en C #

118

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 en los que está trabajando con objetos grandes que no siempre se recolectan en la generación 0, pero donde la memoria es un problema, es ¿Está bien forzar la recolección? ¿Existe alguna práctica recomendada para hacerlo?

Echostorm
fuente

Respuestas:

112

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

Según MSDN:

"Es posible forzar la recolección de basura llamando a Collect, pero la mayoría de las veces, esto debe evitarse porque puede crear problemas de rendimiento".

Sin embargo, si puede probar su código de manera confiable para confirmar que llamar a Collect () no tendrá un impacto negativo, continúe ...

Solo trate de asegurarse de que los objetos estén limpios cuando ya no los necesite. Si tiene objetos personalizados, observe el uso de la "declaración de uso" y la interfaz IDisposable.

Este enlace tiene algunos buenos consejos prácticos con respecto a la liberación de memoria / recolección de basura, etc.

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

Mark Ingram
fuente
3
Además, puede configurar diferentes LatencyMode msdn.microsoft.com/en-us/library/bb384202.aspx
David d C e Freitas
4
Si sus objetos apuntan a memoria no administrada, puede informar al recolector de basura a través de la API de GC.AddMemoryPressure ( msdn.microsoft.com/en-us/library/… ). Esto le da al recolector de basura más información sobre su sistema sin interferir con los algoritmos de recolección.
Govert
+1: * mire el uso de la "declaración de uso" y la interfaz IDisposable. * Ni siquiera consideraría forzar excepto como último recurso: un buen consejo (léase 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, finalmente lanzando un TargetOfInvocationNullException.
IAbstract
33

Mírelo de esta manera: ¿es más eficiente 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, está perdiendo el tiempo caminando hacia y desde el contenedor de basura afuera. Esto es análogo a lo que sucede cuando se ejecuta el subproceso de GC: todos los subprocesos administrados se suspenden mientras se ejecuta. Y si no me equivoco, el subproceso GC se puede compartir entre múltiples AppDomains, por lo que la recolección de basura los afecta a todos.

Por supuesto, es posible que encuentre una situación en la que no agregará nada a la basura en el corto plazo, por ejemplo, si se va a tomar unas vacaciones. Entonces, sería una buena idea tirar la basura antes de salir.

Esta PUEDE ser una ocasión en la que forzar un GC puede ayudar: si su programa está inactivo, la memoria en uso no se recolecta como basura porque no hay asignaciones.

Maxam
fuente
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 lugar de hacerlo todo de una vez. Desafortunadamente, el método GC :: Collect () no es más rápido cuanto más a menudo lo llama. 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 debajo de este, jajaja).
Jin
1
En mi caso, estoy ejecutando un algoritmo A * (ruta más corta) REPETIDAMENTE para ajustar el rendimiento ... que, en producción, solo se ejecutará una vez (en cada "mapa"). Así que quiero que el GC se realice antes de cada iteración, fuera de mi "bloque de medición 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 por cada "mapa".
corlettk
Llamar a GC antes del bloque medido en realidad no modela la situación en producción, porque en producción el GC se realizará en tiempos impredecibles. Para mitigar eso, debe tomar una medición larga que incluya varias ejecuciones de GC y factorizar los picos durante la GC en su análisis y estadísticas.
Corrió el
32

La mejor práctica es no forzar la recolección de basura en la mayoría de los casos. (Todos los sistemas en los que he trabajado que tenían recolecciones de basura forzadas, tenían problemas de subrayado que, de ser resueltos, habrían eliminado la necesidad de forzar la recolección de basura y aceleraron enormemente el sistema).

Hay algunos casos en los que 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 tipos de procesamiento por lotes , sabe más que el GC. Por ejemplo, considere una aplicación que.

  • Se le da una lista de nombres de archivos en la línea de comando.
  • Procesa un solo archivo y luego escribe el resultado en un archivo de resultados.
  • Mientras procesa el archivo, crea una gran cantidad de objetos interconectados que no se pueden recopilar hasta que el procesamiento del archivo se haya completado (por ejemplo, un árbol de análisis)
  • No guarda mucho estado entre los archivos que ha procesado .

Es posible que pueda hacer un caso (después de una prueba cuidadosa) de que debe forzar una recolección de basura completa después de haber procesado cada archivo.

Otro caso es un servicio que se activa cada pocos minutos para procesar algunos elementos y no mantiene ningún estado mientras está inactivo . Entonces puede valer la pena forzar una colección completa justo antes de irse a dormir .

La única vez que consideraría forzar una colección es cuando sé que se ha creado una gran cantidad de objetos recientemente y actualmente se hace referencia a muy pocos objetos.

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

Ver también " Tidbits de rendimiento de Rico Mariani "

Ian Ringrose
fuente
2
Analogía: Playschool (sistema) mantiene crayones (recursos). Dependiendo de la cantidad de niños (tareas) y la escasez de colores, el maestro (.Net) decide cómo distribuir y compartir entre los niños. Cuando se solicita un color raro, el maestro puede asignarlo del grupo o buscar uno que no se esté usando. El maestro tiene la discreción de recolectar periódicamente crayones sin usar (recolección de basura) para mantener las cosas ordenadas (optimizar el uso de recursos). Generalmente, un padre (programador) no puede predeterminar la mejor política de limpieza de crayones en el salón de clases. Es poco probable que la siesta programada de un niño sea un buen momento para interferir con la coloración de los otros niños.
AlanK
1
@AlanK, 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 ordenar bien sin que los niños se interpongan en el camino. (Los sistemas recientes en los que he trabajado acaban de reiniciar el proceso de servicio en esos momentos en la lectura de forzar GC.)
Ian Ringrose
21

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.

Denis Phillips
fuente
2
O justo antes de abrir un gran objeto contiguo que ha mostrado un historial de fallas y no presenta una resolución eficiente para aumentar su granularidad.
crokusek
17

Hay pocas pautas generales en la programación que sean absolutas. La mitad de las veces, cuando alguien dice 'lo estás haciendo mal', simplemente está lanzando una cierta cantidad de dogma. En C, solía ser miedo a cosas como el código o los subprocesos que se modifican automáticamente, en los lenguajes de GC es forzar el GC o, alternativamente, evitar que el GC se ejecute.

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

Los problemas de programación son muy variados y requieren un enfoque flexible. He visto casos en los que tiene sentido bloquear GC en lenguajes de recolección de basura y lugares donde tiene sentido activarlo en lugar de esperar a que ocurra de forma natural. El 95% de las veces, cualquiera de estos sería un indicador de no haber abordado correctamente el problema. Pero 1 vez de cada 20, probablemente haya un caso válido para hacerlo.


fuente
12

He aprendido a no intentar burlarme de la recolección de basura. Dicho esto, me limito a usar usingpalabras clave cuando trato con recursos no administrados como E / S de archivos o conexiones de bases de datos.

Kon
fuente
27
¿compilador? ¿Qué tiene que ver el compilador con GC? :)
KristoferA
1
Nada, solo compila y optimiza código. Y esto definitivamente no tiene nada que ver con CLR ... o incluso .NET.
Kon
1
Los objetos que ocupan mucha memoria, como imágenes muy grandes, pueden terminar sin recolectarse basura, a menos que los recolecte explícitamente. Creo que esto (objetos grandes) fue el problema del OP, más o menos.
code4life
1
Envolverlo en un uso asegurará que esté programado para GC una vez que esté fuera del alcance de uso. A menos que la computadora explote, lo más probable es que esa memoria se limpie.
Kon
9

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 Gráficos / Imagen / Mapa de bits), dejo que GC.Collect.

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

Michael Stum
fuente
¿Estás seguro de que necesitas esto? El GC se recogerá si se necesita memoria, incluso si su código no está inactivo.
Konrad Rudolph
No estoy seguro de cómo es en .net 3.5 SP1 ahora, pero anteriormente (1.1 y creo que probé contra 2.0) hizo una diferencia en el uso de la memoria. El GC, por supuesto, siempre se recopilará cuando sea necesario, pero es posible que termine desperdiciando 100 Megas de RAM cuando solo necesita 20. Sin embargo
Michael Stum
2
GC se activa en la asignación de memoria cuando la generación 0 alcanza un cierto umbral (por ejemplo, 1 MB), no cuando "algo está inactivo". De lo contrario, podría terminar con OutOfMemoryException en un bucle simplemente asignando y descartando objetos de inmediato.
liggett78
5
los 100 megas de RAM no se desperdician si ningún otro proceso lo necesita. Te está dando un buen impulso de rendimiento :-P
Orion Edwards
9

Un caso que encontré recientemente que requirió llamadas manuales GC.Collect()fue cuando trabajaba con objetos grandes de C ++ que estaban empaquetados en pequeños objetos administrados de C ++, a los que a su vez se accedía desde C #.

Nunca se llamó al recolector de basura porque la cantidad de memoria administrada utilizada fue insignificante, pero la cantidad de memoria no administrada utilizada fue enorme. Llamar manualmente Dispose()a los objetos requeriría que yo mismo realice un seguimiento de cuándo los objetos ya no son necesarios, mientras que la llamada GC.Collect()limpiará cualquier objeto que ya no se haga referencia ...

Morten
fuente
6
Una mejor manera de resolver esto es llamando GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)al constructor y luego GC.RemoveMemoryPressure(addedSize)al 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 podrían recolectar. stackoverflow.com/questions/1149181/…
HugoRune
Y una forma aún mejor de resolver el problema es llamar a Dispose (), que se supone que debes hacer de todos modos.
fabspro
2
La mejor forma es utilizar la estructura Using. Probar / Finalmente .Dispose es una molestia
TamusJRoyce
7

Creo que ya enumeraste las mejores prácticas y NO debes usarlas a menos que REALMENTE sea necesario. Recomendaría encarecidamente mirar su código con más detalle, utilizando potencialmente herramientas de creación de perfiles si es necesario para responder a estas preguntas primero.

  1. ¿Tiene algo en su código que declare elementos en un alcance mayor al necesario?
  2. ¿El uso de memoria es realmente demasiado alto?
  3. Compare el rendimiento antes y después de usar GC.Collect () para ver si realmente ayuda.
Vendedores de Mitchel
fuente
5

Suponga 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 a ellos durante mucho tiempo, así que acceda a Gen1 y Gen2; 2) Son objetos grandes (> 80K) así que entra en LOH (Large Object Heap). Y LOH no compacta como en Gen0, Gen1 y Gen2.

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

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

Morgan Cheng
fuente
4

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

Dicho esto, creo que, por otro lado, GC se ajustará y recopilará memoria de manera más agresiva cuando trabaje con objetos grandes y la presión de la memoria esté aumentando.

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

liggett78
fuente
4

Me gustaría agregar que: Llamar a GC.Collect () (+ WaitForPendingFinalizers ()) es una parte de la historia. Como han mencionado correctamente otros, GC.COllect () es una colección no determinista y se deja a la discreción del propio GC (CLR). Incluso si agrega una llamada a WaitForPendingFinalizers, es posible que no sea determinista. Tome el código de este enlace msdn y ejecute el código con la iteración del bucle del objeto como 1 o 2. Encontrará qué significa no determinista (establezca un punto de interrupción en el destructor del objeto). Precisamente, el destructor no se llama cuando solo había 1 (o 2) objetos persistentes por Wait .. (). [Req. Cita]

Si su código está tratando con recursos no administrados (por ejemplo: identificadores de archivos externos), debe implementar destructores (o finalizadores).

Aquí hay un ejemplo interesante:

Nota : Si ya ha probado el ejemplo anterior de MSDN, el siguiente código aclarará las cosas.

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, primero analice cuál podría ser la salida y luego ejecute y luego lea el motivo a continuación:

{El destructor solo se llama implícitamente una vez que finaliza el programa. } Para limpiar determinísticamente el objeto, uno debe implementar IDisposable y hacer una llamada explícita a Dispose (). ¡Esa es la esencia! :)

Vaibhav
fuente
2

Una cosa más, activar GC Collect de forma explícita NO puede mejorar el rendimiento de su programa. Es muy posible que empeore.

El .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" de uso de la memoria de su programa. Por lo tanto, se adaptará a su programa después de un tiempo de ejecución. Una vez que invoque GC.Collect explícitamente, ¡los umbrales se restablecerán! Y .NET tiene que dedicar tiempo a adaptarse nuevamente al "hábito" de su programa.

Mi sugerencia es confiar siempre en .NET GC. Si surge algún problema de memoria, compruebe el contador de rendimiento ".NET Memory" y diagnostique mi propio código.

Morgan Cheng
fuente
6
Creo que es mejor que fusione esta respuesta con su respuesta anterior.
Salamander
1

No estoy seguro de si es una buena práctica ...

Sugerencia: no implemente esto ni nada cuando no esté seguro. Reevalúe cuando se conozcan los hechos, luego realice pruebas de desempeño antes / después para verificar.

usuario957965
fuente
0

Sin embargo, si puede probar su código de manera confiable para confirmar que llamar a Collect () no tendrá un impacto negativo, continúe ...

En mi humilde opinión, esto es similar a decir "Si puede demostrar que su programa nunca tendrá ningún error en el futuro, entonces adelante ..."

Con toda seriedad, forzar el GC es útil para fines de depuración / prueba. Si siente que necesita hacerlo en cualquier otro momento, entonces está equivocado o su programa se ha construido mal. De cualquier manera, la solución no es forzar el GC ...

Orion Edwards
fuente
"Entonces, o está equivocado, o su programa se ha construido mal. De cualquier manera, la solución no es forzar el GC ..." Los absolutos casi siempre no son ciertos. Hay algunas circunstancias excepcionales que tiene sentido.
llega el