2012-02-14 28 views
14

¿Alguien puede explicar el concepto de buffers un poco más explícitamente? Entiendo que los almacenamientos intermedios son estructuras de datos donde se almacenan los caracteres y el lugar donde se deben leer los datos. ¿Cuál es la idea de limpiar los buffers?C++ cout y cin buffers, y buffers en general

Cuando se vacía un búfer, ¿esto se refiere al acto de escribir los caracteres almacenados en él?

A partir del texto:

To avoid the overhead of writing in response to each output request, the library uses the 
buffer to accumulate the characters to be written, and flushes the buffer, by writing its 
contents to the output device, only when necessary. By doing so, it can combine several 
output operations into a single write. 

Cuando se hace referencia a 'lavado' que casi hace que suene como si el buffer está siendo escrito, sino también borra al mismo tiempo. Solo especulación.

Entonces, para que se escriba para ver en una pantalla, se requiere el lavado con buffer?

When our program writes its prompt to cout, that output goes into the buffer associated 
with the standard output stream. Next, we attempt to read from cin. This read flushes 
the cout buffer, so we are assured that our user will see the prompt. 

Aquí, suena como si el mediante el uso de 'endl' al final le dice al sistema que necesita para escribir inmediatamente (lo que implica que no es otra cosa?) Lo que no se usa endl?

Writing the value of std::endl ends the line of 
output, and then flushes the buffer, which forces the system to write to the output 
stream immediately. 
+0

esta pregunta ha sido formulada y respondida durante mucho tiempo en SO. http://stackoverflow.com/a/4752069/1155650 –

Respuesta

2

pensar en lo que pasaría si, cada vez que "escribió" un byte en un archivo de disco, el programa realmente salió en el disco, leído en la corriente del sector/grupo/bloque, cambiado el solo byte luego lo escribió de nuevo en el disco físico.

yo diría que su software podría ser mejor descrito como "glacial" en términos de rendimiento :-)

búfer es una forma de dosificación de hasta lee y escribe para que sean más eficientes.

Por ejemplo, si está escribiendo en un archivo de disco, puede esperar hasta tener un bloque 4K completo antes de escribirlo en el disco.

Al leer, puede obtener un bloque 4K a pesar de que solo solicitó diez bytes, en el supuesto de que puede solicitar el resto en breve.

El vaciado en escritura tiene lugar implícitamente una vez que la caché está llena o explícitamente si se solicita (con una llamada de descarga o si cierra el archivo).

Tenga en cuenta que este almacenamiento en búfer de archivos es solamente uno tipo de almacenamiento en búfer, el concepto se puede utilizar en cualquier lugar donde se puedan obtener ganancias de eficiencia "fragmentando" las lecturas y escrituras.

21

La idea básica del almacenamiento en búfer es combinar operaciones en trozos más grandes: en lugar de leer un número pequeño de bytes, lea una página entera y haga que esté disponible según lo solicitado; en lugar de escribir un número pequeño de bytes, almacenarlos en búfer y escribir una página completa o cuando se solicite explícitamente la escritura. Básicamente, esta es una optimización de rendimiento importante. Es bastante claro para las operaciones de E/S, pero generalmente también se aplica para otros usos: el procesamiento de varias unidades a la vez generalmente termina siendo más rápido que el procesamiento de unidades individuales.

Con respecto a las E/S , el enjuague se refiere a escribir los bytes actualmente almacenados en el búfer a su destino, lo que esto signifique en la práctica. Para C++, IOStreams vaciar una secuencia equivale a llamar a la función miembro std::ostream::flush() que a su vez llama a std::streambuf::pubsync() en el almacenamiento intermedio de flujo asociado (esto ignora el detalle de que la transmisión en realidad son plantillas de clase, p.std::basic_ostream<cT, traits>; para el propósito de esta discusión, no importa que sean plantillas de clase): la clase base std::streambuf es la abstracción de C++ sobre cómo se procesará una determinada secuencia. Conceptualmente consiste en una entrada y un buffer de salida más una función virtual responsable de leer o escribir los buffers, respectivamente. La función std::streambuf::pubsync() llama a la función virtual std::streambuf::sync() que debe anularse para cada almacenamiento intermedio de flujo que potencialmente pueda almacenar caracteres. Es decir, lo que flushing realmente significa depende de cómo se implementa esta función virtual.

