2011-03-09 10 views
21

La lectura de esta edad pero clásico documento Writing High-Performance Managed Applications - A Primer, me encontré siguiente DECLARACIÓNPara GC. ¿Recoger o no?

El GC se ajusta automáticamente y se ajustará de acuerdo con los requisitos de memoria de aplicaciones. En la mayoría de los casos, la invocación programática de un GC obstaculizará esa puesta a punto. "Ayudar" al GC llamando a GC.Collect probablemente no mejore el rendimiento de sus aplicaciones

Estoy trabajando con aplicaciones que, durante un determinado momento, consumen mucha memoria. Cuando termine el código consumiendo esa memoria, estoy llamando a GC.Collect. Si no lo hago obtengo la excepción de falta de memoria. Este comportamiento es inconstante, pero justamente hablando, el 30% de las veces me sale una memoria. Después de agregar GC.Collect, nunca me salió esta excepción de memoria. ¿Está justificada mi acción a pesar de que este documento de mejores prácticas está desaconsejando?

Respuesta

28

Parte de lo que ocurre en el GC es que los objetos en la memoria son generacional, de manera que las primeras generaciones se recogen con más frecuencia que otros. Esto ayuda a ahorrar rendimiento al no intentar recopilar objetos de larga duración todo el tiempo.

Teniendo esto en cuenta, pueden ocurrir dos cosas cuando llamas al GC.Collect(). La primera es que termina gastando más tiempo haciendo colecciones. Esto se debe a que las colecciones normales de fondo seguirán apareciendo además de su manual GC.Collect(). La segunda es que se aferrará a la memoria más larga, porque forzó algunas cosas en una generación de orden superior que no tenía que ir allí. En otras palabras, usar GC.Collect() es casi siempre una mala idea.

En algunos casos, el recolector de basura no siempre funciona bien. Uno de estos es el gran montón de objetos. Esta es una generación especial para objetos de un tamaño mayor (80,000 bytes, IIRC, pero eso podría ser viejo ahora).Esta generación casi nunca se recoge y casi nunca compacta. Eso significa que con el tiempo puede terminar con muchos agujeros importantes en la memoria que no se liberarán. La memoria física no se usa realmente y está disponible para otros procesos, pero todavía consume espacio de direcciones dentro de su proceso, de los cuales está limitado a 2 GB de forma predeterminada.

Esta es una fuente muy común para las excepciones OutOfMemory — en realidad no está usando esa cantidad de memoria, pero tiene todo este espacio de direcciones ocupado por agujeros en el montón de objetos grandes. Con mucho, la forma más común de que esto suceda se agrega a cadenas grandes o documentos. Probablemente este no sea usted, porque en este escenario, ninguna cantidad de llamadas a GC.Collect() acompañará al LOH, pero en su caso sí parece ayudar. Sin embargo, esta es la fuente de la gran mayoría de las excepciones de OutOfMemory que he visto.

Otro lugar donde el recolector de basura no siempre funciona bien es cuando ciertas cosas hacen que los objetos permanezcan enraizados. Un ejemplo es que los controladores de eventos pueden evitar la recopilación de un objeto. Una forma de evitar esto es asegurarse de que cada operación += para suscribir un evento tenga una operación correspondiente -= para anular su suscripción. Pero, de nuevo, es poco probable que un GC.Collect() ayude aquí: el objeto aún está rooteado en algún lugar, por lo que no se puede recopilar.

Afortunadamente, esto le brinda una vía de investigación para resolver su problema subyacente que causa la necesidad de utilizar GC.Collect() en primer lugar. Pero si no es, por supuesto, es mejor tener un programa que funcione que un programa que falla. En cualquier lugar que use GC.Collect(), me aseguraré de que el código esté bien documentado con el motivo por el que lo necesita (obtiene excepciones sin) y los pasos exactos y los datos necesarios para reproducirlo de manera confiable para que los futuros programadores que Es posible que desee eliminar esto, puede saber con seguridad cuándo es seguro hacerlo.

+2

+ ve Vota por pensamientos muy perspicaces –

+3

En realidad, llamar a GC.Collect() puede que no sea tan malo (como inicialmente pensé) especialmente leyendo este artículo de Jeffery Richter http://msdn.microsoft.com/en-us/magazine/ bb985011.aspx "ya que su aplicación sabe más acerca de su comportamiento que el tiempo de ejecución, podría ayudarlo forzando explícitamente algunas colecciones". Aunque todavía tengo curiosidad por saber por qué GC.Collect ayuda a resolver OOM cuando el objeto está en LOH, donde GC.Collect no tiene control. –

+0

@palmsnow, también sentía curiosidad por eso. Después de buscar mucho he encontrado la respuesta aquí: http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even-when-needed – SiberianGuy

7

La mayoría de las personas diría que hacer que el código funcione correctamente es más importante que hacerlo rápidamente. Por lo tanto, no funciona el 30% del tiempo cuando no se llama al GC.Collect(), entonces eso supera todas las demás preocupaciones.

Por supuesto, esto lleva a la pregunta más profunda de "¿por qué se producen errores OOM? ¿Hay una cuestión más profunda que debe ser fijo, en lugar de llamar a GC.Collect().

Pero los consejos que encontró habla sobre el rendimiento ¿Le importa el rendimiento si hace que su aplicación falle el 30% del tiempo?

+0

@jalf, Esta aplicación básicamente compara una imagen con una biblioteca de imágenes almacenadas en la memoria caché. Dependiendo del tamaño de las imágenes y el tamaño de la caché (configurado por los usuarios de la aplicación), podemos encontrar la instancia que causa OOM. Obviamente, necesitaba la disponibilidad de la aplicación más que el rendimiento en ese momento (es por eso que agregué GC.Collect()), sin embargo, trato de determinar la línea fina entre la escalabilidad y el rendimiento frente a la disponibilidad. –

+0

@palm - Apuesto a que tus imágenes están en el montón de objetos grandes. Cargas y descargas un montón de imágenes y con el tiempo el LOH se fragmentará. Si puede hacer la comparación en fragmentos utilizando segmentos de menos de 80000 bytes, el LOH nunca estará involucrado y su problema desaparecerá. Dependiendo de su comparación, esto también será ** mucho más rápido **, ya que puede significar que no necesita evaluar la imagen completa para cada imagen en la biblioteca cada vez. –

+0

@Joel, tienes razón; cada imagen tiene al menos 1MB de tamaño y podemos tener más de 50,000 imágenes en la memoria caché. –

0

De su descripción parece que Dispose no se eliminan los objetos, o no está configurando valores de miembro que se reemplazarán por nulos antes de la operación. Como ejemplo de esto último:

  • obtener la tabla, pantalla de rejilla
  • (accesos de los usuarios Actualizar)
  • formulario se deshabilita mientras se actualizan los datos
  • consulta regresa, los nuevos datos se rellena en la red

Puede borrar fuera de la red en el ínterin ya que está a punto de ser reemplazado de todos modos; tendrá temporalmente ambas tablas en la memoria (innecesariamente) mientras se reemplaza si no lo hace.

2

En general, GC.Collect no debería ser necesario. Si sus imágenes existen en memoria no administrada, asegúrese de utilizar GC.AddMemoryPressure y GC.RemoveMemoryPressure de manera adecuada.

Cuestiones relacionadas