Saya membandingkan yang berikut ini
tail -n 1000000 stdout.log | grep -c '"success": true'
tail -n 1000000 stdout.log | grep -c '"success": false'
dengan berikut ini
log=$(tail -n 1000000 stdout.log)
echo "$log" | grep -c '"success": true'
echo "$log" | grep -c '"success": false'
dan mengejutkan yang kedua memakan waktu hampir 3 kali lebih lama dari yang pertama. Seharusnya lebih cepat, bukan?
bash
performance
io
phhehehe
sumber
sumber
$( command substitution )
yaitu tidak mengalir. Semua sisanya terjadi melalui pipa secara bersamaan, tetapi dalam contoh kedua Anda harus menunggulog=
sampai selesai. Cobalah dengan << DI SINI \ n $ {log = $ (command)} \ nHERE - lihat apa yang Anda dapatkan.grep
, Anda mungkin melihat beberapatee
peningkatan kecepatan menggunakan sehingga file tersebut pasti hanya dibaca sekali.cat stdout.log | tee >/dev/null >(grep -c 'true'>true.cnt) >(grep -c 'false'>false.cnt); cat true.cnt; cat false.cnt
tail -n 10000 | fgrep -c '"success": true'
false.Jawaban:
Di satu sisi, metode pertama memanggil
tail
dua kali, sehingga harus melakukan lebih banyak pekerjaan daripada metode kedua yang hanya melakukan ini sekali. Di sisi lain, metode kedua harus menyalin data ke dalam shell dan kemudian mundur, sehingga harus melakukan lebih banyak pekerjaan daripada versi pertama di manatail
langsung disalurkan kegrep
. Metode pertama memiliki keunggulan ekstra pada mesin multi-prosesor:grep
dapat bekerja secara paralel dengantail
, sedangkan metode kedua adalah serial, pertamatail
, kemudiangrep
.Jadi tidak ada alasan yang jelas mengapa yang satu harus lebih cepat dari yang lain.
Jika Anda ingin melihat apa yang terjadi, lihat sistem apa yang dibuat oleh shell. Coba dengan cangkang yang berbeda juga.
Dengan metode 1, tahapan utamanya adalah:
tail
membaca dan berusaha menemukan titik awalnya.tail
menulis potongan 4096-byte yanggrep
berbunyi secepat mereka diproduksi.Dengan metode 2, tahapan utamanya adalah:
tail
membaca dan berusaha menemukan titik awalnya.tail
menulis 4096-byte bash yang bash bertuliskan 128 byte pada satu waktu, dan zsh membaca 4096 byte sekaligus.grep
berbunyi secepat yang dihasilkan.Potongan 128 byte Bash ketika membaca output dari substitusi perintah memperlambatnya secara signifikan; zsh keluar secepat metode 1 untuk saya. Jarak tempuh Anda dapat bervariasi tergantung pada jenis dan nomor CPU, konfigurasi penjadwal, versi alat yang terlibat, dan ukuran data.
sumber
st_blksize
nilai untuk pipa, yaitu 4096 pada mesin ini (dan saya tidak tahu apakah itu karena ukuran halaman MMU). Bash's 128 harus berupa konstanta bawaan.Saya telah melakukan tes berikut dan pada sistem saya perbedaan yang dihasilkan sekitar 100 kali lebih lama untuk skrip kedua.
File saya adalah output strace yang disebut
bigfile
Skrip
Saya sebenarnya tidak memiliki korek api untuk grep sehingga tidak ada yang ditulis ke pipa terakhir sampai
wc -l
Berikut ini waktunya:
Jadi saya menjalankan dua skrip lagi melalui perintah strace
Berikut adalah hasil jejaknya:
Dan p2.strace
Analisis
Tidak mengherankan, dalam kedua kasus sebagian besar waktu dihabiskan menunggu proses untuk menyelesaikan, tetapi p2 menunggu 2,63 kali lebih lama dari p1, dan seperti yang telah disebutkan, Anda mulai terlambat di p2.sh.
Jadi sekarang lupakan
waitpid
, abaikan%
kolom dan lihat kolom detik di kedua jejak.Waktu terbesar p1 menghabiskan sebagian besar waktunya dalam membaca mungkin dapat dimengerti, karena ada file besar untuk dibaca, tetapi p2 menghabiskan 28,82 kali lebih lama dalam membaca daripada p1. -
bash
tidak mengharapkan untuk membaca file sebesar itu menjadi variabel dan mungkin membaca buffer pada satu waktu, membelah menjadi beberapa baris dan kemudian mendapatkan yang lain.baca hitungan p2 adalah 705k vs 84k untuk p1, masing-masing read membutuhkan konteks switch ke ruang kernel dan keluar lagi. Hampir 10 kali jumlah bacaan dan konteks beralih.
Waktunya menulis p2 menghabiskan 41,93 kali lebih lama menulis daripada p1
tulis hitungan p1 tidak lebih banyak menulis daripada p2, 42k vs 21k, namun mereka jauh lebih cepat.
Mungkin karena
echo
garis-garis menjadi yanggrep
bertentangan dengan buffer penulisan ekor.Lebih jauh lagi , p2 menghabiskan lebih banyak waktu dalam menulis daripada membaca, p1 adalah sebaliknya!
Faktor lain Lihatlah jumlah
brk
panggilan sistem: p2 menghabiskan 2,42 kali lebih lama melanggar daripada membaca! Di p1 (bahkan tidak mendaftar).brk
adalah ketika program perlu memperluas ruang alamatnya karena cukup tidak dialokasikan pada awalnya, ini mungkin karena bash harus membaca file itu ke dalam variabel, dan tidak berharap itu menjadi sebesar itu, dan seperti yang disebutkan @scai, jika file menjadi terlalu besar, bahkan itu tidak akan berhasil.tail
mungkin merupakan pembaca file yang cukup efisien, karena ini adalah apa yang dirancang untuk dilakukan, mungkin memmaps file dan memindai jeda baris, sehingga memungkinkan kernel untuk mengoptimalkan i / o. bash tidak sebagus waktu yang dihabiskan untuk membaca dan menulis.p2 menghabiskan 44ms dan 41ms
clone
danexecv
itu bukan jumlah yang terukur untuk p1. Mungkin bash membaca dan membuat variabel dari tail.Akhirnya Totals p1 mengeksekusi ~ 150k panggilan sistem vs p2 740k (4,93 kali lebih besar).
Menghilangkan waitpid, p1 menghabiskan 0,014416 detik untuk mengeksekusi panggilan sistem, p2 0,439132 detik (30 kali lebih lama).
Jadi tampaknya p2 menghabiskan sebagian besar waktu di ruang pengguna tanpa melakukan apa pun kecuali menunggu panggilan sistem untuk menyelesaikan dan kernel untuk mengatur kembali memori, p1 melakukan lebih banyak menulis, tetapi lebih efisien dan menyebabkan beban sistem secara signifikan lebih sedikit, dan karenanya lebih cepat.
Kesimpulan
Saya tidak akan pernah mencoba khawatir tentang pengkodean melalui memori saat menulis skrip bash, itu tidak berarti mengatakan bahwa Anda tidak mencoba menjadi efisien.
tail
dirancang untuk melakukan apa yang dilakukannya, mungkinmemory maps
file tersebut sehingga efisien untuk dibaca dan memungkinkan kernel untuk mengoptimalkan i / o.Cara yang lebih baik untuk mengoptimalkan masalah Anda mungkin dengan yang pertama
grep
untuk '"sukses":' garis dan kemudian menghitung tanda dan kesalahan,grep
memiliki opsi menghitung yang lagi-lagi menghindariwc -l
, atau bahkan lebih baik lagi, menyalurkan ekor melaluiawk
dan menghitung tanda dan falses bersamaan. p2 tidak hanya memakan waktu lama tetapi menambah beban ke sistem sementara memori sedang dikocok dengan brks.sumber
Sebenarnya solusi pertama membaca file ke dalam memori juga! Ini disebut caching dan secara otomatis dilakukan oleh sistem operasi.
Dan seperti yang sudah dijelaskan dengan benar oleh mikeserv , solusi pertama exectutes
grep
ketika file sedang dibaca sedangkan solusi kedua mengeksekusi setelah file telah dibaca olehtail
.Jadi solusi pertama lebih cepat karena berbagai optimasi. Tetapi ini tidak selalu harus benar. Untuk file yang benar-benar besar yang OS putuskan untuk tidak melakukan cache solusi kedua bisa menjadi lebih cepat. Tetapi perhatikan bahwa untuk file yang lebih besar yang tidak sesuai dengan memori Anda, solusi kedua tidak akan bekerja sama sekali.
sumber
Saya pikir perbedaan utamanya sangat sederhana yaitu
echo
lambat. Pertimbangkan ini:Seperti yang Anda lihat di atas, langkah yang memakan waktu adalah mencetak data. Jika Anda hanya mengarahkan ke file baru dan lihat itu jauh lebih cepat ketika hanya membaca file sekali.
Dan seperti yang diminta, dengan string di sini:
Yang ini bahkan lebih lambat, mungkin karena string di sini menggabungkan semua data ke satu garis panjang dan itu akan memperlambat
grep
:Jika variabel dikutip sehingga tidak terjadi pemisahan, hal-hal sedikit lebih cepat:
Tetapi masih lambat karena langkah pembatasan tingkat adalah mencetak data.
sumber
<<<
, akan menarik untuk melihat apakah itu membuat perbedaan.Saya sudah mencoba ini ... Pertama, saya membuat file:
Jika Anda menjalankan sendiri di atas, Anda harus menghasilkan 1,5 juta baris
/tmp/log
dengan rasio"success": "true"
2:01 untuk"success": "false"
garis.Hal berikutnya yang saya lakukan adalah menjalankan beberapa tes. Saya menjalankan semua tes melalui proxy
sh
begitutime
hanya perlu menonton satu proses - dan karena itu dapat menunjukkan hasil tunggal untuk seluruh pekerjaan.Ini tampaknya menjadi yang tercepat, meskipun itu menambahkan deskriptor file kedua dan
tee,
meskipun saya pikir saya bisa menjelaskan mengapa:Inilah yang pertama Anda:
Dan yang kedua:
Anda dapat melihat bahwa dalam pengujian saya ada lebih dari 3 * perbedaan kecepatan ketika membacanya menjadi variabel seperti yang Anda lakukan.
Saya pikir bagian dari itu adalah bahwa variabel shell harus dipecah dan ditangani oleh shell ketika sedang dibaca - itu bukan file.
A
here-document
di sisi lain, untuk semua maksud dan tujuan, adalahfile
-file descriptor,
pokoknya. Dan seperti yang kita semua tahu - Unix berfungsi dengan file.Yang paling menarik bagi saya
here-docs
adalah bahwa Anda dapat memanipulasi merekafile-descriptors
- secara langsung|pipe
- dan mengeksekusinya. Ini sangat berguna karena memungkinkan Anda sedikit lebih banyak kebebasan dalam menunjukkan|pipe
tempat yang Anda inginkan.Aku harus
tee
yangtail
karena pertamagrep
makan yanghere-doc |pipe
dan ada yang tersisa untuk yang kedua untuk membaca. Tetapi karena saya|piped
memahaminya/dev/fd/3
dan mengambilnya lagi untuk diberikan,>&1 stdout,
itu tidak masalah. Jika Anda menggunakangrep -c
banyak orang lain merekomendasikan:Ini bahkan lebih cepat.
Tapi ketika saya menjalankannya tanpa
. sourcing
ituheredoc
saya tidak bisa berhasil latar belakang proses pertama yang menjalankan mereka sepenuhnya secara bersamaan. Ini dia tanpa latar belakang sepenuhnya:Tetapi ketika saya menambahkan
&:
Meski begitu, perbedaannya tampaknya hanya beberapa ratus detik, setidaknya bagi saya, jadi anggaplah seperti yang Anda mau.
Lagi pula, alasan mengapa ia berjalan lebih cepat
tee
adalah karena keduanyagreps
berjalan pada saat yang sama dengan hanya satu permintaantail. tee
duplikat file untuk kami dan membagi ke yang keduagrep
proses semua dalam-aliran - semuanya berjalan sekaligus dari awal hingga akhir, sehingga mereka semua selesai sekitar waktu yang sama juga.Jadi kembali ke contoh pertama Anda:
Dan yang kedua:
Tetapi ketika kami membagi input kami dan menjalankan proses kami secara bersamaan:
sumber