Hvorfor vil du unngå å skylle stdout?

Jeg snublet over et spørsmål i Kodevisning , og i ett svar var tilbakemeldingen til unngå std::endl fordi det skyller strømmen. Det fulle tilbudet er:

Jeg anbefaler å unngå std::endl generelt. Sammen med å skrive en ny- linje til strømmen, den skyller strømmen. Du vil ha den nye linjen, men nesten aldri ønsker å skylle strømmen, så det er generelt bedre å bare skrive en \ n. I den sjeldne anledningen at du faktisk vil skylle, gjør det eksplisitt: std::cout << "\n" << std::flush;.

Plakaten gjorde ikke forklare dette, verken i innlegget eller kommentarene. Så spørsmålet mitt er rett og slett dette:

Hvorfor vil du unngå rødming?

Det som gjorde meg enda mer nysgjerrig var at plakaten sier at det er veldig sjeldent du vil skylle. Jeg har ikke noe problem med å forestille meg situasjoner der du vil unngå å skylle, men jeg trodde likevel at du generelt vil skyll når du skriver ut en ny linje. Tross alt, er det ikke grunnen til at std::endl skyller i utgangspunktet?

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

Jeg anser ikke denne oppfatningen som basert. Det du foretrekker kan være meningsbasert, men det er objektive grunner til å ta hensyn til. Svarene så langt beviser dette. Spyling påvirker ytelsen.

Kommentarer

Svar

Det korte og enkle svaret er at bruk av std::endl kan og vil redusere produksjonen med stor margin. Jeg er faktisk rimelig overbevist om at std::endl er ansvarlig for det meste av forestillingen om at C ++ iostreams er vesentlig tregere enn C-stil I / O.

Tenk for eksempel på 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 standard utdata omdirigert til en fil, gir 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 

Bruk av std::endl reduserte programmet med en faktor på omtrent 20 i dette tilfellet. Hvis du skrev kortere strenger, avbremsing kan / ville være enda større.

Det er noen få tilfeller der du virkelig og virkelig ønsker å skylle en strøm manuelt – men de er ærlig talt ganske få mellom.

De fleste ganger må en strøm skylles (f.eks. hvis du skriver ut en melding og deretter venter på noen innspill), «vil det skje automatisk med mindre du har brukt ting som std::tie og / eller std::sync_with_stdio for å forhindre det.

Det etterlater bare et lite antall virkelig uvanlige situasjoner der du har god grunn til å skylle en strøm manuelt. Slike tilfeller er sjeldne nok til at det er verdt å bruke std::flush når de skjer, for å gjøre det tydelig for alle som leser koden at du skyller strømmen med vilje (og oftere enn ikke, fortjener sannsynligvis også en kommentar om hvorfor dette er en av de sjeldne tilfellene når det virkelig er fornuftig å skylle strømmen).

Svar

Hver gang en prosess produserer output, må den kalle en funksjon som faktisk gjør jobben. I de fleste tilfeller er denne funksjonen til slutt write(2). På et multitasking-operativsystem vil samtalen til write() fange seg inn i kjernen, som må stoppe prosessen, håndtere I / O, gjøre andre ting mens eventuelle blokkeringer er ryddet, satt den på klar kø og få den til å kjøre igjen når tiden kommer. Til sammen kan du ringe all den aktiviteten til systemanropsoverhead . Hvis det høres ut som mye, er det det.

Spyling av en bufret strøm * etter at du har skrevet en liten mengde data eller ikke har noen buffer i det hele tatt, medfører overhead hver gang du gjø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)  

Slik ble det gjort de aller første dagene til noen fant ut at det brant en mye systemtid. Overheaden kunne holdes nede ved å samle produksjonen i en buffer til den var full eller programmet bestemte at den måtte sendes umiddelbart.(Det kan være lurt å gjøre sistnevnte hvis du produserer output sporadisk som må sees eller konsumeres.) Hvis du unngår flush på slutten av hver linje, kuttes antall systemanrop, og overhead påløper:

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

* Merk at begrepet standardutgang er en filbeskrivelse assosiert med en prosess og gitt et kjent tall. Dette skiller seg fra stdout definert av C, C ++ og andre, som er identifikatorer for implementeringer av en bufret strøm som lever helt i brukerland og skriver til standardutgang. write() systemanropet er ikke bufret.

Kommentarer

  • Hvorvidt og når det skal spyles avhenger av konteksten som utdataene skal brukes i. Et gjennomstrømningsorientert program skal bare skylles når bufferne er fulle. Et latenssensitivt program bør skylles oftere. F.eks. hvis utgangen går til en konsoll, vil du Skyll etter hver ny linje. Interaktive programmer (som viser noen inntaksmeldinger) bør skylles umiddelbart, selv om linjen ikke er full ennå.
  • » hvis utgangen går til en konsoll, vil du ville fortsatt spyle etter hver ny linje. » Sant, men hvis utdataene går til en konsoll, blir det automatisk spyling etter hver ny linje. Du trenger ikke å gjøre det eksplisitt.
  • @amon Du vil fremdeles ønske å samle påfølgende utdata, da det er nok å spyle i tide, det vil si før du ber om innspill og uten forsinket forsinkelse. Riktignok er det bedre å skylle en gang for ofte enn å ha for mye forsinkelse i å vise utdata, eller å investere for mye arbeid i å optimalisere …
  • Et triks som buffere noen ganger bruker er å starte / starte en timer når nye ting kommer, og skyll når timeren er tom.

Svar

Hvorfor spyling skal unngås:

Fordi IO fungerer best når operativsystemet kan jobbe med relativt store datamengder. Regelmessig spyling med små datamengder forårsaker forsinkelser, noen ganger veldig betydelig.

Hvorfor du nesten aldri skal skylle manuelt:

Det er automatiske spylinger som dekker de fleste brukssaker. For eksempel, hvis et program skriver til konsollen, skylles systemet som standard etter hver ny linje. Eller hvis du skriver til filen, blir dataene skrevet når det er nok data som skal skrives på en gang, og også når filen lukkes.

Når bør du tømme manuelt:

Hvis du eksplisitt trenger å oppdatere utdataene umiddelbart. Eksempel: hvis du oppretter en spinner eller fremdriftslinje som overskriver gjeldende linje gjentatte ganger. Eller hvis du sender til fil og virkelig vil at filen skal oppdateres til bestemte øyeblikk.

Kommentarer

  • Systemet kan ikke skylle noen brukerbuffere. Eller bruker du biblioteker, spesielt standardbiblioteket, også under » system «? Selvfølgelig er stdin og stdout generelt koblet i det minste hvis de begge bruker samme konsoll.
  • Ja, Jeg trodde ikke ‘ at det ville være lurt å gå for mye i detaljer om hvordan ting blir delt mellom brukerområdebiblioteker og kjerneplass.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *