Optimización de programas para recolector de basura

No hace mucho tiempo, apareció un excelente artículo sobre Habré Optimization de recolección de basura en un servicio .NET altamente cargado . Este artículo es muy interesante porque los autores, armados con la teoría, hicieron lo imposible antes: optimizaron su aplicación utilizando el conocimiento del GC. Y si antes no teníamos idea de cómo funciona este GC, ahora se nos presenta en bandeja de plata gracias a los esfuerzos de Konrad Cocos en su libro Pro .NET Memory Management . ¿Qué conclusiones he sacado yo mismo? Hagamos una lista de áreas problemáticas y pensemos cómo resolverlas.


En el reciente taller CLRium # 5: Garbage Collector, hablamos sobre GC todo el día. Sin embargo, decidí publicar un informe con decodificación de texto. Esta es una charla sobre conclusiones con respecto a la optimización de aplicaciones.



Reduce la conectividad entre generaciones


El problema


Para optimizar la velocidad de recolección de basura, el GC recolecta la generación más joven siempre que sea posible. Pero para hacer esto, también necesita información sobre enlaces de generaciones anteriores (en este caso, actúan como una raíz adicional): de la tabla de tarjetas.


Al mismo tiempo, un enlace de la generación más vieja a la más joven te obliga a cubrir el área con una tabla de cartas:


  • Superposición de 4 bytes 4 kb o máx. 320 objetos - para arquitectura x86
  • 8 bytes superpuestos 8 kb o máx. 320 objetos - para arquitectura x64

Es decir GC, al verificar la tabla de tarjetas, al encontrar un valor distinto de cero en ella, se ve obligado a verificar un máximo de 320 objetos para detectar la presencia de enlaces salientes en nuestra generación.


Por lo tanto, los enlaces dispersos en la generación más joven harán que GC requiera más tiempo


Solución


  • Localice objetos con conexiones en la generación más joven, cercana;
  • Si se supone el tráfico de objetos de generación cero, use tirar. Es decir hacer un grupo de objetos (no habrá nuevos: no habrá objetos de generación cero). Y además, al "calentar" el grupo con dos GC consecutivos de modo que se garantice que su contenido fallará en la segunda generación, evitará los enlaces a la generación más joven y tendrá ceros en la tabla de cartas;
  • Evite los vínculos con la generación más joven;

Evite la conectividad fuerte


El problema


Como se desprende de los algoritmos de la fase de compresión de objetos en SOH:


  • Para comprimir el montón, debe rodear el árbol y verificar todos los enlaces, corrigiéndolos para nuevos valores
  • Además, los enlaces de la tabla de cartas afectan a grupos enteros de objetos.

Por lo tanto, la fuerte conectividad general de los objetos puede conducir a un hundimiento durante la GC.


Solución


  • Tener objetos fuertemente conectados cerca, en una generación
  • Evite enlaces innecesarios en general (por ejemplo, en lugar de duplicar esto-> manejar enlaces, use el ya existente this-> Service-> handle)
  • Evite el código con conectividad oculta. Por ejemplo, cierres

Monitorear el uso del segmento


El problema


Durante el trabajo intensivo, puede surgir una situación en la que la asignación de nuevos objetos genera demoras: la asignación de nuevos segmentos bajo el montón y su posterior descompresión al limpiar la basura


Solución


  • Uso de las utilidades PerfMon / Sysinternal para controlar los puntos de selección de nuevos segmentos y su liberación y liberación
  • Si estamos hablando de LOH, que es un tráfico denso de búfer, use ArrayPool
  • Si hablamos de SOH, asegúrese de que los objetos de la misma vida estén resaltados cerca, proporcionando Barrido en lugar de Recolección
  • SOH: usar agrupaciones de objetos

No asigne memoria en secciones cargadas de código


El problema


La sección cargada de código asigna memoria:


  • Como resultado, GC selecciona una ventana de asignación no de 1Kb, sino de 8Kb.
  • Si la ventana se queda sin espacio, esto conduce a un GC y a la expansión de la zona cerrada
  • Un flujo denso de nuevos objetos hará que los objetos de corta duración de otros hilos pasen rápidamente a la generación anterior con peores condiciones de recolección de basura.
  • Lo que aumentará el tiempo de recolección de basura.
  • Lo que conducirá a detener el mundo por más tiempo incluso en modo concurrente

Solución


  • Prohibición total del uso de cierres en secciones críticas del código.
  • Prohibición completa del boxeo en secciones críticas del código (puede usar la emulación tirando si es necesario)
  • Cuando sea necesario crear un objeto temporal para el almacenamiento de datos, use estructuras. Mejor es la estructura de ref. Cuando el número de campos es más de 2, transmita por ref.

