¿Cómo se determina el tamaño de búfer ideal cuando se utiliza FileInputStream?

Tengo un método que crea un MessageDigest (un hash) de un archivo, y tengo que hacer esto en muchos archivos (> = 100,000). ¿Qué tan grande debería hacer el buffer usado para leer de los archivos para maximizar el rendimiento?

La mayoría de las personas está familiarizada con el código básico (que repetiré aquí por las dudas):

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

¿Cuál es el tamaño ideal del buffer para maximizar el rendimiento? Sé que esto depende del sistema, y ​​estoy bastante seguro de que su sistema operativo, FileSystem y HDD dependen, y que tal vez haya otro hardware / software en la mezcla.

(Debo señalar que soy algo nuevo en Java, así que esto puede ser solo una llamada a la API de Java que desconozco).

Editar: No sé de antemano los tipos de sistemas en los que se utilizará, así que no puedo suponer mucho. (Estoy usando Java por ese motivo)

Editar: Al código anterior le faltan cosas como try..catch para hacer que la publicación sea más pequeña

El tamaño del búfer óptimo se relaciona con varias cosas: tamaño del bloque del sistema de archivos, tamaño del caché de la CPU y latencia de la caché.

La mayoría de los sistemas de archivos están configurados para usar tamaños de bloque de 4096 u 8192. En teoría, si configura el tamaño del búfer para leer unos pocos bytes más que el bloque de disco, las operaciones con el sistema de archivos pueden ser extremadamente ineficaces (es decir, si configuró su búfer para leer 4100 bytes a la vez, cada lectura requeriría 2 lecturas de bloque por el sistema de archivos). Si los bloques ya están en caché, terminas pagando el precio de la latencia de caché RAM -> L3 / L2. Si no tiene suerte y los bloques aún no están en la memoria caché, también paga el precio de la latencia de memoria del disco RAM.

Esta es la razón por la que ve la mayoría de los buffers dimensionados como una potencia de 2, y generalmente más grande que (o igual a) el tamaño del bloque de disco. Esto significa que una de las lecturas de la secuencia puede dar como resultado lecturas múltiples de bloque de disco, pero esas lecturas siempre usarán un bloque completo, sin lecturas desperdiciadas.

Ahora, esto se compensa bastante en un escenario típico de transmisión porque el bloque que se lee desde el disco todavía estará en la memoria cuando toques la siguiente lectura (estamos haciendo lecturas secuenciales aquí, después de todo), así que terminas pagando el precio de latencia del caché RAM -> L3 / L2 en la siguiente lectura, pero no en el disco-> latencia de RAM. En términos de orden de magnitud, la latencia de disco-> RAM es tan lenta que prácticamente inunda cualquier otra latencia con la que puedas estar lidiando.

Por lo tanto, sospecho que si ejecutó una prueba con diferentes tamaños de caché (no lo he hecho yo mismo), probablemente encontrará un gran impacto del tamaño de la caché hasta el tamaño del bloque del sistema de archivos. Por encima de eso, sospecho que las cosas se nivelarán bastante rápido.

Hay un montón de condiciones y excepciones aquí: las complejidades del sistema son realmente asombrosas (solo obtener un control sobre las transferencias de caché L3 -> L2 es alucinantemente complejo, y cambia con cada tipo de CPU).

Esto lleva a la respuesta del “mundo real”: si su aplicación tiene el 99% de posibilidades, establezca el tamaño de la memoria caché en 8192 y continúe (aún mejor, elija la encapsulación sobre el rendimiento y use BufferedInputStream para ocultar los detalles). Si está en el 1% de las aplicaciones que dependen en gran medida del rendimiento del disco, cree su implementación para que pueda intercambiar las diferentes estrategias de interacción del disco y proporcione las perillas y diales para permitir que los usuarios prueben y optimicen (o inventen algunas sistema de auto optimización).

Sí, es probable que dependa de varias cosas, pero dudo que haga una gran diferencia. Tiendo a optar por 16K o 32K como un buen equilibrio entre el uso de la memoria y el rendimiento.

