2012-01-03 17 views
37

Mam moduł odpowiedzialny za odczytywanie, przetwarzanie i zapisywanie bajtów na dysku. Bajty przechodzą przez UDP i po złożeniu poszczególnych datagramów, ostateczna tablica bajtów, która jest przetwarzana i zapisywana na dysk, ma zwykle rozmiar od 200 bajtów do 500 000 bajtów. Zdarza się, że będą macierze bajtowe, które po złożeniu mają ponad 500 000 bajtów, ale są one stosunkowo rzadkie.W którym momencie jest pakowanie FileOutputStream z sensie BufferedOutputStream, pod względem wydajności?

Obecnie używam FileOutputStream 's write(byte\[\]) method. Eksperymentuję również z zawijaniem FileOutputStream w BufferedOutputStream, w tym przy użyciu the constructor that accepts a buffer size as a parameter.

Wygląda na to, że używanie BufferedOutputStream prowadzi do nieco lepszej wydajności, ale dopiero zacząłem eksperymentować z różnymi rozmiarami buforów. Mam tylko ograniczony zestaw przykładowych danych do pracy (dwa zestawy danych z próbnych przebiegów, które mogę przepuścić przez moją aplikację). Czy istnieje ogólna zasada, którą mógłbym zastosować, aby spróbować obliczyć optymalne rozmiary buforów, aby zmniejszyć zapisy na dysku i zmaksymalizować wydajność zapisu na dysku, biorąc pod uwagę informacje, które znam o danych, które piszę?

Odpowiedz

28

BufferedOutputStream pomaga, gdy zapisy są mniejsze niż rozmiar bufora, np. 8 KB. W przypadku większych zapisów nie pomaga, ani nie czyni go znacznie gorszym. Jeśli WSZYSTKIE twoje zapisy są większe niż rozmiar bufora lub zawsze wypróżniasz() po każdym zapisie, nie używałbym bufora. Jednak jeśli znaczna część twoich zapisów jest mniejsza niż rozmiar bufora i nie używasz flush() za każdym razem, warto.

Może się okazać, że zwiększenie rozmiaru bufora do 32 KB lub większego spowoduje marginalną poprawę lub pogorszenie. YMMV


Można znaleźć kod BufferedOutputStream.write przydatnych

/** 
* Writes <code>len</code> bytes from the specified byte array 
* starting at offset <code>off</code> to this buffered output stream. 
* 
* <p> Ordinarily this method stores bytes from the given array into this 
* stream's buffer, flushing the buffer to the underlying output stream as 
* needed. If the requested length is at least as large as this stream's 
* buffer, however, then this method will flush the buffer and write the 
* bytes directly to the underlying output stream. Thus redundant 
* <code>BufferedOutputStream</code>s will not copy data unnecessarily. 
* 
* @param  b  the data. 
* @param  off the start offset in the data. 
* @param  len the number of bytes to write. 
* @exception IOException if an I/O error occurs. 
*/ 
public synchronized void write(byte b[], int off, int len) throws IOException { 
    if (len >= buf.length) { 
     /* If the request length exceeds the size of the output buffer, 
      flush the output buffer and then write the data directly. 
      In this way buffered streams will cascade harmlessly. */ 
     flushBuffer(); 
     out.write(b, off, len); 
     return; 
    } 
    if (len > buf.length - count) { 
     flushBuffer(); 
    } 
    System.arraycopy(b, off, buf, count, len); 
    count += len; 
} 
+0

Coś jeszcze nie znaleziono - jaki jest domyślny rozmiar bufora dla BufferedOutputStream w Javie 6? Wspomniałeś 8 KB - czy to jest domyślne w Javie? Javadocs dla wersji 1.4.2 mówią, że bufor ma 512 bajtów, co oznacza, że ​​większość tego, co napiszę, ma zazwyczaj wartość od 200 do 400 bajtów na tablicę. Jednak te informacje są usuwane z dokumentacji Java 6. –

+3

