J'ai voulu comparer la lecture des lignes de la chaîne d'entrée à partir de stdin à l'aide de Python et C++ et a été choqué de voir mon code C++ exécuter un ordre de grandeur inférieure à celle de l'équivalent du code Python. Depuis mon C++ est rouillé et je ne suis pas encore un expert Pythonista, s'il vous plaît dites-moi si je fais quelque chose de mal ou si je suis un malentendu quelque chose.
(tl;dr réponse: inclure l'énoncé: cin.sync_with_stdio(false), ou simplement utiliser fgets à la place.
tl;dr résultats: faites défiler tout le chemin vers le bas de ma question et de regarder le tableau.)
Le code C++:
#include <iostream>
#include <time.h>
using namespace std;
int main() {
string input_line;
long line_count = 0;
time_t start = time(NULL);
int sec;
int lps;
while (cin) {
getline(cin, input_line);
if (!cin.eof())
line_count++;
};
sec = (int) time(NULL) - start;
cerr << "Read " << line_count << " lines in " << sec << " seconds." ;
if (sec > 0) {
lps = line_count / sec;
cerr << " LPS: " << lps << endl;
} else
cerr << endl;
return 0;
}
//Compiled with:
//g++ -O3 -o readline_test_cpp foo.cpp
Python Équivalent:
#!/usr/bin/env python
import time
import sys
count = 0
start = time.time()
for line in sys.stdin:
count += 1
delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
lines_per_sec = int(round(count/delta_sec))
print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
lines_per_sec))
Voici mes résultats:
$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889
$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000
Merci à l'avance!
Edit: je tiens à noter que j'ai essayé cette fois sous OS-X (10.6.8) et Linux 2.6.32 (RHEL 6.2). Le premier est un macbook pro, ce dernier est un très costaud serveur, pas qu'il est trop pertinent.
Edit 2: (Supprimé cette édition, qui n'est plus applicable)
$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP: Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP: Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP: Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP: Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP: Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Edit 3:
Bon, j'ai essayé de J. N. la suggestion d'essayer d'avoir python magasin de la ligne de lire: mais il ne fait aucune différence python vitesse.
J'ai aussi essayé J. N. la suggestion de l'utilisation de scanf dans un char tableau au lieu de getline dans un std::string. Bingo! Cela a abouti à des performances équivalentes pour les deux python et c++. (3,333,333 LPS avec mes données d'entrée, qui sont tout juste en deçà des lignes de trois champs de chaque, habituellement d'environ 20 caractères de large, et parfois plus).
Code:
char input_a[512];
char input_b[32];
char input_c[512];
while(scanf("%s %s %s\n", input_a, input_b, input_c) != EOF) {
line_count++;
};
Vitesse:
$ cat test_lines | ./readline_test_cpp2
Read 10000000 lines in 3 seconds. LPS: 3333333
$ cat test_lines | ./readline_test2.py
Read 10000000 lines in 3 seconds. LPS: 3333333
(Oui, j'ai couru plusieurs fois.) Donc, je pense que je vais maintenant utiliser scanf au lieu de getline. Mais, je suis toujours curieux de savoir si les gens pensent que ce gain de performance de std::string/getline est typique et raisonnable.
Edit 4 (a: Final Edition / Solution):
En ajoutant: cin.sync_with_stdio(false);
Immédiatement au-dessus de ma boucle while résultats ci-dessus dans le code qui s'exécute plus rapidement que Python.
Nouvelle comparaison des performances (c'est sur mon Macbook Pro 2011), avec le code d'origine, l'original avec la synchronisation désactivée, et l'original python, respectivement, sur un fichier avec 20M de lignes de texte. Oui, j'ai couru plusieurs fois pour éliminer le cache disque confondre.
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp
33.30 real 0.04 user 0.74 sys
Read 20000001 lines in 33 seconds. LPS: 606060
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp1b
3.79 real 0.01 user 0.50 sys
Read 20000000 lines in 4 seconds. LPS: 5000000
$ /usr/bin/time cat test_lines_double | ./readline_test.py
6.88 real 0.01 user 0.38 sys
Read 20000000 lines in 6 seconds. LPS: 3333333
Grâce à @Vaughn Cato pour sa réponse! Toute élaboration de gens peuvent faire ou de bonnes références les gens peuvent point à la raison pour laquelle cette synchronisation se passe, ce que cela signifie, lorsque c'est utile, et quand c'est ok pour désactiver serait grandement apprécié par la postérité. :-)
Edit 5 / Une Meilleure Solution:
Comme suggéré par Gandalf Le Gris ci-dessous, est même plus rapidement que scanf ou la désynchronisation du cin approche. J'ai aussi appris que le scanf et obtient sont à la fois DANGEREUX et ne doit PAS ÊTRE UTILISÉ en raison du risque de dépassement de la mémoire tampon. Alors, j'ai écrit cette itération en utilisant fgets, l'alternative plus sûre aux gets. Voici les lignes de mes collègues noobs:
char input_line[MAX_LINE];
char *result;
//<snip>
while((result = fgets(input_line, MAX_LINE, stdin )) != NULL)
line_count++;
if (ferror(stdin))
perror("Error reading stdin.");
Maintenant, voici les résultats à l'aide d'un même fichier de plus grande taille (100 M lignes; ~3,4 GO) sur un serveur rapide à très rapide du disque, en comparant le python, le unsynced cin, et le fgets approches, ainsi que la comparaison avec les wc de l'utilitaire. [Le scanf version segfaulted et je n'ai pas envie de dépannage.]:
$ /usr/bin/time cat temp_big_file | readline_test.py
0.03user 2.04system 0:28.06elapsed 7%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 28 seconds. LPS: 3571428
$ /usr/bin/time cat temp_big_file | readline_test_unsync_cin
0.03user 1.64system 0:08.10elapsed 20%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 8 seconds. LPS: 12500000
$ /usr/bin/time cat temp_big_file | readline_test_fgets
0.00user 0.93system 0:07.01elapsed 13%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 7 seconds. LPS: 14285714
$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
100000000
Recap (lines per second):
python: 3,571,428
cin (no sync): 12,500,000
fgets: 14,285,714
wc: 54,644,808
Comme vous pouvez le voir, fgets est mieux, mais encore assez loin de wc performance; je suis sûr que cela est dû au fait que les wc examine chaque personnage, sans aucun souvenir de la copie. Je soupçonne que, à ce stade, d'autres parties du code va devenir le goulot d'étranglement, donc je ne pense pas que l'optimisation à ce niveau même de la peine, même si c'est possible (car, après tout, j'ai réellement besoin de stocker la lecture des lignes de la mémoire).
Notez également qu'un petit compromis avec l'aide d'un char * buffer et fgets vs unsynced cin de chaîne est que cette dernière peut lire les lignes de n'importe quelle longueur, alors que les anciennes impose de limiter l'entrée à certains nombre fini. Dans la pratique, ce n'est probablement pas un problème pour la lecture de la plupart de la ligne de base de fichiers d'entrée, le tampon peut être réglé à une valeur très élevée qui ne pourrait être dépassé par d'entrée valide.
Cela a été instructif. Merci à tous pour vos commentaires et vos suggestions.
Edit 6:
Comme l'a suggéré J. F. Sebastian dans les commentaires ci-dessous, la GNU wc utilitaire utilise la plaine C read() (dans le coffre-lire.c wrapper) pour la lecture de morceaux (de 16 k octets) à un moment, et le comte de nouvelles lignes. Voici un python équivalent basé sur J. F. code (juste montrer pertinentes de l'extrait de code qui remplace le python la boucle for:
BUFFER_SIZE = 16384
count = sum(chunk.count('\n') for chunk in iter(partial(sys.stdin.read, BUFFER_SIZE), ''))
Les performances de cette version est assez rapide (bien que toujours un peu plus lent que le c brutes wc utilitaire, bien sûr:
$ /usr/bin/time cat temp_big_file | readline_test3.py
0.01user 1.16system 0:04.74elapsed 24%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 4.7275 seconds. LPS: 21152829
Encore une fois, c'est un peu idiot de me comparer C++ fgets/cin et le premier code python d'une part, de wc-l et ce dernier extrait python sur l'autre, comme les deux derniers ne stockent pas réellement la lecture des lignes, mais simplement de compter les retours à la ligne. Pourtant, il est intéressant d'explorer toutes les différentes implémentations et de réfléchir à l'incidence sur les performances. Merci encore!
Edit 7: un petit indice additif et le reboucher
(Bonjour HN lecteurs!)
Pour être complet, je pensais que je mettrais à jour la vitesse de lecture pour le même fichier sur la même case avec l'original (synchronisés) du code C++. Encore une fois, c'est pour un 100M de la ligne de fichier sur un disque rapide. Voici le tableau complet maintenant:
Implementation Lines per second
python (default) 3,571,428
cin (default/naive) 819,672
cin (no sync) 12,500,000
fgets 14,285,714
wc (not fair comparison) 54,644,808
Aussi, voir mon suivi de question sur le fractionnement des lignes en C++ vs Python... une vitesse similaire histoire, où l'approche naïve est plus lente en C++!
Edit: pour plus de clarté, enlevé tout petit bug dans le code d'origine qui n'était pas liée à la question. Enfin, de petits réglages à l'espace blanc et de sortie des chaînes de rendre la comparaison plus facile/plus clair.