Stdout'a yazdırma neden bu kadar yavaş? Hızlandırılabilir mi?


166

Ben her zaman bir baskı bildirimi ile terminal çıktı için ne kadar sürer ile şaşırdım / hayal kırıklığına uğradım. Son zamanlarda ağrılı yavaş oturum açtıktan sonra buna bakmaya karar verdim ve harcanan neredeyse tüm zamanların terminalin sonuçları işlemesini beklediğini bulmak oldukça şaşırdı .

Stdout'a yazmak bir şekilde hızlandırılabilir mi?

print_timer.py100k satır stdout, dosya ve stdout yönlendirilen ile yazarken zamanlamayı karşılaştırmak için bir komut dosyası ( bu sorunun altında ' ') yazdım /dev/null. Zamanlama sonucu şöyledir:

$ python print_timer.py
this is a test
this is a test
<snipped 99997 lines>
this is a test
-----
timing summary (100k lines each)
-----
print                         :11.950 s
write to file (+ fsync)       : 0.122 s
print with stdout = /dev/null : 0.050 s

Vay. Python'un stdout'u / dev / null veya başka bir şeye yeniden atadığımı fark etmek gibi perde arkasında bir şey yapmadığından emin olmak için, komut dosyasının dışındaki yönlendirmeyi yaptım ...

$ python print_timer.py > /dev/null
-----
timing summary (100k lines each)
-----
print                         : 0.053 s
write to file (+fsync)        : 0.108 s
print with stdout = /dev/null : 0.045 s

Yani bir python hilesi değil, sadece terminal. Ben her zaman / dev / null hızlandırmak şeyler damping çıktı biliyordum, ama asla bu önemli olduğunu anladım!

Tty'nin ne kadar yavaş olduğu beni şaşırtıyor. Nasıl fiziksel diske yazmak "ekrana" (muhtemelen bir tüm-RAM op) yazmaktan daha hızlı ve etkili bir şekilde / dev / null ile çöp dökümü kadar hızlı olabilir?

Bu bağlantı , terminalin G / Ç'yi nasıl engelleyeceğini açıklar, böylece pencereyi kaydırmak için "girişi" ayrıştırabilir, çerçeve arabelleğini güncelleyebilir, X sunucusuyla iletişim kurabilir " ... tamamen olsun. Ne bu kadar uzun sürebilir?

Ben hiçbir çıkış yolu (daha hızlı tty uygulama kısa?) Bekliyoruz ama yine de soracağım rakam.


GÜNCELLEME: bazı yorumları okuduktan sonra ekran boyutumun baskı süresi üzerinde gerçekte ne kadar etkisi olduğunu merak ettim ve bunun bir önemi var. Yukarıdaki gerçekten yavaş sayılar Gnome terminalim 1920x1200'e kadar şişmiş durumda. Çok küçük düşürürsem ...

-----
timing summary (100k lines each)
-----
print                         : 2.920 s
write to file (+fsync)        : 0.121 s
print with stdout = /dev/null : 0.048 s

Bu kesinlikle daha iyi (~ 4x), ama sorumu değiştirmiyor. Terminal ekran oluşturma neden bir uygulama yazma stdout yavaşlatmak anlamıyorum çünkü sadece benim soruya ekler . Programımın ekran görüntülemenin devam etmesini neden beklemesi gerekiyor?

Tüm terminal / tty uygulamaları eşit oluşturulmamış mı? Henüz denemedim. Gerçekten bir terminalin tüm gelen verileri arabelleğe alması, görünmez bir şekilde ayrıştırması / işlemesi ve yalnızca geçerli ekran yapılandırmasında görünür olan en yeni yığını makul bir kare hızında oluşturması gerektiği gibi görünüyor. Bu yüzden ~ 0.1 saniye içinde diske + fsync yazabilirsem, bir terminal aynı işlemi o sırayla tamamlayabilmelidir (bunu yaparken birkaç ekran güncellemesiyle).

Hala bu tarafı programcı için daha iyi hale getirmek için uygulama tarafından değiştirilebilen bir tty ayarı olduğunu umuyorum. Bu kesinlikle bir terminal uygulaması sorunu ise, o zaman bu bile StackOverflow ait değil?