@Thomas - [patrząc na kod źródłowy] (http://www.docjar.com/html/api/java/io/BufferedOutputStream.java.html#51), domyślny rozmiar to 8192. Założę się, że usunięto domyślną specyfikację rozmiaru, aby móc ją zmienić, gdy pojawi się nowe "najbardziej sensowne ustawienie domyślne". Jeśli ważny jest określony rozmiar bufora, prawdopodobnie będziesz chciał go wyraźnie określić. – gustafc

+1

@gustafc Dzięki. Zawsze zapominam, że mogę spojrzeć na kod źródłowy Java. –

1

Mam ostatnio próbuje zbadać wydajność IO. Z tego co zauważyłem, bezpośrednie napisanie do FileOutputStream doprowadziło do lepszych wyników; które przypisałem natywnemu wywołaniu FileOutputStream dla write(byte[], int, int). Co więcej, zauważyłem również, że kiedy opóźnienie BufferedOutputStream zaczyna się zbiegać w kierunku opóźnienia w kierunku bezpośredniej wartości FileOutputStream, zmienia się ono o wiele więcej, to znaczy może gwałtownie nawet podwajać się (nie byłem jeszcze w stanie dowiedzieć się dlaczego).

P.S. Używam Java 8 i nie będę mógł teraz komentować, czy moje obserwacje będą się utrzymywać dla poprzednich wersji java.

Oto kod I przetestowane, gdzie mój wkład był plik ~ 10kB

public class WriteCombinationsOutputStreamComparison { 
    private static final Logger LOG = LogManager.getLogger(WriteCombinationsOutputStreamComparison.class); 

public static void main(String[] args) throws IOException { 

    final BufferedInputStream input = new BufferedInputStream(new FileInputStream("src/main/resources/inputStream1.txt"), 4*1024); 
    final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
    int data = input.read(); 
    while (data != -1) { 
     byteArrayOutputStream.write(data); // everything comes in memory 
     data = input.read(); 
    } 
    final byte[] bytesRead = byteArrayOutputStream.toByteArray(); 
    input.close(); 

    /* 
    * 1. WRITE USING A STREAM DIRECTLY with entire byte array --> FileOutputStream directly uses a native call and writes 
    */ 
    try (OutputStream outputStream = new FileOutputStream("src/main/resources/outputStream1.txt")) { 
     final long begin = System.nanoTime(); 
     outputStream.write(bytesRead); 
     outputStream.flush(); 
     final long end = System.nanoTime(); 
     LOG.info("Total time taken for file write, writing entire array [nanos=" + (end - begin) + "], [bytesWritten=" + bytesRead.length + "]"); 
     if (LOG.isDebugEnabled()) { 
      LOG.debug("File reading result was: \n" + new String(bytesRead, Charset.forName("UTF-8"))); 
     } 
    } 

    /* 
    * 2. WRITE USING A BUFFERED STREAM, write entire array 
    */ 

    // changed the buffer size to different combinations --> write latency fluctuates a lot for same buffer size over multiple runs 
    try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("src/main/resources/outputStream1.txt"), 16*1024)) { 
     final long begin = System.nanoTime(); 
     outputStream.write(bytesRead); 
     outputStream.flush(); 
     final long end = System.nanoTime(); 
     LOG.info("Total time taken for buffered file write, writing entire array [nanos=" + (end - begin) + "], [bytesWritten=" + bytesRead.length + "]"); 
     if (LOG.isDebugEnabled()) { 
      LOG.debug("File reading result was: \n" + new String(bytesRead, Charset.forName("UTF-8"))); 
     } 
    } 
} 
} 

WYJŚCIE:

2017-01-30 23:38:59.064 [INFO] [main] [WriteCombinationsOutputStream] - Total time taken for file write, writing entire array [nanos=100990], [bytesWritten=11059] 

2017-01-30 23:38:59.086 [INFO] [main] [WriteCombinationsOutputStream] - Total time taken for buffered file write, writing entire array [nanos=142454], [bytesWritten=11059] 
+0

Uruchomiłem podobne testy i mogę potwierdzić, że użycie 'BufferedOutputStream' sprawia, że ​​zapisywanie plików nie odbywa się szybciej, ale wolniej, najprawdopodobniej dlatego, że zapisywane dane są już buforowane na wielu poziomach w drodze od JVM przez system operacyjny do fizycznego. średni. –

+0

@GOTO Dzięki za potwierdzenie. Czy są jakieś zasoby, o których być może wiesz, to może pomóc mi głębiej zrozumieć, jak działa IO i wewnętrzne pamięci podręczne? –

+0

Niezupełnie. Jeśli pomaga ono w wyszukiwaniu w Google, składniki buforowania plików są nazywane Menedżerem pamięci podręcznej w systemie Windows i pamięci podręcznej stron w systemie Linux. Dyski twarde i inne urządzenia pamięciowe są dostarczane z różnymi rodzajami pamięci podręcznych operacji wejścia/wyjścia (chociaż podstawy są prawdopodobnie takie same). –

Powiązane problemy