Chaque fois que je mentionne ralentir les performances de C++ standard library iostreams, je reçois rencontré une vague d'incrédulité. Pourtant, j'ai profiler les résultats montrent de grandes quantités de temps passé dans la bibliothèque iostream code (optimisations du compilateur complet), et de passer d'iostreams OS-I/O Api et personnalisé à la gestion des tampons donne un ordre de grandeur de l'amélioration.
Ce travail supplémentaire est la norme C++ bibliothèque à faire, est-il requis par la norme, et est-il utile dans la pratique? Ou faire un peu de compilateurs fournir des implémentations de iostreams qui entrent en concurrence avec le manuel de gestion de tampon?
Repères
Pour obtenir les questions de déplacement, j'ai écrit un couple de programmes courts à l'exercice de la iostreams interne de mise en mémoire tampon:
- mettre des données binaires dans un
ostringstream
http://ideone.com/2PPYw - mettre des données binaires en
char[]
tampon http://ideone.com/Ni5ct - mettre des données binaires en
vector<char>
l'aideback_inserter
http://ideone.com/Mj2Fi -
NOUVEAU:
vector<char>
itérateur simple http://ideone.com/9iitv -
NOUVEAU: mettre les données binaires directement dans
stringbuf
http://ideone.com/qc9QA -
NOUVEAU:
vector<char>
simple itérateur plus de vérification de limites http://ideone.com/YyrKy
Notez que l' ostringstream
et stringbuf
versions exécuter en moins d'itérations, car ils sont beaucoup plus lent.
Sur ideone, l' ostringstream
est environ 3 fois plus lent que l' std:copy
+ back_inserter
+ std::vector
, et 15 fois plus lent que l' memcpy
en raw d'un tampon. C'est cohérent avec avant-et-après profilage quand j'ai changé mon véritable application personnalisée pour la mise en mémoire tampon.
Ces sont tous en mémoire tampon, de sorte que la lenteur des iostreams ne peut pas être blâmé sur les e/S disque lente, trop de rinçage, la synchronisation avec stdio, ou toutes les autres choses que les gens utilisent pour excuse observé la lenteur de la norme C++ de la bibliothèque iostream.
Il serait agréable de voir des tests sur d'autres systèmes et des commentaires sur des choses communes des implémentations ne (comme gcc de la libc++, Visual C++, Intel C++) et comment une grande partie de la charge est mandaté par le standard.
Justification pour ce test
Un certain nombre de personnes ont fait remarquer à juste titre que iostreams sont plus couramment utilisés pour une sortie formatée. Cependant, ils sont également les seuls moderne API fournie par la norme C++ pour l'accès aux fichiers binaires. Mais la vraie raison pour faire des tests de performance sur les tampons internes s'applique à la typique formaté I/O: si iostreams ne pouvez pas garder le contrôleur de disque fourni avec les données brutes, comment peuvent-ils rester en place lorsqu'ils sont responsables de la mise en forme ainsi?
Référence Timing
Tous ces éléments sont par itération de l'extérieur (k
) en boucle.
Sur ideone (gcc-4.3.4 inconnu de l'OS et du matériel):
-
ostringstream
: 53 millisecondes -
stringbuf
: 27 ms -
vector<char>
etback_inserter
: 17.6 ms -
vector<char>
ordinaire itérateur: 10.6 ms -
vector<char>
itérateur et de vérification de limites: 11.4 ms -
char[]
: 3.7 ms
Sur mon portable (Visual C++ 2010 x86, cl /Ox /EHsc
, Windows 7 Ultime 64 bits, Intel Core i7, 8 GO de RAM):
-
ostringstream
: 73.4 millisecondes, 71.6 ms -
stringbuf
: 21.7 ms, 21.3 ms -
vector<char>
etback_inserter
: 34.6 ms, de 34,4 ms -
vector<char>
ordinaire itérateur: 1.10 ms, 1.04 ms -
vector<char>
itérateur et de vérification de limites: 1.11 ms, 0.87 ms, 1.12 ms, de 0,89 ms, 1.02 ms, 1.14 ms -
char[]
: 1.48 ms, ms 1.57
Visual C++ 2010 x86, avec Profil de l'Optimisation orientée cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, exécuter, link /ltcg:pgo
, mesure:
-
ostringstream
: 61.2 ms, ms 60.5 -
vector<char>
ordinaire itérateur: 1.04 ms, ms 1.03
Même l'ordinateur portable, le même système d'exploitation, l'utilisation de cygwin gcc 4.3.4 g++ -O3
:
-
ostringstream
: 62.7 ms, ms 60.5 -
stringbuf
: 44.4 ms, ms 44.5 -
vector<char>
etback_inserter
: 13.5 ms, 13.6 ms -
vector<char>
ordinaire itérateur: 4.1 ms, de 3,9 ms -
vector<char>
itérateur et de vérification de limites: 4.0 ms, 4.0 ms -
char[]
: 3.57 ms, 3.75 ms
Même ordinateur portable, Visual C++ 2008 SP1, cl /Ox /EHsc
:
-
ostringstream
: 88.7 ms, ms 87.6 -
stringbuf
: 23.3 ms, de 23,4 ms -
vector<char>
etback_inserter
: 26.1 ms, ms 24.5 -
vector<char>
ordinaire itérateur: 3.13 ms, 2.48 ms -
vector<char>
itérateur et de vérification de limites: 2.97 ms, 2.53 ms -
char[]
: 1.52 ms, 1,25 ms
Même ordinateur portable, Visual C++ 2010 compilateur 64 bits:
-
ostringstream
: 48.6 ms, 45.0 ms -
stringbuf
: 16.2 ms, 16.0 ms -
vector<char>
etback_inserter
: 26.3 ms, ms 26.5 -
vector<char>
ordinaire itérateur: 0.87 ms, de 0,89 ms -
vector<char>
itérateur et de vérification de limites: 0.99 ms, ms 0.99 -
char[]
: 1,25 ms, 1.24 ms
EDIT: toutes Couru deux fois pour voir le degré de cohérence ont été les résultats. Assez cohérente de l'OMI.
NOTE: Sur mon portable, depuis que je peux consacrer davantage de temps CPU que ideone permet, j'ai mis le nombre d'itérations pour 1000 pour l'ensemble des méthodes. Cela signifie qu' ostringstream
et vector
réaffectation, qui n'a lieu que lors de la première passe, devrait avoir peu d'impact sur le résultat final.
EDIT: Oups, a trouvé un bug dans l' vector
-avec-ordinaire-itérateur, l'itérateur n'a pas été avancé et donc il y avait trop de cache. Je me demandais comment vector<char>
a été surclassé char[]
. Il n'a pas beaucoup de différence si, vector<char>
est encore plus rapide qu' char[]
sous VC++ 2010.
Conclusions
Mise en mémoire tampon du flux de sortie nécessite trois étapes chaque fois que des données sont présentés en annexe:
- Vérifiez que le nouveau bloc s'adapte à la disposition de l'espace tampon.
- Copie entrant bloc.
- Mise à jour de la fin-de-pointeur de données.
Le dernier extrait de code que j'ai posté, "vector<char>
simple itérateur plus de limites cochez la case" non seulement cela, il alloue également de plus d'espace et déplace les données existantes lorsque les entrants bloc ne rentre pas. Comme Clifford souligné, la mise en mémoire tampon dans un fichier I/O de la classe à ne pas avoir à le faire, il suffit de vider le buffer courant et de le réutiliser. Donc, cela devrait être une limite supérieure sur le coût de mise en mémoire tampon de sortie. Et c'est exactement ce qui est nécessaire pour faire un travail en mémoire tampon.
Alors, pourquoi est - stringbuf
2,5 x plus lent sur ideone, et au moins 10 fois plus lent quand je l'ai tester? Il n'est pas utilisé polymorphically dans ce simple micro-benchmark, afin que ne l'explique pas.