Tenga en cuenta que debe tener un bloque try / finally en el código para asegurarse de que la transmisión esté cerrada incluso si se lanza una excepción.

En la mayoría de los casos, realmente no importa tanto. Simplemente elija un buen tamaño como 4K o 16K y quédese con él. Si está seguro de que este es el cuello de botella en su aplicación, entonces debe comenzar a perfilar para encontrar el tamaño de búfer óptimo. Si elige un tamaño demasiado pequeño, perderá tiempo realizando operaciones adicionales de E / S y llamadas a funciones adicionales. Si eliges un tamaño que sea demasiado grande, comenzarás a ver una gran cantidad de errores de caché que realmente te ralentizarán. No use un buffer más grande que su tamaño de caché L2.

En el caso ideal, deberíamos tener suficiente memoria para leer el archivo en una sola operación de lectura. Ese sería el mejor rendimiento porque dejamos que el sistema administre File System, unidades de asignación y HDD a voluntad. En la práctica, tiene la suerte de conocer los tamaños de archivo por adelantado, solo use el tamaño promedio de archivo redondeado a 4K (unidad de asignación predeterminada en NTFS). Y lo mejor de todo: crea un punto de referencia para probar múltiples opciones.

La lectura de archivos usando FileChannel y MappedByteBuffer de Java NIO probablemente dará como resultado una solución que será mucho más rápida que cualquier solución que involucre FileInputStream. Básicamente, mapeo de memoria de archivos de gran tamaño y uso de búferes directos para los pequeños.

Puede usar BufferedStreams / readers y luego usar sus tamaños de buffer.

Creo que los BufferedXStreams están usando 8192 como el tamaño del búfer, pero como dijo Ovidiu, probablemente deberías ejecutar una prueba en un montón de opciones. Dependerá realmente del sistema de archivos y las configuraciones del disco en cuanto a cuáles son los mejores tamaños.

Como ya se mencionó en otras respuestas, use BufferedInputStreams.

Después de eso, creo que el tamaño del búfer en realidad no importa. O bien el progtwig está vinculado a E / S, y el creciente tamaño del búfer por sobre el BIS por defecto no tendrá un gran impacto en el rendimiento.

O el progtwig está vinculado a la CPU dentro de MessageDigest.update (), y la mayoría del tiempo no se gasta en el código de la aplicación, por lo que ajustarlo no ayudará.

(Hmm … con múltiples núcleos, los hilos pueden ayudar.)

En la fuente de BufferedInputStream encontrará: private static int DEFAULT_BUFFER_SIZE = 8192;
Así que está bien para ti usar ese valor predeterminado.
Pero si puede encontrar más información, obtendrá respuestas más valiosas.
Por ejemplo, su adsl tal vez prefiera un búfer de 1454 bytes, eso es debido a la carga útil de TCP / IP. Para los discos, puede usar un valor que coincida con el tamaño de bloque de su disco.

1024 es apropiado para una amplia variedad de circunstancias, aunque en la práctica puede ver un mejor rendimiento con un tamaño de búfer mayor o menor.

Esto dependerá de una serie de factores que incluyen el tamaño del bloque del sistema de archivos y el hardware de la CPU.

También es común elegir una potencia de 2 para el tamaño del búfer, ya que la mayoría del hardware subyacente está estructurado con bloques de archivos y tamaños de caché que tienen una potencia de 2. Las clases almacenadas en el búfer le permiten especificar el tamaño del búfer en el constructor. Si no se proporciona ninguno, usan un valor predeterminado, que es una potencia de 2 en la mayoría de las JVM.

Independientemente del tamaño de búfer que elija, el mayor aumento de rendimiento que verá pasar del acceso de archivo no almacenado a búfer. Ajustar el tamaño del búfer puede mejorar ligeramente el rendimiento, pero a menos que use un tamaño de búfer extremadamente pequeño o extremadamente grande, es poco probable que tenga un impacto significativo.