Bagaimana Anda menentukan ukuran buffer ideal saat menggunakan FileInputStream?

156

Saya memiliki metode yang membuat MessageDigest (hash) dari sebuah file, dan saya perlu melakukan ini pada banyak file (> = 100.000). Seberapa besar saya harus membuat buffer digunakan untuk membaca dari file untuk memaksimalkan kinerja?

Sebagian besar orang akrab dengan kode dasar (yang akan saya ulangi untuk berjaga-jaga):

MessageDigest md = MessageDigest.getInstance( "SHA" );
FileInputStream ios = new FileInputStream( "myfile.bmp" );
byte[] buffer = new byte[4 * 1024]; // what should this value be?
int read = 0;
while( ( read = ios.read( buffer ) ) > 0 )
    md.update( buffer, 0, read );
ios.close();
md.digest();

Berapa ukuran ideal buffer untuk memaksimalkan throughput? Saya tahu ini tergantung pada sistem, dan saya cukup yakin tergantung pada OS, FileSystem, dan HDD, dan mungkin ada perangkat keras / lunak lain dalam campuran.

(Saya harus menunjukkan bahwa saya agak baru di Jawa, jadi ini mungkin hanya beberapa panggilan Java API yang saya tidak tahu.)

Sunting: Saya tidak tahu sebelumnya jenis sistem yang akan digunakan, jadi saya tidak bisa berasumsi banyak. (Saya menggunakan Java karena alasan itu.)

Sunting: Kode di atas tidak ada, seperti try..catch untuk membuat postingan lebih kecil

ARKBAN
sumber

Jawaban:

213

Ukuran buffer optimal terkait dengan sejumlah hal: ukuran blok sistem file, ukuran cache CPU, dan latensi cache.

Sebagian besar sistem file dikonfigurasikan untuk menggunakan ukuran blok 4096 atau 8192. Secara teori, jika Anda mengkonfigurasi ukuran buffer sehingga Anda membaca beberapa byte lebih banyak daripada blok disk, operasi dengan sistem file bisa sangat tidak efisien (yaitu jika Anda mengkonfigurasi buffer Anda untuk membaca 4100 byte pada suatu waktu, setiap pembacaan akan membutuhkan 2 blok yang dibaca oleh sistem file). Jika blok sudah dalam cache, maka Anda akhirnya membayar harga RAM -> L3 / L2 cache latency. Jika Anda kurang beruntung dan blok belum ada dalam cache, Anda membayar harga latensi disk-> RAM juga.

Inilah sebabnya mengapa Anda melihat sebagian besar buffer berukuran sebagai kekuatan 2, dan umumnya lebih besar dari (atau sama dengan) ukuran blok disk. Ini berarti bahwa salah satu aliran Anda membaca dapat menghasilkan banyak pembacaan blok disk - tetapi pembacaan tersebut akan selalu menggunakan blok penuh - tidak ada pembacaan yang sia-sia.

Sekarang, ini diimbangi sedikit dalam skenario streaming yang khas karena blok yang dibaca dari disk akan tetap berada di memori ketika Anda menekan membaca berikutnya (setelah semua, kami melakukan membaca berurutan di sini) - sehingga Anda berakhir membayar RAM -> L3 / L2 harga latensi cache pada bacaan berikutnya, tetapi bukan disk-> RAM latensi. Dalam hal urutan besarnya, disk-> RAM latensi sangat lambat sehingga cukup banyak menggantikan latensi lain yang mungkin Anda hadapi.

Jadi, saya menduga bahwa jika Anda menjalankan tes dengan ukuran cache yang berbeda (belum melakukannya sendiri), Anda mungkin akan menemukan dampak besar ukuran cache hingga ukuran blok sistem file. Di atas itu, saya curiga bahwa segala sesuatunya akan naik dengan cepat.

Ada satu ton kondisi dan pengecualian di sini - kompleksitas sistem sebenarnya cukup mengejutkan (hanya menangani L3 -> L2 cache transfer sangat membingungkan, dan itu berubah dengan setiap jenis CPU).

Ini mengarah ke jawaban 'dunia nyata': Jika aplikasi Anda seperti 99% di luar sana, atur ukuran cache ke 8192 dan lanjutkan (bahkan lebih baik, pilih enkapsulasi daripada kinerja dan gunakan BufferedInputStream untuk menyembunyikan detail). Jika Anda berada dalam 1% aplikasi yang sangat bergantung pada throughput disk, buat implementasi Anda sehingga Anda dapat menukar strategi interaksi disk yang berbeda, dan memberikan kenop dan tombol untuk memungkinkan pengguna Anda menguji dan mengoptimalkan (atau menghasilkan beberapa sistem optimalisasi diri).

Kevin Day
sumber
3
Saya melakukan banchmarking pada ponsel (Nexus 5X) untuk aplikasi Android saya untuk keduanya: file kecil (3,5Mb) dan file besar (175 Mb). Dan menemukan bahwa ukuran emas adalah byte [] dari panjang 524288. Nah, Anda mungkin menang 10-20 ms jika Anda beralih antara buffer kecil 4Kb dan buffer besar 524Kb tergantung pada ukuran file tetapi tidak sepadan. Jadi 524 Kb adalah pilihan terbaik dalam kasus saya.
Kirill Karmazin
19

Ya, itu mungkin tergantung pada berbagai hal - tetapi saya ragu itu akan membuat banyak perbedaan. Saya cenderung memilih 16K atau 32K sebagai keseimbangan yang baik antara penggunaan memori dan kinerja.

Perhatikan bahwa Anda harus memiliki blok coba / akhirnya dalam kode untuk memastikan aliran ditutup bahkan jika ada pengecualian.

