Pourquoi istream / ostream est-il lent
À 50: 40 de http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly Andrei Alexandrescu fait une blague sur la façon dont istream n'est pas efficace/lent.
J'ai eu un problème dans le passé avec ostream étant lent et fwrite étant significativement plus rapide (réduisant de nombreuses secondes lors de l'exécution de la boucle principale une fois) mais je n'ai jamais compris pourquoi ni regardé dedans.
Qu'est-ce qui rend istream et ostream lents en C++? ou au moins lent par rapport à d'autres choses (comme fread / fget, fwrite) qui satisferait également les besoins.
4 réponses
En fait, IOStreams n'a pas à être lent! Il s'agit de les mettre en œuvre d'une manière raisonnable pour les rendre rapides, cependant. La plupart des bibliothèques C++ standard ne semblent pas accorder trop d'attention à la mise en œuvre D'IOStreams. Il y a longtemps, quand mon CXXRT était toujours maintenu, il était à peu près aussi rapide que stdio - lorsqu'il était utilisé correctement!
Notez qu'il y a peu de pièges de performance pour les utilisateurs disposés avec IOStreams, cependant. Les directives suivantes s'appliquent à tous les IOStream implémentations mais surtout à celles qui sont adaptées pour être rapides:
- Lors de l'utilisation de
std::cin
,std::cout
, etc. vous devez appelerstd::sync_with_stdio(false)
! Sans cet appel, toute utilisation des objets de flux standard est nécessaire pour synchroniser avec les flux standard de C. Bien sûr, lors de l'utilisation destd::sync_with_stdio(false)
, il est supposé que vous ne mélangez passtd::cin
avecstdin
,std::cout
avecstdout
, etc. -
Do pas utiliser
std::endl
car il des mandats bien des vidages de mémoire tampon. De même, Ne définissez passtd::ios_base::unitbuf
ou utilisezstd::flush
inutilement. - lorsque vous créez vos propres tampons de flux (OK, peu d'utilisateurs le font), assurez-vous qu'ils utilisent un tampon interne! Le traitement des caractères individuels passe par plusieurs conditions et une fonction
virtual
qui le rend hideusement lent.
Peut-être que cela peut donner une idée de ce à quoi vous avez affaire:
#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;
}
En exécutant ceci, j'obtiens des résultats comme ceci (avec 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
Et ceci (avec 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
Comme nous pouvons le voir dans les résultats, il ne s'agit pas vraiment d'iostreams étant catégoriquement lent. Plutôt, beaucoup dépend exactement comment vous utilisez iostreams (et dans une moindre mesure FILE *
ainsi). Il y a aussi une variation assez importante juste entre ceux-ci à application.
Néanmoins, les versions les plus rapides avec chacune (fread
et istream::read
) sont essentiellement liées. Avec VC++ getc
est un peu plus lent que istream::read
ou et istreambuf_iterator
.
Bottom line: obtenir de bonnes performances avec iostreams nécessite un peu plus de soin qu'avec FILE *
- mais c'est certainement possible. Ils vous donnent également plus d'options: commodité lorsque vous ne vous souciez pas beaucoup de la vitesse et des performances directement compétitives avec le meilleur que vous pouvez obtenir des e/s de style C, avec un peu de travail supplémentaire.
Sur un sujet similaire, STL dit :" Vous pouvez appeler setvbuf() pour activer la mise en mémoire tampon sur stdout."
Bien que cette question soit assez ancienne, je suis étonné que personne n'ait mentionné la construction d'objets iostream.
C'est-à-dire, chaque fois que vous créez un STL iostream
(et d'autres variantes de flux), si vous entrez dans le code, le constructeur appelle une fonction interne Init
. Dans ce cas, operator new
est appelé pour créer un nouvel objet locale
.
Et de même, est détruit sur la destruction.
C'est hideux, à mon humble avis. Et contribue certainement à ralentir la construction/destruction de l'objet, car la mémoire est alloué / désalloué à l'aide d'un verrou système, à un moment donné.
En outre, certains flux STL vous permettent de spécifier un allocator
, alors pourquoi le locale
n'est-il pas créé en utilisant l'allocateur spécifié?
En utilisant des flux dans un environnement multithread, vous pouvez également imaginer le goulot d'étranglement imposé en appelant operator new
chaque fois qu'un nouvel objet stream est construit.
Désordre hideux si vous me demandez, comme je le découvre moi-même en ce moment!