2010-05-12 8 views
9

Tras leer varias preguntas sobre la lectura y la escritura corrientes, todas las diversas respuestas definen algo así como la forma correcta de hacerlo:Por qué tampones utilizar para leer/escribir corrientes

private void CopyStream(Stream input, Stream output) 
{ 
    byte[] buffer = new byte[16 * 1024]; 
    int read; 
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 
    { 
     output.Write(buffer, 0, read); 
    } 
} 

Dos preguntas:

¿Por qué leer y escribir en estos trozos más pequeños?

¿Cuál es la importancia del tamaño del búfer utilizado?

Respuesta

6

Si se lee un byte a la vez, entonces cada byte se llama tiene la sobrecarga de llamar la función para leer el byte, y los gastos adicionales (por ejemplo, haciendo un fileposition += 1 recordar en qué parte del archivo que está , verificando si has llegado al final del archivo, etc.)

Si lees 4000 bytes, entonces tienes los mismos gastos generales (en el ejemplo anterior, 1 función llamada, un complemento (fileposition + = 4000) , y una comprobación para ver si está al final del archivo. Por lo tanto, en términos de los gastos generales, acaba de hacerlo 4000 veces más rápido. (En realidad, hay otros costos, por lo que no verá una gran ganancia, pero ha cortado drásticamente los gastos generales)

Por supuesto, puede crear un búfer tan grande como el archivo completo y obtener los gastos indirectos mínimos absolutos. Sin embargo:

  • el archivo puede ser enorme, más grande que la memoria disponible para su programa, por lo que esto simplemente fallaría. O podría ser tan grande que comiences a usar memoria virtual y esto reducirá drásticamente las cosas. Por lo que dividirlo en trozos más pequeños significa que puede copiar una cantidad ilimitada de datos utilizando un pequeño buffer de tamaño fijo

  • su sistema operativo y sus dispositivos pueden leer y escribir datos simultáneamente (por ejemplo, copiar desde un disco físico a otro). Si lee todos los datos antes de escribir todos los datos, entonces debe esperar toda la lectura antes de poder comenzar a escribir. Pero en muchos casos, es posible que pueda realizar ambas operaciones en paralelo, por lo tanto, lea un trozo pequeño y comience a escribir "asincrónicamente" (en el fondo) mientras retrocede y lee el siguiente fragmento.

  • Obtiene rendimientos decrecientes. Leer 4 bytes en lugar de 1 puede ser 4 veces más rápido. Pero leer 4,000, 40,000 o 400,000 no acelerará las cosas (de hecho, por las razones anteriores, los buffers más grandes en realidad podrían desacelerar las cosas).

  • En algunos casos, los dispositivos físicos funcionan con tamaños de datos específicos (por ejemplo 4096 bytes por sector, 128 bytes por línea de caché o 1500 bytes por paquete de datos u 8 bytes (64 bits) por bus de CPU). Dividir los datos en fragmentos que coinciden (o son múltiplos de) el mecanismo de transporte/almacenamiento subyacente puede ayudar al hardware a procesar los datos de manera más eficiente.

lo general de E/S buffers de entre 4 KB a 128 kB trabajo mejor para la mayoría de las situaciones, y se puede sintonizar estos para la operación particular que se lleva a cabo, por lo que no hay un tamaño "perfecta" que se adapte a todas las situaciones.

Tenga en cuenta que en la mayoría de las situaciones de E/S, se utilizan muchos almacenamientos intermedios. p.ej. Al copiar datos de un disco, (en términos simplistas) se lee del disco a un caché de lectura (búfer) en el disco duro, y luego se envía a través del cable de interfaz al controlador de la unidad, que también puede almacenar los datos. Luego, puede transferirse a la memoria RAM a través de un búfer de E/S, donde se retiene hasta que el programa esté listo para recibirlo (es probable que incluso esté buscando los datos antes de solicitarlos, ya que espera que continúe leyendo desde el mismo archivo, e intenta almacenar los datos de manera que no tenga que esperarlos). Luego lo lees en tu buffer y lo escribes. Luego pasa a otro búfer de E/S, se envía al controlador de la unidad, se transfiere a la unidad y se almacena en caché en una memoria caché de escritura. Finalmente, el disco duro decidirá almacenar realmente los datos en su caché de escritura, y se completará su copia; la mayoría de esto sucede en segundo plano, por lo que puede no finalizar hasta varios segundos después de que el programa crea que ha terminado de escribir y ha pasado a otra tarea. (Esta es la razón por la cual debe "extraer de manera segura" las unidades USB antes de desconectarlas; es posible que el sistema operativo aún no haya escrito todos los datos en el dispositivo, incluso muchos segundos después de que la computadora dijo que su operación de copia había finalizado)

4

Por lo general, siempre puede elegir el tamaño para leer y escribir. Sin embargo, algunos valores serán más óptimos para arquitecturas particulares. Lo que estos son es, bueno, más allá de mi conocimiento. Siempre he tendido a atenerme a las figuras con las que estoy familiarizado, como 4K (el tamaño de página en los sistemas NT para los que utilicé los controladores de escritura). Pero he experimentado en modo de usuario con tamaños más grandes y nunca he tenido problemas. Intento mantener el número de llamadas IO lo más bajo posible.

Mi sugerencia es que actualmente el tamaño del fragmento es realmente tan importante si es muy simple (sobrecarga de operación contra la cantidad ganada) o muy grande (bloqueo y saturación del sistema IO).

Creo que para cualquier caso particular debe

  1. Reducir al mínimo el número de IO llama
  2. Alter esta estrategia si verdadero rendimiento es un problema.