Jon Skeet
sumber
Saya mengedit posting tentang try..catch. Dalam kode asli saya, saya punya satu, tetapi saya meninggalkannya untuk membuat posting lebih pendek.
ARKBAN
1
jika kita ingin menentukan ukuran tetap untuk itu, ukuran mana yang lebih baik? 4k, 16k atau 32k?
BattleTested
2
@MohammadrezaPanahi: Tolong jangan gunakan komentar untuk pengguna musang. Anda menunggu kurang dari satu jam sebelum komentar kedua. Harap diingat bahwa pengguna dapat dengan mudah tertidur, atau dalam rapat, atau pada dasarnya sibuk dengan hal-hal lain dan tidak memiliki kewajiban untuk menjawab komentar. Tetapi untuk menjawab pertanyaan Anda: sepenuhnya tergantung pada konteks. Jika Anda menjalankan sistem yang sangat terbatas pada memori, Anda mungkin ingin buffer kecil. Jika Anda menjalankan sistem yang besar, menggunakan buffer yang lebih besar akan mengurangi jumlah panggilan yang dibaca. Jawaban Kevin Day sangat bagus.
Jon Skeet
7

Dalam kebanyakan kasus, itu tidak terlalu menjadi masalah. Pilih saja ukuran yang bagus seperti 4K atau 16K dan tetap menggunakannya. Jika Anda yakin ini adalah hambatan dalam aplikasi Anda, maka Anda harus mulai membuat profil untuk menemukan ukuran buffer yang optimal. Jika Anda memilih ukuran yang terlalu kecil, Anda akan membuang waktu untuk melakukan operasi I / O tambahan dan panggilan fungsi ekstra. Jika Anda memilih ukuran yang terlalu besar, Anda akan mulai melihat banyak kesalahan cache yang benar-benar akan memperlambat Anda. Jangan gunakan buffer yang lebih besar dari ukuran cache L2 Anda.

Adam Rosenfield
sumber
4

Dalam kasus ideal kita harus memiliki cukup memori untuk membaca file dalam satu operasi baca. Itu akan menjadi pemain terbaik karena kami membiarkan sistem mengelola Sistem File, unit alokasi, dan HDD sesuka hati. Dalam praktiknya Anda beruntung mengetahui ukuran file di muka, cukup gunakan ukuran file rata-rata dibulatkan hingga 4K (unit alokasi default pada NTFS). Dan yang terbaik: buat patokan untuk menguji beberapa opsi.

Ovidiu Pacurar
sumber
maksud Anda ukuran buffer terbaik untuk membaca dan menulis dalam file adalah 4k?
BattleTested
4

Anda bisa menggunakan BufferedStreams / pembaca dan kemudian menggunakan ukuran buffer mereka.

Saya percaya BufferedXStreams menggunakan 8192 sebagai ukuran buffer, tapi seperti kata Ovidiu, Anda mungkin harus menjalankan tes pada sejumlah opsi. Itu benar-benar akan tergantung pada konfigurasi sistem file dan disk untuk apa ukuran terbaik.

John Gardner
sumber
4

Membaca file menggunakan Java NIO FileChannel dan MappedByteBuffer kemungkinan besar akan menghasilkan solusi yang akan jauh lebih cepat daripada solusi apa pun yang melibatkan FileInputStream. Pada dasarnya, memori file peta besar, dan gunakan buffer langsung untuk yang kecil.

Alexander
sumber
4

Dalam sumber BufferedInputStream Anda akan menemukan: private static int DEFAULT_BUFFER_SIZE = 8192;
Jadi tidak apa-apa bagi Anda untuk menggunakan nilai default itu.
Tetapi jika Anda bisa mencari tahu lebih banyak informasi, Anda akan mendapatkan jawaban yang lebih berharga.
Sebagai contoh, adsl Anda mungkin lebih suka buffer dari 1454 byte, itu karena payload TCP / IP. Untuk disk, Anda dapat menggunakan nilai yang cocok dengan ukuran blok disk Anda.

GoForce5500
sumber
1

Seperti yang sudah disebutkan dalam jawaban lain, gunakan BufferedInputStreams.

Setelah itu, saya kira ukuran buffer tidak terlalu penting. Entah program terikat I / O, dan semakin besar ukuran buffer di atas standar BIS, tidak akan membuat dampak besar pada kinerja.

Atau program ini terikat CPU di dalam MessageDigest.update (), dan sebagian besar waktu tidak dihabiskan dalam kode aplikasi, jadi mengutak-atik itu tidak akan membantu.

(Hmm ... dengan banyak inti, utas mungkin membantu.)

Maglob
sumber
0

1024 sesuai untuk berbagai keadaan, meskipun dalam praktiknya Anda mungkin melihat kinerja yang lebih baik dengan ukuran buffer yang lebih besar atau lebih kecil.

Ini akan tergantung pada sejumlah faktor termasuk ukuran blok sistem file dan perangkat keras CPU.

Juga umum untuk memilih kekuatan 2 untuk ukuran buffer, karena sebagian besar perangkat keras yang mendasarinya disusun dengan blok fle dan ukuran cache yang merupakan kekuatan 2. Kelas Buffered memungkinkan Anda menentukan ukuran buffer dalam konstruktor. Jika tidak ada yang disediakan, mereka menggunakan nilai default, yang merupakan kekuatan 2 di sebagian besar JVM.

Terlepas dari ukuran buffer yang Anda pilih, peningkatan kinerja terbesar yang akan Anda lihat adalah beralih dari akses file tanpa buffer ke buffered. Menyesuaikan ukuran buffer mungkin sedikit meningkatkan kinerja, tetapi kecuali jika Anda menggunakan ukuran buffer yang sangat kecil atau sangat besar, itu tidak akan berdampak signifikan.

Adrian Krebs
sumber