Neyi kaçırıyorum?


İşte zamanlamayı oluşturmak için kullanılan python programı:

import time, sys, tty
import os

lineCount = 100000
line = "this is a test"
summary = ""

cmd = "print"
startTime_s = time.time()
for x in range(lineCount):
    print line
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

#Add a newline to match line outputs above...
line += "\n"

cmd = "write to file (+fsync)"
fp = file("out.txt", "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
os.fsync(fp.fileno())
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

cmd = "print with stdout = /dev/null"
sys.stdout = file(os.devnull, "w")
startTime_s = time.time()
for x in range(lineCount):
    fp.write(line)
t = time.time() - startTime_s
summary += "%-30s:%6.3f s\n" % (cmd, t)

print >> sys.stderr, "-----"
print >> sys.stderr, "timing summary (100k lines each)"
print >> sys.stderr, "-----"
print >> sys.stderr, summary

9
Stdout'a yazmanın tüm amacı, bir insanın çıktıyı okuyabilmesidir. Dünyadaki hiçbir insan 12 saniyede 10.000 satır metin okuyamaz, bu yüzden stdout'u daha hızlı yapmanın anlamı nedir ???
Seun Osewa

14
@Seun Osewa: Bir örnek (sorumu sürdü), print deyimi hata ayıklaması gibi şeyler yaparken . Programınızı çalıştırmak ve sonuçları gerçekleştikçe görmek istiyorsunuz. Açıkçası çoğu çizginin göremeyeceğiniz şekilde uçacağını haklısınız, ancak bir istisna olduğunda (veya dikkatlice yerleştirdiğiniz koşullu getch / raw_input / sleep ifadesine çarptığınızda) doğrudan baskı çıktısına bakmak yerine sürekli bir dosya görünümünü açmak veya yenilemek zorunda.
Russ

3
Yazdırma deyimi hata ayıklaması, tty aygıtlarının (örn. Terminaller) blok arabellekleme yerine satır arabelleğe almayı varsayılan olarak belirlemesinin nedenlerinden biridir: program askıda kaldığında hata ayıklama çıktısı çok fazla kullanılmaz ve son birkaç hata ayıklama çıktı satırı arabellekte kalırsa terminale akıtmak yerine.
Stephen C. Steel

@Stephen: Bu yüzden bir yorumcunun arabellek boyutunu artırarak iddia ettiği büyük iyileştirmeleri takip etmekle fazla uğraşmadım. Hata ayıklama baskısının amacını tamamen yener! Araştırırken biraz deney yaptım, ancak net bir gelişme görmedim. Hala tutarsızlığı merak ediyorum, ama gerçekten değil.
Russ

Bazen çok uzun süren programlar için, geçerli satır stdout'unu her n saniyede bir yazarım - bir curses uygulamasında yenileme gecikmesine sahip olmaya benzer. Mükemmel değil, ama arada sırada nerede olduğum hakkında bir fikir veriyor.
rkulla

Yanıtlar:


155

Nasıl fiziksel diske yazmak "ekrana" (muhtemelen bir tüm-RAM op) yazmaktan daha hızlı ve etkili bir şekilde / dev / null ile çöp dökümü kadar hızlı olabilir?

Tebrikler, G / Ç arabelleklemenin önemini yeni keşfettiniz. :-)

Disk daha hızlı görünüyor , çünkü oldukça arabelleğe alınmış: write()Fiziksel diske herhangi bir şey yazılmadan önce tüm Python'un çağrıları geri dönüyor. (İS daha sonra bunu yapıyor ve binlerce bireysel yazıyı büyük ve verimli bir yığın halinde birleştiriyor.)

Terminal, diğer taraftan, çok az tamponlama yapar ya da hiç arabelleğe almaz: her bir birey print/ tam yazma (yani çıkış cihazına görüntüleme) işleminin tamamlanmasını write(line)bekler .

Karşılaştırmayı adil yapmak için, dosya testini terminal ile aynı çıktı arabelleğini kullanmanız gerekir; bu, örneğinizi şu şekilde değiştirerek yapabilirsiniz:

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

