В какой момент имеет смысл оборачивать FileOutputStream с BufferedOutputStream с точки зрения производительности? - PullRequest
44 голосов
/ 03 января 2012

У меня есть модуль, который отвечает за чтение, обработку и запись байтов на диск. Байты поступают через UDP, и после того, как отдельные дейтаграммы собраны, конечный байтовый массив, который обрабатывается и записывается на диск, обычно составляет от 200 до 500 000 байтов. Иногда встречаются байтовые массивы, которые после сборки превышают 500 000 байтов, но это относительно редко.

В настоящее время я использую FileOutputStream write(byte\[\]) метод . Я также экспериментирую с переносом FileOutputStream в BufferedOutputStream, включая использование конструктора, который принимает размер буфера в качестве параметра .

Похоже, что использование BufferedOutputStream имеет тенденцию к чуть более высокой производительности, но я только начал экспериментировать с различными размерами буфера. У меня есть только ограниченный набор образцов данных для работы (два набора данных из примеров прогонов, которые я могу передать через свое приложение). Есть ли общее практическое правило, которое я мог бы применить, чтобы попытаться рассчитать оптимальные размеры буфера, чтобы уменьшить объем записи на диск и максимально повысить производительность записи на диск, учитывая информацию, которую я знаю о данных, которые я пишу?

Ответы [ 2 ]

32 голосов
/ 03 января 2012

BufferedOutputStream помогает, когда записи меньше размера буфера, например, 8 КБ.Для больших записей это не помогает и не делает это намного хуже.Если ВСЕ ваши записи превышают размер буфера или вы всегда используете flush () после каждой записи, я бы не использовал буфер.Однако, если большая часть ваших записей меньше размера буфера и вы не используете flush () каждый раз, его стоит иметь.

Вы можете обнаружить, что увеличение размера буфера до 32 КБ или больше даетнезначительное улучшение или еще хуже.YMMV


Может оказаться полезным код для BufferedOutputStream.write

/**
 * 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;
}
1 голос
/ 30 января 2017

В последнее время я пытался изучить производительность ввода-вывода. Из того, что я заметил, прямое письмо в FileOutputStream привело к лучшим результатам; который я приписал нативному вызову FileOutputStream для write(byte[], int, int). Более того, я также заметил, что когда латентность BufferedOutputStream начинает сходиться к прямой FileOutputStream, она колеблется гораздо больше, то есть может резко увеличиться даже вдвое (я пока не смог выяснить, почему ).

P.S. Я использую Java 8 и не смогу сейчас прокомментировать, будут ли мои наблюдения сохраняться для предыдущих версий Java.

Вот код, который я тестировал, где мой входной файл был размером ~ 10 КБ

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

ВЫВОД:

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]
...