Java NIO FileChannel versus FileOutputstream rendimiento / utilidad

169

Estoy tratando de averiguar si hay alguna diferencia en el rendimiento (o ventajas) cuando usamos nio FileChannelversus normal FileInputStream/FileOuputStreampara 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 FileChannelcamino es más lento. ¿Puedo saber más detalles comparando estos dos métodos? Aquí está el código que usé, el archivo con el que estoy probando está disponible 350MB. ¿Es una buena opción usar clases basadas en NIO para E / S de archivos, si no estoy buscando acceso aleatorio u otras características avanzadas?

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");
    }
}
Keshav
fuente
55
transferTo/ transferFromsería más convencional para copiar archivos. Cualquiera que sea la técnica no debería hacer que su disco duro sea más rápido o más lento, aunque supongo que podría haber un problema si lee pequeños fragmentos a la vez y hace que la cabeza pase una cantidad excesiva de tiempo buscando.
Tom Hawtin - atajo
1
(No menciona qué sistema operativo está utilizando, o qué proveedor y versión de JRE.)
Tom Hawtin - línea de ataque el
Ups, lo siento, estoy usando FC10 con Sun JDK6.
Keshav el

Respuestas:

202

Mi experiencia con archivos de mayor tamaño ha sido java.niomás rápida que java.io. Sólidamente más rápido. Como en el rango> 250%. Dicho esto, estoy eliminando cuellos de botella obvios, lo que sugiero que podría sufrir su micro-punto de referencia. Áreas potenciales para investigar:

El tamaño del búfer. El algoritmo que básicamente tienes es

  • copiar del disco al búfer
  • copiar del búfer al disco

Mi propia experiencia ha sido que este tamaño de búfer está maduro para el ajuste. Me decidí por 4KB para una parte de mi aplicación, 256 KB para otra. Sospecho que su código está sufriendo con un búfer tan grande. Ejecute algunos puntos de referencia con buffers de 1 KB, 2 KB, 4KB, 8 KB, 16 KB, 32 KB y 64 KB para probarlo usted mismo.

No realice pruebas de referencia de Java que lean y escriban en el mismo disco.

Si lo hace, entonces realmente está comparando el disco, y no Java. También sugeriría que si su CPU no está ocupada, entonces probablemente esté experimentando algún otro cuello de botella.

No use un búfer si no lo necesita.

¿Por qué copiar a la memoria si su objetivo es otro disco o una NIC? Con archivos más grandes, la latencia incurada no es trivial.

Como otros han dicho, use FileChannel.transferTo()o FileChannel.transferFrom(). La ventaja clave aquí es que la JVM usa el acceso del sistema operativo a DMA ( Acceso directo a memoria ), si está presente. (Esto depende de la implementación, pero las versiones modernas de Sun e IBM en CPU de uso general son buenas para usar). Lo que sucede es que los datos van directamente a / desde el disco, al bus y luego al destino ... sin pasar por ningún circuito RAM o la CPU.

La aplicación web en la que pasé mis días y mi noche trabajando es muy pesada. También hice micro benchmarks y benchmarks del mundo real. Y los resultados están en mi blog, eche un vistazo:

Utilice 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 exactamente lo que planea hacer, 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 robusto, un sistema bajo carga, reunidos en registros. No es el disco SATA de 7200 RPM de 2.5 "mientras miraba intensamente mientras la JVM trabajaba mi disco duro.

¿En qué estás corriendo? Importa.

Stu Thompson
fuente
@Stu Thompson - Gracias por tu publicación. Encontré tu respuesta ya que estoy investigando sobre el mismo tema. Estoy tratando de entender las mejoras del sistema operativo que nio expone a los programadores de Java. Algunos de ellos son: DMA y archivos mapeados de memoria. ¿Has encontrado más de tales mejoras? PD: los enlaces de tu blog están rotos.
Andy Dufresne
@AndyDufresne mi blog está caído en este momento, estará más tarde esta semana, en el proceso de moverlo.
Stu Thompson
1
¿Qué tal simplemente copiar archivos de un directorio a otro? (Cada unidad de disco diferente)
Deckard
1
Interesante: mailinator.blogspot.in/2008/02/…
Jeryl Cook
38

Si lo que desea comparar es el rendimiento de la copia de archivos, entonces, para la prueba del canal, debe hacer esto en su lugar:

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 almacenarse en un búfer de un canal a otro, y potencialmente será enormemente 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 de destino sin copiarlos realmente.

uckelman
fuente
7

Basado en 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 volteo del buffer de datos NIO siempre supera al IO estándar.

  • Copiando 1000x2MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (flip directo de datababuffer 5000b) ~ 3500ms
    3. IO estándar (buffer 5000b) ~ 6000ms
  • Copiando 100x20mb

    1. NIO (flip directo de datababuffer 5000b) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. IO estándar (buffer 5000b) ~ 6500ms
  • Copiando 1x1000mb

    1. NIO (flip directo de datababuffer 5000b) ~ 4500s
    2. IO estándar (buffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

El método transferTo () funciona en fragmentos de un archivo; no fue pensado como un método de copia de archivos de alto nivel: ¿Cómo copiar un archivo grande en Windows XP?

Erkki Nokso-Koivisto
fuente
6

Respondiendo la parte de "utilidad" de la pregunta:

Un inconveniente bastante sutil de usar FileChannelover FileOutputStreames que realizar 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 java.nio.channels.ClosedByInterruptException.

Ahora, esto podría ser algo bueno si lo que sea que FileChannelse haya utilizado es parte de la función principal del hilo, y el diseño lo tuvo en cuenta.

Pero también podría ser molesto si lo utiliza alguna característica 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 lamentable que esto sea tan sutil porque no tener en cuenta esto puede provocar errores que afectan la integridad de la escritura. [1] [2]

antak
fuente
3

Probé el rendimiento de FileInputStream vs. FileChannel para decodificar archivos codificados en base64. En mis experimentos probé un archivo bastante grande y el io tradicional siempre fue un poco más rápido que el nio.

FileChannel podría haber tenido una ventaja en versiones anteriores de jvm debido a la sobrecarga de sincronización en varias clases relacionadas con io, pero los jvm modernos son bastante buenos para eliminar bloqueos innecesarios.

Jörn Horstmann
fuente
2

Si no está utilizando la función transferTo o las funciones sin bloqueo, no notará una diferencia entre IO tradicional y NIO (2) porque el IO tradicional se asigna a NIO.

Pero si puede usar las funciones de NIO como transferFrom / To o desea usar Buffers, entonces, por supuesto, NIO es el camino a seguir.

Eckes
fuente
0

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.

tangenos
fuente
44
¿Te confundiste? Mi propia experiencia es que java.nioes más rápido con archivos más grandes que java.io, no más pequeños.
Stu Thompson, el
No, mi experiencia es al revés. java.nioes rápido siempre y cuando el archivo sea lo suficientemente pequeño como para mapearse en la memoria. Si se hace más grande (200 MB y más) java.ioes más rápido.
tangens
Guau. Todo lo contrario de mí. Tenga en cuenta que no necesariamente necesita asignar un archivo para leerlo, se puede leer desde el FileChannel.read(). No hay un solo enfoque para leer archivos usando java.nio.
Stu Thompson el
2
@tangens, ¿revisaste esto?
sinedsem