2011-05-09 11 views
28

Tenemos este caso de uso en el que nos gustaría comprimir y almacenar objetos (en memoria) y descomprimirlos cuando sea necesario.Implementación de la compresión en memoria para objetos en Java

Los datos que queremos comprimir son bastante variados, desde vectores flotantes hasta cadenas y fechas.

¿Alguien puede sugerir alguna buena técnica de compresión para hacer esto?

Estamos viendo la facilidad de compresión y la velocidad de descompresión como los factores más importantes.

Gracias.

+3

java.util.Date no ocupa más que unas pocas palabras de memoria. ¿Estás seguro de que comprimirlo es beneficioso? –

+2

Una pregunta que debe hacerse: ¿por qué comprimir estos objetos en la memoria en lugar de escribirlos en el disco (por ejemplo, un archivo de memoria asignada)? El sistema de archivos simplemente se puede ver como una extensión de la jerarquía de la memoria (es decir, caché L1, caché L2, RAM, disco) con tiempos de acceso más lentos. Podría considerar almacenar en memoria caché los objetos usados ​​recientemente en la memoria mientras vaciando el resto en el disco. Las bibliotecas como Berkeley DB ofrecen esta funcionalidad. – Adamski

+0

Escribirlos en el disco no es una opción, ya que estamos muy preocupados por el rendimiento (por lo tanto, mantenerlos todos en la memoria). – Kichu

Respuesta

45

Si desea comprimir las instancias de MyObject usted podría tener que implementar Serializable y luego transmitir los objetos en una matriz de bytes comprimido, así:

ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
GZIPOutputStream gzipOut = new GZIPOutputStream(baos); 
ObjectOutputStream objectOut = new ObjectOutputStream(gzipOut); 
objectOut.writeObject(myObj1); 
objectOut.writeObject(myObj2); 
objectOut.close(); 
byte[] bytes = baos.toByteArray(); 

Entonces para descomprimir su byte[] de nuevo en los objetos:

ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 
GZIPInputStream gzipIn = new GZIPInputStream(bais); 
ObjectInputStream objectIn = new ObjectInputStream(gzipIn); 
MyObject myObj1 = (MyObject) objectIn.readObject(); 
MyObject myObj2 = (MyObject) objectIn.readObject(); 
objectIn.close(); 
+2

No todas las clases pueden implementar 'Serializable', especialmente si se trata de una clase JDK – BullyWiiPlaza

+1

Hay algunas buenas bibliotecas de serialización que pueden ocuparse de este problema – piegames

1

Si necesita comprimir objetos arbitrarios, un posible enfoque es serializar el objeto en una matriz de bytes, y luego usar, p. Ej. el algoritmo DEFLATE (el utilizado por GZIP) para comprimirlo. Cuando necesite el objeto, puede descomprimirlo y deserializarlo. No estoy seguro de cuán eficiente sería, pero será completamente general.

2

La mejor tecnología de compresión que conozco es ZIP. Java es compatible con ZipStream. Todo lo que necesita es serializar su objeto en una matriz de bytes y luego comprimirlo.

Sugerencias: utilice ByteArrayOutputStream, DataStream, ZipOutputStream.

+1

En no estoy de acuerdo. El algoritmo zip DEFLATE es bastante antiguo y el algoritmo utilizado en gzip, bzip2 o 7zip/lzma es probablemente más eficiente. – gabuzo

+4

Es bastante inútil tener una discusión sobre "la mejor tecnología de compresión" sin definir si está hablando de "mejor velocidad" o "mejor relación de compresión", porque realmente no puede tener ambas. –

2

