À 50:40 sur http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly Andrei Alexandrescu fait une blague sur le manque d'efficacité/de lenteur de la circulation.
Par le passé, j’avais un problème de lenteur avec ostream et d’écriture beaucoup plus rapide (réduction de plusieurs secondes lors de l’exécution de la boucle principale une fois), mais je n’ai jamais compris pourquoi ni examiné la question.
Qu'est-ce qui ralentit istream et ostream en C++? ou du moins lente par rapport à d’autres choses (comme fread/fget, fwrite) qui satisferaient également les besoins.
En fait, IOStreams n'a pas besoin d'être lent! Il s’agit toutefois de les mettre en œuvre de manière raisonnable. La plupart des bibliothèques C++ standard ne semblent pas accorder trop d’attention à la mise en oeuvre d’IOStreams. Il y a longtemps, quand mon CXXRT était toujours maintenu, il était aussi rapide que stdio - utilisé correctement!
Notez qu'il existe peu de pièges de performance pour les utilisateurs configurés avec IOStreams. Les instructions suivantes s'appliquent à toutes les implémentations d'IOStream, mais plus particulièrement à celles conçues pour être rapides:
std::cin
, std::cout
, etc., vous devez appeler std::sync_with_stdio(false)
! Sans cet appel, toute utilisation des objets de flux standard est nécessaire pour la synchronisation avec les flux standard du C. Bien sûr, lorsque vous utilisez std::sync_with_stdio(false)
, on suppose que vous ne mélangez pas std::cin
avec stdin
, std::cout
avec stdout
, etc.std::endl
car il impose de nombreux vidages inutiles de tout tampon. De même, ne définissez pas std::ios_base::unitbuf
et n'utilisez pas std::flush
inutilement.virtual
qui le rend terriblement lent.Cela peut peut-être donner une idée de ce à quoi vous faites face:
#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 celui-ci (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 constater dans les résultats, il n’est pas vraiment question de lenteur catégorique dans iostreams. Cela dépend en grande partie de la manière dont vous utilisez iostreams (et dans une moindre mesure FILE *
également). Il y a aussi une assez grande variation entre ces implémentations.
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
et ou istreambuf_iterator
.
En résumé: obtenir de bonnes performances de la part d’iostreams nécessite un peu plus de soin qu’avec FILE *
- mais c’est certainement possible. Ils vous offrent également plus d'options: confort lorsque vous ne vous souciez pas trop de vitesse et performances directement en concurrence avec le meilleur des E/S de style C, avec un peu de travail supplémentaire.
Bien que cette question soit assez ancienne, je suis étonné que personne n'ait mentionné la construction d'objet iostream.
Autrement dit, chaque fois que vous créez une STL iostream
(et d’autres variantes de flux), si vous entrez dans le code, le constructeur appelle une fonction interne Init
. operator new
est appelé pour créer un nouvel objet locale
. De même, il est détruit lors de la destruction.
C'est hideux, à mon humble avis. Et contribue certainement à la lenteur de la construction/destruction des objets, car la mémoire est allouée/désallouée à l'aide d'un verrou système, à un moment donné.
De plus, certains flux STL vous permettent de spécifier une allocator
, alors pourquoi la locale
est-elle créée PAS 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 de flux est construit.
Hideux désordre si vous me le demandez, comme je le découvre moi-même en ce moment!
Sur un sujet similaire, STL indique: "Vous pouvez appeler setvbuf () pour activer la mise en mémoire tampon sur stdout."