c# - Cuantificación del rendimiento de la recolección de basura frente a la administración de memoria explícita
java c++ (5)
Encontré este artículo aquí:
Cuantificación del rendimiento de la recolección de basura frente a la administración de memoria explícita
http://www.cs.umass.edu/~emery/pubs/gcvsmalloc.pdf
En la sección de conclusión, se lee:
Al comparar el tiempo de ejecución, el consumo de espacio y las huellas de memoria virtual en un rango de puntos de referencia, mostramos que el rendimiento en tiempo de ejecución del recolector de basura con mejor rendimiento es competitivo con la administración de memoria explícita cuando se le da suficiente memoria. En particular, cuando la recolección de basura tiene cinco veces más memoria que la requerida, su rendimiento en tiempo de ejecución coincide o excede ligeramente el de la administración de memoria explícita. Sin embargo, el rendimiento de la recolección de basura se degrada sustancialmente cuando debe usar montones más pequeños. Con tres veces más memoria, corre un 17% más lento en promedio, y con el doble de memoria, corre un 70% más lento. La recolección de basura también es más susceptible de paginación cuando la memoria física es escasa. En tales condiciones, todos los recolectores de basura que examinamos aquí sufren penalizaciones de rendimiento de orden de magnitud en relación con la gestión de memoria explícita.
Por lo tanto, si mi entendimiento es correcto: si tengo una aplicación escrita en C ++ nativo que requiere 100 MB de memoria, para lograr el mismo rendimiento con un lenguaje "administrado" (es decir, basado en el recolector de basura) (por ejemplo, Java, C #), la aplicación debería requieren 5 * 100 MB = 500 MB? (Y con 2 * 100 MB = 200 MB, ¿la aplicación administrada se ejecutaría un 70% más lenta que la aplicación nativa?)
¿Sabe si los recolectores de basura actuales (es decir, las últimas máquinas virtuales de Java y .NET 4.0) sufren los mismos problemas descritos en el artículo mencionado anteriormente? ¿Ha mejorado el rendimiento de los modernos recolectores de basura?
Gracias.
Si tengo una aplicación escrita en C ++ nativo que requiere 100 MB de memoria, para lograr el mismo rendimiento con un lenguaje "gestionado" (es decir, basado en el recolector de basura) (por ejemplo, Java, C #), la aplicación debería requerir 5 * 100 MB = 500 MB ? (Y con 2 * 100 MB = 200 MB, ¿la aplicación administrada se ejecutaría un 70% más lenta que la aplicación nativa?)
Solo si la aplicación tiene un cuello de botella en la asignación y desasignación de memoria. Tenga en cuenta que el documento habla exclusivamente sobre el rendimiento del propio recolector de basura .
Al menos mientras lo leo, su pregunta real es si ha habido desarrollos significativos en la recolección de basura o en la administración de memoria manual desde que se publicó ese documento que invalidaría sus resultados. La respuesta a eso es algo mixta. Por un lado, los proveedores que proveen recolectores de basura los afinan, por lo que su desempeño tiende a mejorar con el tiempo. Por otro lado, no ha habido nada como avances importantes, como los nuevos algoritmos de recolección de basura.
Los administradores de pilas manuales generalmente también mejoran con el tiempo. Dudo que la mayoría esté en sintonía con la regularidad de los recolectores de basura, pero en el transcurso de 5 años, probablemente la mayoría haya tenido al menos un poco de trabajo realizado.
En resumen, indudablemente, ambos han mejorado al menos un poco, pero en ningún caso ha habido nuevos algoritmos nuevos que cambien el panorama fundamental. Es dudoso que las implementaciones actuales den una diferencia de exactamente el 17% según lo citado en el artículo, pero existe una gran probabilidad de que si repitiera las pruebas hoy, aún obtendría una diferencia en algún lugar alrededor del 15-20% o más. Las diferencias entre entonces y ahora son probablemente menores que las diferencias entre algunos de los diferentes algoritmos que probaron en ese momento.
Michael Borgwardt tiene razón en cuanto a si la aplicación tiene un cuello de botella en la asignación de memoria. Esto está de acuerdo con la ley de Amdahl.
Sin embargo, he usado C ++, Java y VB .NET. En C ++, existen potentes técnicas disponibles que asignan memoria en la pila en lugar del montón. La asignación de pila es fácilmente cientos de veces más rápida que la asignación de pila. Yo diría que el uso de estas técnicas podría eliminar tal vez una asignación en ocho, y el uso de cadenas de escritura una asignación en cuatro.
No es una broma cuando la gente dice que el código C ++ altamente optimizado puede generar el mejor código Java posible. Es la verdad plana.
Microsoft afirma que la sobrecarga en el uso de cualquiera de la familia de lenguajes .NET sobre C ++ es de dos a uno. Creo que ese número es el correcto para la mayoría de las cosas.
SIN EMBARGO, los entornos administrados tienen un beneficio particular, ya que cuando se trata de programadores inferiores, no tiene que preocuparse por que un módulo destruya la memoria de otro módulo y la falla resultante sea atribuida al desarrollador incorrecto y el error es difícil de encontrar.
No estoy seguro de cuán renuente es tu pregunta hoy. Una aplicación de rendimiento crítico no debería gastar una parte significativa de su tiempo en la creación de objetos (como es muy probable que lo haga el micro-punto de referencia) y es más probable que el rendimiento en los sistemas modernos esté determinado por la forma en que la aplicación se adapta a las CPU. caché, en lugar de la cantidad de memoria principal que utiliza.
Por cierto: hay muchos ticks que puedes hacer en C ++ que son compatibles con esto y que no están disponibles en Java.
Si le preocupa el costo de la creación de GC o de objetos, puede tomar medidas para minimizar la cantidad de objetos que crea. Esta es generalmente una buena idea donde el rendimiento es crítico en cualquier idioma.
El costo de la memoria principal no es tan problemático como lo era para mí. Una máquina con 48 GB es relativamente barata en estos días. Se puede alquilar un servidor de 8 núcleos con 48 GB de memoria principal por £ 9 / día. Intenta contratar un desarrollador por £ 9 / d. ;) Sin embargo, lo que sigue siendo relativamente caro es la memoria caché de la CPU. Es bastante difícil encontrar un sistema con más de 16 MB de caché de CPU. cf 48,000 MB de memoria principal. Un sistema funciona mucho mejor cuando una aplicación está utilizando su caché de CPU y esta es la cantidad de memoria que se debe considerar si el rendimiento es crítico.
Parece que estás preguntando dos cosas:
- han mejorado los GC desde que se realizó la investigación, y
- ¿Puedo usar las conclusiones del documento como una fórmula para predecir la memoria requerida?
La respuesta a la primera es que no ha habido avances importantes en los algoritmos GC que invalidarían las conclusiones generales:
- La gestión de memoria GC''ed todavía requiere significativamente más memoria virtual.
- Si intenta restringir el tamaño del almacenamiento dinámico, el rendimiento del GC disminuye significativamente.
- Si la memoria real está restringida, el enfoque de administración de memoria GCeded en un rendimiento sustancialmente peor debido a los gastos generales de paginación.
Sin embargo, las conclusiones no se pueden utilizar realmente como una fórmula:
- El estudio original se realizó con JikesRVM en lugar de Sun JVM.
- Los recolectores de basura de Sun JVM han mejorado en los ~ 5 años transcurridos desde el estudio.
- El estudio no parece tener en cuenta que las estructuras de datos de Java ocupan más espacio que las estructuras de datos de C ++ equivalentes por razones que no están relacionadas con GC.
En el último punto, he visto una presentación de alguien que habla sobre los gastos generales de memoria de Java. Por ejemplo, encontró que el tamaño de representación mínimo de una cadena Java es algo así como 48 bytes. (Una cadena consta de dos objetos primitivos; uno, un objeto con 4 campos del tamaño de una palabra y el otro una matriz con un mínimo de 1 palabra de contenido. Cada objeto primitivo también tiene 3 o 4 palabras de cabecera). usa mucho más memoria de lo que la gente piensa.
Estos gastos generales no están relacionados con el GC per se . Más bien, son consecuencias directas e indirectas de las decisiones de diseño en el lenguaje Java, JVM y bibliotecas de clases. Por ejemplo:
- Cada encabezado 1 del objeto primitivo de Java reserva una palabra para el valor del "código de hash de identidad" del objeto y una o más palabras para representar el bloqueo del objeto.
- La representación de una cadena tiene que usar una "matriz de caracteres" separada debido a las limitaciones de JVM. Dos de los otros tres campos son un intento de hacer que la operación de
substring
menos memoria. - Los tipos de colección de Java utilizan mucha memoria porque los elementos de la colección no se pueden encadenar directamente. Así, por ejemplo, los gastos generales de una clase de colección de listas (hipotéticas) vinculadas individualmente en Java serían 6 palabras por elemento de lista. Por el contrario, una lista enlazada C / C ++ óptima (es decir, con cada elemento que tiene un puntero "siguiente") tiene una sobrecarga de una palabra por elemento de lista.
1 - De hecho, los gastos generales son menos que esto en promedio. La JVM solo "infla" un bloqueo después del uso y la contención, y se utilizan trucos similares para el código hash de identidad. La sobrecarga fija es sólo de unos pocos bits. Sin embargo, estos bits se suman a un encabezado de objeto considerablemente más grande ... que es el punto real aquí.