Dosya yazma testini makinemde çalıştırdım ve arabelleğe alma ile burada 100.000 satır için 0.05s.

Ancak, arabelleksiz yazmak için yukarıdaki değişikliklerle, diske yalnızca 1.000 satır yazmak 40 saniye sürer. 100.000 satır yazmak için vazgeçtim, ama öncekinden tahmin edersek, bir saatten fazla .

Bu terminalin 11 saniyesini perspektife sokar, değil mi?

Orijinal sorunuza cevap vermek için, bir terminale yazmak gerçekten hızlıdır, her şey göz önünde bulundurulur ve çok daha hızlı hale getirmek için çok fazla yer yoktur (ancak bireysel terminaller ne kadar iş yaptıkları konusunda farklılık gösterir; Russ'ın yorumuna bakın. Cevap).

(Disk G / Ç'de olduğu gibi daha fazla yazma arabelleği ekleyebilirsiniz, ancak daha sonra arabellek temizlenene kadar terminalinize ne yazdığını göremezsiniz. Bu bir değiş tokuş: etkileşim ve toplu verimlilik.


6
G / Ç arabelleklemesi alıyorum ... Kesinlikle bana tamamlanma zamanının gerçek bir karşılaştırması için fsync'd gerektiğini hatırlattınız (soruyu güncelleyeceğim), ancak satır başına bir fsync delilik. Bir tty'nin bunu etkili bir şekilde yapması gerekiyor mu? Dosyalar için eşdeğer bir terminal / os tarafı arabelleğe alma işlemi yok mu? ie: Uygulamalar stdout'a yazar ve terminal ekrana gelmeden önce, terminal (veya os) tümünü arabelleğe alır. Terminal daha sonra kuyruğu görünür bir kare hızında hassas bir şekilde ekrana getirebilir. Her hatta etkili bir şekilde engelleme aptalca görünüyor. Hâlâ bir şey eksik olduğumu hissediyorum.
Russ

Büyük bir arabellekle stdout yapmak için bir tutamak açabilirsiniz os.fdopen(sys.stdout.fileno(), 'w', BIGNUM). Bu neredeyse hiçbir zaman yararlı olmaz: neredeyse tüm uygulamalar, kullanıcı odaklı çıktıların her satırından sonra açıkça yıkamayı hatırlamak zorunda kalacaktır.
Pi Delport

1
Daha önce devasa (10MB'a kadar fp = os.fdopen(sys.__stdout__.fileno(), 'w', 10000000)) python tarafı arabellekleri denedim. Etki sıfırdı. yani: hala uzun tty gecikmeleri. Bu bana yavaş tty sorununun ertelendiğini düşündürdü / fark etti ... Python'un tamponu nihayet yıkandığında, geri dönmeden önce akışta aynı toplam işlem miktarını yapıyor gibi görünüyor.
Russ

8
Bu cevabın yanıltıcı ve yanlış olduğunu unutmayın (özür dileriz!). Özellikle "[11 saniyeden hızlı] yapmak için fazla yer yok" demek yanlıştır. Lütfen wterm terminalinin aynı 11s sonucunu 0.26s ile elde ettiğini gösterdiğim soruya kendi cevabımı görün.
Russ

2
Russ: Geri bildiriminiz için teşekkürler! Benim tarafımda, daha büyük bir fdopentampon (2MB) kesinlikle büyük bir fark yarattı: yazdırma süresini dosya çıkışıyla aynı kullanarak birkaç saniyeden 0,05 saniyeye düşürdü gnome-terminal.
Pi Delport

88

Tüm yorumlar için teşekkürler! Sonunda yardımınla kendime cevap verdim. Yine de kendi sorunuzu cevaplamak kirli hissediyor.

Soru 1: Stdout'a yazdırma neden yavaş?

Cevap: Stdout'a yazdırma doğal olarak yavaş değildir . Bu yavaş çalıştığınız terminaldir. Ve uygulama tarafında G / Ç arabelleklemesi ile hemen hemen sıfırdır (örneğin: python dosya arabelleği). Aşağıya bakınız.

Soru 2: Hızlandırılabilir mi?

Cevap: Evet olabilir, ancak görünüşe göre program tarafından değil (stdout'a 'baskı' yapan taraf). Hızlandırmak için daha hızlı farklı bir terminal emülatörü kullanın.

Açıklama ...

Kendinden tarif edilen bir 'hafif' terminal programını denedim wtermve çok daha iyi sonuçlar aldım . Aşağıda, wterm1920x1200'de temel baskı seçeneğinin gnome terminalini kullanarak 12 saniye aldığı aynı sistemde çalışırken test komutumun çıktısı (sorunun altında) :

-----
zamanlama özeti (her biri 100 bin satır)
-----
baskı: 0.261 s
dosyaya yaz (+ fsync): 0.110 s
stdout ile yazdırma = / dev / null: 0,050 s

0.26s 12s çok daha iyi! Ben bilmiyorum wtermo, yoksa sadece "does az" olsun daha ben düşündüren nasıl hatları (makul bir kare hızında 'görünür' kuyruk oluşturma) boyunca ekrana nasıl oluşturduğunu hakkında daha zeki olduğunu gnome-terminal. Sorumun amacı için yanıtı aldım. gnome-terminalyavaş.

Yani - yavaş olduğunu düşündüğünüz uzun süren bir komut dosyanız varsa ve stdout'a çok miktarda metin yayarsa ... farklı bir terminal deneyin ve daha iyi olup olmadığını görün!

Ben hemen hemen rasgele wtermubuntu / debian depolarından çekti unutmayın . Bu bağlantı aynı terminal olabilir, ancak emin değilim. Başka terminal emülatörlerini test etmedim.


Güncelleme: Kaşıntıyı çizmek zorunda olduğum için, aynı komut dosyası ve tam ekran (1920x1200) ile diğer bir yığın emülatör yığınını test ettim. Manuel olarak toplanan istatistiklerim burada:

wterm 0.3s
aterm 0.3s
rxvt 0.3s
mrxvt 0.4s
Konsol 0.6s
yakuake 0.7s
lxterminal 7s
xterm 9s
gnome terminali 12'ler
xfce4-terminal 12s
vala-terminal 18'ler
xvt 48s

Kaydedilen süreler manuel olarak toplanır, ancak oldukça tutarlıdırlar. En iyi (ish) değeri kaydettim. YMMV, tabii ki.

Bonus olarak, orada mevcut çeşitli terminal emülatörlerinden bazıları ilginç bir tur oldu! İlk 'alternatif' testimin grupların en iyisi olduğu ortaya çıktı.


1
Ayrıca atermi de deneyebilirsiniz. İşte betiğimi kullanarak testimdeki sonuçlar. Aterm - baskı: 0.491 s, dosyaya yaz (+ fsync): 0.110 s, stdout ile yazdır = / dev / null: 0.087 s wterm - baskı: 0.521 s, dosyaya yaz (+ fsync): 0.105 s, stdout ile yazdır = / dev / null: 0,085 s
frogstarr78

1
Urxvt ile rxvt arasındaki fark nedir?
Daenyth

3
Ayrıca, screen(program) listeye dahil edilmelidir! (Veya byobu, screengeliştirmeler için bir sarıcıdır ) Bu yardımcı program, X terminallerindeki sekmeler gibi birçok terminale sahip olmanızı sağlar. Akımın screenterminaline yazdırmanın düz bir kağıda yazdırmakla aynı olduğunu varsayıyorum, ama bir screenterminalin birinde baskı yapmak ve daha sonra herhangi bir etkinlik olmadan diğerine geçmek ne olacak?
Armando Pérez Marqués

1
Garip, bir süre önce farklı terminalleri hız açısından karşılaştırıyordum ve gnome-terminali oldukça ciddi testlerde en iyi çıktı, xterm en yavaştı. Belki de o zamandan beri tamponlama üzerinde çok çalıştılar. Ayrıca unicode desteği büyük bir fark yaratabilir.
Tomas Pruzina

2
OSX'te iTerm2 bana verdi: print: 0.587 s, write to file (+fsync): 0.034 s, print with stdout = /dev/null : 0.041 s. Ve iTerm2'de 'ekran' çalışıyorken:print: 1.286 s, write to file (+fsync): 0.043 s, print with stdout = /dev/null : 0.033 s
rkulla

13

Programlar çıkış FD'lerinin bir tty'yi gösterip göstermediğini belirleyebileceğinden, yeniden yönlendirmeniz muhtemelen hiçbir şey yapmaz.

Stdout'un bir terminale işaret ederken satır tamponlu olması muhtemeldir (C'nin stdoutakış davranışı ile aynı ).

Eğlenceli bir deney olarak, çıktıyı borulamaya çalışın cat.


Kendi eğlenceli denememi denedim ve sonuçlar burada.

$ 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

Ben onun çıkış FS kontrol python düşünmüyordu. Python'un perde arkasında bir numara çekip çekmediğini merak ediyorum. Beklemiyorum ama bilmiyorum.
Russ

Arabelleğe almanın tüm önemli farkına işaret eden +1
Peter G.

@Russ: the -u seçenek kuvvetleri stdin, stdoutve stderrblok (nedeniyle ek yük) arabelleğe daha yavaş olacak, tamponsuz olduğu
Hasturkun

4

Teknik detaylar hakkında konuşamam çünkü onları tanımıyorum, ama bu beni şaşırtmıyor: terminal böyle birçok veri yazdırmak için tasarlanmadı. Gerçekten de, bir şey yazdırmak istediğiniz her seferinde yapması gereken bir sürü GUI öğesine bağlantı sağlarsınız! Komut dosyasını bununla çağırırsanız, pythonw15 saniye sürmediğine dikkat edin; bu tamamen bir GUI sorunudur. Bundan stdoutkaçınmak için bir dosyaya yönlendirin :

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):
    ...

3

Terminale yazdırma yavaş olacaktır. Ne yazık ki yeni bir terminal uygulaması yazma konusunda kısa sürdüğümde bunu nasıl önemli ölçüde hızlandıracağınızı gerçekten göremiyorum.


2

Muhtemelen bir satır arabellekli moda ayarlanan çıkışa ek olarak, bir terminale çıkış da verilerinizin maksimum verim ile bir terminal ve seri hatta veya bir sahte terminal ve bir ekranı işleyen ayrı bir işleme akmasına neden olur. olay döngüsü, bazı yazı tipinden karakter oluşturma, kaydırma ekranını uygulamak için ekran bitlerini taşıma. İkinci senaryo muhtemelen birden fazla işleme yayılmıştır (örn. Telnet sunucusu / istemci, terminal uygulaması, X11 ekran sunucusu), dolayısıyla bağlam değiştirme ve gecikme sorunları da vardır.


Doğru! Bu, terminal penceresi boyutumu (Gnome'da) cılız bir şeye (1920x1200'den) indirmeye çalışmamı istedi. Tabii ki ... 2.8s baskı süresi vs 11.5s. Çok daha iyi, ama yine de ... neden duruyor? Stdout tamponunun (hmm) tüm 100k hatlarını işleyebileceğini ve terminal ekranının, tamponun kuyruk ucundan ekrana sığabilecek her şeyi yakalayacağını ve bir hızlı çekimde yapabileceğini düşünürdünüz.
Russ

Xterm (veya bu durumda gterm), diğer tüm çıkışları da yol boyunca görüntülemek zorunda kalmazsa, nihai ekranınızı daha hızlı hale getirecektir. Bu rotaya gitmeye çalışacak olsaydı, küçük ekran güncellemelerinin ortak vakasının daha az duyarlı görünmesini sağlayabilirdi. Bu tür bir yazılımı yazarken bazen farklı modlara sahip olarak ve küçükten toplu işleme moduna ne zaman geçmeniz gerektiğini algılamaya çalışabilirsiniz. Bu hızlanma için cat big_file | tailveya hatta cat big_file | tee big_file.cpy | tailçok sık kullanabilirsiniz .
nategoose
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.