Jak określić idealny rozmiar buforu podczas korzystania z FileInputStream?

156

Mam metodę, która tworzy MessageDigest (skrót) z pliku i muszę to zrobić dla wielu plików (> = 100 000). Jak duży powinien być bufor używany do odczytu plików, aby zmaksymalizować wydajność?

Prawie każdy jest zaznajomiony z podstawowym kodem (który powtórzę tutaj na wszelki wypadek):

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

Jaki jest idealny rozmiar bufora, aby zmaksymalizować przepustowość? Wiem, że jest to zależne od systemu i jestem prawie pewien, że zależy to od systemu operacyjnego, systemu plików i dysku twardego, a może być inny sprzęt / oprogramowanie w miksie.

(Powinienem zaznaczyć, że jestem trochę nowy w Javie, więc może to być po prostu wywołanie Java API, o którym nie wiem.)

Edycja: Nie wiem z wyprzedzeniem, w jakich systemach będzie to używane, więc nie mogę zakładać zbyt wiele. (Z tego powodu używam Javy).

Edycja: w powyższym kodzie brakuje rzeczy takich jak spróbuj..catch, aby zmniejszyć post

ARKBAN
źródło

Odpowiedzi:

213

Optymalny rozmiar bufora jest związany z wieloma czynnikami: rozmiarem bloku systemu plików, rozmiarem pamięci podręcznej procesora i opóźnieniem pamięci podręcznej.

Większość systemów plików jest skonfigurowana tak, aby używać bloków o rozmiarach 4096 lub 8192. Teoretycznie, jeśli skonfigurujesz rozmiar bufora tak, aby odczytać kilka bajtów więcej niż blok dysku, operacje na systemie plików mogą być bardzo nieefektywne (tj. Jeśli skonfigurował bufor do odczytu 4100 bajtów na raz, każdy odczyt wymagałby 2 odczytów bloków przez system plików). Jeśli bloki są już w pamięci podręcznej, ostatecznie płacisz cenę pamięci RAM -> opóźnienie pamięci podręcznej L3 / L2. Jeśli masz pecha i bloki nie są jeszcze w pamięci podręcznej, zapłacisz również cenę za dysk-> opóźnienie pamięci RAM.

Dlatego większość buforów jest traktowana jako potęga 2 i ogólnie jest większa (lub równa) rozmiarowi bloku dysku. Oznacza to, że jeden z odczytów strumienia może spowodować wielokrotne odczyty bloków dysku - ale te odczyty zawsze będą używać pełnego bloku - bez zmarnowanych odczytów.

Teraz jest to nieco przesunięte w typowym scenariuszu przesyłania strumieniowego, ponieważ blok, który jest odczytywany z dysku, nadal będzie w pamięci, gdy trafisz na następny odczyt (w końcu wykonujemy tutaj odczyty sekwencyjne) - więc kończysz płacenie pamięci RAM -> cena opóźnienia pamięci podręcznej L3 / L2 przy następnym odczycie, ale nie za opóźnienie dysku-> RAM. Jeśli chodzi o rząd wielkości, opóźnienie dysku-> pamięci RAM jest tak wolne, że prawie całkowicie zapycha wszelkie inne opóźnienia, z którymi możesz mieć do czynienia.

Podejrzewam więc, że jeśli przeprowadziłeś test z różnymi rozmiarami pamięci podręcznej (sam tego nie zrobiłem), prawdopodobnie zauważysz duży wpływ rozmiaru pamięci podręcznej do rozmiaru bloku systemu plików. Poza tym podejrzewam, że sytuacja szybko się wyrównuje.

Jest tu mnóstwo warunków i wyjątków - złożoność systemu jest w rzeczywistości dość oszałamiająca (samo uzyskanie uchwytu dla transferów pamięci podręcznej L3 -> L2 jest zadziwiająco skomplikowane i zmienia się z każdym typem procesora).

Prowadzi to do odpowiedzi „w prawdziwym świecie”: jeśli Twoja aplikacja jest dostępna w 99%, ustaw rozmiar pamięci podręcznej na 8192 i przejdź dalej (jeszcze lepiej, wybierz hermetyzację zamiast wydajności i użyj BufferedInputStream, aby ukryć szczegóły). Jeśli znajdujesz się w 1% aplikacji, które są w dużym stopniu zależne od przepustowości dysku, stwórz swoją implementację, aby móc zamienić różne strategie interakcji z dyskiem oraz zapewnij pokrętła i pokrętła, aby umożliwić użytkownikom testowanie i optymalizowanie (lub wymyślanie niektórych system samooptymalizacji).

Kevin Day
źródło
3
Zrobiłem kilka banchmarków na telefonie komórkowym (Nexus 5X) dla mojej aplikacji na Androida zarówno dla małych plików (3,5 MB), jak i dużych plików (175 Mb). Okazało się, że złoty rozmiar to bajt [] o długości 524288. Cóż, możesz wygrać 10-20 ms, jeśli przełączysz się między małym buforem 4Kb a dużym buforem 524Kb w zależności od rozmiaru pliku, ale nie jest to tego warte. Więc 524 Kb było najlepszą opcją w moim przypadku.
Kirill Karmazin
19

Tak, prawdopodobnie zależy to od różnych rzeczy - ale wątpię, że zrobi to dużą różnicę. Zwykle wybieram 16K lub 32K jako dobrą równowagę między zużyciem pamięci a wydajnością.

Zwróć uwagę, że powinieneś mieć blok try / final w kodzie, aby upewnić się, że strumień jest zamknięty, nawet jeśli zostanie zgłoszony wyjątek.

