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
dash
とzsh
についても同様です。 /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
変数から同じコンテンツを読み取るよりもファイルを開く方が速いのはなぜですか?
ここでは、ファイルを開く対変数の内容を読み取るではありませんが、余分なプロセスをフォークするかどうかの詳細。
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
はgrep
を実行するプロセスを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
の使用)。
最初のケースでは、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
どちらの場合も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秒しかかかりません。