¿Por qué gran montón de objetos y por qué nos importa?

He leído sobre Generations y Large Heap Heap. Pero todavía no entiendo cuál es la importancia (o beneficio) de tener un gran montón de objetos.

¿Qué podría haber salido mal (en términos de rendimiento o memoria) si CLR simplemente hubiera confiado en la Generación 2 (teniendo en cuenta que el umbral para Gen0 y Gen1 es pequeño para manejar objetos grandes) para almacenar objetos grandes?

Una recolección de basura no solo elimina objetos no referenciados, sino que también compacta el montón. Esa es una optimización muy importante. No solo hace que el uso de memoria sea más eficiente (sin agujeros no utilizados), hace que la memoria caché de la CPU sea mucho más eficiente. La memoria caché es realmente importante en los procesadores modernos, son un orden de magnitud mucho más rápido que el bus de memoria.

La compactación se hace simplemente copiando bytes. Eso sin embargo toma tiempo. Cuanto más grande sea el objeto, más probable es que el costo de copiarlo supere las posibles mejoras en el uso de la memoria caché de la CPU.

Entonces corrieron un montón de puntos de referencia para determinar el punto de equilibrio. Y llegó a 85,000 bytes como el punto de corte donde la copia ya no mejora el rendimiento. Con una excepción especial para matrices de doble, se consideran ‘grandes’ cuando la matriz tiene más de 1000 elementos. Esa es otra optimización para el código de 32 bits, el gran asignador de montón de objetos tiene la propiedad especial de que asigna memoria en direcciones que están alineadas a 8, a diferencia del asignador generacional regular que solo asigna alineado a 4. Esa alineación es un gran problema para el doble Leer o escribir un doble mal alineado es muy caro. Curiosamente, la escasa información de Microsoft nunca menciona arreglos de larga duración, no estoy seguro de qué pasa con eso.

Vaya, hay mucha angustia de progtwigdor sobre el gran montón de objetos que no se compacta. Invariablemente, esto se dispara cuando escriben progtwigs que consumen más de la mitad del espacio de direcciones disponible. Seguido por el uso de una herramienta como un generador de perfiles de memoria para descubrir por qué el progtwig bombardeó a pesar de que todavía había mucha memoria virtual no utilizada disponible. Tal herramienta muestra los agujeros en el LOH, fragmentos de memoria no utilizados donde anteriormente vivía un objeto grande pero obtenía la basura recolectada. Tal es el precio inevitable del LOH, el agujero solo puede ser reutilizado por una asignación para un objeto que es igual o más pequeño en tamaño. El verdadero problema es suponer que un progtwig debe poder consumir toda la memoria virtual en cualquier momento.

Un problema que de lo contrario desaparece completamente al ejecutar el código en un sistema operativo de 64 bits. Un proceso de 64 bits tiene 8 terabytes de espacio de direcciones de memoria virtual disponible, 3 órdenes de magnitud más que un proceso de 32 bits. Simplemente no puedes quedarte sin agujeros.

Para resumir, el LOH hace que el código sea más eficiente. A costa de utilizar el espacio de direcciones de memoria virtual disponible menos eficiente.


ACTUALIZAR, .NET 4.5.1 ahora admite la compactación de la propiedad LOH, GCSettings.LargeObjectHeapCompactionMode . Cuidado con las consecuencias, por favor.

Si el tamaño del objeto es mayor que algún valor fijado (85000 bytes en .NET 1), CLR lo coloca en el Montón de objetos grandes. Esto optimiza:

  1. Asignación de objetos (los objetos pequeños no se mezclan con objetos grandes)
  2. Recolección de basura (LOH recolectada solo en GC completo)
  3. Desfragmentación de memoria (LOH nunca se compacta con poca frecuencia)

La diferencia esencial de Small Heap Heap (SOH) y Large Object Heap (LOH) es que la memoria en SOH se compacta cuando se recostack, mientras que LOH no, como se ilustra en este artículo . La compactación de objetos grandes cuesta mucho. Similar a los ejemplos en el artículo, digamos que mover un byte en memoria necesita 2 ciclos, luego compactar un objeto de 8MB en una computadora de 2GHz necesita 8ms, lo cual es un gran costo. Teniendo en cuenta que los objetos grandes (matrices en la mayoría de los casos) son bastante comunes en la práctica, supongo que esa es la razón por la cual Microsoft inserta objetos grandes en la memoria y propone LOH.

Por cierto, según esta publicación , LOH por lo general no genera problemas de fragmentos de memoria.

El principal es que es poco probable (y posiblemente sea un mal diseño) que un proceso cree muchos objetos grandes de vida corta, por lo que CLR asigna objetos grandes a un montón separado en el que ejecuta GC en un cronogtwig diferente al montón normal. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

No soy un experto en el CLR, pero me imagino que tener un montón dedicado para objetos grandes puede evitar innecesarios barridos GC de los montones generacionales existentes. La asignación de un objeto grande requiere una gran cantidad de memoria libre contigua . Para proporcionar eso de los “agujeros” dispersos en los montones generacionales, necesitarías compactaciones frecuentes (que solo se realizan con ciclos de GC).