Hay varios algoritmos de compresión implementados en el JDK. Compruebe el [java.util.zip](http://download.oracle.com/javase/6/docs/api/java/util/zip/package-summary.html) para todos los algoritmos implementados. Sin embargo, puede no ser bueno comprimir todos sus datos. Por ejemplo, una matriz vacía serializada puede tener varias docenas de bytes de longitud ya que el nombre de la clase subyacente se encuentra en la secuencia de datos serializada. Además, la mayoría de los algoritmos de compresión están diseñados para eliminar la redundancia de grandes bloques de datos. En objetos Java pequeños o medianos, probablemente tendrá muy poca o ninguna ganancia.

7

Similar a las respuestas anteriores, excepto que sugiero que use DeflatorOutputStream e InflatorInputStream ya que son más simples/más rápidas/más pequeñas que las alternativas. La razón por la que es más pequeño es que solo hace la compresión, mientras que las alternativas agregan extensiones de formato de archivo, como las comprobaciones y los encabezados de CRC.

Si el tamaño es importante, es posible que desee tener una serialización simple propia. Esto se debe a que ObjectOutputStream tiene una sobrecarga significativa que hace que los objetos pequeños sean mucho más grandes.(Mejora para objetos más grandes, especialmente cuando está comprimido)

p. un Integer toma 81 bytes, y la compresión no ayudará mucho para una cantidad tan pequeña de bytes. Es posible cortar esto significativamente.

+0

No es tan rápido como Gzip por alguna razón – Harish

+0

@Harish GZIP es el deflactor con un encabezado y un cheque de suma, tiene que hacer más trabajo por lo que sería sorprendente si fuera más lento. –

2

Este es un problema difícil:

En primer lugar, el uso de ObjectOutputStream probablemente no es la respuesta. El formato de secuencia incluye una gran cantidad de metadatos relacionados con el tipo. Si está serializando objetos pequeños, los metadatos obligatorios dificultarán que el algoritmo de compresión se "equilibre", incluso si implementa métodos de serialización personalizados.

Usando DataOutputStream con un mínimo (o no) que se añade información de tipo dará un mejor resultado, pero los datos mixto no es generalmente que compresible usando un propósito general algoritmos de compresión.

Para una mejor compresión, puede que tenga que mirar las propiedades de los datos que está comprimiendo. Por ejemplo:

  • Date objetos podrían ser representados como valores int si sabe que tiene una precisión de 1 día.
  • Las secuencias de los valores int pueden codificarse en longitud de ejecución o codificarse en delta si tienen las propiedades correctas.
  • y así sucesivamente.

Sin embargo manera en que lo hace, usted tendrá que hacer una cantidad seria de trabajo para obtener una cantidad de mérito de la compresión. OMI, una mejor idea sería escribir los objetos en una base de datos, un almacén de datos o un archivo y usar el almacenamiento en caché para mantener los objetos usados ​​frecuentemente en la memoria.

2

La compresión de objetos buscados en Java generalmente no es buena ... no tan buena.

Antes que nada, debe comprender que un objeto Java tiene mucha información adicional que no es necesaria. Si tiene millones de objetos, tiene esta sobrecarga millones de veces.

Como ejemplo, nos permite una lista de doble enlace. Cada elemento tiene un puntero anterior y uno siguiente + almacena un valor largo (marca de tiempo) + byte para el tipo de interacción y dos enteros para los identificadores de usuario. Como usamos la compresión del puntero, somos 6Bytes * 2 + 8 + 4 * 2 = 28Bytes. Java agrega 8 Bytes + 12bytes para el relleno. Esto hace 48Bytes por Elemento.

Ahora creamos 10 millones de listas con 20 elementos cada una (serie temporal de eventos de clic de los usuarios durante los últimos tres años (queremos encontrar patrones)).

Así que tenemos 200Million * 48 Bytes de elementos = 10GB de memoria (bueno, no mucho).

Aceptar junto a la recolección de basura nos mata y la sobrecarga dentro de los skyrocks JDK, terminamos con 10 GB de memoria.

Ahora permitamos usar nuestro propio almacenamiento de memoria/objeto. Lo almacenamos como una tabla de datos en columnas donde cada objeto es en realidad una sola fila. Así que tenemos 200 millones de filas en una marca de tiempo, anterior, siguiente, colección de userIdA y userIdB.

Anterior y siguiente ahora son ID de fila a fila y se convierten en 4 bytes (o 5 bytes si excedemos las 4 billones de entradas (poco probable)).

Así que tenemos 8 + 4 + 4 + 4 + 4 => 24 * 200 Mio = 4.8GB + sin problema de GC.

Dado que la columna de marca de tiempo almacena las marcas de tiempo de forma mínima y nuestras marcas de tiempo están dentro de los tres años, solo necesitamos 5 bytes para almacenar cada una de las marcas de tiempo. Dado que el puntero ahora está almacenado relativo (+ y -) y debido a que las series de clic están estrechamente relacionadas oportunamente, solo necesitamos 2 bytes en promedio tanto para el anterior como para el siguiente y para los identificadores de usuario usamos un diccionario ya que la serie de clic es para aproximadamente 500k usuarios solo necesitamos tres bytes cada uno.

Así que ahora tenemos 5 + 2 + 2 + 3 + 3 => 15 * 200Mio => 3GB + Diccionario de 4 * 500k * 4 = 8MB = 3GB + 8MB. Suena diferente a 10GB ¿verdad?

Pero todavía no hemos terminado. Como ahora no tenemos más objetos que filas y datos, almacenamos cada serie como una fila de tabla y usamos columnas especiales que son colecciones de matriz que en realidad almacenan 5 valores y un puntero a los siguientes cinco valores + un puntero anterior.

Tenemos listas de 10Mio con 20 enries cada uno (ya que tenemos gastos generales), tenemos por lista 20 * (5 + 3 + 3) + 4 * 6 (vamos a agregar algunos gastos generales de elementos parcialmente llenos) => 20 * 11 + 5 * 6 => 250 * 10Mio => 2,5 GB + podemos acceder a las matrices más rápido que los elementos que caminan.

Pero bueno, no ha terminado aún ... las indicaciones de fecha y hora están ahora relativamente almacenadas y solo requieren 3 bytes por entrada + 5 en la primera entrada. -> entonces ahorramos mucho más 20 * 9 + 2 + 5 * 6 => 212 * 10Mio => 2,12 GB. Y ahora lo guardamos todo en la memoria usando gzip it y da como resultado 1GB ya que podemos almacenarlo todo lineal primero almacenando la longitud de la matriz, todas las marcas de tiempo, todos los identificadores de usuario, lo que hace muy recomendable que haya patrones en los bits para ser comprimibles . Como utilizamos un diccionario, simplemente lo clasificamos de acuerdo con la capacidad de propagación de cada ID de usuario para formar parte de una serie.

Y como todo es una tabla, puedes deserializar todo en velocidad casi de lectura, por lo que 1GB en una SSD moderna cuesta 2 segundos para cargar. Pruebe esto con la serialización/deserialización y puede escuchar llorar al usuario interno.

Por lo tanto, antes de comprimir los datos serializados, almacenarlos en tablas, verificar cada columna/propiedad si se puede comprimir lógicamente. Y finalmente diviértete con eso.

Y recuerda que 1TB (ECC) costó 10k hoy. No es nada. Y 1TB SSD 340 Euro. Así que no pierdas tu tiempo en ese tema a menos que realmente tengas que hacerlo.

+0

Err, bueno ... en primer lugar, gracias por la larga explicación detallada. Nuestra aplicación (o más bien ahora su. He dejado el trabajo :)) está completamente en la memoria, tiene que ser y es increíblemente complicado y podría haber simplificado demasiado el problema. Larga historia corta, los diferentes requisitos significaban que tenía que permanecer en la memoria. No pudimos hacer nada más. – Kichu

+0

Así que de nuevo, gracias por la explicación detallada. Aunque dejé el trabajo, etc. fue bueno leer eso. Creo que la mayoría de las otras respuestas coinciden fundamentalmente en que sería mejor usar un DB y un SSD seguramente ayudaría, pero, por desgracia, teníamos requisitos de rendimiento muy estrictos y terminamos no haciendo nada. Acabo de comprar algunos servidores más para poder tratar con más datos (era una aplicación distribuida). – Kichu

+0

Sé que hacemos lo mismo. Vamos a arrojarle tantos dispositivos. De lo que escribí principalmente fue sobre un objeto compacto menos gestión de memoria con una relación de 5: 1 a 10: 1 en el uso actual de la memoria. Con una declaración tan compacta, simplemente volcamos en el disco y tenemos un arranque increíblemente rápido sin nosotros para reconstruir la representación interna. Eso vino finalmente gratis. Los datos están todos en memoria sin acceso de disco debido al uso. –

Cuestiones relacionadas