可変コンテンツを読み取るよりもファイルを開くのが速いのはなぜですか?


36

ではbashスクリプト私はからのさまざまな値必要な/proc/ファイルを。今までは、そのようにファイルを直接grepする数十行がありました。

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

それをより効率的にするために、ファイルの内容を変数に保存し、それをgrepしました:

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

ファイルを複数回開くのではなく、一度開くだけで変数の内容をgrepする必要があります。

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

同じことが当てはまりますdashzsh/proc/理由としてファイルの特別な状態が疑われましたが、コンテンツを/proc/meminfo通常のファイルにコピーして使用すると、結果は同じになります。

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

hereの文字列を使用してパイプを保存すると、わずかに高速になりますが、ファイルの場合ほど高速ではありません。

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

変数から同じコンテンツを読み取るよりもファイルを開く方が速いのはなぜですか?


@ l0b0この仮定は誤りではありません。質問は私がどのように思いついたのかを示し、答えはなぜそうなのかを説明しています。あなたの編集は、タイトルの質問にそれ以上答えないようになりました:彼らはそうであるかどうかを言いません。
デザート

わかりました。大部分のケースで見出しが間違っていたため、特定のメモリマップスペシャルファイルだけではありませんでした。
l0b0

@ l0b0いいえ、私はここに求めているものだという。「私は特別な状態が疑わ/proc/理由としてファイルを、私はの内容をコピーする際に/proc/meminfo通常のファイルと使用に結果が同じであることは、」それはないに特別な/proc/ファイル、通常のファイルの読み取りも高速です!
デザート

回答:


47

ここでは、それはについてではありませんファイルを開く変数の内容を読んだが、より多くの余分なプロセスをフォークかについて。

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfogrep開くプロセス/proc/meminfo(メモリ内の仮想ファイル、ディスクI / Oを含まない)がそれを読み取り、正規表現に一致するプロセスをフォークします。

その中で最も高価な部分は、プロセスをフォークし、grepユーティリティとそのライブラリの依存関係をロードし、動的リンクを行い、ロケールデータベースを開き、ディスク上にある(ただしメモリにキャッシュされている可能性が高い)ファイルです。

読み取りに関する部分/proc/meminfoは比較すると重要ではありません。カーネルはそこに情報を生成grepするのにほとんど時間を必要とせず、それを読むのにほとんど時間を必要としません。

そのstrace -c上で実行すると、読み取りに使用される1つopen()および1つのread()システムコールが、/proc/meminfo他のすべてのgrep起動と比較してピーナッツであることがわかります(strace -c分岐はカウントされません)。

に:

a=$(</proc/meminfo)

その$(<...)ksh演算子をサポートするほとんどのシェルでは、シェルはファイルを開いてその内容を読み取ります(そして、末尾の改行文字を取り除きます)。bashは、プロセスをフォークしてその読み取りを実行し、パイプを介して親にデータを渡すという点で異なります。しかし、ここでは、それは重要ではないので一度行われています。

に:

printf '%s\n' "$a" | grep '^MemFree'

シェルは2つのプロセスを生成する必要があります。これらのプロセスは同時に実行されていますが、パイプを介して相互に対話します。そのパイプの作成、分解、および書き込みと読み取りには多少のコストがかかります。はるかに大きなコストは、余分なプロセスの生成です。プロセスのスケジューリングも影響を及ぼします。

zsh <<<演算子を使用すると、少し速くなることがあります。

grep '^MemFree' <<< "$a"

zshとbashでは$a、一時ファイルにコンテンツを書き込むことで行われます。これは、余分なプロセスを生成するよりも安価ですが、データを直接取得する場合と比べておそらく利益はありません/proc/meminfo/proc/meminfo一時ファイルの書き込みは反復ごとに行われるため、ディスクにコピーするアプローチよりも効率は劣ります。

dashヒア文字列はサポートしていませんが、ヒアドキュメントは余分なプロセスの生成を伴わないパイプで実装されています。に:

 grep '^MemFree' << EOF
 $a
 EOF

シェルはパイプを作成し、プロセスを分岐します。子はgrepパイプの読み取り端としてstdinを使用して実行され、親はパイプのもう一方の端にコンテンツを書き込みます。

しかし、パイプの処理とプロセスの同期は、データを直接取得するよりも高価になる可能性があります/proc/meminfo

の内容/proc/meminfoは短く、制作にそれほど時間はかかりません。CPUサイクルをいくらか節約したい場合は、プロセスの分岐や外部コマンドの実行など、高価な部分を削除する必要があります。

のような:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

bashただし、パターンマッチングが非常に不十分な場合は避けてください。を使用するとzsh -o extendedglob、次のように短縮できます。

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

^多くのシェル(少なくとも、extendedglobオプションを使用したBourne、fish、rc、es、およびzsh)では特別であることに注意してください。引用することをお勧めします。また、echo任意のデータを出力するために使用できないことに注意してください(したがって、printf上記の私の使用)。


4
printfあなたの場合、シェルは2つのプロセスを生成する必要があると言いますが、シェルはprintf組み込まれていませんか?
デビッドコンラッド

6
@DavidConradですが、ほとんどのシェルは、現在のプロセスで実行できる部分のパイプラインを分析しようとしません。それはそれ自体を分岐させ、子供たちにそれを理解させます。この場合、親プロセスは2回分岐します。左側の子は、組み込みを見て実行します。右側の子はsees grepおよびexecsです。
chepner

1
@DavidConrad、パイプはIPCメカニズムであるため、いずれの場合も、両側は異なるプロセスで実行する必要があります。ではA | B、AT&T kshやzshなどBのシェルが組み込みコマンド、複合コマンド、または関数コマンドである場合に現在のシェルプロセスで実行されますが、現在のプロセスで実行されるものは知りませんA。どちらかといえば、それを行うには、A子プロセスで実行されているかのように複雑な方法でSIGPIPEを処理する必要がありBます。B親プロセスで実行する方がはるかに簡単です。
ステファンシャゼラス

Bashサポート<<<
D.ベンノーブル

1
@ D.BenKnoble、私はbashサポートしなかったことを意味するつもりはありませんでした<<<、ただ演算子がkshから来たzshように$(<...)来たというだけです。
ステファンシャゼラス

6

あなただけのgrepユーティリティを使用して、ファイルから何かを見つけて、あなたの最初のケースでは/proc/meminfo/procように、仮想ファイルシステムである/proc/meminfoファイルがメモリにある、そしてそれはそのコンテンツを取得するために非常に少し時間が必要です。

しかし、2番目のケースでは、パイプを作成してから、このパイプを使用して最初のコマンドの出力を2番目のコマンドに渡します。これはコストがかかります。

違いは/proc(メモリ内にあるため)パイプであるため、以下の例を参照してください。

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s

1

両方のケースで外部コマンドを呼び出しています(grep)。外部呼び出しにはサブシェルが必要です。そのシェルをフォークすることが遅延の根本的な原因です。両方のケースは類似しているため、同様の遅延があります。

外部ファイルを1回だけ読み取り、(変数から)複数回使用する場合は、シェルから出ないでください。

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

これは、grep呼び出しの完全な1秒ではなく、約0.1秒しかかかりません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.