java - example - La mejor práctica para crear millones de pequeños objetos temporales
garbage collector java example (13)
¡Bien, hay varias preguntas en una aquí!
1 - ¿Cómo se gestionan los objetos de vida corta?
Como se dijo anteriormente, la JVM puede tratar perfectamente una gran cantidad de objetos efímeros, ya que sigue la Hipótesis Generacional Débil .
Tenga en cuenta que estamos hablando de objetos que alcanzaron la memoria principal (montón). Este no es siempre el caso. Muchos de los objetos que crea ni siquiera dejan un registro de CPU. Por ejemplo, considere esto for-loop
for(int i=0, i<max, i++) {
// stuff that implies i
}
No pensemos en el desenrollado de bucles (una optimización que la JVM realiza en gran medida en su código). Si max
es igual a Integer.MAX_VALUE
, es posible que el loop tarde un tiempo en ejecutarse. Sin embargo, la variable i
nunca escapará del bloque de bucle. Por lo tanto, la JVM colocará esa variable en un registro de CPU, la incrementará regularmente pero nunca la enviará de vuelta a la memoria principal.
Por lo tanto, crear millones de objetos no es gran cosa si solo se usan localmente. Estarán muertos antes de ser almacenados en el Edén, por lo que el GC ni siquiera los notará.
2 - ¿Es útil reducir la sobrecarga del GC?
Como de costumbre, depende.
En primer lugar, debe habilitar el registro de GC para tener una visión clara de lo que está sucediendo. Puede habilitarlo con -Xloggc:gc.log -XX:+PrintGCDetails
.
Si su aplicación está pasando mucho tiempo en un ciclo de GC, entonces, sí, ajuste el GC, de lo contrario, puede que no valga la pena.
Por ejemplo, si tiene un GC joven cada 100 ms que tarda 10 ms, gasta el 10% de su tiempo en el CG, y tiene 10 colecciones por segundo (que es enorme). En tal caso, no pasaría ningún tiempo en la sintonización de GC, ya que esos 10 GC / s seguirían allí.
3 - Alguna experiencia
Tuve un problema similar en una aplicación que creaba una gran cantidad de una clase determinada. En los registros de GC, noté que la tasa de creación de la aplicación era de alrededor de 3 GB / s, que es demasiado (vamos ... 3 gigabytes de datos por segundo?).
El problema: Demasiados GC frecuentes causados por la creación de demasiados objetos.
En mi caso, adjunté un generador de perfiles de memoria y noté que una clase representaba un gran porcentaje de todos mis objetos. Localicé las instancias para descubrir que esta clase era básicamente un par de booleanos envueltos en un objeto. En ese caso, dos soluciones estaban disponibles:
Vuelva a trabajar el algoritmo para que no devuelva un par de booleanos, sino que tengo dos métodos que devuelven cada booleano por separado
Guarde en caché los objetos, sabiendo que solo había 4 instancias diferentes
Elegí el segundo, ya que tuvo el menor impacto en la aplicación y fue fácil de introducir. Me llevó minutos poner una fábrica con un caché que no sea seguro para subprocesos (no necesitaba seguridad de subprocesos ya que eventualmente tendría solo 4 instancias diferentes).
La tasa de asignación bajó a 1 GB / s, al igual que la frecuencia de GC jóvenes (dividido por 3).
Espero que ayude !
¿Cuáles son las "mejores prácticas" para crear (y liberar) millones de objetos pequeños?
Estoy escribiendo un programa de ajedrez en Java y el algoritmo de búsqueda genera un solo objeto "Mover" para cada movimiento posible, y una búsqueda nominal puede generar fácilmente más de un millón de movimientos de objetos por segundo. El JVM GC ha sido capaz de manejar la carga en mi sistema de desarrollo, pero estoy interesado en explorar enfoques alternativos que:
- Minimice la sobrecarga de la recolección de basura, y
- reducir la huella de memoria pico para sistemas de gama baja.
La gran mayoría de los objetos tienen una vida muy corta, pero aproximadamente el 1% de los movimientos generados persisten y se devuelven como el valor persistente, por lo que cualquier técnica de agrupamiento o almacenamiento en caché debería permitir excluir objetos específicos de su reutilización. .
No espero un código de ejemplo completamente desarrollado, pero agradecería sugerencias para lecturas e investigaciones adicionales, o ejemplos de código abierto de naturaleza similar.
Creo que deberías leer sobre asignación de pila en Java y análisis de escape.
Porque si profundiza en este tema, puede encontrar que sus objetos ni siquiera están asignados en el montón, y GC no los recopila en la forma en que están los objetos en el montón.
Hay una explicación de wikipedia del análisis de escape, con un ejemplo de cómo funciona esto en Java:
Dado que estás escribiendo un programa de ajedrez, existen algunas técnicas especiales que puedes usar para un rendimiento decente. Un enfoque simple es crear una gran variedad de largos (o bytes) y tratarlos como una pila. Cada vez que su generador de movimientos crea movimientos, empuja un par de números en la pila, por ejemplo, se mueve de cuadrado a cuadrado. A medida que evalúa el árbol de búsqueda, se deshace de movimientos y actualiza una representación de la placa.
Si quieres objetos de uso de energía expresiva. Si quieres que la velocidad (en este caso) te vuelvas nativa.
Desde la versión 6, el modo de servidor de JVM emplea una técnica de análisis de escape . Utilizándolo, puedes evitar GC todos juntos.
Ejecute la aplicación con la recolección de basura detallado:
java -verbose:gc
Y te dirá cuándo se acumula. Habría dos tipos de barridos, uno rápido y otro completo.
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]
La flecha es antes y después del tamaño.
Mientras solo haga GC y no un GC completo, estará en casa seguro. El GC común es un recopilador de copias en la "generación joven", por lo que los objetos que ya no se referencian son simplemente olvidados, que es exactamente lo que usted desearía.
Lectura de Java SE 6 HotSpot Máquina virtual Recolección de basura El ajuste probablemente sea útil.
He encontrado un problema similar. En primer lugar, intente reducir el tamaño de los objetos pequeños. Presentamos algunos valores de campo predeterminados que hacen referencia a ellos en cada instancia de objeto.
Por ejemplo, MouseEvent tiene una referencia a la clase Point. Almacenamos puntos en caché y los referenciamos en lugar de crear nuevas instancias. Lo mismo para, por ejemplo, cadenas vacías.
Otra fuente eran múltiples booleanos que fueron reemplazados por un int y para cada booleano usamos solo un byte del int.
He tratado este escenario con algún código de procesamiento XML hace algún tiempo. Me encontré creando millones de objetos de etiquetas XML que eran muy pequeños (generalmente solo una cadena) y extremadamente efímeros (el fracaso de una comprobación de XPath significaba no coincidencia, por lo que se descartó).
Realicé algunas pruebas serias y llegué a la conclusión de que solo podía lograr una mejora del 7% en la velocidad usando una lista de etiquetas descartadas en lugar de crear nuevas. Sin embargo, una vez implementado, descubrí que la cola libre necesitaba un mecanismo agregado para eliminarla si se hacía demasiado grande, esto anulaba por completo mi optimización, así que la cambié a una opción.
En resumen, probablemente no valga la pena, pero me alegra ver que lo estás pensando, demuestra que te importa.
Los grupos de objetos proporcionan mejoras tremendas (en algún momento 10x) sobre la asignación de objetos en el montón. ¡Pero la implementación anterior que usa una lista vinculada es ingenua e incorrecta! La lista vinculada crea objetos para administrar su estructura interna anulando el esfuerzo. Un Ringbuffer que usa una matriz de objetos funciona bien. En el ejemplo give (un programa de ajedrez que gestiona movimientos) el Ringbuffer debe envolverse en un objeto titular para la lista de todos los movimientos calculados. Solo las referencias de objetos del soporte de movimientos se pasarían.
No soy un gran admirador de GC, así que siempre trato de encontrar formas de evitarlo. En este caso, sugeriría usar el patrón Object Pool :
La idea es evitar la creación de objetos nuevos almacenándolos en una pila para que puedas reutilizarlos más tarde.
Class MyPool
{
LinkedList<Objects> stack;
Object getObject(); // takes from stack, if it''s empty creates new one
Object returnObject(); // adds to stack
}
Si acaba de valorar objetos (es decir, no hace referencia a otros objetos) y realmente, pero realmente quiero decir toneladas y toneladas de ellos, puede usar ByteBuffers
directos con ordenamiento nativo de bytes [este último es importante] y necesita unos pocos cientos de líneas de código para asignar / reutilizar + getter / setters. Los getters son similares a long getQuantity(int tupleIndex){return buffer.getLong(tupleInex+QUANTITY_OFFSSET);}
Eso resolvería el problema de GC casi por completo siempre que lo asignes una sola vez, es decir, una gran parte y luego gestiones los objetos tú mismo. En lugar de referencias, solo tendrías índice (es decir, int
) en el ByteBuffer
que debe pasarse. Es posible que necesite hacer la memoria alinearse también.
La técnica se sentiría como usar C and void*
, pero con un poco de envoltura es soportable. Un inconveniente de rendimiento podría ser la comprobación de límites si el compilador no puede eliminarlo. Una ventaja importante es la localidad si procesa las tuplas como vectores, la falta del encabezado del objeto también reduce la huella de memoria.
Aparte de eso, es probable que no necesites un enfoque como la generación joven de prácticamente todas las JVM muere trivialmente y el costo de asignación es solo un bache de puntero. El costo de asignación puede ser un poco mayor si utiliza los campos final
ya que requieren una valla de memoria en algunas plataformas (es decir, ARM / Power), pero en x86 es gratuito.
Simplemente cree sus millones de objetos y escriba su código de la manera adecuada: no guarde referencias innecesarias a estos objetos. GC hará el trabajo sucio por ti. Puede jugar con GC detallado como se menciona para ver si realmente son GC. Java IS acerca de crear y liberar objetos. :)
Suponiendo que encuentre que GC es un problema (como otros señalan que podría no serlo), implementará su propia gestión de memoria para su caso especial, es decir, una clase que sufre un abandono masivo. Dale a los objetos una oportunidad de agrupar, he visto casos en los que funciona bastante bien. La implementación de pools de objetos es una ruta bien trillada, por lo que no es necesario volver a visitarla aquí, tenga cuidado con:
- multi-threading: el uso de grupos locales de subprocesos podría funcionar para su caso
- estructura de datos de respaldo: considere usar ArrayDeque, ya que funciona bien en eliminar y no tiene gastos de asignación
- limita el tamaño de tu piscina :)
Medir antes / después, etc., etc.
Una solución que he usado para tales algoritmos de búsqueda es crear un solo objeto Move, mutarlo con un nuevo movimiento y luego deshacer el movimiento antes de abandonar el alcance. Probablemente estés analizando solo un movimiento a la vez, y luego solo guardes el mejor movimiento en algún lugar.
Si eso no es posible por alguna razón, y desea reducir el uso máximo de memoria, un buen artículo sobre la eficiencia de la memoria está aquí: cs.virginia.edu/kim/publicity/pldi09tutorials/…