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
$ wc -l bigfile.log
1617000 bigfile.log
Skrip
xtian@clafujiu:~/tmp$ cat p1.sh
tail -n 1000000 bigfile.log | grep '"success": true' | wc -l
tail -n 1000000 bigfile.log | grep '"success": false' | wc -l
xtian@clafujiu:~/tmp$ cat p2.sh
log=$(tail -n 1000000 bigfile.log)
echo "$log" | grep '"success": true' | wc -l
echo "$log" | grep '"success": true' | wc -l
Saya sebenarnya tidak memiliki korek api untuk grep sehingga tidak ada yang ditulis ke pipa terakhir sampai wc -l
Berikut ini waktunya:
xtian@clafujiu:~/tmp$ time bash p1.sh
0
0
real 0m0.381s
user 0m0.248s
sys 0m0.280s
xtian@clafujiu:~/tmp$ time bash p2.sh
0
0
real 0m46.060s
user 0m43.903s
sys 0m2.176s
Jadi saya menjalankan dua skrip lagi melalui perintah strace
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
Berikut adalah hasil jejaknya:
$ cat p1.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
97.24 0.508109 63514 8 2 waitpid
1.61 0.008388 0 84569 read
1.08 0.005659 0 42448 write
0.06 0.000328 0 21233 _llseek
0.00 0.000024 0 204 146 stat64
0.00 0.000017 0 137 fstat64
0.00 0.000000 0 283 149 open
0.00 0.000000 0 180 8 close
...
0.00 0.000000 0 162 mmap2
0.00 0.000000 0 29 getuid32
0.00 0.000000 0 29 getgid32
0.00 0.000000 0 29 geteuid32
0.00 0.000000 0 29 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 7 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.522525 149618 332 total
Dan p2.strace
$ cat p2.strace
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
75.27 1.336886 133689 10 3 waitpid
13.36 0.237266 11 21231 write
4.65 0.082527 1115 74 brk
2.48 0.044000 7333 6 execve
2.31 0.040998 5857 7 clone
1.91 0.033965 0 705681 read
0.02 0.000376 0 10619 _llseek
0.00 0.000000 0 248 132 open
...
0.00 0.000000 0 141 mmap2
0.00 0.000000 0 176 126 stat64
0.00 0.000000 0 118 fstat64
0.00 0.000000 0 25 getuid32
0.00 0.000000 0 25 getgid32
0.00 0.000000 0 25 geteuid32
0.00 0.000000 0 25 getegid32
0.00 0.000000 0 3 1 fcntl64
0.00 0.000000 0 6 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 1.776018 738827 293 total
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 yang grep
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
dan execv
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, mungkin memory 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 menghindari wc -l
, atau bahkan lebih baik lagi, menyalurkan ekor melalui awk
dan menghitung tanda dan falses bersamaan. p2 tidak hanya memakan waktu lama tetapi menambah beban ke sistem sementara memori sedang dikocok dengan brks.