В какой момент имеет смысл оборачивать FileOutputStream с BufferedOutputStream с точки зрения производительности?
У меня есть модуль, который отвечает за чтение, обработку и запись байтов на диск. Байты поступают через UDP, и после того, как отдельные дейтаграммы собраны, конечный байтовый массив, который обрабатывается и записывается на диск, обычно составляет от 200 до 500 000 байтов. Иногда встречаются байтовые массивы, которые после сборки составляют более 500 000 байтов, но они встречаются относительно редко.
Я в настоящее время использую FileOutputStream
"s write(byte\[\])
метод. Я также экспериментирую с упаковкой FileOutputStream
в BufferedOutputStream
включая использование конструктора, который принимает размер буфера в качестве параметра.
Похоже, что с помощью BufferedOutputStream
немного улучшается производительность, но я только начал экспериментировать с различными размерами буфера. У меня есть только ограниченный набор образцов данных для работы (два набора данных из примеров прогонов, которые я могу передать через свое приложение). Существует ли общее практическое правило, которое я мог бы применить, чтобы попытаться рассчитать оптимальные размеры буфера, чтобы уменьшить объем записи на диск и максимально повысить производительность записи на диск, учитывая информацию, которую я знаю о данных, которые я пишу?
2 ответа
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;
}
В последнее время я пытался изучить производительность ввода-вывода. Из того, что я заметил, прямо пишу FileOutputStream
привело к лучшим результатам; который я приписал FileOutputStream
родной призыв write(byte[], int, int)
, Кроме того, я также заметил, что когда BufferedOutputStream
латентность начинает сходиться к прямой FileOutputStream
она колеблется намного больше, то есть она может резко удвоиться (я еще не смог выяснить, почему).
PS Я использую 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]