次のテストを実行しましたが、私のシステムでは、2番目のスクリプトの結果の差は約100倍長くなります。
私のファイルはと呼ばれるstrace出力です bigfile
$ wc -l bigfile.log
1617000 bigfile.log
スクリプト
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
実際にはgrepに一致するものがないので、最後のパイプまで何も書き込まれません wc -l
タイミングは次のとおりです。
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
そこで、straceコマンドを使用して2つのスクリプトを再度実行しました
strace -cfo p1.strace bash p1.sh
strace -cfo p2.strace bash p2.sh
トレースの結果は次のとおりです。
$ 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
そして、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
分析
当然のことながら、どちらの場合もほとんどの時間はプロセスの完了を待つことに費やされますが、p2はp1より2.63倍長く待機します。
では、を忘れて、列をwaitpid
無視して、%
両方のトレースの秒列を見てください。
最大の時間 p1は、おそらく大きなファイルを読み込むために、おそらく読み取りにほとんどの時間を費やしますが、p2はp1の28.82倍の読み取りに費やします。- bash
このような大きなファイルを変数に読み込むことを期待しておらず、おそらく一度にバッファーを読み込み、行に分割してから別のものを取得します。
読み込みカウント p2は705k対p1の84kであり、各読み込みではコンテキストの切り替えが必要であり、カーネル空間に切り替えて再度出力します。読み取りおよびコンテキスト切り替えの数のほぼ10倍。
書き込み時間 p2は、p1よりも書き込みに41.93倍長くかかります
書き込みカウント p1は、p2よりも多くの書き込みを行いますが、42k対21kですが、はるかに高速です。
おそらく、末尾の書き込みバッファーとは対照的にecho
、行の数が多いためですgrep
。
さらに、p2は読み取りよりも書き込みに多くの時間を費やします。p1は逆です。
その他の要因brk
システムコールの数を見てください。p2は、読み取りよりも2.42倍長い中断を費やします。p1では(登録すらしません)。brk
最初に十分な容量が割り当てられなかったためにプログラムがアドレス空間を拡張する必要がある場合、これはおそらくbashがそのファイルを変数に読み込む必要があり、その大きさを期待していないためです。ファイルが大きすぎる場合でも、それは動作しません。
tail
これはおそらく非常に効率的なファイルリーダーです。これは、ファイルのmemmapを実行して改行をスキャンし、カーネルがI / Oを最適化できるように設計されているためです。bashは、読み取りと書き込みに費やした時間の両方であまり良くありません。
p2は44ミリ秒と41ミリ秒を費やしてclone
おりexecv
、p1の測定可能な量ではありません。おそらくbashを読み取って、末尾から変数を作成します。
最後に、合計 p1は、 p2 740k(4.93倍)に対して、約15万のシステムコールを実行します。
waitpidを削除すると、p1はシステムコールの実行に0.014416秒、p2は0.439132秒(30倍長い)かかります。
したがって、p2はユーザー空間でほとんどの時間を費やしてシステムコールの完了とカーネルのメモリ再編成を待機する以外に何もしませんが、p1はより多くの書き込みを実行しますが、より効率的でシステム負荷が大幅に少なくなり、したがって高速です。
結論
私は、bashスクリプトを記述するときにメモリを介したコーディングについて心配することは決してありません。
tail
おそらくmemory maps
ファイルであるため、読み取りが効率的になり、カーネルがI / Oを最適化できるようになります。
あなたの問題を最適化するためのより良い方法は、最初にあるかもしれないgrep
「『成功』:」の行と、その後truesとfalsesをカウントし、grep
カウント再び回避オプションがあるwc -l
に至るまで、まださらに良く、または、パイプ尾をawk
してtruesをカウントし、同時に偽。p2は時間がかかるだけでなく、メモリがbrksでシャッフルされている間にシステムに負荷を追加します。