Jon Skeet
źródło
Edytowałem post o try..catch. W moim prawdziwym kodzie mam jeden, ale pominąłem go, aby post był krótszy.
ARKBAN,
1
jeśli chcemy zdefiniować dla niego stały rozmiar, który rozmiar jest lepszy? 4k, 16k czy 32k?
BattleTested
2
@MohammadrezaPanahi: Nie używaj komentarzy do użytkowników borsuka. Czekałeś mniej niż godzinę na drugi komentarz. Pamiętaj, że użytkownicy mogą z łatwością zasnąć, uczestniczyć w spotkaniach lub w zasadzie zajęci innymi sprawami i nie mają obowiązku odpowiadania na komentarze. Ale odpowiadając na twoje pytanie: wszystko zależy od kontekstu. Jeśli pracujesz w systemie o bardzo ograniczonej pamięci, prawdopodobnie potrzebujesz małego bufora. Jeśli pracujesz w dużym systemie, użycie większego bufora zmniejszy liczbę wywołań odczytu. Odpowiedź Kevina Daya jest bardzo dobra.
Jon Skeet
7

W większości przypadków nie ma to większego znaczenia. Po prostu wybierz dobry rozmiar, na przykład 4K lub 16K i trzymaj się go. Jeśli masz pewność, że jest to wąskie gardło w Twojej aplikacji, powinieneś rozpocząć profilowanie, aby znaleźć optymalny rozmiar bufora. Jeśli wybierzesz zbyt mały rozmiar, stracisz czas na wykonywanie dodatkowych operacji we / wy i dodatkowych wywołań funkcji. Jeśli wybierzesz zbyt duży rozmiar, zaczniesz widzieć wiele błędów w pamięci podręcznej, co naprawdę Cię spowolni. Nie używaj bufora większego niż rozmiar pamięci podręcznej L2.

Adam Rosenfield
źródło
4

W idealnym przypadku powinniśmy mieć wystarczająco dużo pamięci, aby odczytać plik w jednej operacji odczytu. Byłoby to najlepsze rozwiązanie, ponieważ pozwalamy systemowi dowolnie zarządzać systemem plików, jednostkami alokacji i dyskiem twardym. W praktyce masz szczęście znać rozmiary plików z wyprzedzeniem, po prostu użyj średniego rozmiaru pliku zaokrąglonego w górę do 4K (domyślna jednostka alokacji w NTFS). A co najważniejsze: stwórz punkt odniesienia, aby przetestować wiele opcji.

Ovidiu Pacurar
źródło
czy masz na myśli, że najlepszy rozmiar bufora do odczytu i zapisu w pliku to 4k?
BattleTested
4

Możesz użyć BufferedStreams / readers, a następnie użyć ich rozmiarów buforów.

Uważam, że BufferedXStreams używają 8192 jako rozmiaru bufora, ale tak jak powiedział Ovidiu, prawdopodobnie powinieneś przeprowadzić test na całej gamie opcji. To naprawdę będzie zależeć od systemu plików i konfiguracji dysków, jakie są najlepsze rozmiary.

John Gardner
źródło
4

Czytanie plików za pomocą FileChannel i MappedByteBuffer w Javie NIO najprawdopodobniej da rozwiązanie, które będzie znacznie szybsze niż jakiekolwiek rozwiązanie obejmujące FileInputStream. Zasadniczo mapuj pamięć dużych plików i używaj bezpośrednich buforów dla małych.

Alexander
źródło
4

W źródle BufferedInputStream znajdziesz: private static int DEFAULT_BUFFER_SIZE = 8192;
Więc możesz użyć tej wartości domyślnej.
Ale jeśli zdobędziesz więcej informacji, otrzymasz bardziej wartościowe odpowiedzi.
Na przykład, twój adsl może preferować bufor o rozmiarze 1454 bajtów, to dlatego, że ładunek TCP / IP. W przypadku dysków możesz użyć wartości, która odpowiada rozmiarowi bloku dysku.

GoForce5500
źródło
1

Jak już wspomniano w innych odpowiedziach, użyj BufferedInputStreams.

Po tym, wydaje mi się, że rozmiar bufora nie ma znaczenia. Albo program jest powiązany z operacjami we / wy, a zwiększenie rozmiaru bufora w stosunku do wartości domyślnej BIS nie będzie miało dużego wpływu na wydajność.

Lub program jest powiązany z procesorem wewnątrz MessageDigest.update (), a większość czasu nie jest spędzana na kodzie aplikacji, więc modyfikowanie go nie pomoże.

(Hmm ... w przypadku wielu rdzeni wątki mogą pomóc.)

Maglob
źródło
0

1024 jest odpowiednie w wielu różnych okolicznościach, chociaż w praktyce można zauważyć lepszą wydajność przy większym lub mniejszym rozmiarze bufora.

Zależałoby to od wielu czynników, w tym rozmiaru bloku systemu plików i sprzętu procesora.

Powszechne jest również wybieranie potęgi 2 dla rozmiaru bufora, ponieważ większość sprzętu jest zbudowana z bloków Fle i rozmiarów pamięci podręcznej, które są potęgą 2. Klasy buforowane umożliwiają określenie rozmiaru bufora w konstruktorze. Jeśli żadna nie zostanie podana, używają wartości domyślnej, która w większości maszyn JVM wynosi potęgę 2.

Bez względu na wybrany rozmiar bufora, największy wzrost wydajności to przejście z niebuforowanego do buforowanego dostępu do plików. Dostosowanie rozmiaru bufora może nieznacznie poprawić wydajność, ale jeśli nie używasz bardzo małego lub bardzo dużego rozmiaru buforu, jest mało prawdopodobne, aby miało to znaczący wpływ.

Adrian Krebs
źródło