std :: fstream buffering versus manual buffering (¿por qué 10x de ganancia con buffer manual)?

He probado dos configuraciones de escritura:

1) almacenamiento intermedio Fstream:

// Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream; stream.rdbuf()->pubsetbuf(buffer, length); stream.open("test.dat", std::ios::binary | std::ios::trunc) // To write I use : stream.write(reinterpret_cast(&x), sizeof(x)); 

2) almacenamiento en búfer manual:

 // Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc); // Then I put manually the data in the buffer // To write I use : stream.write(buffer, length); 

Esperaba el mismo resultado …

Pero mi almacenamiento en memoria intermedia manual mejora el rendimiento en un factor de 10 para escribir un archivo de 100 MB, y el almacenamiento en memoria intermedia de flujo no cambia nada en comparación con la situación normal (sin redefinir un almacenamiento intermedio).

¿Alguien tiene una explicación de esta situación?

EDITAR: Estas son las noticias: un punto de referencia que se acaba de hacer en una supercomputadora (architecture Linux de 64 bits, dura intel Xeon 8-core, sistema de archivos Lustre y … con suerte comstackdores bien configurados) punto de referencia (y no explico el motivo de la “resonancia” para un buffer manual de 1kB …)

EDIT 2: Y la resonancia en 1024 B (si alguien tiene una idea al respecto, estoy interesado): enter image description here

Esto se debe básicamente a la sobrecarga de llamada de función y la indirección. El método ofstream :: write () se hereda de ostream. Esa función no está en línea en libstdc ++, que es la primera fuente de sobrecarga. Entonces ostream :: write () tiene que llamar a rdbuf () -> sputn () para hacer la escritura real, que es una llamada de función virtual.

Además de eso, libstdc ++ redirige sputn () a otra función virtual xsputn () que agrega otra llamada a función virtual.

Si coloca los caracteres en el búfer usted mismo, puede evitar esa sobrecarga.

Me gustaría explicar cuál es la causa del pico en el segundo gráfico

De hecho, las funciones virtuales utilizadas por std::ofstream conducen a la disminución del rendimiento como vemos en la primera imagen, pero no da una respuesta de por qué el rendimiento más alto fue cuando el tamaño del búfer manual era inferior a 1024 bytes.

El problema se relaciona con el alto costo de las writev() y write() ) y la implementación interna de std::filebuf clase interna de std::ofstream .

Para mostrar cómo influye la write() en el rendimiento, hice una prueba simple usando la herramienta dd en mi máquina Linux para copiar un archivo de 10MB con diferentes tamaños de búfer (opción bs):

 test@test$ time dd if=/dev/zero of=zero bs=256 count=40000 40000+0 records in 40000+0 records out 10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s real 0m2.370s user 0m0.000s sys 0m0.952s test$test: time dd if=/dev/zero of=zero bs=512 count=20000 20000+0 records in 20000+0 records out 10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s real 0m1.324s user 0m0.000s sys 0m0.476s test@test: time dd if=/dev/zero of=zero bs=1024 count=10000 10000+0 records in 10000+0 records out 10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s real 0m0.798s user 0m0.008s sys 0m0.236s test@test: time dd if=/dev/zero of=zero bs=4096 count=2500 2500+0 records in 2500+0 records out 10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s real 0m0.293s user 0m0.000s sys 0m0.064s 

Como se puede ver que cuanto menos memoria intermedia exista, menor será la velocidad de escritura y tanto tiempo dd en el espacio del sistema. Por lo tanto, la velocidad de lectura / escritura disminuye cuando disminuye el tamaño del búfer.

Pero ¿por qué la mayor velocidad se produjo cuando el tamaño del búfer manual era inferior a 1024 bytes en las pruebas del buffer manual del creador de temas? ¿Por qué fue casi constante?

La explicación se relaciona con la implementación std::ofstream , especialmente con std::basic_filebuf .

Por defecto usa un buffer de 1024 bytes (variable BUFSIZ). Entonces, cuando escribe sus datos usando paces menores a 1024, la llamada al sistema writev() (no write() ) se llama al menos una vez para dos ofstream::write() (las paces tienen un tamaño de 1023 <1024 - primero se escribe a la memoria intermedia, y las segundas fuerzas escribiendo de primera y segunda). En base a esto, podemos concluir que ofstream::write() speed no depende del tamaño del buffer manual antes del pico ( write() se llama al menos dos veces raramente).

Cuando intenta escribir mayor o igual a 1024 bytes de búfer a la vez usando la ofstream::write() , se writev() sistema writev() para cada uno ofstream::write . Entonces, ves que la velocidad aumenta cuando el buffer manual es mayor a 1024 (después del pico).

Además, si desea establecer std::ofstream buffer std::ofstream mayor que 1024 buffer (por ejemplo, 8192 bytes buffer) usando streambuf::pubsetbuf() y llame a ostream::write() para escribir datos usando paces de tamaño 1024, se sorprendería de que la velocidad de escritura sea la misma que usará el buffer 1024. Es porque la implementación de std::basic_filebuf – la clase interna de std::ofstream – está codificada para forzar el llamado del sistema writev() para cada llamada ofstream::write() cuando el búfer pasado es mayor o igual a 1024 bytes ( ver basic_filebuf :: xsputn () código fuente). También hay un problema abierto en la bugzilla de GCC que se informó en 2014-11-05 .

Entonces, la solución de este problema se puede hacer utilizando dos posibles casos:

  • reemplace std::filebuf por su propia clase y redefina std::ofstream
  • devide un búfer, que debe pasarse a ofstream::write() , a las paces menos de 1024 y pasarlas a ofstream::write() una por una
  • no pase pequeñas cantidades de datos a ofstream::write() para evitar la disminución del rendimiento en las funciones virtuales de std::ofstream

Me gustaría agregar a las respuestas existentes que este comportamiento de rendimiento (toda la sobrecarga del método virtual llama / indirectamente) normalmente no es un problema si se escriben grandes bloques de datos. Lo que parece haberse omitido de la pregunta y estas respuestas previas (aunque probablemente se entienden implícitamente) es que el código original estaba escribiendo una pequeña cantidad de bytes cada vez. Solo para aclarar a los demás: si está escribiendo grandes bloques de datos (~ kB +), no hay razón para esperar que el almacenamiento en memoria intermedia manual tenga una diferencia de rendimiento significativa con el uso del almacenamiento en búfer de std::fstream .