Díky za všechny komentáře! Nakonec jsem si na to s vaší pomocí odpověděl sám. Je však špinavé odpovídat na vlastní otázku.
Otázka 1:Proč je tisk na stdout pomalý?
Odpověď: Tisk na stdout není ze své podstaty pomalé. Je to terminál, se kterým pracujete, který je pomalý. A nemá to skoro nic společného s I/O bufferingem na straně aplikace (např.:python file buffer). Viz níže.
Otázka 2:Lze to urychlit?
Odpověď: Ano, může, ale zdánlivě ne ze strany programu (strana, která „tiskne“ na stdout). Chcete-li to urychlit, použijte rychlejší jiný emulátor terminálu.
Vysvětlení...
Vyzkoušel jsem samostatně popsaný „odlehčený“ terminálový program s názvem wterm
a dostal významně lepší výsledky. Níže je výstup mého testovacího skriptu (ve spodní části otázky) při spuštění v wterm
na 1920x1200 in na stejném systému, kde základní možnost tisku trvala 12 s pomocí gnome-terminálu:
----- timing summary (100k lines each) ----- print : 0.261 s write to file (+fsync) : 0.110 s print with stdout = /dev/null : 0.050 s
0,26s je MNOHEM lepší než 12s! Nevím, zda wterm
je inteligentnější ohledně toho, jak se vykresluje na obrazovce v souladu s tím, jak jsem navrhoval (vykresluje „viditelný“ konec při rozumné snímkové frekvenci), nebo zda prostě „dělá méně“ než gnome-terminal
. Pro účely mé otázky jsem však dostal odpověď. gnome-terminal
je pomalý.
Takže - Pokud máte dlouho spuštěný skript, o kterém se vám zdá, že je pomalý a chrlí obrovské množství textu do standardního výstupu... zkuste jiný terminál a zjistěte, zda je lepší!
Všimněte si, že jsem v podstatě náhodně vytáhl wterm
z repozitářů ubuntu/debian. Tento odkaz může být stejný terminál, ale nejsem si jistý. Žádné jiné emulátory terminálu jsem netestoval.
Aktualizace:Protože jsem musel škrábat svrab, otestoval jsem celou hromadu dalších emulátorů terminálu se stejným skriptem a celou obrazovkou (1920x1200). Moje ručně shromážděné statistiky jsou zde:
wterm 0.3s aterm 0.3s rxvt 0.3s mrxvt 0.4s konsole 0.6s yakuake 0.7s lxterminal 7s xterm 9s gnome-terminal 12s xfce4-terminal 12s vala-terminal 18s xvt 48s
Zaznamenané časy jsou ručně shromažďovány, ale byly docela konzistentní. Zaznamenal jsem nejlepší (ish) hodnotu. YMMV, samozřejmě.
Jako bonus to byla zajímavá prohlídka některých různých emulátorů terminálů, které jsou k dispozici! Jsem ohromen, můj první „alternativní“ test dopadl jako nejlepší ze všech.
Vaše přesměrování pravděpodobně nedělá nic, protože programy mohou určit, zda jejich výstupní FD ukazuje na tty.
Je pravděpodobné, že stdout má při ukazování na terminál řádkovou vyrovnávací paměť (stejně jako stdout
v C chování streamu).
Jako zábavný experiment zkuste nastavit výstup na cat
.
Vyzkoušel jsem svůj vlastní zábavný experiment a zde jsou výsledky.
$ python test.py 2>foo
...
$ cat foo
-----
timing summary (100k lines each)
-----
print : 6.040 s
write to file : 0.122 s
print with stdout = /dev/null : 0.121 s
$ python test.py 2>foo |cat
...
$ cat foo
-----
timing summary (100k lines each)
-----
print : 1.024 s
write to file : 0.131 s
print with stdout = /dev/null : 0.122 s
Jak je možné, že zápis na fyzický disk je MNOHEM rychlejší než zápis na "obrazovku" (pravděpodobně operační systém s plnou RAM) a je efektivně tak rychlý, jako když se jednoduše uloží do odpadu pomocí /dev/null?
Gratulujeme, právě jste objevili důležitost ukládání do vyrovnávací paměti I/O. :-)
Objeví se disk být rychlejší, protože je vysoce vyrovnávací paměti:všechny Python write()
volání se vracejí dříve, než je něco skutečně zapsáno na fyzický disk. (Operační systém to udělá později a spojí mnoho tisíc jednotlivých zápisů do velkých, efektivních bloků.)
Terminál na druhé straně neprovádí ukládání do vyrovnávací paměti nebo vůbec žádné:každý jednotlivý print
/ write(line)
čeká na úplné zápis (tj. zobrazení na výstupní zařízení) pro dokončení.
Aby bylo srovnání spravedlivé, musíte nastavit, aby test souboru používal stejné výstupní vyrovnávací paměti jako terminál, což můžete provést úpravou svého příkladu na:
fp = file("out.txt", "w", 1) # line-buffered, like stdout
[...]
for x in range(lineCount):
fp.write(line)
os.fsync(fp.fileno()) # wait for the write to actually complete
Provedl jsem váš test zápisu souborů na svém počítači a s ukládáním do vyrovnávací paměti je to také 0,05 s pro 100 000 řádků.
S výše uvedenými úpravami pro zápis bez vyrovnávací paměti však zápis pouze 1 000 řádků na disk trvá 40 sekund. Vzdal jsem čekání na napsání 100 000 řádků, ale extrapolovat z předchozího by to trvalo přes hodinu .
To dává terminálu 11 sekund do perspektivy, ne?
Abych tedy odpověděl na vaši původní otázku, zápis na terminál je ve skutečnosti neuvěřitelně rychlý, vezmeme-li v úvahu všechny okolnosti, a není mnoho prostoru pro jeho mnohem rychlejší zrychlení (ale jednotlivé terminály se liší v tom, kolik práce vykonávají; viz Russův komentář k tomuto odpověď).
(Mohli byste přidat více vyrovnávací paměti pro zápis, jako u diskových I/O, ale pak byste neuviděli, co bylo zapsáno na váš terminál, dokud se vyrovnávací paměť nevyprázdní. Je to kompromis:interaktivita versus efektivita hromadného zpracování.)
Nemohu mluvit o technických detailech, protože je neznám, ale nepřekvapuje mě to:terminál nebyl navržen pro tisk velkého množství dat, jako je tento. Ve skutečnosti dokonce poskytujete odkaz na spoustu věcí GUI, které musí udělat pokaždé, když chcete něco vytisknout! Všimněte si, že pokud skript zavoláte pomocí pythonw
místo toho to netrvá 15 sekund; toto je čistě problém GUI. Přesměrování stdout
do souboru, abyste tomu zabránili:
import contextlib, io
@contextlib.contextmanager
def redirect_stdout(stream):
import sys
sys.stdout = stream
yield
sys.stdout = sys.__stdout__
output = io.StringIO
with redirect_stdout(output):
...