2009-02-11 9 views
11

Admitir una aplicación heredada de Java que utiliza archivos planos (texto sin formato) para la persistencia. Debido a la naturaleza de la aplicación, el tamaño de estos archivos puede alcanzar 100s MB por día, y a menudo el factor limitante en el rendimiento de la aplicación es el archivo IO. Actualmente, la aplicación usa un simple ol 'java.io.FileOutputStream para escribir datos en el disco.Rendimiento/estabilidad de un archivo de memoria asignado - Native o MappedByteBuffer - frente a simple ol FileOutputStream

Recientemente, hemos tenido varios desarrolladores que afirman que el uso de archivos mapeados en memoria, implementados en código nativo (C/C++) y accedidos a través de JNI, proporcionaría un mayor rendimiento. Sin embargo, FileOutputStream ya usa métodos nativos para sus métodos principales (es decir, escribir (byte [])), por lo que parece una suposición tenue sin datos duros o al menos evidencia anecdótica.

Tengo varias preguntas sobre este:

  1. ¿Es esta afirmación realmente cierto? ¿Se asignaron los archivos de memoria siempre proporcionan una E/S más rápida en comparación con de Java FileOutputStream?

  2. que hace la clase MappedByteBuffer accede desde un FileChannel proporcionar la misma funcionalidad que una biblioteca de archivo asignado memoria nativa visitada a través de JNI? ¿Qué es MappedByteBuffer que le falta que podría llevarlo a utilizar una solución JNI?

  3. ¿Cuáles son los riesgos de utilizar archivos mapeados en memoria para disco IO en una aplicación de producción ? Es decir, las aplicaciones que tienen un tiempo de actividad continuo con reinicios mínimos (una vez al mes, máx.). Anécdotas de la vida real de la producción aplicaciones (Java o no) preferido.

Pregunta # 3 es importante - de que pudiera responder a esta pregunta a mí mismo parcialmente escribiendo una aplicación de "juguete" que perf pruebas IO utilizando las diversas opciones descritas anteriormente, pero mediante la publicación de SO que estoy esperando anécdotas/datos del mundo real para masticar.

[EDITAR] Aclaración: cada día de funcionamiento, la aplicación crea múltiples archivos que varían en tamaño de 100 MB a 1 gig. En total, la aplicación podría estar escribiendo múltiples gigas de datos por día.

Respuesta

4

Puede acelerar un poco las cosas examinando cómo se están almacenando los datos durante las escrituras. Esto tiende a ser específico de la aplicación, ya que necesitaría una idea de los patrones de escritura de datos esperados. Si la coherencia de los datos es importante, habrá soluciones de compromiso aquí.

Si solo está escribiendo nuevos datos en el disco desde su aplicación, la E/S asignada en la memoria probablemente no ayude mucho. No veo ninguna razón por la que desee invertir tiempo en alguna solución nativa codificada de forma personalizada. Simplemente parece demasiada complejidad para su aplicación, por lo que ha proporcionado hasta ahora.

Si está seguro de que realmente necesita un mejor rendimiento de E/S o solo un rendimiento O en su caso, buscaría una solución de hardware como una matriz de discos sintonizados. Lanzar más hardware al problema muchas veces es más rentable desde el punto de vista comercial que pasar el tiempo optimizando el software. También es generalmente más rápido de implementar y más confiable.

En general, hay muchas trampas en la optimización excesiva del software.Introducirá nuevos tipos de problemas en su aplicación. Es posible que se encuentre con problemas de memoria/golpeteo de GC que conducirían a más mantenimiento/ajuste. La peor parte es que muchos de estos problemas serán difíciles de probar antes de entrar en producción.

Si fuera mi aplicación, probablemente me quedaría con el FileOutputStream con un posible búfer sintonizado. Después de eso, utilizaría la solución honrada de lanzarle más hardware.

+0

Escogió esta respuesta para repartir los puntos. Además, la frase "o simplemente O performance en tu caso" realmente me quedó grabada. – noahlz

+1

Sí, todo se trata de O. – Gary

1

En cuanto al punto 3: si la máquina falla y hay páginas que no se descargaron al disco, entonces se pierden. Otra cosa es el desperdicio del espacio de direcciones: asignar un archivo a la memoria consume espacio de direcciones (y requiere un área contigua), y bueno, en las máquinas de 32 bits es un poco limitado. Pero has dicho que alrededor de 100 MB, por lo que no debería ser un problema. Y una cosa más: ampliar el tamaño del archivo mmaped requiere algo de trabajo. Por favor, this SO discussion también puede darle algunas ideas.

+0

Actualmente 100 s de MB, por lo que hasta un Gig por archivo. ¡Y algunas implementaciones de la aplicación tienen varios de esos archivos! Voy a editar para ser más claro. – noahlz

2

Desde mi experiencia, los archivos mapeados en memoria funcionan MUCHO mejor que el acceso simple a archivos tanto en tiempo real como en casos de uso de persistencia. He trabajado principalmente con C++ en Windows, pero las prestaciones de Linux son similares, y está planeando usar JNI de todos modos, así que creo que se aplica a su problema.

