.net - ¿Qué hay de malo en usar GC.Collect()?
performance memory-management (20)
¡El deseo de llamar a GC.Collect () por lo general está tratando de encubrir los errores que cometió en otro lugar!
Sería mejor si descubrieras dónde te olvidaste de deshacerse de cosas que ya no necesitas.
Aunque entiendo las graves implicaciones de jugar con esta función (o al menos eso es lo que creo), no veo por qué se está convirtiendo en una de estas cosas que los programadores respetables nunca usarían, incluso aquellos que ni siquiera saben para qué sirve.
Digamos que estoy desarrollando una aplicación donde el uso de memoria varía extremadamente dependiendo de lo que el usuario esté haciendo. El ciclo de vida de la aplicación se puede dividir en dos etapas principales: edición y procesamiento en tiempo real. Durante la etapa de edición, supongamos que se crean miles de millones o incluso trillones de objetos; algunos de ellos pequeños y otros no, algunos pueden tener finalizadores y otros no, y supongan que sus vidas varían de unos pocos milisegundos a largas horas. Luego, el usuario decide cambiar a la etapa en tiempo real. En este punto, supongamos que el desempeño desempeña un papel fundamental y la más mínima alteración en el flujo del programa podría tener consecuencias catastróficas. La creación de objetos se reduce al mínimo posible mediante el uso de grupos de objetos y cosas similares, pero luego, el GC se repite inesperadamente y lo tira todo, y alguien muere.
La pregunta: en este caso, ¿no sería inteligente llamar a GC.Collect () antes de ingresar a la segunda etapa?
Después de todo, estas dos etapas nunca se superponen en el tiempo entre sí y todas las optimizaciones y estadísticas que la CG podría haber reunido serían de poca utilidad aquí ...
Nota: Como algunos de ustedes han señalado, .NET podría no ser la mejor plataforma para una aplicación como esta, pero eso está más allá del alcance de esta pregunta. El objetivo es aclarar si una llamada GC.Collect () puede mejorar el comportamiento / rendimiento general de una aplicación o no. Todos estamos de acuerdo en que las circunstancias en las que harías tal cosa son extremadamente raras, pero una vez más, el GC trata de adivinar y lo hace perfectamente bien la mayor parte del tiempo, pero todavía se trata de adivinar.
Gracias.
¿Qué pasa con eso? El hecho de que está cuestionando el recolector de elementos no utilizados y el asignador de memoria, que entre ellos tienen una idea mucho mayor sobre el uso real de la memoria de su aplicación en tiempo de ejecución que usted.
.NET Framework nunca fue diseñado para ejecutarse en un entorno en tiempo real. Si realmente necesita un procesamiento en tiempo real, puede utilizar un lenguaje embebido en tiempo real que no esté basado en .NET o utilizar .NET Compact Framework ejecutándose en un dispositivo con Windows CE.
Bueno, el GC es una de esas cosas con las que tengo una relación de amor / odio. Lo hemos roto en el pasado a través de VistaDB y hemos publicado un blog sobre él. Lo han arreglado, pero toma mucho tiempo obtener correcciones de ellos en cosas como esta.
El GC es complejo, y un enfoque de talla única es muy, muy difícil de lograr en algo tan grande. MS lo ha hecho bastante bien, pero a veces es posible engañar al GC.
En general, no deberías agregar un Collect
menos que sepas con certeza que acabas de dejar una tonelada de memoria y pasará a una crisis de mitad de vida si el GC no se limpia ahora.
Puede GC.Collect
toda la máquina con una serie de declaraciones GC.Collect
incorrectas. La necesidad de una declaración de recopilación casi siempre apunta a un error subyacente más grande. La pérdida de memoria generalmente tiene que ver con referencias y una falta de comprensión de cómo funcionan. O usar el IDisposable
en objetos que no lo necesitan y colocar una carga mucho más alta en el GC.
Observe detenidamente el% de tiempo pasado en GC a través de los contadores de rendimiento del sistema. Si ve su aplicación usando el 20% o más de su tiempo en el GC, tiene serios problemas de administración de objetos (o un patrón de uso anormal). Desea minimizar siempre el tiempo que gasta el GC porque acelerará toda su aplicación.
También es importante tener en cuenta que el GC es diferente en los servidores que en las estaciones de trabajo. He visto una serie de pequeños problemas difíciles de localizar con gente que no los prueba a los dos (o que ni siquiera saben que son dos de ellos).
Y para estar tan lleno en mi respuesta como sea posible, también debería probar bajo Mono si también está apuntando a esa plataforma. Dado que es una implementación totalmente diferente, puede experimentar problemas totalmente diferentes que la implementación de MS.
Bueno, obviamente no debería escribir código con requisitos en tiempo real en idiomas con recolección de basura no en tiempo real.
En un caso con etapas bien definidas, no hay ningún problema con desencadenar el recolector de basura. Pero este caso es extremadamente raro. El problema es que muchos desarrolladores van a tratar de usar esto para cubrir los problemas en un estilo de culto de carga, y agregarlo indiscriminadamente causará problemas de rendimiento.
Crear imágenes en un bucle: incluso si llama a dispose, la memoria no se recupera. La basura se acumula todo el tiempo. Pasé de 1,7 GB de memoria en mi aplicación de procesamiento de fotos a 24 MB y el rendimiento es excelente.
Hay absolutamente todo el tiempo que necesita para llamar a GC.Collect.
Creo que tienes razón sobre el escenario, pero no estoy seguro acerca de la API.
Microsoft dice que, en tales casos, debe agregar presión de memoria como una pista al GC de que pronto debería realizar una recopilación.
De hecho, no creo que sea una mala práctica llamar a GC.Collect.
Puede haber casos en que lo necesitemos. Solo por ejemplo, tengo un formulario que ejecuta un hilo, que inturn abre tablas diferentes en una base de datos, extrae los contenidos en un campo BLOB a un archivo temporal, encripta el archivo, luego lee el archivo en un binarystream y vuelve a un BLOB campo en otra mesa.
Toda la operación requiere una gran cantidad de memoria, y no es seguro sobre el número de filas y el tamaño del contenido del archivo en las tablas.
Solía obtener excepciones de OutofMemory a menudo y pensé que sería prudente ejecutar GC.Collect periódicamente en función de una variable de contador. Incremente un contador y cuando se alcanza un nivel específico, se llama a GC para recolectar cualquier basura que se haya formado, y para reclamar cualquier pérdida de memoria debido a pérdidas de memoria imprevistas.
Después de esto, creo que está funcionando bien, al menos no es una excepción.
Llamo de la siguiente manera:
GC.Collect(GC.GetGeneration(<object utilizing the memory>
/* in mycase Form itself */,GCCollectionMode.Optimized).
Desde mi experiencia nunca ha sido aconsejable hacer una llamada a GC.Collect () en el código de producción. En la depuración, sí, tiene sus ventajas para ayudar a aclarar posibles fugas de memoria. Supongo que mi razón fundamental es que el GC ha sido escrito y optimizado por programadores mucho más inteligentes que yo, y si llego a un punto en el que siento que necesito llamar a GC.Collect () es una pista de que me he salido del camino algun lado. En su situación, no parece que realmente tenga problemas de memoria, solo que le preocupa la inestabilidad que la colección traerá a su proceso. Al ver que no limpiará los objetos que aún están en uso, y que se adapta muy rápidamente a las demandas de subida y bajada, creo que no tendrá que preocuparse por ello.
En .net, el tiempo requerido para realizar una recolección de basura está mucho más relacionado con la cantidad de material que no es basura, que con la cantidad de material que es. De hecho, a menos que un objeto anule Finalize
(ya sea explícitamente, o mediante C # destructor), es el objetivo de WeakReference
, se ubica en el Large Object Heap o es especial en alguna otra forma relacionada con gc, lo único que identifica la memoria en la que se sienta como un objeto es la existencia de referencias arraigadas a él. De lo contrario, la operación del GC es análoga a tomar de un edificio todo lo que vale, dinamitar el edificio, construir uno nuevo en el sitio del anterior y colocar todos los elementos valiosos en él. El esfuerzo requerido para dinamitar el edificio es totalmente independiente de la cantidad de basura dentro de él.
En consecuencia, llamar a GC.Collect
puede aumentar la cantidad total de trabajo que el sistema tiene que hacer. Se retrasará la aparición de la próxima colección, pero probablemente hará tanto trabajo de inmediato como la próxima colección habría requerido cuando se produjo; en el momento en que se produciría la siguiente recopilación, la cantidad total de tiempo dedicado a la recopilación habrá sido aproximadamente la misma que GC.Collect
se ha llamado a la GC.Collect
, pero el sistema habrá acumulado algo de basura, lo que provocará que la recopilación siguiente se requiera antes. que GC.Collect
no se ha llamado.
Las veces que puedo ver GC.Collect
realmente útil son cuando uno necesita medir el uso de memoria de algún código (ya que las cifras de uso de la memoria solo son realmente significativas después de una colección), o perfil que de varios algoritmos es mejor (llamando a GC. Recolectar () antes de ejecutar cada uno de varios fragmentos de código puede ayudar a garantizar un estado de línea base consistente). Hay algunos otros casos en que uno podría saber cosas que el GC no tiene, pero a menos que uno esté escribiendo un programa de subproceso único, no hay forma de que uno sepa que una llamada GC.Collect
ayudaría a las estructuras de datos de un subproceso a evitar "mid "crisis de la vida" no haría que los datos de otros hilos tuvieran una "crisis de la mitad de la vida" que de otro modo se habría evitado.
En pocas palabras, puede perfilar la aplicación y ver cómo estas colecciones adicionales afectan las cosas. Sugiero que te mantengas alejado, a menos que vayas a hacer un perfil. El GC está diseñado para cuidarse y, a medida que evoluciona el tiempo de ejecución, puede aumentar la eficiencia. No querrá tener un montón de código que pueda arruinar las obras y no pueda aprovechar estas mejoras. Existe un argumento similar para usar foreach en lugar de for, es decir, que las mejoras futuras bajo las carátulas se pueden agregar a foreach y su código no tiene que cambiar para aprovecharlo.
Entonces, ¿qué sucede cuando estás utilizando objetos COM como MS Word o MS Excel de .Net? Sin llamar a GC.Collect
después de liberar los objetos COM, hemos encontrado que las instancias de la aplicación Word o Excel aún existen.
De hecho, el código que usamos es:
Utils.ReleaseCOMObject(objExcel)
'' Call the Garbage Collector twice. The GC needs to be called twice in order to get the
'' Finalizers called - the first time in, it simply makes a list of what is to be finalized,
'' the second time in, it actually does the finalizing. Only then will the object do its
'' automatic ReleaseComObject. Note: Calling the GC is a time-consuming process,
'' but one that may be necessary when automating Excel because it is the only way to
'' release all the Excel COM objects referenced indirectly.
'' Ref: http://www.informit.com/articles/article.aspx?p=1346865&seqNum=5
'' Ref: http://support.microsoft.com/default.aspx?scid=KB;EN-US;q317109
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
Entonces, ¿sería un uso incorrecto del recolector de basura? Si es así, ¿cómo hacemos para que los objetos Interop mueran? Además, si no está destinado a ser usado así, ¿por qué el método Collect
GC
es Public
?
Hay situaciones en las que es útil, pero en general debe evitarse. Podrías compararlo con GOTO o montar un ciclomotor: lo haces cuando lo necesitas, pero no se lo cuentas a tus amigos.
La invocación de GC.Collect () obliga al CLR a realizar un recorrido de pila para ver si cada objeto se puede liberar de verdad mediante la comprobación de referencias. Esto afectará la escalabilidad si la cantidad de objetos es alta y también se sabe que desencadena la recolección de basura con demasiada frecuencia. Confíe en el CLR y deje que el recolector de basura se ejecute solo cuando sea apropiado.
Lo peor que hará es congelar el programa por un tiempo. Entonces, si te parece bien, hazlo. Por lo general, no es necesario para aplicaciones cliente o web gruesas, en su mayoría con interacción del usuario.
He descubierto que, a veces, los programas con subprocesos de larga ejecución o programas por lotes recibirán la excepción OutOfMemory aunque desechen los objetos correctamente. Uno que recuerdo fue un procesamiento de transacciones de base de datos de la línea de negocio; el otro era una rutina de indexación en un hilo de fondo en una aplicación cliente gruesa.
En ambos casos, el resultado fue simple: sin GC. Recoger, sin memoria, consistentemente; GC.Recoge, rendimiento impecable.
Lo intenté para resolver problemas de memoria varias veces más, sin éxito. Lo saqué.
En resumen, no lo pongas a menos que obtengas errores. Si lo coloca y no soluciona el problema de memoria, retírelo. Recuerde probar en modo Release y compare manzanas con manzanas.
El único momento en que las cosas pueden salir mal con esto es cuando te vuelves moralista al respecto. No es un problema de valores; muchos programadores han muerto y se han ido directamente al cielo con muchas GC innecesarias en su código, que les sobrevive.
No hay nada de malo en pedir explícitamente una colección. Algunas personas realmente quieren creer que si se trata de un servicio proporcionado por el proveedor, no lo cuestionen. Ah, ¿y todas esas congelaciones aleatorias en los momentos incorrectos de tu aplicación interactiva? ¡La próxima versión lo hará mejor!
Dejar que un proceso en segundo plano se ocupe de la manipulación de la memoria significa no tener que lidiar con eso nosotros mismos, es cierto. Pero esto no significa lógicamente que sea mejor para nosotros no tratar con él en todas las circunstancias. El GC está optimizado para la mayoría de los casos. Pero esto no significa lógicamente que esté optimizado en todos los casos.
¿Alguna vez ha respondido a una pregunta abierta como ''¿cuál es el mejor algoritmo de clasificación'' con una respuesta definitiva? Si es así, no toque el GC. Para aquellos de ustedes que pidieron las condiciones, o dieron respuestas tipo "en este caso", puede proceder a aprender sobre el GC y cuándo activarlo.
Tengo que decir que he tenido bloqueos de aplicaciones en Chrome y Firefox que me frustran muchísimo, e incluso entonces, en algunos casos, la memoria crece sin obstáculos (si tan solo aprendieran a llamar al recolector de basura) o me dieron una para que, cuando empiece a leer el texto de una página, pueda golpearlo y, por lo tanto, estar libre de heladas durante los próximos 20 minutos.
Si llama a GC.Collect () en el código de producción, básicamente está declarando que sabe más que los autores del GC. Ese puede ser el caso. Sin embargo, por lo general no es así, y por lo tanto, desaconsejado.
Tuvimos un problema similar con el recolector de basura que no recoge basura y libera memoria.
En nuestro programa, procesamos algunas hojas de cálculo de Excel de tamaño modesto con OpenXML. Las hojas de cálculo contenían de 5 a 10 "hojas" con aproximadamente 1000 filas de 14 columnas.
El programa en un entorno de 32 bits (x86) se bloqueará con un error de "falta de memoria". Conseguimos que se ejecutara en un entorno x64, pero queríamos una mejor solución.
Encontramos uno.
Aquí hay algunos fragmentos de código simplificado de lo que no funcionó y lo que funcionó cuando se trata de llamar explícitamente al recolector de basura para liberar memoria de los objetos eliminados.
Llamar al GC desde el interior de la subrutina no funcionó. La memoria nunca fue reclamada ...
For Each Sheet in Spreadsheets
ProcessSheet(FileName,sheet)
Next
Private Sub ProcessSheet(ByVal Filename as string, ByVal Sheet as string)
'' open the spreadsheet
Using SLDoc as SLDocument = New SLDocument(Filename, Sheet)
'' do some work....
SLDoc.Save
End Using
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
Al mover la llamada de GC fuera del alcance de la subrutina, se recolectó la basura y se liberó la memoria.
For Each Sheet in Spreadsheets
ProcessSheet(FileName,sheet)
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
Next
Private Sub ProcessSheet(ByVal Filename as string, ByVal Sheet as string)
'' open the spreadsheet
Using SLDoc as SLDocument = New SLDocument(Filename, Sheet)
'' do some work....
SLDoc.Save
End Using
End Sub
Espero que esto ayude a otros que están frustrados con la recolección de basura .NET cuando parece ignorar las llamadas a GC.Collect()
.
Paul Smith
Una de las principales razones para llamar a GC.Collect () es cuando acabas de realizar un evento significativo que crea mucha basura, como lo que describes. Llamar a GC.Collect () puede ser una buena idea aquí; de lo contrario, el GC puede no entender que fue un evento ''de una sola vez''.
Por supuesto, debes perfilarlo y verlo por ti mismo.
Regla 1
No lo hagas
Esta es realmente la regla más importante. Es justo decir que la mayoría de los usos de GC.Collect () son una mala idea y entré en eso con cierto detalle en la publicación original, así que no voy a repetir todo eso aquí. Pasemos a ...
Regla n. ° 2
Considere llamar a GC.Collect () si acaba de ocurrir un evento no recurrente y es muy probable que este evento haya causado la muerte de muchos objetos antiguos.
Un ejemplo clásico de esto es si está escribiendo una aplicación cliente y muestra un formulario muy grande y complicado que tiene muchos datos asociados. Su usuario acaba de interactuar con este formulario creando potencialmente algunos objetos grandes ... cosas como documentos XML, o un gran DataSet o dos. Cuando se cierra el formulario, estos objetos están muertos, por lo que GC.Collect () recuperará la memoria asociada a ellos ...
Entonces parece que esta situación puede estar bajo la Regla # 2, usted sabe que hay un momento en el tiempo donde muchos objetos viejos han muerto, y no es recurrente. Sin embargo, no olvides las palabras de despedida de Rico.
La regla n. ° 1 debe prevalecer sobre la regla n. ° 2 sin pruebas contundentes.
Mida, mida, mida.