txt - output write java
Java NIO FileChannel versus rendimiento/utilidad de FileOutputstream (7)
Estoy tratando de averiguar si hay alguna diferencia en el rendimiento (o ventajas) cuando usamos nio FileChannel
frente a FileInputStream/FileOuputStream
normal para leer y escribir archivos en el sistema de archivos. Observé que en mi máquina ambos funcionan al mismo nivel, también muchas veces el modo FileChannel
es más lento. ¿Puedo saber más detalles comparando estos dos métodos? Aquí está el código que utilicé, el archivo con el que estoy probando tiene alrededor de 350MB
. ¿Es una buena opción utilizar clases basadas en NIO para E / S de archivos si no estoy buscando acceso aleatorio u otras características avanzadas de este tipo?
package trialjavaprograms;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class JavaNIOTest {
public static void main(String[] args) throws Exception {
useNormalIO();
useFileChannel();
}
private static void useNormalIO() throws Exception {
File file = new File("/home/developer/test.iso");
File oFile = new File("/home/developer/test2");
long time1 = System.currentTimeMillis();
InputStream is = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(oFile);
byte[] buf = new byte[64 * 1024];
int len = 0;
while((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fos.close();
is.close();
long time2 = System.currentTimeMillis();
System.out.println("Time taken: "+(time2-time1)+" ms");
}
private static void useFileChannel() throws Exception {
File file = new File("/home/developer/test.iso");
File oFile = new File("/home/developer/test2");
long time1 = System.currentTimeMillis();
FileInputStream is = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(oFile);
FileChannel f = is.getChannel();
FileChannel f2 = fos.getChannel();
ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
long len = 0;
while((len = f.read(buf)) != -1) {
buf.flip();
f2.write(buf);
buf.clear();
}
f2.close();
f.close();
long time2 = System.currentTimeMillis();
System.out.println("Time taken: "+(time2-time1)+" ms");
}
}
En función de mis pruebas (Win7 64bit, 6GB RAM, Java6), NIO transferFrom es rápido solo con archivos pequeños y se vuelve muy lento en archivos más grandes. El salto de NIO databuffer siempre supera al IO estándar.
Copia de 1000x2MB
- NIO (transferFrom) ~ 2300ms
- NIO (volcado directo de datos 5000b flip) ~ 3500ms
- Standard IO (buffer 5000b) ~ 6000ms
Copiando 100x20mb
- NIO (volcado directo de datos 5000b flip) ~ 4000ms
- NIO (transferFrom) ~ 5000ms
- Standard IO (buffer 5000b) ~ 6500ms
Copiando 1x1000mb
- NIO (flip de datos directos 5000b flip) ~ 4500s
- Standard IO (buffer 5000b) ~ 7000ms
- NIO (transferFrom) ~ 8000ms
El método transferTo () funciona en fragmentos de un archivo; no pretendía ser un método de copia de archivos de alto nivel: ¿cómo copiar un archivo grande en Windows XP?
Mi experiencia con archivos de mayor tamaño ha sido que java.nio
es más rápido que java.io
Sólidamente más rápido. Me gusta en el rango> 250%. Dicho esto, estoy eliminando cuellos de botella obvios, que sugiero que su micro-referencia podría sufrir. Posibles áreas para investigar:
El tamaño del buffer El algoritmo que básicamente tienes es
- copiar desde el disco al búfer
- copiar desde el búfer al disco
Mi propia experiencia ha sido que este tamaño de buffer está maduro para la sintonización. Me he conformado con 4 KB por una parte de mi aplicación, 256 KB por otra. Sospecho que tu código está sufriendo con un buffer tan grande. Ejecute algunos benchmarks con buffers de 1KB, 2KB, 4KB, 8KB, 16KB, 32KB y 64KB para demostrarlo.
No realice pruebas de rendimiento de java que lean y escriban en el mismo disco.
Si lo haces, entonces realmente estás evaluando el disco, y no Java. También sugeriría que si tu CPU no está ocupada, entonces probablemente estés experimentando algún otro cuello de botella.
No use un buffer si no lo necesita.
¿Por qué copiar a la memoria si su destino es otro disco o una NIC? Con archivos más grandes, la latencia incured no es trivial.
Al igual que otros han dicho, use FileChannel.transferTo()
o FileChannel.transferFrom()
. La principal ventaja aquí es que la JVM utiliza el acceso del sistema operativo a DMA (acceso directo a la memoria ), si está presente. (Esto depende de la implementación, pero las versiones modernas de Sun e IBM en CPU de propósito general son buenas para llevar). Lo que sucede es que los datos van directamente a / desde el disco, al bus y luego al destino ... evitando cualquier circuito a través RAM o la CPU.
La aplicación web en la que pasé los días y la noche trabajando es muy pesada. También hice micro benchmarks y benchmarks del mundo real. Y los resultados están en mi blog, echar un vistazo:
- Métricas de rendimiento del mundo real: java.io vs. java.nio
- Métricas de rendimiento del mundo real: java.io vs. java.nio (The Sequel)
Usa datos y entornos de producción
Los micro-puntos de referencia son propensos a la distorsión. Si puede, haga el esfuerzo de recopilar datos de lo que planea hacer exactamente, con la carga que espera, en el hardware que espera.
Mis puntos de referencia son sólidos y confiables porque tuvieron lugar en un sistema de producción, un sistema fornido, un sistema bajo carga, reunido en registros. No es la unidad SATA de 2.500 RPM de 2.5 "de mi computadora portátil mientras veía intensamente cómo la JVM trabaja mi disco duro.
¿En qué estás corriendo? Importa.
Mi experiencia es que NIO es mucho más rápido con archivos pequeños. Pero cuando se trata de archivos grandes FileInputStream / FileOutputStream es mucho más rápido.
Probé el rendimiento de FileInputStream vs. FileChannel para decodificar archivos codificados en base64. En mis experiencias probé un archivo bastante grande y el io tradicional siempre era un poco más rápido que nio.
FileChannel podría haber tenido una ventaja en versiones anteriores de jvm debido a la sobrecarga de sincronización en varias clases relacionadas, pero los jvm modernos son bastante buenos para eliminar bloqueos innecesarios.
Respondiendo a la parte de "utilidad" de la pregunta:
Una forma bastante sutil de utilizar FileChannel
sobre FileOutputStream
es que la realización de cualquiera de sus operaciones de bloqueo (por ejemplo, read()
o write()
) desde un hilo que está en estado interrumpido hará que el canal se cierre abruptamente con java.nio.channels.ClosedByInterruptException
.
Ahora bien, esto podría ser algo bueno si se utilizó el FileChannel
para formar parte de la función principal del subproceso, y el diseño lo tuvo en cuenta.
Pero también podría ser molesto si es utilizado por alguna función auxiliar, como una función de registro. Por ejemplo, puede encontrar que su salida de registro se cierra repentinamente si la función de registro es llamada por un hilo que también se interrumpe.
Es desafortunado que sea tan sutil porque no tenerlo en cuenta puede generar errores que afectan la integridad de la escritura. [1] [2]
Si lo que quiere comparar es el rendimiento de la copia de archivos, entonces, para la prueba de canal, debe hacer esto:
final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();
Esto no será más lento que el almacenamiento en memoria intermedia de un canal a otro, y será potencialmente masivamente más rápido. De acuerdo con los Javadocs:
Muchos sistemas operativos pueden transferir bytes directamente desde la memoria caché del sistema de archivos al canal objetivo sin copiarlos realmente.
Si no está utilizando la característica TransferTo o las funciones que no son de bloqueo, no notará una diferencia entre IO tradicional y NIO (2) porque los IO tradicionales se asignan a NIO.
Pero si puede usar las funciones de NIO como transferFrom / To o quiere usar Buffers, entonces, por supuesto, NIO es el camino a seguir.