2010-07-22 8 views
9

La forma habitual de leer un archivo en C++ es éste:forma elegante de leer un archivo en C++: problema de rendimiento extraña

std::ifstream file("file.txt", std::ios::binary | std::ios::ate); 
std::vector<char> data(file.tellg()); 
file.seekg(0, std::ios::beg); 
file.read(data.data(), data.size()); 

La lectura de un archivo de 1,6 MB es casi instantánea.

Pero recientemente, descubrí std::istream_iterator y quería probarlo para codificar una hermosa forma de una línea para leer el contenido de un archivo. De esta manera:

std::vector<char> data(std::istream_iterator<char>(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator<char>()); 

El código es agradable, pero muy lento. Toma aproximadamente 2/3 segundos leer el mismo archivo de 1.6 MB. Entiendo que puede que no sea la mejor manera de leer un archivo, pero ¿por qué es tan lento?

La lectura de un archivo de una manera clásica dice así (estoy hablando sólo de la función de lectura):

  • la istream contiene una filebuf que contiene un bloque de datos desde el archivo
  • la la función de lectura llama al sgetn desde el archivo bbuf, que copia los caracteres uno a uno (sin memcpy) desde el búfer interno al búfer de "datos"
  • cuando los datos dentro del archivo bbu se leen por completo, el archivo lee el bloque siguiente desde el archivo

Cuando se lee un archivo usando istream_iterator, que dice así:

  • el vector de llamadas * iterador para obtener el siguiente char (esto simplemente lee una variable), lo añade al final y aumenta su propia tamaño
  • si el espacio asignado del vector está lleno (lo que ocurre con poca frecuencia), se realiza una reubicación
  • y luego llama al iterador ++ que lee el siguiente carácter de la secuencia (operador >> con un parámetro char, que Ciertamente, solo llama a la función sbumpc de filebuf)
  • finalmente se compara el iterador con el iterador final, que se realiza mediante la comparación de dos punteros

Debo admitir que la segunda forma no es muy eficiente, pero es al menos 200 veces más lenta que la primera manera, ¿cómo es posible ?

Pensé que el factor determinante del rendimiento eran las reubicaciones o el inserto, pero traté de crear un vector completo y de llamar a std :: copy, y es igual de lento.

// also very slow: 
std::vector<char> data2(1730608); 
std::copy(std::istream_iterator<char>(std::ifstream("file.txt", std::ios::binary)), std::istream_iterator<char>(), data2.begin()); 
+0

"Pensé que el factor determinante del rendimiento eran las reubicaciones o el inserto": esta es la razón por la que debe confiar en los perfiles. –

Respuesta

6

Debe comparar apple-to-apple.

Su primer código lee sin formato datos binarios porque utiliza el miembro de la función "leer". Y no porque use std :: ios_binary por cierto, vea http://stdcxx.apache.org/doc/stdlibug/30-4.html para más explicaciones, pero en resumen: "El efecto del modo abierto binario es frecuentemente malentendido. No coloca los inserters y extractores en un modo binario, y por lo tanto suprimir el formato que suelen realizar. de entrada y salida binaria se realiza únicamente por basic_istream <> :: read() y basic_ostream <> :: write()"

Así que su segundo código con istream_iterator leer formateado texto. Es mucho más lento.

Si desea leer datos binarios sin formato, utilice istreambuf_iterator:

#include <fstream> 
#include <vector> 
#include <iterator> 

std::ifstream file("file.txt", std::ios::binary); 
std::vector<char> buffer((std::istreambuf_iterator<char>(file)), 
          std::istreambuf_iterator<char>()); 

en mi plataforma (VS2008), se trata de istream_iterator x100 más lento que leer(). istreambuf_iterator funciona mejor, pero aún es x10 más lento que read().

+0

Gracias, no pensé en la localización, el ancho para extraer, etc. – Tomaka17

+0

Pero, ¿cómo puede el procesamiento de los datos en la memoria ser 100 veces más lento que el archivo de E/S? – ruslik

1

El enfoque iterador lee el archivo de un carácter a la vez, mientras que el file.read lo hace en un solo golpe.

Si el sistema operativo/manejadores de archivos saben que desea leer una gran cantidad de datos, hay muchas optimizaciones que se pueden hacer; quizás leer todo el archivo en una sola revolución del eje del disco, no copiar datos del sistema operativo almacena en búfer a los búferes de la aplicación.

Cuando realiza transferencias byte a byte, el sistema operativo no tiene idea de lo que realmente desea hacer, por lo que no puede realizar dichas optimizaciones.

+1

El objeto filebuf dentro de fstream lee el archivo bloque por bloque. De todos modos, es la misma forma de leer el archivo en ambos casos, simplemente copiando de filebuf al vector que es lento – Tomaka17

3

Solo los perfiles le dirán exactamente por qué. Supongo que lo que está viendo es solo la sobrecarga de todas las llamadas a funciones adicionales asociadas con el segundo método. En lugar de una sola llamada para traer todos los datos, está haciendo llamadas de 1,6M * ... o algo similar.

* Muchos de ellos son virtuales, lo que significa dos ciclos de CPU por llamada. (Tks Zan)

+1

Sí, y esas llamadas son virtuales indirectas. Esos apestan. –

+1

La generación de perfiles me dice que muchas funciones consumen entre el 3 y el 5% del total de cada una, ninguna función aparece en la parte superior; Te marcó como respuesta aceptada – Tomaka17

+0

@Zan Yeah! Buen punto, se olvidó de mencionar: ¡virtuales! – Gianni

Cuestiones relacionadas