Evite asignaciones de memoria innecesarias en LOH


El problema


Colocar matrices en LOH conduce a la fragmentación o ponderación del procedimiento GC


Solución


  • Use la división de matrices en submatrices y una clase que encapsule la lógica de trabajar con tales matrices (es decir, en lugar de List <T>, donde se almacena la mega-matriz, su MyList con matriz [] [], dividiendo la matriz un poco más corta)
    • Las matrices irán a SOH
    • Después de un par de recolecciones de basura, se acostarán al lado de objetos siempre vivos y dejarán de influir en la recolección de basura.
  • Controle el uso de matrices dobles con una longitud de más de 1000 elementos.

Donde esté justificado y sea posible, use la pila de hilos


El problema


Hay varios objetos ultracortos u objetos que viven dentro de una llamada a un método (incluidas las llamadas internas). Crean tráfico de objetos.


Solución


  • Usando la asignación de memoria en la pila donde sea posible:
    • No carga mucho
    • No carga GC
    • Liberación de memoria: instantánea
  • Utilice Span T x = stackalloc T[]; en lugar de la new T[] donde sea posible
  • Use Span/Memory cuando sea posible
  • Convierta algoritmos a tipos de ref stack (StackList: struct, ValueStringBuilder )

Objetos libres lo antes posible


El problema


Concebidos como de corta duración, los objetos caen en gen1 y, a veces, en gen2.
Esto da como resultado un GC más pesado que dura más


Solución


  • Debe liberar la referencia del objeto lo antes posible
  • Si un algoritmo extenso contiene código que funciona con cualquier objeto, está espaciado por código. Pero que se puede agrupar en un solo lugar, es necesario agruparlo, lo que permite que se recopilen antes.
    • Por ejemplo, en la línea 10, se retiró la colección, y en la línea 120, se filtró.

No es necesario llamar a GC.Collect ()


El problema


A menudo parece que si llama a GC.Collect (), solucionará la situación


Solución


  • Es mucho más correcto aprender los algoritmos de operación del GC, ver la aplicación bajo ETW y otras herramientas de diagnóstico (JetBrains dotMemory, ...)
  • Optimizar las áreas más problemáticas

Evita fijar


El problema


Fijar plantea una serie de problemas:


  • Complica la recolección de basura
  • Crea espacios de memoria libres (elementos de lista libre de nodos, tabla de ladrillos, cubos)
  • Puede dejar algunos objetos en la generación más joven, mientras se forman enlaces desde la mesa de juego.

Solución


Si no hay otra salida, use fixed () {}. Este método de confirmación no realiza una confirmación real: solo ocurre cuando el GC ha funcionado dentro de llaves.


Evitar finalización


El problema


La finalización no se llama determinísticamente:


  • Dispose no invitado () resulta en finalización con todos los enlaces salientes del objeto
  • Los objetos dependientes se retrasan más de lo previsto
  • Envejecer, mudarse a las generaciones mayores
  • Si al mismo tiempo contienen enlaces a los más jóvenes, generan enlaces desde la tabla de cartas
  • Complicando el ensamblaje de generaciones anteriores, fragmentándolas y conduciendo a la compactación en lugar del barrido

Solución


Llama suavemente a Dispose ()


Evita demasiados hilos


El problema


Con una gran cantidad de subprocesos, el contexto de asignación crece a medida que se asignan a cada hilo:


  • Como resultado, GC.Collect llega más rápido.
  • Debido a la falta de espacio en el segmento efímero, Collect seguirá Collective Sweep

Solución


  • Controla la cantidad de hilos por la cantidad de núcleos

Evite el tráfico de objetos de diferentes tamaños.


El problema


Al traficar objetos de diferentes tamaños y vidas, se produce fragmentación:


  • Aumentar la proporción de fragmentación
  • Activación de recopilación con una fase de cambio de dirección en todos los objetos de referencia

Solución


Si se supone el tráfico de objetos:


  • Verifique la presencia de campos adicionales, aproximándose al tamaño
  • Verifique la falta de manipulación de cadenas: cuando sea posible, reemplace con ReadOnlySpan / ReadOnlyMemory
  • Libere el enlace lo antes posible
  • Aprovecha el tirón
  • Cálidos cachés y piscinas con un doble GC para compactar objetos. Por lo tanto, evita problemas con la tabla de cartas.

Source: https://habr.com/ru/post/453082/


All Articles