Saya selalu kagum / frustrasi dengan berapa lama waktu yang dibutuhkan untuk hanya output ke terminal dengan pernyataan cetak. Setelah beberapa logging lambat lambat baru-baru ini saya memutuskan untuk melihatnya dan cukup terkejut menemukan bahwa hampir semua waktu yang dihabiskan menunggu terminal untuk memproses hasilnya.
Bisakah menulis ke stdout dipercepat entah bagaimana?
Saya menulis sebuah skrip (' print_timer.py
' di bagian bawah pertanyaan ini) untuk membandingkan waktu ketika menulis 100k baris ke stdout, ke file, dan dengan stdout dialihkan ke /dev/null
. Berikut ini adalah hasil waktunya:
$ 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
Wow. Untuk memastikan python tidak melakukan sesuatu di balik layar seperti mengenali bahwa saya menetapkan ulang stdout ke / dev / null atau sesuatu, saya melakukan pengalihan di luar skrip ...
$ 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
Jadi ini bukan trik python, itu hanya terminal. Saya selalu tahu membuang output ke / dev / null mempercepat, tetapi tidak pernah mengira itu signifikan!
Ini mengejutkan saya betapa lambatnya tty. Bagaimana bisa menulis ke disk fisik adalah CARA yang lebih cepat daripada menulis ke "layar" (mungkin semua-RAM op), dan secara efektif secepat hanya membuang ke sampah dengan / dev / null?
Tautan ini berbicara tentang bagaimana terminal akan memblokir I / O sehingga dapat "mengurai [input], memperbarui buffer frame-nya, berkomunikasi dengan server X untuk menggulir jendela dan sebagainya" ... tapi saya tidak sepenuhnya mendapatkannya. Apa yang bisa memakan waktu lama?
Saya berharap tidak ada jalan keluar (pendeknya implementasi tty yang lebih cepat?) Tetapi saya akan bertanya.
UPDATE: setelah membaca beberapa komentar saya bertanya-tanya seberapa besar dampak ukuran layar saya pada waktu cetak, dan memang ada beberapa signifikansi. Angka-angka yang sangat lambat di atas adalah dengan terminal Gnome saya meledak hingga 1920x1200. Jika saya menguranginya sangat kecil saya dapatkan ...
-----
timing summary (100k lines each)
-----
print : 2.920 s
write to file (+fsync) : 0.121 s
print with stdout = /dev/null : 0.048 s
Itu tentu lebih baik (~ 4x), tetapi tidak mengubah pertanyaan saya. Itu hanya menambah pertanyaan saya karena saya tidak mengerti mengapa rendering layar terminal harus memperlambat penulisan aplikasi ke stdout. Mengapa program saya harus menunggu rendering layar untuk melanjutkan?
Apakah semua aplikasi terminal / tty tidak dibuat sama? Saya belum bereksperimen. Menurut saya benar-benar seperti terminal harus dapat buffer semua data yang masuk, parse / render itu tak terlihat, dan hanya membuat potongan terbaru yang terlihat dalam konfigurasi layar saat ini pada frame rate yang masuk akal. Jadi jika saya dapat menulis + fsync ke disk dalam ~ 0,1 detik, terminal harus dapat menyelesaikan operasi yang sama dalam sesuatu dari urutan itu (dengan mungkin beberapa pembaruan layar saat melakukannya).
Saya masih agak berharap ada pengaturan tty yang dapat diubah dari sisi aplikasi untuk membuat perilaku ini lebih baik untuk programmer. Jika ini benar-benar masalah aplikasi terminal, maka ini mungkin bahkan bukan milik StackOverflow?
Apa yang saya lewatkan?
Berikut adalah program python yang digunakan untuk menghasilkan waktu:
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
Jawaban:
Selamat, Anda baru saja menemukan pentingnya buffering I / O. :-)
Disk tampaknya lebih cepat, karena sangat buffer: semua
write()
panggilan Python kembali sebelum ada sesuatu yang ditulis ke disk fisik. (OS melakukan ini nanti, menggabungkan ribuan individu menulis menjadi potongan besar, efisien.)Terminal, di sisi lain, melakukan sedikit atau tidak ada buffering: masing-masing individu
print
/write(line)
menunggu penulisan lengkap (yaitu tampilan ke perangkat keluaran) untuk menyelesaikan.Untuk membuat perbandingan adil, Anda harus membuat pengujian file menggunakan buffering output yang sama dengan terminal, yang dapat Anda lakukan dengan memodifikasi contoh Anda menjadi:
Saya menjalankan tes penulisan file Anda di komputer saya, dan dengan buffering, itu juga 0,05 di sini untuk 100.000 baris.
Namun, dengan modifikasi di atas untuk menulis unbuffered, dibutuhkan 40 detik untuk menulis hanya 1.000 baris ke disk. Saya menyerah menunggu 100.000 baris untuk menulis, tetapi memperkirakan dari sebelumnya, itu akan memakan waktu lebih dari satu jam .
Itu menempatkan 11 detik terminal ke dalam perspektif, bukan?
Jadi untuk menjawab pertanyaan awal Anda, menulis ke terminal sebenarnya sangat cepat, semua hal dipertimbangkan, dan tidak ada banyak ruang untuk membuatnya lebih cepat (tetapi masing-masing terminal bervariasi dalam berapa banyak pekerjaan yang mereka lakukan; lihat komentar Russ untuk ini menjawab).
(Anda dapat menambahkan lebih banyak buffering tulis, seperti dengan disk I / O, tetapi kemudian Anda tidak akan melihat apa yang ditulis ke terminal Anda sampai setelah buffer memerah. Ini pertukaran: interaktivitas versus efisiensi massal.)
sumber
os.fdopen(sys.stdout.fileno(), 'w', BIGNUM)
. Ini hampir tidak pernah berguna, meskipun: hampir semua aplikasi harus ingat untuk secara otomatis menyiram setelah setiap baris output yang diinginkan pengguna.fp = os.fdopen(sys.__stdout__.fileno(), 'w', 10000000)
buffer sisi python yang besar (hingga 10MB dengan ). Dampaknya nihil. yaitu: penundaan tty masih panjang. Ini membuat saya berpikir / menyadari bahwa Anda hanya menunda masalah tty lambat ... ketika buffer python akhirnya memerah tty tampaknya masih melakukan jumlah total pemrosesan yang sama di sungai sebelum kembali.fdopen
buffer yang lebih besar (2MB) pasti membuat perbedaan besar: butuh waktu pencetakan turun dari banyak detik menjadi 0,05, sama dengan output file (menggunakangnome-terminal
).Terima kasih atas semua komentarnya! Saya akhirnya menjawab sendiri dengan bantuan Anda. Rasanya kotor menjawab pertanyaan Anda sendiri.
Pertanyaan 1: Mengapa mencetak ke stdout lambat?
Jawab: Mencetak ke stdout secara inheren tidak lambat. Terminal tempat Anda bekerja lambat. Dan hampir tidak ada hubungannya dengan I / O buffering di sisi aplikasi (mis: buffering file python). Lihat di bawah.
Pertanyaan 2: Bisakah dipercepat?
Jawab: Ya bisa, tetapi sepertinya tidak dari sisi program (sisi melakukan 'pencetakan' ke stdout). Untuk mempercepatnya, gunakan emulator terminal berbeda yang lebih cepat.
Penjelasan...
Saya mencoba program terminal 'ringan' yang disebut sendiri
wterm
dan mendapat hasil yang jauh lebih baik. Di bawah ini adalah output dari skrip pengujian saya (di bagian bawah pertanyaan) ketika berjalan diwterm
pada 1920x1200 di pada sistem yang sama di mana opsi cetak dasar mengambil 12-an menggunakan gnome-terminal:0.26 jauh lebih baik dari 12! Saya tidak tahu apakah
wterm
lebih pintar tentang bagaimana membuat untuk menyaring sepanjang bagaimana saya menyarankan (membuat ekor 'terlihat' pada frame rate yang masuk akal), atau apakah itu hanya "kurang" darignome-terminal
. Untuk keperluan pertanyaan saya, saya sudah mendapatkan jawabannya.gnome-terminal
lambat.Jadi - Jika Anda memiliki skrip yang berjalan lama yang Anda rasa lambat dan itu memuntahkan sejumlah besar teks ke stdout ... coba terminal yang berbeda dan lihat apakah itu lebih baik!
Perhatikan bahwa saya cukup banyak menarik secara acak
wterm
dari repositori ubuntu / debian. Tautan ini mungkin terminal yang sama, tetapi saya tidak yakin. Saya tidak menguji emulator terminal lainnya.Pembaruan: Karena saya harus menggaruk gatal, saya menguji seluruh tumpukan emulator terminal lainnya dengan skrip dan layar penuh yang sama (1920x1200). Statistik saya yang dikumpulkan secara manual ada di sini:
Waktu yang direkam dikumpulkan secara manual, tetapi cukup konsisten. Saya mencatat nilai (ish) terbaik. YMMV, jelas.
Sebagai bonus, itu adalah tur yang menarik dari beberapa emulator terminal yang tersedia di luar sana! Saya kagum dengan tes 'pengganti' pertama saya ternyata yang terbaik dari kelompok itu.
sumber
screen
(program) harus dimasukkan dalam daftar! (Ataubyobu
, yang merupakan pembungkusscreen
dengan perangkat tambahan) Utilitas ini memungkinkan untuk memiliki beberapa terminal, seperti tab pada terminal X. Saya berasumsi bahwa mencetak kescreen
terminal saat ini sama dengan mencetak ke terminal biasa, tetapi bagaimana dengan mencetak di salah satuscreen
terminal dan kemudian beralih ke terminal lain tanpa aktivitas?print: 0.587 s, write to file (+fsync): 0.034 s, print with stdout = /dev/null : 0.041 s
. Dan dengan 'layar' berjalan di iTerm2:print: 1.286 s, write to file (+fsync): 0.043 s, print with stdout = /dev/null : 0.033 s
Pengalihan Anda mungkin tidak melakukan apa-apa karena program dapat menentukan apakah output FD mereka menunjuk ke tty.
Kemungkinan stdout adalah buffer line ketika menunjuk ke terminal (sama dengan
stdout
perilaku stream C ).Sebagai percobaan yang lucu, coba perpipkan hasilnya ke
cat
.Saya sudah mencoba eksperimen lucu saya sendiri, dan inilah hasilnya.
sumber
-u
memaksa opsistdin
,stdout
danstderr
untuk tidak dibangun, yang akan lebih lambat daripada menjadi buffered blok (karena overhead)Saya tidak dapat berbicara tentang detail teknis karena saya tidak mengetahuinya, tetapi ini tidak mengejutkan saya: terminal tidak dirancang untuk mencetak banyak data seperti ini. Memang, Anda bahkan memberikan tautan ke banyak hal GUI yang harus dilakukan setiap kali Anda ingin mencetak sesuatu! Perhatikan bahwa jika Anda memanggil skrip
pythonw
, alih - alih membutuhkan waktu 15 detik; ini sepenuhnya merupakan masalah GUI. Redirectstdout
ke file untuk menghindari ini:sumber
Pencetakan ke terminal akan lambat. Sayangnya singkat menulis implementasi terminal baru saya tidak bisa benar-benar melihat bagaimana Anda mempercepat ini secara signifikan.
sumber
Selain output yang mungkin default ke mode buffer-line, output ke terminal juga menyebabkan data Anda mengalir ke terminal dan jalur serial dengan throughput maksimum, atau pseudo-terminal dan proses terpisah yang menangani tampilan. loop acara, rendering karakter dari beberapa font, memindahkan bit tampilan untuk mengimplementasikan tampilan gulir. Skenario yang terakhir mungkin tersebar di beberapa proses (misalnya server telnet / klien, aplikasi terminal, server tampilan X11) sehingga ada masalah pengalihan konteks dan latensi juga.
sumber
cat big_file | tail
atau bahkancat big_file | tee big_file.cpy | tail
sangat sering untuk mempercepat ini.