Si una anulación de sync() realmente hace algo y lo que hace claramente depende de lo que representa el almacenamiento intermedio de flujo. Por ejemplo, para un std::filebuf que es responsable de leer o escribir en un archivo, sync() escribe el contenido actual del búfer y elimina los caracteres eliminados del búfer. Dado que un archivo puede no representar realmente un archivo físico, pero p. una tubería con nombre para comunicarse con un proceso diferente, este es un comportamiento razonable. Por otro lado, vaciar un std::stringbuf que es el buffer de flujo utilizado para implementar la escritura en un std::string utilizado, por ejemplo. por std::ostringstream en realidad no hace nada: la cadena está completamente dentro del programa y el std::string que representa su valor se construye cuando se llama a la función miembro std::stringbuf::str(). Para los búferes de flujo definidos por el usuario, existen muchos comportamientos diferentes. Una clase común de almacenamientos intermedios de flujo está filtrando la salida de alguna manera antes de pasarla a otro búfer de flujo (por ejemplo, un búfer de registro puede agregar un sello de tiempo después de cada nueva línea). Por lo general, solo llaman a la función std::streambuf::pubsync() de los siguientes búferes de transmisión.

Por lo tanto, las descripciones del comportamiento real generalmente se mantienen bastante vagas porque no está realmente claro qué sucede exactamente. Conceptualmente, el enjuague de una secuencia o al llamar al pubsync() en un buffer de transmisión debe actualizar el destino externo del carácter para que coincida con el estado interno actual. En general, esto equivale a reenviar los caracteres actualmente almacenados en el búfer y eliminarlos del búfer interno. En este punto, vale la pena señalar que el buffer generalmente también se escribe cuando está lleno. Cuando se llena depende, de nuevo, en el buffer de transmisión particular. Una buena implementación de std::filebuf rellenará básicamente un búfer de bytes que coincida con el tamaño de la página subyacente (o un múltiplo del mismo) y luego escribirá páginas completas, minimizando el número de operaciones de E/S necesarias (esto es realmente complicado porque el búfer los tamaños son diferentes entre diferentes sistemas de archivos y, dependiendo de la codificación utilizada al escribir, el número de bytes producidos no se puede estimar fácilmente).

La librería estándar C++ típicamente no requiere rubores explícitos:

  • la corriente std::cerr está configurado para eliminar automáticamente cualquier salida producida después de cada operaciones de salida que se llama. Este es el resultado de que el indicador de formato std::ios_base::unitbuf esté configurado de manera predeterminada. Para desactivarlo, puede usar std::cerr << std::nounitbuf o puede simplemente usar std::clog que escribe en el mismo destino pero no para hacer esta descarga.
  • Al leer desde std::istream, el "atado" std::ostream, si lo hay, se vacía. Por defecto std::cout está vinculado a std::cin. Si quiere configurar un std::ostream atado usted mismo, puede usar p. Ej. in.tie(&out) o, si desea eliminar un std::ostream vinculado, puede usar, p. std::cin.tie(0).
  • Cuando se destruye std::ostream (o cuando normalmente se destruye en caso de que el std::ostream sea uno de los flujos estándar), el std::ostream se vacía.
  • Cuando el búfer de una ruta se desborda la función virtual se llama std::streambuf::overflow(), que normalmente escribe el búfer del búfer en uso (más el carácter pasado, si lo hay) en su destino. Esto a menudo se hace simplemente llamando al sync() para borrar el búfer en uso, pero lo que se hace exactamente, de nuevo, depende del búfer de transmisión en concreto.

Puede también solicitar explícitamente un lavado std::ostream:

  • Usted puede llamar a la función miembro std::ostream::flush(), por ejemplo, std::cout.flush().
  • Puede llamar a la función de miembro std::streambuf::pubsync(), p. std::cout.rdbuf()->pubsync() (suponiendo que hay una configuración de búfer de flujo; llamando al std::ostream::flush() no hará nada si no hay búfer de flujo).
  • Puede usar el manipulador std::flush, p. Ej. std::cout << std::flush.
  • Puede usar el manipulador std::endl, p. Ej. std::cout << std::endl para escribir primero un carácter de nueva línea seguido de enjuagar la secuencia. Tenga en cuenta que debe usar std::endlsolo cuando realmente desee vaciar la salida. ¡No use use std::endl cuando realmente quiere crear un final de línea! ¡Simplemente escribe un carácter de nueva línea para este último!

En cualquier caso, si solo escribe un par de caracteres que no causen el desbordamiento del búfer del búfer de la secuencia, no pasará nada con ellos hasta que se vacíen implícita o explícitamente. En general, esto no es un problema porque el caso normal donde la salida almacenada debe estar disponible, es decir, cuando se lee desde std::cin, es manejado por std::cout siendo tie() d a . Cuando hay otros mecanismos de E/S en uso, puede ser necesario explotar explícitamente las secuencias tie(). El búfer a veces es un problema si el programa "falla" o se confirma durante la depuración porque algunas salidas a una secuencia pueden haberse realizado pero todavía no se han enviado. El remedio para esto es usar std::unitbuf.

+3

Wow. ¡Estoy leyendo su libro 'The C++ Standard Library' ahora mismo! – rbtLong

Cuestiones relacionadas