Izinkan saya memberi contoh:
$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2
Di sini Anda dapat melihat bahwa perintah yes
menulis 11504640
baris dalam sedetik sementara saya hanya dapat menulis 1953
baris dalam 5 detik menggunakan bash's for
dan echo
.
Seperti yang disarankan dalam komentar, ada berbagai trik untuk membuatnya lebih efisien tetapi tidak ada yang mendekati kecepatan yes
:
$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4
Ini dapat menulis hingga 20 ribu baris dalam satu detik. Dan mereka dapat ditingkatkan lebih lanjut ke:
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5'
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6
Ini memberi kita hingga 40 ribu baris dalam satu detik. Lebih baik, tetapi masih jauh dari yes
yang bisa menulis sekitar 11 juta baris dalam satu detik!
Jadi, bagaimana yes
menulis ke file begitu cepat?
date
agak berat, ditambah shell harus membuka kembali aliran output untukecho
untuk setiap iterasi loop. Dalam contoh pertama, hanya ada satu perintah perintah dengan redirection output tunggal, dan perintah ini sangat ringan. Keduanya sama sekali tidak sebanding.date
mungkin berat, lihat edit pertanyaan saya.timeout 1 $(while true; do echo "GNU">>file2; done;)
adalah cara yang salah untuk digunakantimeout
karenatimeout
perintah hanya akan mulai setelah substitusi perintah selesai. Gunakantimeout 1 sh -c 'while true; do echo "GNU">>file2; done'
.write(2)
panggilan sistem, bukan pada muatan kapal dari syscall lain, overhead shell, atau bahkan proses pembuatan dalam contoh pertama Anda (yang berjalan dan menunggudate
untuk setiap baris yang dicetak ke file). Satu detik penulisan hampir tidak cukup untuk bottleneck pada disk I / O (bukan CPU / memori), pada sistem modern dengan banyak RAM. Jika dibiarkan berjalan lebih lama, selisihnya akan lebih kecil. (Tergantung pada seberapa buruk implementasi bash yang Anda gunakan, dan kecepatan relatif CPU dan disk, Anda mungkin bahkan tidak memenuhi I / O disk dengan bash).Jawaban:
singkatnya:
yes
menunjukkan perilaku yang mirip dengan sebagian besar utilitas standar lainnya yang biasanya menulis ke STREAM FILE dengan output buffered oleh libC via stdio . Ini hanya melakukan syscallwrite()
setiap 4kb (16kb atau 64kb) atau apa pun blok output BUFSIZ .echo
adalahwrite()
perGNU
. Itu banyak dari modus-switching (yang tidak, tampaknya, sebagai mahal sebagai konteks-switch ) .Dan itu sama sekali tidak menyebutkan bahwa, selain loop optimasi awal,
yes
adalah loop C sangat kecil, dikompilasi dan loop shell Anda sama sekali tidak sebanding dengan program yang dioptimalkan kompiler.tapi saya salah:
Ketika saya mengatakan sebelumnya bahwa
yes
stdio yang digunakan, saya hanya berasumsi itu terjadi karena berperilaku seperti yang dilakukan. Ini tidak benar - hanya meniru perilaku mereka dengan cara ini. Apa yang sebenarnya dilakukannya sangat mirip dengan analog dengan hal yang saya lakukan di bawah ini dengan shell: pertama-tama loop untuk mengacaukan argumennya (atauy
jika tidak ada) sampai mereka tidak dapat tumbuh lagi tanpa melebihiBUFSIZ
.Komentar dari sumber segera sebelum status
for
loop yang relevan :yes
apakah itu sendiriwrite()
setelah itu.penyimpangan:
(Seperti awalnya termasuk dalam pertanyaan dan disimpan untuk konteks untuk penjelasan yang mungkin informatif sudah ditulis di sini) :
The
timeout
masalah Anda dengan substitusi perintah - saya pikir saya mendapatkannya sekarang, dan dapat menjelaskan mengapa itu tidak berhenti.timeout
tidak memulai karena baris perintahnya tidak pernah dijalankan. Cangkang Anda mencangkang cangkang anak, membuka pipa di stdout-nya, dan membacanya. Ini akan berhenti membaca ketika anak berhenti, dan kemudian akan menafsirkan semua anak menulis untuk$IFS
ekspansi mangling dan glob, dan dengan hasilnya ia akan mengganti semuanya dari$(
yang cocok)
.Tetapi jika anak adalah loop tanpa akhir yang tidak pernah menulis ke pipa, maka anak tidak pernah berhenti looping, dan
timeout
baris perintah tidak pernah selesai sebelumnya (seperti yang saya duga) Anda lakukanCTRL-C
dan bunuh loop anak. Jadi tidak pernahtimeout
bisa mematikan loop yang harus diselesaikan sebelum bisa mulai.timeout
s lainnya :... sama sekali tidak relevan dengan masalah kinerja Anda karena jumlah waktu yang harus dihabiskan oleh program shell Anda untuk beralih antara mode pengguna dan kernel untuk menangani output.
timeout
, bagaimanapun, tidak sefleksibel mungkin untuk tujuan ini: di mana shell unggul dalam kemampuan mereka untuk mengurai argumen dan mengelola proses lainnya.Seperti dicatat di tempat lain, hanya memindahkan
[fd-num] >> named_file
redirection Anda ke target keluaran loop daripada hanya mengarahkan output di sana untuk perintah looped secara substansial dapat meningkatkan kinerja karena dengan cara itu setidaknyaopen()
syscall hanya perlu dilakukan sekali. Ini juga dilakukan di bawah dengan|
pipa yang ditargetkan sebagai output untuk loop dalam.perbandingan langsung:
Anda mungkin suka:
Yang jenis seperti perintah sub hubungan dijelaskan sebelumnya, tetapi tidak ada pipa dan anak dilatarbelakangi sampai membunuh orang tua. Dalam
yes
kasus orang tua sebenarnya telah diganti sejak anak itu lahir, tetapi shell memanggilyes
dengan overlay prosesnya sendiri dengan yang baru dan sehingga PID tetap sama dan anak zombie-nya masih tahu siapa yang harus dibunuh setelah semua.buffer yang lebih besar:
Sekarang mari kita lihat tentang meningkatkan
write()
buffer shell .Saya memilih nomor itu karena string keluaran lebih dari 1 kb sedang dibagi menjadi terpisah
write()
untuk saya. Dan inilah loop lagi:Itu 300 kali jumlah data yang ditulis oleh shell dalam jumlah waktu yang sama untuk tes ini daripada yang terakhir. Tidak terlalu buruk. Tapi ternyata tidak
yes
.terkait:
Seperti yang diminta, ada deskripsi yang lebih menyeluruh daripada sekadar komentar kode tentang apa yang dilakukan di sini di tautan ini .
sumber
dd
adalah salah satu alat standar yang pasti tidak menggunakan stdio, misalnya. kebanyakan orang lain melakukannya.open
(sudah ada)write
DANclose
(yang saya percaya masih menunggu flush), DAN membuat proses baru dan mengeksekusidate
, untuk setiap loop.wc -l
loop denganbash
saya mendapatkan 1/5 dari outputsh
loop -bash
mengelola sedikit lebih dari 100rbwrites()
sampaidash
500rb.for((sec0=`date +%S`;...
untuk mengontrol waktu dan pengalihan dalam loop, bukan perbaikan selanjutnya.Pertanyaan yang lebih baik adalah mengapa shell Anda menulis file dengan sangat lambat. Setiap program terkompilasi mandiri yang menggunakan syscalls menulis file secara bertanggung jawab (tidak menyiram setiap karakter pada suatu waktu) akan melakukannya dengan cepat. Apa yang Anda lakukan, adalah menulis baris dalam bahasa yang ditafsirkan (shell), dan di samping itu Anda melakukan banyak operasi input output yang tidak perlu. Apa yang
yes
dilakukan:Apa yang dilakukan skrip Anda:
date
perintah eksternal dan simpan hasilnya (hanya dalam versi asli - dalam versi revisi Anda mendapatkan faktor 10 dengan tidak melakukan ini)echo
perintah parse , mengenalinya (dengan beberapa kode kode yang cocok) sebagai shell builtin, memanggil parameter ekspansi dan segala sesuatu yang lain pada argumen "GNU", dan akhirnya menulis baris ke file yang terbukaBagian yang mahal: keseluruhan interpretasinya sangat mahal (bash melakukan banyak sekali preprocessing dari semua input - string Anda berpotensi mengandung substitusi variabel, proses substitusi, ekspansi brace, karakter pelarian dan banyak lagi), setiap panggilan dari sebuah builtin adalah mungkin pernyataan beralih dengan pengalihan ke fungsi yang berkaitan dengan builtin, dan yang sangat penting, Anda membuka dan menutup file untuk setiap baris output. Anda bisa meletakkan di
>> file
luar loop sementara untuk membuatnya lebih cepat , tetapi Anda masih dalam bahasa yang ditafsirkan. Anda cukup beruntungecho
adalah shell builtin, bukan perintah eksternal - jika tidak, loop Anda akan melibatkan pembuatan proses baru (fork & exec) pada setiap iterasi tunggal. Yang akan menghentikan proses ini - Anda melihat betapa mahalnya itu ketika Anda memilikidate
perintah dalam loop.sumber
Jawaban lain telah membahas poin utama. Di samping catatan, Anda dapat meningkatkan throughput loop sementara Anda dengan menulis ke file output di akhir perhitungan. Membandingkan:
dengan
sumber