¿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) a partir de un archivo y necesito hacer esto con muchos archivos (>= 100.000). ¿Qué tamaño debo hacer el búfer utilizado para leer los archivos para maximizar el rendimiento?
Casi todo el mundo está familiarizado 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 tal vez haya otro hardware/software en la mezcla.
(Debo señalar que soy algo nuevo en Java, por lo que esto puede ser simplemente una llamada a la API de Java que no conozco).
Editar: No sé de antemano en qué tipos de sistemas se utilizará, por lo que no puedo asumir mucho. (Estoy usando Java por ese motivo).
Editar: al código anterior le faltan cosas como try..catch para hacer la publicación más pequeña
El tamaño óptimo del búfer está relacionado con varias cosas: tamaño del bloque del sistema de archivos, tamaño de la 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 o 8192. En teoría, si configura el tamaño del búfer de modo que lea unos pocos bytes más que el bloque del disco, las operaciones con el sistema de archivos pueden ser extremadamente ineficientes (es decir, si configuró su búfer para leer 4100 bytes a la vez, cada lectura requeriría 2 lecturas de bloques por parte del sistema de archivos). Si los bloques ya están en el caché, terminará pagando el precio de la RAM -> latencia del caché L3/L2. Si no tiene suerte y los bloques aún no están en el caché, también pagará el precio del disco->latencia de RAM.
Es por eso que la mayoría de los buffers tienen un tamaño de potencia de 2 y generalmente son mayores (o iguales) que el tamaño del bloque del disco. Esto significa que una de las lecturas de su flujo podría resultar en múltiples lecturas de bloques de disco, pero esas lecturas siempre utilizarán un bloque completo, sin desperdicio de lecturas.
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, aquí estamos haciendo lecturas secuenciales), por lo que terminará pagando la RAM -> precio de latencia de caché L3/L2 en la siguiente lectura, pero no el disco->latencia de RAM. En términos de orden de magnitud, la latencia del disco->RAM es tan lenta que prácticamente inunda cualquier otra latencia con la que pueda estar lidiando.
Entonces, sospecho que si ejecuta una prueba con diferentes tamaños de caché (no lo he hecho yo mismo), probablemente encontrará un gran impacto en el tamaño de la caché hasta el tamaño del bloque del sistema de archivos. Por encima de eso, sospecho que las cosas se estabilizarían bastante rápido.
Aquí hay un montón de condiciones y excepciones: las complejidades del sistema son realmente asombrosas (simplemente controlar 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 tiene un 99% de disponibilidad, establezca el tamaño de la caché en 8192 y continúe (mejor aún, elija la encapsulación en lugar del 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, diseñe su implementación para que pueda intercambiar diferentes estrategias de interacción con el disco y proporcione las perillas y diales que permitan a sus usuarios probar y optimizar (o idear algunas). sistema de autooptimización).
Sí, probablemente dependa de varias cosas, pero dudo que haga una gran diferencia. Tiendo a optar por 16K o 32K como un buen equilibrio entre uso de memoria y 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 genera una excepción.
En la mayoría de los casos, realmente no importa mucho. Simplemente elija un buen tamaño, como 4K o 16K, y manténgalo. Si está seguro de que este es el cuello de botella de su aplicación, entonces debe comenzar a generar perfiles para encontrar el tamaño de búfer óptimo. Si elige un tamaño demasiado pequeño, perderá tiempo realizando operaciones de E/S adicionales y llamadas a funciones adicionales. Si eliges un tamaño demasiado grande, comenzarás a ver muchos errores de caché que realmente te ralentizarán. No utilice un búfer mayor que el tamaño de su 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 permitimos que el sistema administre el sistema de archivos, las unidades de asignación y el disco duro a voluntad. En la práctica, tiene la suerte de conocer los tamaños de los archivos de antemano; simplemente utilice 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.
Puede usar BufferedStreams/readers y luego usar sus tamaños de búfer.
Creo que BufferedXStreams está usando 8192 como tamaño de búfer, pero como dijo Ovidiu, probablemente deberías ejecutar una prueba con un montón de opciones. Realmente dependerá del sistema de archivos y de las configuraciones del disco cuáles son los mejores tamaños.