c++ - sirve - ¿Por qué es istream/ostream lento?
iostream h en c (4)
En realidad, ¡IOStreams no tiene que ser lento! Sin embargo, es una cuestión de implementarlos de una manera razonable para hacerlos rápidos. La mayoría de las bibliotecas estándar de C ++ no parecen prestar demasiada atención a la implementación de IOStreams. Hace mucho tiempo, cuando mi CXXRT aún se mantenía, era tan rápido como el stdio, ¡cuando se usaba correctamente!
Sin embargo, tenga en cuenta que existen pocas trampas de rendimiento para los usuarios distribuidos con IOStreams. Las siguientes pautas se aplican a todas las implementaciones de IOStream, pero especialmente a aquellas que están diseñadas para ser rápidas:
- Cuando use
std::cin
,std::cout
, etc., debe llamar astd::sync_with_stdio(false)
! Sin esta llamada, cualquier uso de los objetos de flujo estándar es necesario para sincronizar con los flujos estándar de C. Por supuesto, cuando se usastd::sync_with_stdio(false)
se asume que no mezclastd::cin
constdin
,std::cout
constdout
, etc. - No use
std::endl
ya que exige muchos vaciados innecesarios de cualquier búfer. Del mismo modo, no establezcastd::ios_base::unitbuf
o usestd::flush
innecesariamente. - Al crear sus propios búferes de flujo (OK, pocos usuarios lo hacen), ¡asegúrese de que utilicen un búfer interno! El procesamiento de caracteres individuales salta a través de múltiples condiciones y una función
virtual
que lo hace terriblemente lento.
A las 50:40 de http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly Andrei Alexandrescu hace una broma acerca de qué tan eficiente / lento es el istream.
En el pasado tuve un problema con ostream que era lento y que la escritura era significativamente más rápida (reduciendo muchos segundos al ejecutar el bucle principal una vez) pero nunca entendí por qué ni lo examiné.
¿Qué hace que istream y ostream sean lentos en C ++? o al menos lento en comparación con otras cosas (como fread / fget, fwrite) que satisfarían igualmente las necesidades.
Quizás esto pueda dar una idea de con qué estás tratando:
#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <time.h>
#include <string>
#include <algorithm>
unsigned count1(FILE *infile, char c) {
int ch;
unsigned count = 0;
while (EOF != (ch=getc(infile)))
if (ch == c)
++count;
return count;
}
unsigned int count2(FILE *infile, char c) {
static char buffer[8192];
int size;
unsigned int count = 0;
while (0 < (size = fread(buffer, 1, sizeof(buffer), infile)))
for (int i=0; i<size; i++)
if (buffer[i] == c)
++count;
return count;
}
unsigned count3(std::istream &infile, char c) {
return std::count(std::istreambuf_iterator<char>(infile),
std::istreambuf_iterator<char>(), c);
}
unsigned count4(std::istream &infile, char c) {
return std::count(std::istream_iterator<char>(infile),
std::istream_iterator<char>(), c);
}
unsigned int count5(std::istream &infile, char c) {
static char buffer[8192];
unsigned int count = 0;
while (infile.read(buffer, sizeof(buffer)))
count += std::count(buffer, buffer+infile.gcount(), c);
count += std::count(buffer, buffer+infile.gcount(), c);
return count;
}
unsigned count6(std::istream &infile, char c) {
unsigned int count = 0;
char ch;
while (infile >> ch)
if (ch == c)
++count;
return count;
}
template <class F, class T>
void timer(F f, T &t, std::string const &title) {
unsigned count;
clock_t start = clock();
count = f(t, ''N'');
clock_t stop = clock();
std::cout << std::left << std::setw(30) << title << "/tCount: " << count;
std::cout << "/tTime: " << double(stop-start)/CLOCKS_PER_SEC << "/n";
}
int main() {
char const *name = "equivs2.txt";
FILE *infile=fopen(name, "r");
timer(count1, infile, "ignore");
rewind(infile);
timer(count1, infile, "using getc");
rewind(infile);
timer(count2, infile, "using fread");
fclose(infile);
std::ifstream in2(name);
timer(count3, in2, "ignore");
in2.clear();
in2.seekg(0);
timer(count3, in2, "using streambuf iterators");
in2.clear();
in2.seekg(0);
timer(count4, in2, "using stream iterators");
in2.clear();
in2.seekg(0);
timer(count5, in2, "using istream::read");
in2.clear();
in2.seekg(0);
timer(count6, in2, "using operator>>");
return 0;
}
Ejecutando esto, obtengo resultados como este (con MS VC ++):
ignore Count: 1300 Time: 0.309
using getc Count: 1300 Time: 0.308
using fread Count: 1300 Time: 0.028
ignore Count: 1300 Time: 0.091
using streambuf iterators Count: 1300 Time: 0.091
using stream iterators Count: 1300 Time: 0.613
using istream::read Count: 1300 Time: 0.028
using operator>> Count: 1300 Time: 0.619
y esto (con MinGW):
ignore Count: 1300 Time: 0.052
using getc Count: 1300 Time: 0.044
using fread Count: 1300 Time: 0.036
ignore Count: 1300 Time: 0.068
using streambuf iterators Count: 1300 Time: 0.068
using stream iterators Count: 1300 Time: 0.131
using istream::read Count: 1300 Time: 0.037
using operator>> Count: 1300 Time: 0.121
Como podemos ver en los resultados, no es realmente una cuestión de que los flujos de información sean categóricamente lentos. Más bien, mucho depende de cómo use iostreams (y, en menor medida, también de FILE *
). También hay una variación bastante sustancial solo entre estas y las implementaciones.
No obstante, las versiones más rápidas con cada ( fread
y istream::read
) están esencialmente vinculadas. Con VC ++, getc
es un poco más lento que istream::read
o istreambuf_iterator
.
Conclusión: obtener un buen rendimiento de iostreams requiere un poco más de cuidado que con FILE *
, pero es ciertamente posible. También le brindan más opciones: conveniencia cuando no le importa mucho la velocidad y un rendimiento directamente competitivo con lo mejor que puede obtener de E / S de estilo C, con un poco de trabajo adicional.
Si bien esta pregunta es bastante antigua, me sorprende que nadie haya mencionado la construcción de objetos iostream.
Es decir, siempre que cree un iostream
STL (y otras variantes de flujo), si ingresa en el código, el constructor llama a una función Init
interna. Allí, se llama al operator new
para crear un nuevo objeto de locale
. Y asimismo, se destruye a la destrucción.
Esto es horrible, en mi humilde opinión. Y ciertamente contribuye a la lenta construcción / destrucción de objetos, porque la memoria se asigna / desasigna mediante un bloqueo del sistema, en algún momento.
Además, algunas de las secuencias de STL le permiten especificar un allocator
, entonces, ¿por qué el locale
creado NO usa el asignador especificado?
Al usar flujos en un entorno de multiproceso, también podría imaginar el cuello de botella impuesto por llamar al operator new
cada vez que se construye un nuevo objeto de flujo.
Lío horrible si me preguntas, ¡como me estoy descubriendo ahora mismo!
Sobre un tema similar, STL dice: "Puede llamar a setvbuf () para habilitar el almacenamiento en búfer en la salida estándar".