Miksi haluat välttää punastumisen?

kompastuin kysymykseen Codereview -palvelussa, ja yhdessä vastauksessa palautetta vältä std::endl, koska se huuhtelee virran. Lainaus on kokonaisuudessaan:

Suosittelen välttämään yleensä std::endl. Uuden tekstin kirjoittamisen ohella linja streamiin, se huuhtelee virran. Haluat uuden rivin, mutta et koskaan halua huuhdella streamia, joten yleensä on parempi kirjoittaa vain \ n. Harvoissa tapauksissa, joissa todella haluat huuhtelun, tee se nimenomaisesti: std::cout << "\n" << std::flush;.

Julistaja teki ei selitä tätä, ei postissa tai kommenteissa. Kysymykseni on siis yksinkertaisesti tämä:

Miksi haluat välttää punastumista?

Mikä teki minut vielä uteliaammaksi, oli, että julistajan mukaan on erittäin harvinaista, että haluat huuhdella. Minulla ei ole mitään ongelmaa kuvitella tilanteita, joissa haluat välttää punastumista, mutta ajattelin silti, että sinä yleensä haluaisit huuhtele, kun tulostat uuden rivin. Eikä loppujen lopuksi ole syy, miksi std::endl on ensinnäkin punastuva?

Vain kommentoidaksesi lopullisia äänestyksiä etukäteen:

En pidä tätä mielipidettä perusteena. Sinun tulisi mieluummin perustua mielipiteisiin, mutta on olemassa objektiivisia syitä, jotka on otettava huomioon. Tähänastiset vastaukset todistavat tämän. Huuhtelu vaikuttaa suorituskykyyn.

Kommentit

Vastaa

Lyhyt ja yksinkertainen vastaus on, että std::endl -toiminnon käyttäminen voi ja hidastaa tulostusta valtavasti. Itse asiassa olen kohtuullisen vakuuttunut siitä, että std::endl on vastuussa suurimmasta osasta ajatuksesta, että C ++ -virrat ovat huomattavasti hitaampia kuin C-tyyliset I / O.

Harkitse esimerkiksi seuraavaa ohjelmaa:

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

Kun vakiolähtö ohjataan tiedostoon, tämä tuottaa seuraavat tulokset:

 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 

std::endl -toiminnon käyttö hidasti tässä tapauksessa ohjelmaa noin 20. Jos kirjoitit lyhyempiä merkkijonoja, hidastuminen voisi / olisi vielä suurempi.

On muutamia tapauksia, joissa todella ja todella haluat huuhdella virran manuaalisesti – mutta rehellisesti niitä on melko vähän ja kaukana toisistaan.

Useimmiten virta on huuhdeltava (esim. tulostat kehotteen ja odotat syötettä). Se tapahtuu automaattisesti, ellet ole käyttänyt esimerkiksi std::tie ja / tai std::sync_with_stdio tämän estämiseksi.

Tämä jättää vain pienen määrän todella epätavallisia tilanteita, joissa sinulla on hyvä syy virrata virta manuaalisesti. Tällaiset tapaukset ovat riittävän harvinaisia, joten kannattaa käyttää std::flush, kun ne tapahtuvat, jotta koodia lukevalle kenelle tahansa käy selväksi, että huuhdot virtaa tarkoituksella (ja useammin kuin ei, todennäköisesti ansaitsee myös kommentin miksi tämä on yksi harvoista tapauksista, kun virran huuhtelu on todella järkevää).

Vastaa

Joka kerta, kun prosessi tuottaa tulosteen, sen on kutsuttava funktio, joka tosiasiallisesti tekee työn. Useimmissa tapauksissa funktio on viime kädessä write(2). Monikäyttöisessä käyttöjärjestelmässä kutsu write() tarttuu ytimeen, jonka on pysäytettävä prosessi, käsiteltävä I / O, tehtävä muita asioita, kun mahdolliset estot on poistettu, laitettu se valmiina jonoon ja saat sen käyntiin uudelleen, kun aika tulee. Voit yhdessä kutsua kaikkia kyseisiä toimintoja järjestelmän puheluiden yleiskustannuksiksi . Jos se kuulostaa paljon, niin se on.

Puskuroidun virta * huuhdellaan pienen tietomäärän kirjoittamisen jälkeen tai jos puskuria ei ole lainkaan, syntyy joka kulutus joka kerta, kun teet sen:

 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)  

