Pourquoi voulez-vous éviter de vider stdout?

Je suis tombé sur une question dans Codereview , et dans une réponse, les commentaires étaient à évitez std::endl car cela vide le flux. La citation complète est:

Je vous conseille déviter std::endl en général. En plus décrire un nouveau- ligne au flux, il vide le flux. Vous voulez la nouvelle ligne, mais vous ne voulez presque jamais vider le flux, il est donc généralement préférable de simplement écrire un \ n. Dans les rares cas où vous voulez réellement le flush, faites-le explicitement: std::cout << "\n" << std::flush;.

Laffiche la fait pas expliquer cela, ni dans le post ni dans les commentaires. Ma question est donc simplement la suivante:

Pourquoi voulez-vous éviter le rinçage?

Ce qui ma rendu encore plus curieux, cest que laffiche dit quil est très rare que vous vouliez rincer. Je nai aucun problème à imaginer des situations où vous voulez éviter de rougir, mais je pensais quand même que vous voudriez en général rincer lorsque vous imprimez une nouvelle ligne. Après tout, n « est-ce pas la raison pour laquelle std::endl vidange en premier lieu?

Juste pour commenter les votes de clôture à lavance:

Je ne considère pas cette opinion fondée. Ce que vous devriez préférer peut être basé sur une opinion, mais il y a des raisons objectives à prendre en compte. Les réponses à ce jour le prouvent. Le rinçage affecte les performances.

Commentaires

Réponse

La réponse courte et simple est-ce que lutilisation de std::endl peut et ralentira la sortie dune énorme marge. En fait, je « suis raisonnablement convaincu que std::endl est responsable de la plupart de la notion selon laquelle les flux iostream C ++ sont sensiblement plus lents que les E / S de style C.

Par exemple, considérons un programme comme celui-ci:

#include <iostream> #include <string> #include <sstream> #include <time.h> #include <iomanip> #include <algorithm> #include <iterator> #include <stdio.h> char fmt[] = "%s\n"; static const int count = 3000000; static char const *const string = "This is a string."; static std::string s = std::string(string) + "\n"; void show_time(void (*f)(), char const *caption) { clock_t start = clock(); f(); clock_t ticks = clock()-start; std::cerr << std::setw(30) << caption << ": " << (double)ticks/CLOCKS_PER_SEC << "\n"; } void use_printf() { for (int i=0; i<count; i++) printf(fmt, string); } void use_puts() { for (int i=0; i<count; i++) puts(string); } void use_cout() { for (int i=0; i<count; i++) std::cout << string << "\n"; } void use_cout_unsync() { std::cout.sync_with_stdio(false); for (int i=0; i<count; i++) std::cout << string << "\n"; std::cout.sync_with_stdio(true); } void use_stringstream() { std::stringstream temp; for (int i=0; i<count; i++) temp << string << "\n"; std::cout << temp.str(); } void use_endl() { for (int i=0; i<count; i++) std::cout << string << std::endl; } void use_fill_n() { std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string); } void use_write() { for (int i = 0; i < count; i++) std::cout.write(s.data(), s.size()); } int main() { show_time(use_printf, "Time using printf"); show_time(use_puts, "Time using puts"); show_time(use_cout, "Time using cout (synced)"); show_time(use_cout_unsync, "Time using cout (un-synced)"); show_time(use_stringstream, "Time using stringstream"); show_time(use_endl, "Time using endl"); show_time(use_fill_n, "Time using fill_n"); show_time(use_write, "Time using write"); return 0; } 

Avec la sortie standard redirigée vers un fichier, cela produit les résultats suivants:

 Time using printf: 0.208539 Time using puts: 0.103065 Time using cout (synced): 0.241377 Time using cout (un-synced): 0.181853 Time using stringstream: 0.223617 Time using endl: 4.32881 Time using fill_n: 0.209951 Time using write: 0.102781 

Lutilisation de std::endl a ralenti le programme dun facteur denviron 20 dans ce cas. Si vous avez écrit des chaînes plus courtes, le le ralentissement pourrait / serait encore plus important.

Il y a quelques cas où vous voulez vraiment et vraiment vider un flux manuellement – mais honnêtement, ils sont assez rares.

La plupart du temps, un flux doit être vidé (par exemple, vous imprimez une invite, puis attendez une entrée), cela se produira automatiquement à moins que vous nayez utilisé des choses comme std::tie et / ou std::sync_with_stdio pour éviter cela.

Cela ne laisse quun petit nombre de situations vraiment inhabituelles où vous avez bonne raison de vider un flux manuellement. De tels cas sont suffisamment rares pour quil « vaille la peine dutiliser std::flush quand ils se produisent, pour faire comprendre à quiconque lit le code que vous » rincez le flux intentionnellement (et plus souvent que non, mérite probablement également un commentaire sur pourquoi cest lun des rares cas où le vidage du flux a vraiment du sens).

Réponse

Chaque fois quun processus produit une sortie, il doit appeler une fonction qui fait réellement le travail. Dans la plupart des cas, cette fonction est finalement write(2). Sur un système dexploitation multitâche, lappel à write() piégera dans le noyau, qui doit arrêter le processus, gérer les E / S, faire dautres choses pendant que les blocages sont effacés, mettre le mettre dans la file dattente prête et le remettre en marche le moment venu. Collectivement, vous pouvez appeler lensemble de cette activité surcharge dappel système . Si cela semble beaucoup, cest le cas.

Vider un flux mis en mémoire tampon * après avoir écrit une petite quantité de données ou ne pas avoir de tampon du tout entraîne cette surcharge à chaque fois que vous le faites:

 1\n (System call that writes two bytes) 2\n (System call that writes two bytes) 3\n (System call that writes two bytes) 4\n (System call that writes two bytes) 5\n (System call that writes two bytes)  

Cest ainsi que cela se faisait au tout début jusquà ce que quelquun se rende compte quil brûlait un beaucoup de temps système. La surcharge pourrait être réduite en accumulant la sortie dans une mémoire tampon jusquà ce quelle soit pleine ou que le programme décide quelle doit être envoyée immédiatement.(Vous voudrez peut-être faire ce dernier si vous « produisez sporadiquement une sortie qui doit être vue ou consommée.) Éviter un vidage à la fin de chaque ligne réduit le nombre dappels système et la surcharge encourue:

 1\n 2\n 3\n 4\n 5\n (Flush) (System call that writes ten bytes)  

* Notez que le concept de sortie standard est un descripteur de fichier associé à un processus et étant donné un numéro bien connu. Cela diffère du stdout défini par C, C ++ et autres, qui sont des identifiants pour les implémentations dun flux mis en mémoire tampon qui vivent entièrement dans lespace utilisateur et écrivent dans le sortie standard. Lappel système write() nest pas mis en mémoire tampon.

Commentaires

  • Si et quand vider dépend du contexte dans lequel cette sortie sera utilisée. Un programme orienté débit ne doit vider que lorsque ses tampons sont pleins. Un programme sensible à la latence doit vider plus souvent. Par exemple, si la sortie va à une console, vous ll rincer après chaque nouvelle ligne. Les programmes interactifs (qui affichent une invite dentrée) doivent être vidés immédiatement, même si la ligne nest pas encore pleine.
  •  » si la sortie va vers une console, vous serait toujours vider après chaque nouvelle ligne.  » Vrai, mais si la sortie va à une console, il y a un vidage automatique après chaque nouvelle ligne. Inutile de le faire explicitement.
  • @amon Vous voudrez quand même fusionner une sortie consécutive, car le vidage en temps opportun, cest-à-dire avant de demander une entrée et sans délai de retard, est largement suffisant. Certes, il vaut mieux vider une fois trop souvent que davoir trop de retard dans laffichage de la sortie, ou dinvestir trop de travail dans loptimisation …
  • Une astuce parfois utilisée par les tampons est de re / démarrer un minuterie lorsque de nouvelles choses arrivent, et vider lorsque la minuterie est épuisée.

Réponse

Pourquoi le vidage doit être évité:

Parce que IO fonctionne mieux lorsque le système dexploitation peut fonctionner avec des quantités de données relativement importantes. Les vidages réguliers avec de petites quantités de données provoquent des ralentissements, parfois très significatifs.

Pourquoi vous ne devriez presque jamais vider manuellement:

Il existe des vidages automatiques qui couvrent la plupart des cas dutilisation. Par exemple, si un programme écrit sur la console, le système vide par défaut après chaque nouvelle ligne. Ou si vous écrivez dans un fichier, les données sont écrites une fois quil y a suffisamment de données pour être écrites à la fois, et aussi lorsque le fichier est fermé.

Quand vous devez vider manuellement:

Si vous avez explicitement besoin de mettre à jour la sortie immédiatement. Exemple: si vous créez une double flèche ou une barre de progression qui écrase à plusieurs reprises la ligne courante. Ou si vous sortez dans un fichier et que vous voulez vraiment que le fichier soit mis à jour à des moments spécifiques.

Commentaires

  • Le système ne peut vider aucun tampon utilisateur. Ou subsumez-vous les bibliothèques, en particulier la bibliothèque standard, également sous  » system « ? Bien sûr, stdin et stdout sont généralement couplés au moins sils utilisent tous les deux la même console.
  • Oui, Je nai ‘ pas pensé que ce serait une bonne idée dentrer trop dans les détails sur la façon dont les choses sont réparties entre les bibliothèques de lespace utilisateur et lespace noyau.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *