¿C++ ofstream file writing utiliza un búfer?


A continuación se muestran dos programas que escriben 50,000,000 bytes en un archivo.

El primer programa, escrito en C, utiliza un búfer, que una vez llenado a un valor arbitrario, escribe en el disco, y luego repite ese proceso hasta que se escriban los 50.000.000 de bytes. Noté que a medida que aumentaba el tamaño del búfer, el programa tardaba menos tiempo en ejecutarse. Por ejemplo, en BUFFER_SIZE = 1, el programa tomó ~88.0463 segundos, mientras que en BUFFER_SIZE = 1024, el programa solo tomó ~1.7773 segundos. El el mejor tiempo que grabé fue cuando BUFFER_SIZE = 131072. Como el BUFFER_SIZE aumentó más alto que eso, me di cuenta de que en realidad comenzó a tomar un poco más de tiempo.

El segundo programa, escrito en C++, utiliza ofstream para escribir un byte a la vez. Para mi sorpresa, el programa solo tomó ~1.87 segundos para ejecutarse. Esperaba que tomara un minuto más o menos, como el programa C con BUFFER_SIZE = 1. Obviamente, el C++ ofstream maneja la escritura de archivos de manera diferente de lo que pensaba. Según mis datos, tiene un rendimiento bastante similar al del archivo C con BUFFER_SIZE = 512. ¿Usa algún tipo de buffer detrás de escena?

Aquí está el programa C:

const int NVALUES = 50000000; //#values written to the file
const char FILENAME[] = "/tmp/myfile";
const int BUFFER_SIZE = 8192; //# bytes to fill in buffer before writing

main()
{
    int fd;  //File descriptor associated with output file
    int i;
    char writeval = '\0';
    char buffer[BUFFER_SIZE];

    //Open file for writing and associate it with the file descriptor
    //Create file if it does not exist; if it does exist truncate its size to 0
    fd = open(FILENAME, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);

    for(i=0;i<NVALUES;i++)
    {
        //Package bytes into BUFFER_SIZE chunks 
                //and write a chunk once it is filled
        buffer[i%BUFFER_SIZE] = writeval;
        if((i%BUFFER_SIZE == BUFFER_SIZE-1 || i == NVALUES-1))
            write(fd, buffer, i%BUFFER_SIZE+1);

    }

    fsync(fd);

    close(fd);
}

Aquí está el programa C++:

int main()
{
    ofstream ofs("/tmp/iofile2");
    int i;

    for(i=0; i<50000000; i++)
        ofs << '\0';

    ofs.flush();
    ofs.close();

    return 0;
}

Gracias por su tiempo.

Author: TimeBomb006, 2012-05-04

3 answers

Sí, los ostreams usan un búfer de flujo, una subclase de una instanciación de la plantilla basic_streambuf. La interfaz de basic_streambuf está diseñada para que una implementación pueda hacer buffering si hay una ventaja en eso.

Sin embargo, este es un problema de calidad de la implementación. Las implementaciones no son necesarias para hacer esto, pero cualquier implementación competente lo hará.

Puede leer todo sobre esto en el capítulo 27 de la norma ISO, aunque tal vez una fuente más legible es El C++ Biblioteca Estándar: Un Tutorial y una Referencia (google search ).

 9
Author: bames53,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 13:49:42

Sí, todas las operaciones de flujo se almacenan en búfer, aunque por defecto la entrada estándar, la salida y la salida de error no son para que las interacciones con C IO sean menos sorprendentes.

Como ya se ha mencionado, hay una clase basestreambuf eso se usa entre bastidores. Está provisto de su propio búfer, cuyo tamaño es un detalle de implementación.

Puede comprobar (experimentalmente) cuánto es este búfer usando streambuf::in_avail, asumiendo que filestream de entrada y filestream de salida están configurados con el mismo tamaño de búfer...

Hay otras dos operaciones que puedes hacer aquí que podrían ser de interés:

  • puede cambiar el objeto streambuf utilizado por un flujo, para cambiar a una versión personalizada
  • puede cambiar el búfer utilizado por el objeto streambuf

Ambos deben hacerse bien después de crear el flujo o después de un flush, para que no se pierdan algunos datos...

Para ilustrar el cambio de búfer, echa un vistazo streambuf::putsetbuf:

#include <fstream>
#include <vector>

int main () {
  std::vector<char> vec(512);

  std::fstream fs;
  fs.rdbuf()->pubsetbuf(&vec.front(), vec.size());

  // operations with file stream here.
  fs << "Hello, World!\n";

  // the stream is automatically closed when the scope ends, so fs.close() is optional
  // the stream is automatically flushed when it is closed, so fs.flush() is optional

  return 0;
}

Ahora puedes repetir los experimentos que hiciste en C para encontrar el punto dulce:)

 11
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 15:12:14

Por esto, ofstream tiene un puntero interno filebuf, se puede leer a través de la función rdbuf, que apunta a un objeto streambuf, que es este:

streambuf los objetos generalmente se asocian con un carácter específico secuencia, de la cual leen y escriben datos a través de un buffer de memoria. El búfer es una matriz en memoria que se espera que sincronizarse cuando sea necesario con el contenido físico del carácter asociado secuencia.

Puse en negrita los bits importantes, parece que hace uso de un búfer, pero no lo sé o no he descubierto qué tipo de búfer es.

 2
Author: Tony The Lion,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 13:30:33