Java NIO FileChannel contro FileOutputstream prestazioni / utilità


169

Sto cercando di capire se c'è qualche differenza nelle prestazioni (o nei vantaggi) quando usiamo nio FileChannelrispetto FileInputStream/FileOuputStreamal normale per leggere e scrivere file nel filesystem. Ho osservato che sulla mia macchina si esibiscono entrambi allo stesso livello, anche molte volte il FileChannelmodo è più lento. Posso per favore conoscere maggiori dettagli confrontando questi due metodi. Ecco il codice che ho usato, il file con cui sto testando è in giro 350MB. È una buona opzione utilizzare le classi basate su NIO per l'I / O dei file, se non sto esaminando l'accesso casuale o altre funzionalità così avanzate?

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

5
transferTo/ transferFromsarebbe più convenzionale per la copia dei file. Qualunque sia la tecnica non dovrebbe rendere il tuo disco rigido più veloce o più lento, anche se immagino che potrebbe esserci un problema se legge piccoli pezzi alla volta e fa sì che la testa passi una quantità eccessiva di tempo alla ricerca.
Tom Hawtin - tackline il

1
(Non menzionate quale sistema operativo state utilizzando, o quale fornitore e versione di JRE.)
Tom Hawtin - tackline

Spiacenti, sto usando FC10 con Sun JDK6.
Keshav,

Risposte:


202

La mia esperienza con file di dimensioni maggiori è stata java.niopiù veloce di java.io. Solidamente più veloce. Come nell'intervallo> 250%. Detto questo, sto eliminando ovvi colli di bottiglia, di cui suggerisco di soffrire il micro-benchmark. Potenziali aree di indagine:

La dimensione del buffer. L'algoritmo che fondamentalmente hai è

  • copia dal disco al buffer
  • copia dal buffer al disco

La mia esperienza personale è stata che questa dimensione del buffer è matura per il tuning. Ho optato per 4KB per una parte della mia applicazione, 256 KB per un'altra. Sospetto che il tuo codice stia soffrendo di un buffer così grande. Esegui alcuni benchmark con buffer di 1 KB, 2 KB, 4KB, 8 KB, 16 KB, 32 KB e 64 KB per dimostrarlo a te stesso.

Non eseguire benchmark Java che leggono e scrivono sullo stesso disco.

Se lo fai, allora stai davvero confrontando il disco e non Java. Vorrei anche suggerire che se la CPU non è occupata, probabilmente si verificano altri colli di bottiglia.

Non utilizzare un buffer se non è necessario.

Perché copiare in memoria se la destinazione è un altro disco o una scheda NIC? Con file più grandi, la latenza incisa non è banale.

Come altri hanno già detto, usa FileChannel.transferTo()o FileChannel.transferFrom(). Il vantaggio chiave qui è che JVM utilizza l'accesso del sistema operativo a DMA ( Direct Memory Access ), se presente. (Questo dipende dall'implementazione, ma le moderne versioni Sun e IBM su CPU per scopi generici vanno bene.) Ciò che accade è che i dati vanno direttamente al / dal disco, al bus e quindi alla destinazione ... bypassando qualsiasi circuito attraverso RAM o CPU.

L'app Web su cui ho lavorato giorno e notte è molto pesante. Ho fatto anche micro-benchmark e benchmark del mondo reale. E i risultati sono saliti sul mio blog, dai un'occhiata:

Usa dati e ambienti di produzione

I micro-benchmark sono soggetti a distorsioni. Se puoi, fai lo sforzo di raccogliere i dati esattamente da quello che pensi di fare, con il carico che ti aspetti, sull'hardware che ti aspetti.

I miei parametri di riferimento sono solidi e affidabili perché si sono svolti su un sistema di produzione, un sistema robusto, un sistema sotto carico, raccolto in registri. Non è l'unità SATA da 2.5 "da 7200 RPM del mio notebook mentre guardavo intensamente mentre la JVM lavorava sul mio disco fisso.

Cosa stai correndo? Importa.


@Stu Thompson - Grazie per il tuo post. Ho trovato la tua risposta da quando sto facendo ricerche sullo stesso argomento. Sto cercando di capire i miglioramenti del sistema operativo che nio espone ai programmatori Java. Alcuni di loro sono: DMA e file mappati in memoria. Hai riscontrato ulteriori miglioramenti? PS: i collegamenti al tuo blog sono interrotti.
Andy Dufresne,

@AndyDufresne il mio blog non è attivo al momento, sarà attivo alla fine di questa settimana - in procinto di spostarlo.
Stu Thompson,


1
Che ne dici di copiare i file da una directory all'altra? (Ogni diversa unità disco)
Deckard


38

Se la cosa che vuoi confrontare è l'esecuzione della copia dei file, allora per il test del canale dovresti invece farlo:

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();

Questo non sarà più lento del buffering da un canale all'altro e potenzialmente sarà enormemente più veloce. Secondo i Javadocs:

Molti sistemi operativi possono trasferire byte direttamente dalla cache del filesystem al canale di destinazione senza copiarli effettivamente.


7

Sulla base dei miei test (Win7 64 bit, 6 GB di RAM, Java6), NIO transferFrom è veloce solo con file di piccole dimensioni e diventa molto lento su file più grandi. La vibrazione del databuffer NIO supera sempre l'IO standard.

  • Copia di 1000x2 MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (vibrazione diretta datababuffer 5000b) ~ 3500ms
    3. IO standard (buffer 5000b) ~ 6000ms
  • Copia 100x20mb

    1. NIO (vibrazione diretta datababuffer 5000b) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. IO standard (buffer 5000b) ~ 6500ms
  • Copia 1x1000mb

    1. NIO (vibrazione diretta datababuffer 5000b) ~ 4500s
    2. IO standard (buffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

Il metodo transferTo () funziona su blocchi di un file; non era inteso come metodo di copia di file di alto livello: come copiare un file di grandi dimensioni in Windows XP?


6

Rispondere alla parte "utile" della domanda:

Un aspetto piuttosto sottile dell'uso di FileChannelover FileOutputStreamè che l'esecuzione di una qualsiasi delle sue operazioni di blocco (ad esempio read()o write()) da un thread in stato di interruzione causerà la chiusura improvvisa del canale java.nio.channels.ClosedByInterruptException.

Ora, questo potrebbe essere una buona cosa se qualunque cosa FileChannelfosse usata è parte della funzione principale del thread e il design ne ha tenuto conto.

Ma potrebbe anche essere fastidioso se utilizzato da alcune funzioni ausiliarie come una funzione di registrazione. Ad esempio, è possibile trovare l'output della registrazione improvvisamente chiuso se la funzione di registrazione viene chiamata da un thread anch'esso interrotto.

È un peccato che sia così sottile perché non tenerne conto può portare a bug che influiscono sull'integrità della scrittura. [1] [2]


3

Ho testato le prestazioni di FileInputStream vs. FileChannel per la decodifica dei file codificati in base64. Nei miei esperimenti ho testato file piuttosto grandi e io tradizionale era sempre un po 'più veloce di nio.

FileChannel avrebbe potuto avere un vantaggio nelle versioni precedenti di jvm a causa del sovraccarico di sincronizzazione in diverse classi correlate a io, ma i jvm moderni sono abbastanza bravi a rimuovere i blocchi non necessari.


2

Se non si utilizza la funzione transferTo o le funzionalità non bloccanti non si noterà una differenza tra IO tradizionale e NIO (2) poiché l'IO tradizionale è mappato su NIO.

Ma se è possibile utilizzare le funzionalità NIO come transferFrom / To o si desidera utilizzare i buffer, ovviamente NIO è la strada da percorrere.


0

La mia esperienza è che NIO è molto più veloce con file di piccole dimensioni. Ma quando si tratta di file di grandi dimensioni, FileInputStream / FileOutputStream è molto più veloce.


4
L'hai fatto confuso? La mia esperienza è che java.nioè più veloce con file più grandi di java.io, non più piccoli.
Stu Thompson,

No, la mia esperienza è al contrario. java.nioè veloce finché il file è abbastanza piccolo da essere mappato in memoria. Se diventa più grande (200 MB e più) java.ioè più veloce.
Tangens,

Wow. Il totale opposto di me. Si noti che non è necessario mappare necessariamente un file per leggerlo - si può leggere da FileChannel.read(). Non esiste un solo approccio per leggere i file utilizzando java.nio.
Stu Thompson,

2
@tangens hai controllato questo?
sinedsem,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.