La plupart des utilisateurs de C ++ qui ont appris C préfèrent utiliser la famille de fonctions printf
/ scanf
même lorsqu’ils codent en C ++.
Bien que j’admette que je trouve l’interface beaucoup mieux (en particulier le format et la localisation de type POSIX), il semble qu’une préoccupation majeure est la performance.
En regardant cette question:
Comment puis-je accélérer la lecture ligne par ligne d’un fichier
Il semble que la meilleure réponse soit d’utiliser fscanf
et que le ifstream
C ++ soit constamment 2 ou 3 fois plus lent.
Je pensais que ce serait génial si nous pouvions comstackr un référentiel de “conseils” pour améliorer les performances de IOStreams, ce qui fonctionne, ce qui ne fonctionne pas.
Points à considérer
rdbuf()->pubsetbuf(buffer, size)
) std::ios_base::sync_with_stdio
) Bien sûr, d’autres approches sont les bienvenues.
Note: une “nouvelle” implémentation, de Dietmar Kuhl, a été mentionnée, mais je n’ai pas pu trouver beaucoup de détails à ce sujet. Les références précédentes semblent être des liens morts.
Voici ce que j’ai rassemblé jusqu’ici:
Mise en mémoire tampon :
Si par défaut le tampon est très petit, augmenter la taille du tampon peut certainement améliorer les performances:
Le tampon peut être défini en accédant à l’implémentation streambuf
sous-jacente.
char Buffer[N]; std::ifstream file("file.txt"); file.rdbuf()->pubsetbuf(Buffer, N); // the pointer reader by rdbuf is guaranteed // to be non-null after successful constructor
Avertissement de @iavr: selon cppreference, il est préférable d’appeler pubsetbuf
avant d’ouvrir le fichier. Diverses implémentations de bibliothèques standard ont des comportements différents.
Gestion locale:
Les parameters régionaux permettent d’effectuer des conversions de caractères, du filtrage et des astuces plus intelligentes lorsque des nombres ou des dates sont impliqués. Ils passent par un système complexe de répartition dynamic et d’appels virtuels, de sorte que leur suppression peut aider à réduire les pénalités.
Les parameters régionaux par défaut de C
sont pas conçus pour effectuer des conversions et pour être uniformes sur les ordinateurs. C’est un bon défaut à utiliser.
Synchronisation:
Je ne pouvais voir aucune amélioration de performance en utilisant cette installation.
On peut accéder à un paramètre global (membre statique de std::ios_base
) en utilisant la fonction statique sync_with_stdio
.
Des mesures:
En jouant avec cela, j’ai joué avec un programme simple, compilé en utilisant gcc 3.4.2
sur SUSE 10p3 avec -O2
.
C: 7.76532e + 06
C ++: 1.0874e + 07
Ce qui représente un ralentissement d’environ 20%
… pour le code par défaut. En effet, la falsification du tampon (en C ou C ++) ou des parameters de synchronisation (C ++) n’a apporté aucune amélioration.
Résultats par d’autres:
@Irfy sur g ++ 4.7.2-2ubuntu1, -O3, Ubuntu virtualisé 11.10, 3.5.0-25-generic, x86_64, suffisamment de ram / cpu, 196Mo de plusieurs exécutions “find / >> largefile.txt”
C: 634572 C ++: 473222
C ++ 25% plus rapide
@Matteo Italia sur g ++ 4.4.5, -O3, Ubuntu Linux 10.10 x86_64 avec un fichier aléatoire de 180 Mo
C: 910390
C ++: 776016
C ++ 17% plus rapide
@Bogatyr sur g ++ i686-apple-darwin10-g ++ – 4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664), mac mini, 4 Go de RAM, inactif sauf pour ce test avec un fichier de 168 Mo
C: 4.34151e + 06
C ++: 9.14476e + 06
C ++ 111% plus lent
@Asu sur clang ++ 3.8.0-2ubuntu4, Kubuntu 16.04 Linux 4.8-rc3, 8 Go de mémoire vive, i5 Haswell, SSD Crucial, fichier de données 88 Mo (archive tar.xz)
C: 270895 C ++: 162799
C ++ 66% plus rapide
La réponse est donc la suivante: il s’agit d’un problème de qualité d’implémentation et dépend vraiment de la plate-forme: /
Le code en entier ici pour ceux qui s’intéressent à l’parsing comparative:
#include #include #include #include #include #include template double benchmark(Func f, size_t iterations) { f(); timeval a, b; gettimeofday(&a, 0); for (; iterations --> 0;) { f(); } gettimeofday(&b, 0); return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) - (a.tv_sec * (unsigned int)1e6 + a.tv_usec); } struct CRead { CRead(char const* filename): _filename(filename) {} void operator()() { FILE* file = fopen(_filename, "r"); int count = 0; while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; } fclose(file); } char const* _filename; char _buffer[1024]; }; struct CppRead { CppRead(char const* filename): _filename(filename), _buffer() {} enum { BufferSize = 16184 }; void operator()() { std::ifstream file(_filename, std::ifstream::in); // comment to remove extended buffer file.rdbuf()->pubsetbuf(_buffer, BufferSize); int count = 0; std::ssortingng s; while ( file >> s ) { ++count; } } char const* _filename; char _buffer[BufferSize]; }; int main(int argc, char* argv[]) { size_t iterations = 1; if (argc > 1) { iterations = atoi(argv[1]); } char const* oldLocale = setlocale(LC_ALL,"C"); if (strcmp(oldLocale, "C") != 0) { std::cout << "Replaced old locale '" << oldLocale << "' by 'C'\n"; } char const* filename = "largefile.txt"; CRead cread(filename); CppRead cppread(filename); // comment to use the default setting bool oldSyncSetting = std::ios_base::sync_with_stdio(false); double ctime = benchmark(cread, iterations); double cpptime = benchmark(cppread, iterations); // comment if oldSyncSetting's declaration is commented std::ios_base::sync_with_stdio(oldSyncSetting); std::cout << "C : " << ctime << "\n" "C++: " << cpptime << "\n"; return 0; }
Deux autres améliorations:
std::cin.tie(nullptr);
avant entrée / sortie lourde. Citant http://fr.cppreference.com/w/cpp/io/cin :
Une fois que std :: cin est construit, std :: cin.tie () renvoie & std :: cout, et de même, std :: wcin.tie () renvoie & std :: wcout. Cela signifie que toute opération de saisie formatée sur std :: cin force un appel à std :: cout.flush () si des caractères sont en attente de sortie.
Vous pouvez éviter de vider le tampon en dé- std::cin
de std::cout
. Ceci est pertinent avec plusieurs appels mixtes vers std::cin
et std::cout
. Notez que l’appel std::cin.tie(std::nullptr);
rend le programme impropre à l’exécution interactive par l’utilisateur, car la sortie peut être retardée.
Référence pertinente:
Fichier test1.cpp
:
#include using namespace std; int main() { ios_base::sync_with_stdio(false); int i; while(cin >> i) cout << i << '\n'; }
Fichier test2.cpp
:
#include using namespace std; int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int i; while(cin >> i) cout << i << '\n'; cout.flush(); }
Les deux compilés par g++ -O2 -std=c++11
. Version du compilateur: g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
(oui, je sais, plutôt vieux).
Résultats de référence:
work@mg-K54C ~ $ time ./test1 < test.in > test1.in real 0m3.140s user 0m0.581s sys 0m2.560s work@mg-K54C ~ $ time ./test2 < test.in > test2.in real 0m0.234s user 0m0.234s sys 0m0.000s
( test.in
compose de 1179648 lignes composées chacune d'un seul 5
Il est de 2,4 Mo, désolé de ne pas l'avoir posté ici.)
Je me souviens d'avoir résolu une tâche algorithmique où le juge en ligne refusait sans cin.tie(nullptr)
mon programme sans cin.tie(nullptr)
mais l'acceptait avec cin.tie(nullptr)
ou printf
/ scanf
au lieu de cin
/ cout
.
'\n'
au lieu de std::endl
. Citant http://fr.cppreference.com/w/cpp/io/manip/endl :
Insère un caractère de nouvelle ligne dans la séquence de sortie os et le vide en appelant os.put (os.widen ('\ n')) suivi de os.flush ().
Vous pouvez éviter de vider le dossier en imprimant '\n'
au lieu de endl
.
Référence pertinente:
Fichier test1.cpp
:
#include using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << endl; }
Fichier test2.cpp
:
#include using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << '\n'; }
Les deux compilés comme ci-dessus.
Résultats de référence:
work@mg-K54C ~ $ time ./test1 > test1.in real 0m2.946s user 0m0.404s sys 0m2.543s work@mg-K54C ~ $ time ./test2 > test2.in real 0m0.156s user 0m0.135s sys 0m0.020s
Il est intéressant de noter que les programmeurs C préfèrent printf lors de l’écriture de C ++, car je vois beaucoup de code C, à part d’utiliser cout
et iostream
pour écrire la sortie.
Les applications peuvent souvent obtenir de meilleures performances en utilisant directement filebuf
(Scott Meyers l’a mentionné dans Effective STL), mais l’utilisation de filebuf direct est relativement peu documentée et la plupart des développeurs préfèrent std::getline
plus simple la plupart du temps.
En ce qui concerne les parameters régionaux, si vous créez des facettes, vous obtiendrez souvent de meilleures performances en créant une fois les parameters régionaux avec toutes vos facettes, en les conservant stockées et en les intégrant dans chaque stream que vous utilisez.
J’ai vu un autre sujet à ce sujet ici récemment, ce qui est presque un doublon.