Näin tehtiin varhaisessa vaiheessa, kunnes joku huomasi, että se poltti paljon järjestelmäaikaa. Yleiskustannuksia voidaan pitää alhaalla keräämällä lähtö puskuriin, kunnes se on täynnä tai ohjelma päättää, että se on lähetettävä välittömästi.(Voit halutessasi tehdä jälkimmäisen, jos tuotat satunnaisesti tuotoksen, joka on nähtävissä tai kulutettava.) Huuhtelun välttäminen jokaisen linjan lopussa vähentää järjestelmän puheluiden määrää ja aiheutuvia yleiskustannuksia:

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

* Huomaa, että vakiolähdön käsite on prosessiin liittyvä tiedostokuvaaja ja antaa hyvin tunnetun numeron. Tämä eroaa C: n, C ++: n ja muiden määrittelemästä stdout: stä, jotka ovat tunnisteita puskuroidun virran toteutuksille, jotka elävät kokonaan käyttäjämaassa ja kirjoittavat vakiolähtö. write() -järjestelmäkutsu ei ole puskuroitu.

Kommentit

  • Huuhdellaanko ja milloin riippuu asiayhteydestä, jossa tuotosta käytetään. Suorituskykyyn suuntautuvan ohjelman tulisi huuhdella vain, kun puskurit ovat täynnä. Latenssiherkän ohjelman tulisi huuhdella useammin. Esimerkiksi, jos lähtö menee konsoliin, sinun tulisi Huuhtele jokaisen uuden rivin jälkeen. Vuorovaikutteisten ohjelmien (jotka näyttävät jonkin verran syöttökehotetta) tulisi huuhdella välittömästi, vaikka rivi ei ole vielä täynnä.
  • ” jos lähtö menee konsolille, sinun on huuhtele silti jokaisen uuden rivin jälkeen. ” True, mutta jos lähtö menee konsoliin, jokaisen uuden rivin jälkeen tapahtuu automaattinen huuhtelu. Sinun ei tarvitse tehdä sitä nimenomaisesti.
  • @amon Haluat silti yhdistää peräkkäisen tuotoksen, koska ajankohtainen huuhtelu, joka tarkoittaa ennen syötteen pyytämistä ja ilman myöhästymistä, riittää. On tosin parempi huuhdella kerran liian usein kuin viivästyttää tuotoksen näyttämistä tai sijoittaa liian paljon työtä optimointiin …
  • Yksi puskureiden joskus käyttämä temppu on ajastin, kun uusia asioita tulee, ja huuhtele, kun ajastin loppuu.

Vastaa

Miksi huuhtelua on vältettävä:

Koska IO toimii parhaiten, kun käyttöjärjestelmä voi toimia suhteellisen suurella tietomäärällä. Säännöllinen huuhtelu pienillä tietomäärillä aiheuttaa hidastumista, joskus erittäin merkittävästi.

Miksi sinun ei pitäisi koskaan huuhdella manuaalisesti:

Useimmissa käyttötapauksissa on automaattisia huuhteluita. Esimerkiksi, jos ohjelma kirjoittaa konsolille, järjestelmä huuhtelee oletuksena jokaisen uuden rivin jälkeen. Tai jos kirjoitat tiedostoon, tiedot kirjoitetaan, kun on riittävästi tietoja kerralla kirjoittamista varten, ja myös silloin, kun tiedosto on suljettu.

Kun huuhtele manuaalisesti:

Jos sinun on nimenomaisesti päivitettävä lähtö välittömästi. Esimerkki: jos luot kiekko- tai edistymispalkin, joka korvaa nykyisen rivin toistuvasti. Tai jos tulostat tiedostoon ja todella haluat tiedoston päivittyvän tiettyinä hetkinä.

Kommentit

  • Järjestelmä ei voi tyhjentää mitään käyttäjän puskureita. Vai pidätkö kirjastoja, etenkin standardikirjastoa, myös ” system -kohdassa ”? Tietenkin stdin ja stdout on yleensä kytketty ainakin yhteen, jos molemmat käyttävät samaa konsolia.
  • Kyllä, En ajatellut ’ mielestäni, että olisi hyvä ajatella mennä liikaa yksityiskohtiin siitä, miten asiat jakautuvat käyttäjätila-kirjastojen ja ydintilan välillä.

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *