Java NIO FileChannel versus kinerja / kegunaan FileOutputstream

169

Saya mencoba mencari tahu apakah ada perbedaan kinerja (atau keuntungan) ketika kita menggunakan nio FileChannelversus normal FileInputStream/FileOuputStreamuntuk membaca dan menulis file ke sistem file. Saya mengamati bahwa pada mesin saya keduanya bekerja pada tingkat yang sama, juga berkali-kali FileChanneljalannya lebih lambat. Bisakah saya tahu lebih detail membandingkan kedua metode ini. Berikut adalah kode yang saya gunakan, file yang saya uji ada di sekitar 350MB. Apakah ini pilihan yang baik untuk menggunakan kelas berbasis NIO untuk File I / O, jika saya tidak melihat akses acak atau fitur canggih lainnya?

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
sumber
5
transferToSaya transferFromakan lebih konvensional untuk menyalin file. Teknik mana pun yang tidak membuat hard drive Anda lebih cepat atau lebih lambat, meskipun saya kira mungkin ada masalah jika membaca potongan kecil pada suatu waktu dan menyebabkan kepala menghabiskan banyak waktu mencari.
Tom Hawtin - tackline
1
(Anda tidak menyebutkan OS yang Anda gunakan, atau vendor dan versi JRE mana.)
Tom Hawtin - tackline
Ups maaf, saya menggunakan FC10 dengan Sun JDK6.
Keshav

Jawaban:

202

Pengalaman saya dengan ukuran file yang lebih besar java.nioadalah lebih cepat dari java.io. Sangat cepat. Seperti di kisaran> 250%. Yang mengatakan, saya menghilangkan hambatan yang jelas, yang saya sarankan mikro-benchmark Anda mungkin menderita. Area potensial untuk diselidiki:

Ukuran buffer. Algoritma yang pada dasarnya Anda miliki adalah

  • salin dari disk ke buffer
  • salin dari buffer ke disk

Pengalaman saya sendiri adalah bahwa ukuran buffer ini sudah siap untuk penyetelan. Saya telah menggunakan 4KB untuk satu bagian dari aplikasi saya, 256KB untuk bagian lain. Saya menduga kode Anda menderita dengan buffer yang begitu besar. Jalankan beberapa tolok ukur dengan buffer 1KB, 2KB, 4KB, 8KB, 16KB, 32KB, dan 64KB untuk membuktikannya sendiri.

Jangan lakukan tolok ukur java yang membaca dan menulis ke disk yang sama.

Jika Anda melakukannya, maka Anda benar-benar membuat benchmark disk, dan bukan Java. Saya juga menyarankan bahwa jika CPU Anda tidak sibuk, maka Anda mungkin mengalami beberapa hambatan lainnya.

Jangan gunakan buffer jika Anda tidak perlu.

Mengapa menyalin ke memori jika target Anda adalah disk lain atau NIC? Dengan file yang lebih besar, latensi yang ditimbulkan tidak mudah.

Seperti kata orang lain, gunakan FileChannel.transferTo()atau FileChannel.transferFrom(). Keuntungan utama di sini adalah bahwa JVM menggunakan akses OS ke DMA ( Akses Memori Langsung ), jika ada. (Ini tergantung pada implementasi, tetapi versi Sun dan IBM modern pada CPU tujuan umum baik untuk digunakan.) Yang terjadi adalah data langsung menuju / dari disk, ke bus, dan kemudian ke tujuan ... melewati sirkuit apa pun melalui RAM atau CPU.

Aplikasi web yang saya habiskan siang dan malam bekerja sangat berat. Saya telah melakukan tolok ukur mikro dan tolok ukur dunia nyata juga. Dan hasilnya di blog saya, lihat-lihat:

Gunakan data produksi dan lingkungan

Micro-benchmark rentan terhadap distorsi. Jika Anda bisa, lakukan upaya untuk mengumpulkan data dari apa yang Anda rencanakan, dengan beban yang Anda harapkan, pada perangkat keras yang Anda harapkan.

Tolok ukur saya solid dan dapat diandalkan karena terjadi pada sistem produksi, sistem gemuk, sistem di bawah beban, dikumpulkan dalam log. Bukan drive SATA 7200 RPM 2.5 "saya sementara saya menonton dengan intens ketika JVM mengerjakan hard disk saya.

Apa yang sedang Anda jalankan? Itu penting.

Stu Thompson
sumber
@ Sit Thompson - Terima kasih atas posting Anda. Saya menemukan jawaban Anda karena saya meneliti topik yang sama. Saya mencoba untuk memahami perbaikan OS yang nio memaparkan ke programmer java. Beberapa dari mereka sedang - file DMA dan memori dipetakan. Sudahkah Anda menemukan lebih banyak perbaikan seperti itu? PS - Tautan blog Anda rusak.
Andy Dufresne
@AndyDufresne blog saya sedang down saat ini, akan naik akhir minggu ini - dalam proses memindahkannya.
Stu Thompson
1
Bagaimana kalau hanya menyalin file dari satu direktori ke direktori lain? (Setiap drive disk yang berbeda)
Deckard
1
Menarik: mailinator.blogspot.in/2008/02/...
Jeryl Masak
38

Jika hal yang ingin Anda bandingkan adalah kinerja penyalinan file, maka untuk pengujian saluran Anda harus melakukan ini sebagai gantinya:

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

Ini tidak akan lebih lambat daripada melindungi diri Anda dari satu saluran ke saluran lainnya, dan berpotensi akan secara besar-besaran lebih cepat. Menurut Javadocs:

Banyak sistem operasi dapat mentransfer byte langsung dari cache sistem file ke saluran target tanpa benar-benar menyalinnya.

uckelman
sumber
7

Berdasarkan tes saya (Win7 64bit, 6GB RAM, Java6), NIO transferFrom hanya cepat dengan file kecil dan menjadi sangat lambat pada file yang lebih besar. NIO databuffer flip selalu mengungguli IO standar.

  • Menyalin 1000x2MB

    1. NIO (transferFrom) ~ 2300ms
    2. NIO (datababuffer 5000b flip langsung) ~ 3500ms
    3. IO standar (buffer 5000b) ~ 6000ms
  • Menyalin 100x20mb

    1. NIO (datababuffer langsung 5000b flip) ~ 4000ms
    2. NIO (transferFrom) ~ 5000ms
    3. IO standar (buffer 5000b) ~ 6500ms
  • Menyalin 1x1000mb

    1. NIO (datababuffer 5000b flip langsung) ~ 4500-an
    2. IO standar (buffer 5000b) ~ 7000ms
    3. NIO (transferFrom) ~ 8000ms

Metode transferTo () bekerja pada potongan file; tidak dimaksudkan sebagai metode penyalinan file tingkat tinggi: Bagaimana cara menyalin file besar di Windows XP?

Erkki Nokso-Koivisto
sumber
6

Menjawab bagian "kegunaan" dari pertanyaan:

Satu hal yang agak halus dari penggunaan FileChannellebih FileOutputStreamadalah melakukan salah satu operasi pemblokirannya (mis. read()Atau write()) dari utas yang dalam keadaan terputus akan menyebabkan saluran menutup dengan tiba-tiba java.nio.channels.ClosedByInterruptException.

Sekarang, ini bisa menjadi hal yang baik jika apa pun yang FileChanneldigunakan adalah bagian dari fungsi utama utas, dan desain memperhitungkannya.

Tetapi bisa juga sial jika digunakan oleh beberapa fitur tambahan seperti fungsi logging. Misalnya, Anda dapat mendapati output logging Anda tiba-tiba ditutup jika fungsi logging kebetulan dipanggil oleh utas yang juga terputus.

Sangat disayangkan ini begitu halus karena tidak memperhitungkan ini dapat menyebabkan bug yang mempengaruhi integritas penulisan. [1] [2]

antak
sumber
3

Saya menguji kinerja FileInputStream vs FileChannel untuk mendekode file base64 yang disandikan. Dalam pengalaman saya, saya menguji file yang agak besar dan io tradisional sedikit lebih cepat daripada nio.

FileChannel mungkin memiliki keuntungan dalam versi sebelumnya dari jvm karena sinkronisasi biaya overhead di beberapa kelas terkait, tetapi jvm modern cukup baik dalam menghapus kunci yang tidak dibutuhkan.

Jörn Horstmann
sumber
2

Jika Anda tidak menggunakan fitur transferTo atau fitur non-blocking, Anda tidak akan melihat perbedaan antara IO tradisional dan NIO (2) karena IO tradisional memetakan ke NIO.

Tetapi jika Anda dapat menggunakan fitur NIO seperti transferFrom / Ke atau ingin menggunakan Buffer, maka tentu saja NIO adalah cara yang harus dilakukan.

eek
sumber
0

Pengalaman saya adalah, bahwa NIO jauh lebih cepat dengan file-file kecil. Tetapi ketika datang ke file besar FileInputStream / FileOutputStream jauh lebih cepat.

tangens
sumber
4
Apakah Anda bingung? Pengalaman saya sendiri java.nioadalah lebih cepat dengan file yang lebih besar daripada java.io, tidak lebih kecil.
Stu Thompson
Tidak, pengalaman saya sebaliknya. java.niocepat selama file cukup kecil untuk dipetakan ke memori. Jika bertambah besar (200 MB dan lebih banyak) java.iolebih cepat.
tangens
Wow. Kebalikan dari saya. Perhatikan bahwa Anda tidak perlu memetakan file untuk membacanya - orang dapat membaca dari FileChannel.read(). Tidak hanya ada satu pendekatan tunggal untuk membaca file menggunakan java.nio.
Stu Thompson
2
@ talang apakah Anda memeriksa ini?
sinedsem