web-dev-qa-db-ja.com

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

bashスクリプトでは、/proc/ファイルのさまざまな値が必要です。今までは、何十行ものファイルを直接次のようにgreppingしています。

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

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

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

ファイルを複数回開くのではなく、これを1回開いて変数の内容を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

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

36
dessert

ここでは、ファイルを開く変数の内容を読み取るではありませんが、余分なプロセスをフォークするかどうかの詳細。

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

その中で最も費用のかかる部分は、プロセスの分岐とgrepユーティリティとそのライブラリの依存関係の読み込み、動的リンクの実行、ロケールデータベースのオープン、ディスク上にある数十のファイル(ただし、メモリにキャッシュされている可能性が高い)です。

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

その上でstrace -cを実行すると、/proc/meminfoを読み取るために使用される1つのopen()および1つのread()システムコールは、他のgrepが開始するすべてのものと比較してピーナッツです(strace -cはフォークを数えません)。

に:

a=$(</proc/meminfo)

$(<...) ksh演算子をサポートするほとんどのシェルでは、シェルはファイルを開いてその内容を読み取るだけです(そして、末尾の改行文字を削除します)。 bashは、読み取りを行うためにプロセスをforkし、データをパイプ経由で親に渡すという点で異なり、はるかに効率的ではありません。しかし、ここでは一度だけ行われるため、問題ではありません。

に:

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

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

Zsh <<<演算子を使用すると、わずかに速くなる場合があります。

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

Zshとbashでは、これは$aの内容を一時ファイルに書き込むことによって行われます。これは、追加のプロセスを生成するよりもコストはかかりませんが、データを/proc/meminfoから直接取得する場合と比較すると、おそらくメリットはありません。一時ファイルの書き込みは反復ごとに行われるため、これはディスクに/proc/meminfoをコピーするアプローチよりも効率的ではありません。

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

 grep '^MemFree' << EOF
 $a
 EOF

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

ただし、パイプの処理とプロセスの同期は、データを/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'*}

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

47

最初のケースでは、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
6
Prvt_Yadav

どちらの場合もexternalコマンドを呼び出しています(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秒しかかかりません。

1
Isaac