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

156

Tengo un método que crea un MessageDigest (un hash) a partir de un archivo, y necesito hacer esto en muchos archivos (> = 100,000). ¿Qué tan grande debo hacer que el búfer se use para leer los archivos para maximizar el rendimiento?

Casi todos están familiarizados con el código básico (que repetiré aquí por si acaso):

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 búfer para maximizar el rendimiento? Sé que esto depende del sistema, y ​​estoy bastante seguro de que depende del sistema operativo, el sistema de archivos y el disco duro, y puede que haya otro hardware / software en la mezcla.

(Debo señalar que soy algo nuevo en Java, por lo que esto puede ser una llamada a la API de Java que no conozco).

Editar: No sé de antemano los tipos de sistemas en los que se utilizará, por lo que no puedo suponer mucho. (Estoy usando Java por esa razón).

Editar: en el código anterior faltan cosas como try..catch para hacer la publicación más pequeña

Arkán
fuente

Respuestas:

213

El tamaño óptimo del búfer está relacionado con varias cosas: tamaño del bloque del sistema de archivos, tamaño del caché de la CPU y latencia del 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 de su búfer para leer algunos bytes más que el bloque de disco, las operaciones con el sistema de archivos pueden ser extremadamente ineficientes (es decir, si usted 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é, entonces terminas pagando el precio de RAM -> Latencia de caché L3 / L2. Si no tiene suerte y los bloques aún no están en caché, también paga el precio del disco-> latencia RAM.

Esta es la razón por la que ve que la mayoría de los búferes tienen un tamaño de potencia de 2 y, en general, son más grandes (o iguales) que el tamaño del bloque de disco. Esto significa que una de las lecturas de su flujo podría dar como resultado múltiples lecturas 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 del disco todavía estará en la memoria cuando llegue a la siguiente lectura (después de todo, estamos haciendo lecturas secuenciales), así que terminará pagar el precio de latencia de caché RAM -> L3 / L2 en la siguiente lectura, pero no el disco-> latencia 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 pueda estar lidiando.

Entonces, sospecho que si realizó 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ían bastante rápido.

Aquí hay un montón de condiciones y excepciones: las complejidades del sistema son realmente asombrosas (solo manejar las transferencias de caché L3 -> L2 es increíblemente complejo y cambia con cada tipo de CPU).

Esto lleva a la respuesta del "mundo real": si su aplicación es como el 99%, establezca el tamaño de caché en 8192 y continúe (aún mejor, elija la encapsulación sobre el rendimiento y use BufferedInputStream para ocultar los detalles). Si se encuentra en el 1% de las aplicaciones que dependen en gran medida del rendimiento del disco, cree su implementación para que pueda intercambiar diferentes estrategias de interacción de disco y proporcione los mandos y diales para permitir a sus usuarios probar y optimizar (o inventar algunos sistema de auto optimización).

Kevin Day
fuente
3
Realicé algunas evaluaciones comparativas en un teléfono móvil (Nexus 5X) para mi aplicación de Android para ambos: archivos pequeños (3,5Mb) y archivos grandes (175 Mb). Y descubrí que el tamaño dorado sería byte [] de 524288 longitudes. Bueno, puede ganar 10-20 ms si cambia entre un búfer pequeño 4Kb y un búfer grande 524Kb dependiendo del tamaño del archivo, pero no vale la pena. Entonces 524 Kb fue la mejor opción en mi caso.
Kirill Karmazin el
19

Sí, probablemente depende de varias cosas, pero dudo que haga mucha diferencia. Tiendo a optar por 16K o 32K como un buen equilibrio entre el uso de memoria y el rendimiento.

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

Jon Skeet
fuente
Edité la publicación sobre el try..catch. En mi código real tengo uno, pero lo dejé para acortar la publicación.
ARKBAN
1
si queremos definir un tamaño fijo para él, ¿qué tamaño es mejor? ¿4k, 16k o 32k?
BattleTested
2
@MohammadrezaPanahi: Por favor, no use comentarios para los usuarios de tejones. Esperó menos de una hora antes de un segundo comentario. Recuerde que los usuarios pueden estar fácilmente dormidos, o en reuniones, o básicamente ocupados con otras cosas y no tienen ninguna obligación de responder comentarios. Pero para responder a su pregunta: depende completamente del contexto. Si está ejecutando en un sistema con muy poca memoria, probablemente desee un búfer pequeño. Si está ejecutando en un sistema grande, el uso de un búfer más grande reducirá la cantidad de llamadas de lectura. La respuesta de Kevin Day es muy buena.
Jon Skeet
7

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 crear perfiles para encontrar el tamaño óptimo del búfer. Si elige un tamaño que es demasiado pequeño, perderá tiempo haciendo operaciones de E / S adicionales y llamadas de funciones adicionales. Si elige un tamaño que es demasiado grande, comenzará a ver muchos errores de caché que realmente lo retrasarán. No use un búfer más grande que el tamaño de su caché L2.

Adam Rosenfield
fuente
4

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

Ovidiu Pacurar
fuente
¿Quieres decir que el mejor tamaño de búfer para leer y escribir en un archivo es 4k?
BattleTested
4

Puede usar los BufferedStreams / lectores y luego usar sus tamaños de búfer.

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

John Gardner
fuente
4

La lectura de archivos utilizando 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, asigne memoria a archivos grandes y use buffers directos para archivos pequeños.

Alejandro
fuente
4

En la fuente de BufferedInputStream encontrará: private static int DEFAULT_BUFFER_SIZE = 8192;
Por lo tanto, está bien que use ese valor predeterminado.
Pero si puede encontrar más información, obtendrá respuestas más valiosas.
Por ejemplo, su adsl puede preferir 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.

GoForce5500
fuente
1

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

Después de eso, supongo que el tamaño del buffer realmente no importa. O bien el programa está vinculado a E / S, y el tamaño creciente del búfer sobre el valor predeterminado de BIS no tendrá un gran impacto en el rendimiento.

O el programa está vinculado a la CPU dentro de MessageDigest.update (), y la mayor parte 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 podrían ayudar).

Maglob
fuente
0

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ía de varios factores, incluido el tamaño de 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 son una potencia de 2. Las clases de búfer le permiten especificar el tamaño del búfer en el constructor. Si no se proporciona ninguno, utilizan 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á es pasar del acceso a archivos sin búfer a búfer. El ajuste del tamaño del búfer puede mejorar ligeramente el rendimiento, pero a menos que esté utilizando un tamaño de búfer extremadamente pequeño o extremadamente grande, es poco probable que tenga un impacto significativo.

Adrian Krebs
fuente