2012-01-03 7 views
37

Tengo un módulo que es responsable de leer, procesar y escribir bytes en el disco. Los bytes entran a través de UDP y, después de que se ensamblan los datagramas individuales, la matriz de bytes final que se procesa y escribe en el disco suele tener entre 200 bytes y 500,000 bytes. Ocasionalmente, habrá conjuntos de bytes que, después del ensamblaje, tienen más de 500,000 bytes, pero estos son relativamente raros.¿En qué punto tiene sentido envolver un FileOutputStream con un BufferedOutputStream, en términos de rendimiento?

Actualmente estoy usando el FileOutputStream 's write(byte\[\]) method. También estoy experimentando con el ajuste de FileOutputStream en un BufferedOutputStream, incluido el uso de the constructor that accepts a buffer size as a parameter.

Parece que usar el BufferedOutputStream tiende a un rendimiento ligeramente mejor, pero apenas he empezado a experimentar con diferentes tamaños de búfer. Solo tengo un conjunto limitado de datos de muestra para trabajar (dos conjuntos de datos de ejecuciones de muestra que puedo canalizar a través de mi aplicación). ¿Existe una regla empírica general que pueda aplicar para tratar de calcular los tamaños de búfer óptimos para reducir las grabaciones en disco y maximizar el rendimiento de la escritura del disco dada la información que conozco sobre los datos que estoy escribiendo?

Respuesta

28

BufferedOutputStream ayuda cuando las escrituras son más pequeñas que el tamaño del búfer, p. 8 KB. Para escrituras más grandes no ayuda ni empeora las cosas. Si TODAS sus escrituras son más grandes que el tamaño del búfer o usted siempre vacía() después de cada escritura, no usaría un búfer. Sin embargo, si una buena parte de sus escrituras es menor que el tamaño del búfer y no utiliza flush() cada vez, vale la pena tener.

Puede encontrar que aumentar el tamaño del búfer a 32 KB o más le da una mejora marginal o lo empeora. YMMV


Usted puede encontrar el código para BufferedOutputStream.write útil

/** 
* 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

algo que no he encontrado todavía - ¿cuál es el tamaño de búfer predeterminado de la BufferedOutputStream en Java 6? Mencionas 8 KB: ¿es el valor predeterminado en Java? Los Javadocs para 1.4.2 dicen que el búfer es de 512 bytes, lo que significa que la mayor parte de lo que escribo tiende a estar entre 200 y 400 bytes por matriz. Sin embargo, esta información se elimina de la documentación de Java 6. –

+3

@Thomas - [mirando el código fuente] (http://www.docjar.com/html/api/java/io/BufferedOutputStream.java.html#51), el tamaño predeterminado es 8192. Supongo que eliminó la especificación de tamaño predeterminada para poder cambiarla cuando aparece un nuevo "valor predeterminado más razonable". Si es importante tener un tamaño de búfer específico, es probable que desee especificarlo explícitamente. – gustafc

+1

@gustafc Gracias. Siempre me olvido de que puedo mirar el código fuente de Java. –

1

Últimamente he estado tratando de explorar el rendimiento IO. Por lo que he observado, escribir directamente en FileOutputStream ha conducido a mejores resultados; que he atribuido a la llamada nativa FileOutputStream para write(byte[], int, int). Además, también he observado que cuando la latencia de BufferedOutputStream comienza a converger hacia la del FileOutputStream directo, fluctúa mucho más, es decir, puede duplicarse abruptamente (todavía no he podido averiguar por qué).

P.S. Estoy usando Java 8 y no podré comentar en este momento si mis observaciones se mantendrán para las versiones anteriores de Java.

Aquí está el código que probé, donde mi entrada era un archivo ~ 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"))); 
     } 
    } 
} 
} 

SALIDA:

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

Realicé pruebas similares y puedo confirmar que usar 'BufferedOutputStream' hace que escribir archivos no sea más rápido pero más lento, muy probablemente porque los datos que se escriben ya están en caché en múltiples niveles en su camino desde la JVM a través del sistema operativo hasta el físico medio. –

+0

@GOTO Gracias por confirmar. ¿Hay algún recurso del que pueda estar al tanto, que me pueda ayudar a profundizar en cómo funcionan el IO y las cachés internas? –

+0

No realmente. Si ayuda a buscar en Google, los componentes de caché de archivos se denominan Administrador de caché en Windows y Caché de páginas en Linux. Los discos duros y otros dispositivos de almacenamiento también vienen con diferentes tipos de cachés de E/S (aunque los conceptos básicos son probablemente los mismos). –

Cuestiones relacionadas