Dans quelques commentaires, je me suis fait l'écho des réponses des gens qui ont dit que le problème venait probablement de la copie supplémentaire effectuée par votre version C++, qui copie les lignes en mémoire dans une chaîne de caractères. Mais je voulais tester cela.
J'ai d'abord implémenté les versions fgetc et getline et je les ai chronométrées. J'ai confirmé qu'en mode débogage, la version getline est plus lente, environ 130 µs contre 60 µs pour la version fgetc. Ceci n'est pas surprenant étant donné la sagesse conventionnelle selon laquelle les iostreams sont plus lents que l'utilisation de stdio. Cependant, dans le passé, j'ai constaté que les iostreams gagnaient en vitesse grâce à l'optimisation. Cela a été confirmé lorsque j'ai comparé mes temps en mode release : environ 20 µs en utilisant getline et 48 µs avec fgetc.
Le fait que l'utilisation de getline avec iostreams soit plus rapide que fgetc, au moins en mode release, va à l'encontre du raisonnement selon lequel copier toutes ces données doit être plus lent que de ne pas les copier, donc je ne suis pas sûr de ce que l'optimisation permet d'éviter, et je n'ai pas vraiment cherché à trouver d'explication, mais il serait intéressant de comprendre ce qui est optimisé. edit : lorsque j'ai regardé les programmes avec un profileur, il n'était pas évident de comparer les performances puisque les différentes méthodes semblaient si différentes les unes des autres.
Quoi qu'il en soit, j'ai voulu voir si je pouvais obtenir une version plus rapide en évitant la copie à l'aide de la fonction get()
sur l'objet fstream et faire exactement ce que fait la version C. Lorsque j'ai fait cela, j'ai été assez surpris de constater que l'utilisation de fstream::get()
était beaucoup plus lente que les méthodes fgetc et getline, aussi bien en version debug qu'en version release ; environ 230 µs en version debug, et 80 µs en version release.
Afin de déterminer la cause du ralentissement, j'ai réalisé une autre version, cette fois en utilisant le stream_buf attaché à l'objet fstream, et j'ai obtenu les résultats suivants snextc()
sur ce point. Cette version est de loin la plus rapide : 25 µs en debug et 6 µs en release.
Je suppose que la chose qui rend le fstream::get()
est qu'elle construit un objet sentinelle pour chaque appel. Bien que je ne l'aie pas testé, je ne vois pas en quoi la méthode get()
ne fait rien d'autre que de récupérer le caractère suivant dans le stream_buf, à l'exception de ces objets sentinelles.
Quoi qu'il en soit, la morale de l'histoire est que si vous voulez un io rapide, il est préférable d'utiliser des fonctions iostream de haut niveau plutôt que stdio, et pour un io vraiment rapide, d'accéder au stream_buf sous-jacent. edit : en fait, cette morale ne s'applique qu'à MSVC, voir la mise à jour en bas de page pour les résultats obtenus avec une chaîne d'outils différente.
Pour référence :
J'ai utilisé VS2010 et le chrono de boost 1.47 pour le timing. J'ai construit des binaires 32 bits (apparemment requis par boost chrono parce qu'il ne semble pas trouver de version 64 bits de cette librairie). Je n'ai pas modifié les options de compilation mais il se peut qu'elles ne soient pas tout à fait standard puisque j'ai fait cela dans un projet scratch vs que je garde à portée de main.
Le fichier que j'ai testé est la version texte brut de 1,1 Mo et 20 000 lignes des Oeuvres Complètes de Frédéric Bastiat, tome 1 de Frédéric Bastiat du Projet Gutenberg, http://www.gutenberg.org/ebooks/35390
Durée du mode de déclenchement
fgetc time is: 48150 microseconds
snextc time is: 6019 microseconds
get time is: 79600 microseconds
getline time is: 19881 microseconds
Durée du mode débogage :
fgetc time is: 59593 microseconds
snextc time is: 24915 microseconds
get time is: 228643 microseconds
getline time is: 130807 microseconds
Voici mon fgetc()
version :
{
auto begin = boost::chrono::high_resolution_clock::now();
FILE *cin = fopen("D:/bames/automata/pg35390.txt","rb");
assert(cin);
unsigned maxLength = 0;
unsigned i = 0;
int ch;
while(1) {
ch = fgetc(cin);
if(ch == 0x0A || ch == EOF) {
maxLength = std::max(i,maxLength);
i = 0;
if(ch==EOF)
break;
} else {
++i;
}
}
fclose(cin);
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << '\n';
std::cout << "fgetc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
Voici mon getline()
version :
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
unsigned maxLength = 0;
std::string line;
while(std::getline(fin,line)) {
maxLength = std::max(line.size(),maxLength);
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << '\n';
std::cout << "getline time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
les fstream::get()
version
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
unsigned maxLength = 0;
unsigned i = 0;
while(1) {
int ch = fin.get();
if(fin.good() && ch == 0x0A || fin.eof()) {
maxLength = std::max(i,maxLength);
i = 0;
if(fin.eof())
break;
} else {
++i;
}
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << '\n';
std::cout << "get time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
et le snextc()
version
{
auto begin = boost::chrono::high_resolution_clock::now();
std::ifstream fin("D:/bames/automata/pg35390.txt",std::ios::binary);
std::filebuf &buf = *fin.rdbuf();
unsigned maxLength = 0;
unsigned i = 0;
while(1) {
int ch = buf.snextc();
if(ch == 0x0A || ch == std::char_traits<char>::eof()) {
maxLength = std::max(i,maxLength);
i = 0;
if(ch == std::char_traits<char>::eof())
break;
} else {
++i;
}
}
auto end = boost::chrono::high_resolution_clock::now();
std::cout << "max line is: " << maxLength << '\n';
std::cout << "snextc time is: " << boost::chrono::duration_cast<boost::chrono::microseconds>(end-begin) << '\n';
}
mettre à jour :
J'ai refait les tests en utilisant clang (trunk) sur OS X avec libc++. Les résultats pour les implémentations basées sur iostream sont restés relativement les mêmes (avec l'optimisation activée) ; fstream::get()
beaucoup plus lent que std::getline()
beaucoup plus lent que filebuf::snextc()
. Mais la performance des fgetc()
s'est améliorée par rapport à la getline()
et est devenu plus rapide. Cela s'explique peut-être par le fait que la copie effectuée par getline()
devient un problème avec cette chaîne d'outils alors qu'il ne l'était pas avec MSVC ? Peut-être que l'implémentation CRT de Microsoft de fgetc() est mauvaise ou quelque chose comme ça ?
Quoi qu'il en soit, voici les temps (j'ai utilisé un fichier beaucoup plus grand, 5,3 MB) :
en utilisant -Os
fgetc time is: 39004 microseconds
snextc time is: 19374 microseconds
get time is: 145233 microseconds
getline time is: 67316 microseconds
en utilisant -O0
fgetc time is: 44061 microseconds
snextc time is: 92894 microseconds
get time is: 184967 microseconds
getline time is: 209529 microseconds
-O2
fgetc time is: 39356 microseconds
snextc time is: 21324 microseconds
get time is: 149048 microseconds
getline time is: 63983 microseconds
-O3
fgetc time is: 37527 microseconds
snextc time is: 22863 microseconds
get time is: 145176 microseconds
getline time is: 67899 microseconds