Warum möchten Sie vermeiden, stdout zu spülen?

Ich bin auf eine Frage in Codereview gestoßen, und in einer Antwort lautete das Feedback: Vermeiden Sie std::endl, da dadurch der Stream geleert wird. Das vollständige Zitat lautet:

Ich würde raten, std::endl im Allgemeinen zu vermeiden. Zeile zum Stream, es löscht den Stream. Sie möchten die neue Zeile, möchten aber fast nie den Stream leeren, daher ist es im Allgemeinen besser, nur ein \ n zu schreiben. In den seltenen Fällen, in denen Sie die Spülung tatsächlich möchten, führen Sie dies explizit aus: std::cout << "\n" << std::flush;.

Das Poster hat dies getan Erkläre dies weder im Beitrag noch in den Kommentaren. Meine Frage lautet also einfach:

Warum möchten Sie ein Spülen vermeiden?

Was mich noch neugieriger machte, war, dass auf dem Poster steht, dass Sie sehr selten spülen möchten. Ich habe kein Problem damit, mir Situationen vorzustellen, in denen Sie das Spülen vermeiden möchten, aber ich dachte immer noch, dass Sie dies generell möchten Leeren Sie, wenn Sie eine neue Zeile drucken. Ist das nicht der Grund, warum std::endl überhaupt leert?

Nur um die engen Abstimmungen im Voraus zu kommentieren:

Ich halte diese Meinung nicht für begründet. Was Sie bevorzugen sollten, kann meinungsbasiert sein, aber es gibt objektive Gründe, dies zu berücksichtigen. Die bisherigen Antworten beweisen dies. Das Spülen wirkt sich auf die Leistung aus.

Kommentare

Antwort

Die kurze und einfache Antwort ist, dass die Verwendung von std::endl die Ausgabe mit großem Abstand verlangsamen kann und wird. Tatsächlich bin ich ziemlich davon überzeugt, dass std::endl für die meisten der Vorstellung verantwortlich ist, dass C ++ – Iostreams wesentlich langsamer sind als C / Style-E / A.

Stellen Sie sich beispielsweise ein Programm wie das folgende vor:

#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; } 

Wenn die Standardausgabe in eine Datei umgeleitet wird, führt dies zu den folgenden Ergebnissen:

 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 

Mit std::endl wurde das Programm in diesem Fall um den Faktor 20 verlangsamt. Wenn Sie kürzere Zeichenfolgen geschrieben haben, wird die Die Verlangsamung könnte / würde sogar noch größer sein.

Es gibt einige Fälle, in denen Sie einen Stream wirklich und wirklich manuell spülen möchten – aber ehrlich gesagt sind sie ziemlich selten.

Meistens muss ein Stream geleert werden (z. B. Sie drucken eine Eingabeaufforderung und warten dann auf eine Eingabe). Dies geschieht automatisch, es sei denn, Sie haben Dinge wie std::tie und verwendet / oder std::sync_with_stdio, um dies zu verhindern.

Damit bleibt nur eine winzige Anzahl wirklich ungewöhnlicher Situationen, in denen Sie sich befinden guter Grund, einen Stream manuell zu spülen. Solche Fälle sind selten genug, dass es sich lohnt, std::flush zu verwenden, um jedem, der den Code liest, klar zu machen, dass Sie den Stream absichtlich (und häufiger) spülen als nicht, verdient wahrscheinlich auch einen Kommentar darüber, warum dies einer der seltenen Fälle ist, in denen das Leeren des Streams wirklich Sinn macht.

Antwort

Jedes Mal, wenn ein Prozess eine Ausgabe erzeugt, muss er eine Funktion aufrufen, die die Arbeit tatsächlich erledigt. In den meisten Fällen ist diese Funktion letztendlich write(2). Unter einem Multitasking-Betriebssystem wird der Aufruf von write() in den Kernel eingefangen, der den Prozess stoppen, die E / A verarbeiten und andere Dinge tun muss, während alle Blockierungen beseitigt werden Stellen Sie es in die Warteschlange und bringen Sie es wieder zum Laufen, wenn es soweit ist. Zusammen können Sie all diese Aktivitäten Systemaufruf-Overhead aufrufen. Wenn das nach viel klingt, ist es das.

Wenn Sie einen gepufferten Stream * spülen, nachdem Sie eine kleine Datenmenge geschrieben haben oder überhaupt keinen Puffer haben, entsteht bei jedem Vorgang ein Overhead:

 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)  

So wurde es in den frühen Tagen gemacht, bis jemand herausfand, dass es brannte a viel Systemzeit. Der Overhead konnte durch Akkumulieren der Ausgabe in einem Puffer verringert werden, bis sie voll war oder das Programm entschied, dass sie sofort gesendet werden muss.(Möglicherweise möchten Sie Letzteres tun, wenn Sie „sporadisch eine Ausgabe produzieren, die gesehen oder verbraucht werden muss.) Durch Vermeiden eines Flushs am Ende jeder Zeile wird die Anzahl der Systemaufrufe und der Overhead verringert:

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

* Beachten Sie, dass das Konzept der Standardausgabe ein Dateideskriptor ist, der einem Prozess zugeordnet ist Dies unterscheidet sich von der durch C, C ++ und andere definierten stdout, die Bezeichner für Implementierungen eines gepufferten Streams sind, die vollständig im Benutzerland leben und in das schreiben Standardausgabe. Der Systemaufruf write() wird nicht gepuffert.

Kommentare

  • Gibt an, ob und wann gelöscht werden soll Dies hängt vom Kontext ab, in dem diese Ausgabe verwendet wird. Ein durchsatzorientiertes Programm sollte nur geleert werden, wenn seine Puffer voll sind. Ein latenzempfindliches Programm sollte häufiger geleert werden. Wenn die Ausgabe beispielsweise an eine Konsole geht, würden Sie sti Ich werde nach jeder neuen Zeile spülen. Interaktive Programme (die eine Eingabeaufforderung anzeigen) sollten sofort gelöscht werden, auch wenn die Zeile noch nicht voll ist.
  • “ Wenn die Ausgabe an eine Konsole geht, können Sie würde nach jeder neuen Zeile immer noch geleert. “ Richtig, aber wenn die Ausgabe an eine Konsole geht, erfolgt nach jeder neuen Zeile eine automatische Leerung. Dies muss nicht explizit erfolgen.
  • @amon Sie möchten dennoch aufeinanderfolgende Ausgaben zusammenführen, da eine rechtzeitige Spülung, dh vor der Anforderung von Eingaben und ohne überfällige Verzögerung, ausreichend ist. Zugegeben, es ist besser, einmal zu oft zu spülen, als zu viel Verzögerung beim Anzeigen der Ausgabe zu haben oder zu viel Arbeit in die Optimierung zu investieren …
  • Ein Trick, der manchmal von den Puffern verwendet wird, besteht darin, a neu zu starten / zu starten Timer, wenn neues Material kommt, und Flush, wenn der Timer abgelaufen ist.

Antwort

Warum Flush vermieden werden sollte:

Weil E / A am besten funktioniert, wenn das Betriebssystem mit relativ großen Datenmengen arbeiten kann. Regelmäßige Löschvorgänge mit kleinen Datenmengen führen zu Verlangsamungen, die manchmal sehr erheblich sind.

Warum Sie fast nie manuell spülen sollten:

Es gibt automatische Spülungen, die die meisten Anwendungsfälle abdecken. Wenn beispielsweise ein Programm in die Konsole schreibt, wird das System standardmäßig nach jeder neuen Zeile geleert. Wenn Sie in eine Datei schreiben, werden die Daten geschrieben, sobald genügend Daten gleichzeitig geschrieben werden können, und auch, wenn die Datei geschlossen wird.

Wann Sie sollten manuell spülen:

Wenn Sie die Ausgabe explizit sofort aktualisieren müssen. Beispiel: Wenn Sie einen Dreh- oder Fortschrittsbalken erstellen, der die aktuelle Zeile wiederholt überschreibt. Oder wenn Sie in eine Datei ausgeben und wirklich möchten, dass die Datei zu bestimmten Zeitpunkten aktualisiert wird.

Kommentare

  • Das System kann keine Benutzerpuffer leeren. Oder subsumieren Sie Bibliotheken, insbesondere die Standardbibliothek, auch unter “ system „? Natürlich sind stdin und stdout im Allgemeinen zumindest dann gekoppelt, wenn beide dieselbe Konsole verwenden.
  • Ja, Ich habe nicht ‚ gedacht, dass es eine gute Idee wäre, zu detailliert darauf einzugehen, wie Dinge zwischen Userspace-Bibliotheken und Kernel-Space aufgeteilt werden.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.