2009-10-22 12 views
150

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 la forma FileChannel es más lenta. ¿Puedo saber más detalles comparando estos dos métodos? Aquí está el código que utilicé, el archivo con el que estoy probando está 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?Java NIO FileChannel versus rendimiento/utilidad de FileOutputstream

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"); 
    } 
} 
+4

'transferTo' /' transferFrom' sería más convencional para copiar archivos. Cualquiera que sea la técnica no debe 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 trozos a la vez y hace que la cabeza pase una cantidad desorbitada de tiempo buscando. –

+1

(No menciona qué sistema operativo está usando o qué proveedor y versión de JRE). –

+0

Vaya, lo siento, estoy usando FC10 con Sun JDK6. – Keshav

Respuesta

183

Mi experiencia con los archivos de mayor tamaño ha sido que java.nio es más rápido que . 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 búfer. El algoritmo que básicamente tienen es

  • copia del disco a la memoria intermedia
  • copia de la memoria intermedia en el disco

Mi propia experiencia ha sido que este tamaño del búfer es madura 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 hace, entonces realmente está 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 utilice 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.

Como 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 (Direct Memory Access), 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) 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 de la RAM o la CPU.

La aplicación web en la que pasé el día y la noche trabajando es muy pesado. También hice micro benchmarks y benchmarks del mundo real. Y los resultados son en mi blog, echar un vistazo-ver:

datos de uso y entornos de producción

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 de mi bloc de notas 7200 RPM 2.5" duro SATA mientras yo miraba intensamente como funciona la JVM mi disco duro.

¿Qué está ejecutando en? Importa.

+0

@Stu Thompson - Gracias por su 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 de memoria mapeados. ¿Has encontrado más de esas mejoras? P.S: los enlaces de tu blog están rotos. –

+0

@AndyDufresne, mi blog está caído en este momento, aparecerá más adelante esta semana, en el proceso de moverlo. –

+13

aquí están los enlaces de blogs en archive.org: http://web.archive.org/web/20120815094827/http://geekomatic.ch/2008/09.html http: //web.archive. org/web/20120821114802/http: //geekomatic.ch/2009/01.html –

-1

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.

+4

¿Lo confundiste? Mi propia experiencia es que 'java.nio' es más rápido con archivos * más grandes que' java.io', no más pequeños. –

+0

No, mi experiencia es al revés. 'java.nio' es rápido, siempre que el archivo sea lo suficientemente pequeño como para ser mapeado en la memoria. Si aumenta (200 MB y más) 'java.io' es más rápido. – tangens

+0

Wow. El total opuesto a mi Tenga en cuenta que no necesariamente necesita asignar un archivo para leerlo, se puede leer desde 'FileChannel.read()'. No hay un solo enfoque para leer archivos usando 'java.nio'. –

32

Si lo desea comparar es el rendimiento de la copia de archivos, a continuación, para la prueba de una vía que 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 de lo mismo buffering de un canal a otro , y será potencialmente masivamente más rápido. Según los Javadocs:

Muchos sistemas operativos pueden transferir bytes directamente desde el caché del sistema de archivos al canal de destino sin copiarlos.

3

Probé el rendimiento de FileInputStream frente a 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 las versiones anteriores de jvm debido a la sobrecarga de sincronización en varias clases relacionadas, pero los modernos jvm son bastante buenos para eliminar los bloqueos innecesarios.

7

Basado en mis pruebas (64 bits Win7, 6 GB de RAM , Java6), NIO transferFrom es rápido solo con archivos pequeños y se vuelve muy lento en archivos más grandes. NIO databuffer flip siempre supera el IO estándar.

  • Copia 1000x2MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (datababuffer directa 5000B flip) ~ 3500ms
    3. estándar IO (5000B buffer) ~ 6000ms
  • Copia 100x20mb

    1. NIO (datababuffer directa 5000B flip) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. estándar IO (tampón 5000B) ~ 6500ms
  • copia 1x1000mb

    1. NIO (directa datababuffer 5000b flip) ~ 4500s
    2. Standard IO (buffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

El TransferTo() método funciona en trozos de un archivo; No fue concebido como un método de archivos de alto nivel de copia: How to copy a large file in Windows XP?

2

Si no está utilizando la función TransferTo o características de no bloqueo no notará una diferencia entre lo tradicional IO y NIO (2) debido a que el tradicional IO mapas a NIO.

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

0

Responder a la parte de "utilidad" de la pregunta:

Una Gotcha bastante sutil de utilizar FileChannel sobre FileOutputStream es que la realización de cualquiera de sus operaciones de bloqueo (por ejemplo read() o write()) de un hilo que está en interrupted state hará que el canal para cerrar abruptamente con java.nio.channels.ClosedByInterruptException.

Ahora bien, esto podría ser algo bueno si se utilizó cualquiera de los FileChannel para la función principal de la secuencia, y el diseño tuvo esto en cuenta.

Pero también podría ser molesto si se utiliza con 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 lamentable que esto sea tan sutil porque no tenerlo en cuenta puede dar lugar a errores que afectan la integridad de la escritura.[1] [2]

Cuestiones relacionadas