Evitar OutOfMemoryException durante asignaciones de memoria grandes, rápidas y frecuentes en C #

Nuestra aplicación asigna continuamente matrices para grandes cantidades de datos (por ejemplo, de decenas a cientos de megabytes) que viven durante un corto período de tiempo antes de ser descartados.

Hecho ingenuamente, esto puede causar una gran fragmentación del montón de objetos, lo que eventualmente hace que la aplicación se bloquee con una excepción OutOfMemoryException a pesar de que el tamaño de los objetos actualmente en vivo no sea excesivo.

Una forma en que lo hemos logrado con éxito en el pasado es dividir los arreglos para asegurarnos de que no terminen en el LOH, la idea es evitar la fragmentación permitiendo que el recolector de basura compacte la memoria.

Nuestra última aplicación maneja más datos que antes, y pasa estos datos serializados con mucha frecuencia entre complementos alojados en AppDomains separados o procesos separados. Adoptamos el mismo enfoque que antes, asegurando que nuestra memoria siempre estuviera fragmentada y teniendo mucho cuidado de evitar las grandes asignaciones de objetos.

Sin embargo, tenemos un complemento que debe hospedarse en un proceso externo de 32 bits (porque nuestra aplicación principal es de 64 bits y el complemento debe usar una biblioteca de 32 bits). Bajo una carga particularmente pesada, cuando muchos trozos de memoria SOH se asignan y descartan rápidamente poco después, incluso nuestro enfoque de trozos no ha sido suficiente para guardar nuestro complemento de 32 bits y se bloquea con una excepción OutOfMemory.

Usando WinDbg en el momento en que ocurre una excepción OutOfMemoryException,!heapstat -inclUnrooted muestra esto:

Heap             Gen0         Gen1         Gen2          LOH
Heap0           24612      4166452    228499692      9757136

Free space:                                                 Percentage
Heap0              12           12      4636044        12848SOH:  1% LOH:  0%

Unrooted objects:                                           Percentage
Heap0              72            0         5488            0SOH:  0% LOH:  0%

!dumpheap -stat muestra esto:

-- SNIP --

79b56c28     3085       435356 System.Object[]
79b8ebd4        1      1048592 System.UInt16[]
79b9f9ac    26880      1301812 System.String
002f7a60       34      4648916      Free
79ba4944     6128     87366192 System.Byte[]
79b8ef28    17195    145981324 System.Double[]
Total 97166 objects
Fragmented blocks larger than 0.5 MB:
    Addr     Size      Followed by
18c91000    3.7MB         19042c7c System.Threading.OverlappedData

Estos me dicen que nuestro uso de memoria no es excesivo, y nuestro montón de objetos grandes es muy pequeño como se esperaba (por lo que definitivamente no estamos tratando con la fragmentación del montón de objetos grandes aquí).

Sin embargo,!eeheap -gc muestra esto:

Number of GC Heaps: 1
generation 0 starts at 0x7452b504
generation 1 starts at 0x741321d0
generation 2 starts at 0x01f91000
ephemeral segment allocation context: none
 segment     begin allocated  size
01f90000  01f91000  02c578d0  0xcc68d0(13396176)
3cb10000  3cb11000  3d5228b0  0xa118b0(10557616)
3ece0000  3ece1000  3fc2ef48  0xf4df48(16047944)
3db10000  3db11000  3e8fc8f8  0xdeb8f8(14596344)
42e20000  42e21000  4393e1f8  0xb1d1f8(11653624)
18c90000  18c91000  19c53210  0xfc2210(16523792)
14c90000  14c91000  15c85c78  0xff4c78(16731256)
15c90000  15c91000  168b2870  0xc21870(12720240)
16c90000  16c91000  17690744  0x9ff744(10483524)
5c0c0000  5c0c1000  5d05381c  0xf9281c(16328732)
69c80000  69c81000  6a88bc88  0xc0ac88(12627080)
6b2d0000  6b2d1000  6b83e8a0  0x56d8a0(5691552)
6c2d0000  6c2d1000  6d0f2608  0xe21608(14816776)
6d2d0000  6d2d1000  6defc67c  0xc2b67c(12760700)
6e2d0000  6e2d1000  6ee7f304  0xbae304(12247812)
70000000  70001000  70bfb41c  0xbfa41c(12559388)
71ca0000  71ca1000  72893440  0xbf2440(12526656)
73b40000  73b41000  74531528  0x9f0528(10421544)
Large object heap starts at 0x02f91000
 segment     begin allocated  size
02f90000  02f91000  038df1d0  0x94e1d0(9757136)
Total Size:              Size: 0xe737614 (242447892) bytes.
------------------------------
GC Heap Size:            Size: 0xe737614 (242447892) bytes.

Lo que me sorprende aquí es que nuestro segmento final de pila SOH comienza en 0x73b41000, que está justo en el límite de nuestra memoria disponible en nuestro complemento de 32 bits.

Entonces, si estoy leyendo eso correctamente, nuestro problema parece ser que nuestra memoria virtual se ha fragmentado con segmentos de montón administrados.

Supongo que mis preguntas aquí serían:

¿Mi análisis es correcto?¿Es razonable nuestro enfoque para evitar la fragmentación de LOH mediante el uso de trozos?¿Existe una buena estrategia para evitar la fragmentación de la memoria que ahora parece que estamos viendo?

La respuesta más obvia que se me ocurre es agrupar y reutilizar nuestros fragmentos de memoria. Esto es potencialmente factible, pero es algo que preferiría evitar, ya que nos involucra en el manejo efectivo de esa parte de nuestra memoria.

Respuestas a la pregunta(1)

Su respuesta a la pregunta