Para obtener un ejemplo de un motor de persistencia creado en un archivo asignado a la memoria, consulte Metakit. Lo he usado en una aplicación donde los objetos eran simples vistas sobre datos mapeados en memoria, el motor se encargó de todas las cosas del mapeo detrás de las cortinas. Esto fue rápido y eficiente desde el punto de vista de la memoria (al menos en comparación con los enfoques tradicionales como los que usaba la versión anterior), y obtuvimos transacciones de compromiso/reversión de forma gratuita.

En otro proyecto tuve que escribir aplicaciones de red multicast. Los datos se enviaron en orden aleatorio para minimizar el impacto de la pérdida consecutiva de paquetes (combinada con FEC y esquemas de bloqueo). Además, los datos podrían exceder el espacio de direcciones (los archivos de video eran más grandes que 2Gb) por lo que la asignación de memoria estaba fuera de cuestión. En el lado del servidor, las secciones de archivo se mapearon a la memoria bajo demanda y la capa de red seleccionó directamente los datos de estas vistas; como consecuencia, el uso de memoria fue muy bajo. En el lado del receptor, no había manera de predecir el orden en que se recibieron los paquetes, por lo que debe mantener un número limitado de vistas activas en el archivo de destino, y los datos se copiaron directamente en estas vistas. Cuando un paquete se tenía que colocar en un área no asignada, la vista más antigua no se mapeó (y el sistema la vació en el archivo) y se reemplazó por una nueva vista en el área de destino. Las actuaciones fueron excepcionales, sobre todo porque el sistema hizo un gran trabajo al comprometer los datos como una tarea en segundo plano, y las restricciones en tiempo real se cumplieron fácilmente.

Desde entonces estoy convencido de que incluso el mejor esquema de software fino no puede superar la política de E/S predeterminada del sistema con el archivo mapeado en memoria, porque el sistema conoce más que las aplicaciones de espacio ser escrito. Además, lo importante es saber que el mapeo de memoria es imprescindible cuando se trata de datos grandes, porque los datos nunca se asignan (de ahí que consuman memoria) sino que se asignan dinámicamente al espacio de direcciones y son administrados por el administrador de memoria virtual del sistema. siempre más rápido que el montón. Por lo tanto, el sistema siempre usa la memoria de manera óptima y envía datos siempre que lo necesita, detrás de la aplicación sin afectarla.

Espero que ayude.

5

La memoria asignada de E/S no hará que sus discos funcionen más rápido (!). Para el acceso lineal parece un poco sin sentido.

Un búfer asignado NIO es el verdadero (advertencia habitual sobre cualquier implementación razonable).

Al igual que con otros búferes asignados directamente de NIO, los búferes no son memoria normal y no se convertirán en GC de manera eficiente. Si crea muchos de ellos, es posible que se quede sin memoria/espacio de direcciones sin quedarse sin Java Heap. Esto es obviamente una preocupación con los procesos de larga ejecución.

+0

La aplicación escribe en el disco * mucho * más frecuentemente (> 99%) de lo que lee. ¿Podría explicar lo que quiere decir con "Para el acceso lineal, parece un poco inútil", ¿se aplica para agregar operaciones? – noahlz

+0

Añadir operaciones sería lineal (el sistema de archivos puede fragmentar su archivo, pero eso debería ser un problema menor). –

0

Si escribe menos bytes, será más rápido. ¿Qué pasa si lo filtraste a través de gzipoutputstream, o qué pasa si escribiste tus datos en ZipFiles o JarFiles?

+0

Luego está intercambiando los gastos generales de las operaciones de E/S por la sobrecarga de codificar/decodificar los datos. Tomaría algunos experimentos para ver si esto era viable. – noahlz

0

Como se mencionó anteriormente, use NIO (a.k.a. nuevo IO). También hay un IO nuevo y nuevo que sale.

El uso adecuado de una solución de disco duro RAID podría ayudarlo, pero eso sería un problema.

Me gusta mucho la idea de comprimir los datos. Ve por el tio gzipoutputstream! Eso duplicaría su rendimiento si la CPU puede mantener el ritmo. Es probable que pueda aprovechar las máquinas de doble núcleo ahora estándar, ¿eh?

-Stosh

1

hice un study donde comparo el rendimiento de escritura a una prima ByteBuffer frente al rendimiento de escritura a un MappedByteBuffer. Los archivos mapeados en memoria son compatibles con el sistema operativo y sus latencias de escritura son muy buenas, como puede ver en mis números de referencia. La realización de escrituras sincrónicas a través de un FileChannel es aproximadamente 20 veces más lenta y es por eso que las personas realizan un registro asincrónico todo el tiempo. En mi estudio, también doy un ejemplo de cómo implementar el registro asíncrono a través de una cola sin bloqueos y libre de basura para un rendimiento máximo muy cercano a un ByteBuffer crudo.

+0

Mi descubrimiento fue que tenía que llamar periódicamente a 'force()' para asegurarme de que mis cambios llegaran al archivo. Por lo tanto, fue más lento. Por supuesto, esto fue hace varios años ... – noahlz

+0

NO es necesario que llame a force() a menos que desee protegerse de un bloqueo del sistema operativo. – TraderJoeChicago

Cuestiones relacionadas