Hvorfor vil du undgå at skylle stdout?

Jeg snuble over et spørgsmål i Codereview , og i et svar var feedbacken at undgå std::endl, fordi det skyller strømmen. Det fulde citat er:

Jeg vil råde dig til at undgå std::endl generelt. Sammen med at skrive en ny- linje til strømmen, den skyller strømmen. Du vil have den nye linje, men næsten aldrig ønsker at skylle strømmen, så det er generelt bedre bare at skrive en \ n. I den sjældne lejlighed, at du rent faktisk vil skylle, skal du gøre det eksplicit: std::cout << "\n" << std::flush;.

Plakaten gjorde ikke forklare dette, hverken i posten eller kommentarerne. Så mit spørgsmål er simpelthen dette:

Hvorfor vil du undgå at skylle?

Hvad gjorde mig endnu mere nysgerrig var, at plakaten siger, at det er meget sjældent, at du vil skylle. Jeg har ikke noget problem med at forestille mig situationer, hvor du vil undgå at skylle, men jeg troede stadig, at du generelt ville skylles, når du udskriver en ny linje. Når alt kommer til alt, er det ikke grunden til, at std::endl skyller i første omgang?

Bare for at kommentere de nære stemmer på forhånd:

Jeg anser ikke denne udtalelse for at være baseret. Hvilket du foretrækker kan være meningsbaseret, men der er objektive grunde til at tage højde for. Svarene hidtil beviser dette. Flushing påvirker ydeevnen.

Kommentarer

Svar

Det korte og enkle svar er, at brug af std::endl kan og vil sænke output med en enorm margin. Faktisk er jeg med rimelighed overbevist om, at std::endl er ansvarlig for de fleste forestillinger om, at C ++ iostreams er væsentligt langsommere end C-style I / O.

Overvej f.eks. et program som dette:

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

Med standardoutput omdirigeret til en fil, giver dette følgende resultater:

 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 

Brug af std::endl sænkede programmet med en faktor på ca. 20 i dette tilfælde. Hvis du skrev kortere strenge, afmatning kunne / ville være endnu større.

Der er et par tilfælde, hvor du virkelig og virkelig ønsker at skylle en strøm manuelt – men de er ærligt talt ganske få.

De fleste gange skal en stream skylles (f.eks. du udskriver en prompt, så vent på noget input), “det sker automatisk, medmindre du har brugt ting som std::tie og / eller std::sync_with_stdio for at forhindre det.

Det efterlader kun et lille antal virkelig usædvanlige situationer, hvor du har god grund til at skylle en strøm manuelt. Sådanne tilfælde er sjældne nok til, at det er værd at bruge std::flush når de sker, for at gøre det klart for enhver, der læser koden, at du skyller strømmen med vilje (og oftere end ikke, fortjener sandsynligvis også en kommentar til hvorfor dette er et af de sjældne tilfælde, når skylning af strømmen virkelig giver mening).

Svar

Hver gang en proces producerer output, skal den kalde en funktion, der rent faktisk udfører arbejdet. I de fleste tilfælde er denne funktion i sidste ende write(2). På et multitasking-operativsystem vil opkaldet til write() falde ind i kernen, som skal stoppe processen, håndtere I / O, gøre andre ting, mens eventuelle blokeringer er ryddet, sæt det i den klare kø, og få det til at køre igen, når tiden kommer. Samlet kan du kalde al denne aktivitet systemopkaldsudgifter . Hvis det lyder som meget, er det det.

Skylning af en bufret strøm * efter at have skrevet en lille mængde data eller slet ikke har nogen buffer medfører overhead, hver gang du gør det:

 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)  

Sådan blev det gjort i de meget tidlige dage, indtil nogen regnede med, at det brændte en meget systemtid. Overhead kunne holdes nede ved at akkumulere output i en buffer, indtil den var fuld, eller programmet besluttede, at det skal sendes straks.(Det kan være en god idé at gøre sidstnævnte, hvis du sporadisk producerer output, der skal ses eller forbruges.) Hvis du undgår en flush i slutningen af hver linje, reduceres antallet af systemopkald, og omkostningerne afholdes:

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

* Bemærk, at begrebet standardoutput er en filbeskrivelse, der er knyttet til en proces og givet et velkendt nummer. Dette adskiller sig fra stdout defineret af C, C ++ og andre, som er identifikatorer til implementeringer af en bufret strøm, der lever udelukkende i brugerlandet og skriver til standardoutput. write() systemopkald er ikke bufferet.

Kommentarer

  • Om og hvornår der skal skylles afhænger af den kontekst, som output vil blive brugt i. Et gennemløbsorienteret program skal kun skylles, når dets buffere er fulde. Et latensfølsomt program skal skylles oftere. F.eks. hvis output går til en konsol, ville du sti Skyl efter hver ny linje. Interaktive programmer (der viser en vis inputprompt) skal skylles med det samme, selvom linjen endnu ikke er fuld.
  • ” hvis output går til en konsol, du ville stadig skylle efter hver ny linje. ” Sandt, men hvis output går til en konsol, er der en automatisk skylning efter hver ny linje. Ingen grund til at gøre det eksplicit.
  • @amon Du vil stadig gerne samle på hinanden følgende output, da skylning rettidigt, hvilket betyder, før du beder om input og uden forsinket forsinkelse, er tilstrækkelig. Ganske vist er det bedre at skylle en gang for ofte end at have for meget forsinkelse med at vise output eller investere for meget arbejde i at optimere …
  • Et trick, der undertiden bruges af bufferne, er at genstarte / starte en timer når nye ting kommer, og skyl når timeren løber tør.

Svar

Hvorfor skyl skal undgås:

Fordi IO fungerer bedst, når operativsystemet kan arbejde med relativt store datamængder. Regelmæssige skylninger med små mængder data forårsager afmatning, nogle gange meget markant.

Hvorfor skal du næsten aldrig skylle manuelt:

Der er automatiske skylninger, der dækker de fleste brugssager. For eksempel, hvis et program skriver til konsollen, skylles systemet som standard efter hver ny linje. Eller hvis du skriver til filen, skrives dataene, når der er nok data til at blive skrevet på én gang, og også når filen lukkes.

Når skal du skylle manuelt:

Hvis du udtrykkeligt har brug for at opdatere output med det samme. Eksempel: hvis du opretter en spinner eller statuslinje, der gentagne gange overskriver den aktuelle linje. Eller hvis du output til fil og virkelig ønsker, at filen skal opdateres på bestemte øjeblikke.

Kommentarer

  • Systemet kan ikke skylle nogen brugerbuffere. Eller optager du biblioteker, især standardbiblioteket, også under ” system “? Naturligvis er stdin og stdout generelt koblet i det mindste, hvis de begge bruger den samme konsol.
  • Ja, Jeg troede ikke ‘ at det ville være en god ide at gå i for mange detaljer om, hvordan ting er delt mellem brugerrumsbiblioteker og kernerum.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *