例を挙げましょう。
$ timeout 1 yes "GNU" > file1
$ wc -l file1
11504640 file1
$ for ((sec0=`date +%S`;sec<=$(($sec0+5));sec=`date +%S`)); do echo "GNU" >> file2; done
$ wc -l file2
1953 file2
ここで、コマンドyes
が11504640
行を1秒で書き込めますが、書き込みは1953
行は5秒でbashのfor
とecho
を使用します。
コメントで示唆されているように、より効率的にするためのさまざまなトリックがありますが、yes
の速度に匹敵するものはありません。
$ ( while :; do echo "GNU" >> file3; done) & pid=$! ; sleep 1 ; kill $pid
[1] 3054
$ wc -l file3
19596 file3
$ timeout 1 bash -c 'while true; do echo "GNU" >> file4; done'
$ wc -l file4
18912 file4
これらは、1秒間に最大2万行を書き込むことができます。さらに、次のようにさらに改善できます。
$ timeout 1 bash -c 'while true; do echo "GNU"; done >> file5'
$ wc -l file5
34517 file5
$ ( while :; do echo "GNU"; done >> file6 ) & pid=$! ; sleep 1 ; kill $pid
[1] 5690
$ wc -l file6
40961 file6
これにより、1秒間に最大4万行が得られます。 1秒間に約1100万行を書き込むことができるyes
とは程遠いですが、
それで、yes
はどのようにファイルにすばやく書き込むのですか?
yes
は、通常 write から FILE STREAM に出力がバッファリングされる他のほとんどの標準ユーティリティと同様の動作を示します。 stdio によるlibCこれらは、syscall write()
を4kbごとにのみ実行します(16kbまたは64kb)または出力ブロック [〜#〜] bufsiz [〜#〜] はです。 echo
はGNU
ごとのwrite()
です。これはlot of mode-switching(これは明らかに コンテキストほどコストがかかりません)スイッチ )。
最初の最適化ループに加えて、yes
は非常にシンプルで小さなコンパイル済みCループであり、シェルループはコンパイラー最適化プログラムに匹敵するものではありません。
以前にyes
がstdioを使用することを言ったとき、私はそれがそうであると思っていました。これは正しくありませんでした。この方法で動作をエミュレートするだけです。それが実際に行うことは、シェルを使用して以下で行ったものと非常に似ています。最初にループして、引数を統合します(またはy
なしの場合)成長しなくなるまでBUFSIZ
を超えずに。
関連するfor
ループの直前にある source からのコメントは次のとおりです。
/* Buffer data locally once, rather than having the
large overhead of stdio buffering each item. */
yes
は、その後独自のwrite()
sを実行します。
(元々質問に含まれており、すでにここに書かれた有益な説明へのコンテキストのために保持されている):
timeout 1 $(while true; do echo "GNU">>file2; done;)
を試しましたが、ループを停止できません。
コマンド置換で発生するtimeout
の問題-私は今それを理解していると思いますが、停止しない理由を説明できます。 timeout
は、コマンドラインが実行されないため、起動しません。シェルは子シェルをフォークし、その標準出力でパイプを開いて読み取ります。子が終了すると読み取りを停止し、次に$IFS
のマングリングとグロブの展開用に書き込まれたすべての子を解釈し、その結果、$(
から対応する)
までのすべてを置き換えます。
しかし、子がパイプに書き込みを行わない無限ループである場合、子はループを停止することはなく、timeout
のコマンドラインは(推測どおり)の前に完了しません。 CTRL-C
および子ループを強制終了します。したがって、timeout
can never開始する前に完了する必要があるループを強制終了します。
timeout
s:... Shellプログラムが出力を処理するためにユーザーモードとカーネルモード間の切り替えに費やす必要がある時間の長さほど、パフォーマンスの問題とはそれほど関係ありません。ただし、timeout
は、シェルがこの目的に使用できるほど柔軟ではありません。Excelは、引数をマングルし、他のプロセスを管理する機能を備えています。
他の場所で説明されているように、ループの出力先にループするだけでなく、ループの出力先に[fd-num] >> named_file
リダイレクトを移動するだけで、少なくとも のパフォーマンスが大幅に向上します。 open()
syscallは一度だけ実行する必要があります。これも、内部ループの出力としてターゲットに設定された|
パイプを使用して以下で実行されます。
あなたは好きかもしれません:
for cmd in exec\ yes 'while echo y; do :; done'
do set +m
sh -c '{ sleep 1; kill "$$"; }&'"$cmd" | wc -l
set -m
done
256659456
505401
これはkindのコマンドサブ関係と同じですが、パイプはなく、子が親を強制終了するまでバックグラウンドで処理されます。 yes
の場合、子が生成されてから親は実際に置き換えられていますが、シェルは独自のプロセスを新しいプロセスでオーバーレイすることによりyes
を呼び出します。これにより、PIDは同じままで、そのゾンビが子供はまだ結局誰を殺すか知っています。
シェルのwrite()
バッファーの増加について見てみましょう。
IFS="
"; set y "" ### sets up the macro expansion
until [ "${512+1}" ] ### gather at least 512 args
do set "$@$@";done ### exponentially expands "$@"
printf %s "$*"| wc -c ### 1 write of 512 concatenated "y\n"'s
1024
1kbを超える出力文字列が個別のwrite()
に分割されていたため、この番号を選択しました。そしてここに再びループがあります:
for cmd in 'exec yes' \
'until [ "${512+:}" ]; do set "$@$@"; done
while printf %s "$*"; do :; done'
do set +m
sh -c $'IFS="\n"; { sleep 1; kill "$$"; }&'"$cmd" shyes y ""| wc -l
set -m
done
268627968
15850496
これは、前回のテストよりも、このテストの同じ時間内にシェルによって書き込まれたデータ量の300倍です。汚すぎる格好はやめて。しかし、それはyes
ではありません。
this link でここで行われていることについての単なるコードコメントよりも、要求されたとおりの詳細な説明があります。
より良い質問は、シェルがファイルを非常にゆっくりと書き込む理由です。責任を持って(一度にすべての文字をフラッシュするのではなく)ファイル書き込みシステムコールを使用する自己完結型のコンパイル済みプログラムは、それをかなり迅速に実行します。あなたがやっていることは、解釈された言語(シェル)で行を書いていて、それに加えて不必要な入出力操作のロット。 yes
が行うこと:
スクリプトの機能:
date
外部コマンドを呼び出してその出力を保存します(元のバージョンでのみ-改訂バージョンでは、これを行わないことで10倍になります)echo
コマンドを解析し、それを(いくつかのパターンマッチングコードで)シェル組み込みとして認識し、引数「GNU」でパラメーター展開およびその他すべてを呼び出し、最後に開いているファイルに行を書き込みます高価な部分:全体の解釈は非常に高価です(bashはすべての入力の非常に多くの前処理を行っています-文字列に変数置換、プロセス置換、中括弧拡張、エスケープ文字などが含まれる可能性があります)、組み込みのすべての呼び出しはおそらく、組み込みを処理する関数へのリダイレクトを伴うswitchステートメントであり、非常に重要なことに、出力のすべての行のファイルを開いたり閉じたりします。あなたは>> file
whileループの外ではるかに速くにしますが、まだインタプリタ言語です。 echo
が外部コマンドではなくシェルの組み込みであることは非常に幸運です。それ以外の場合、ループはすべての反復で新しいプロセス(fork&exec)を作成することになります。これはプロセスを停止して停止させます-ループにdate
コマンドがあると、どれほどのコストがかかるかがわかりました。
他の答えは主要なポイントに対処しました。余談ですが、計算の最後に出力ファイルに書き込むことで、whileループのスループットを向上させることができます。比較:
$ i=0;time while [ $i -le 1000 ]; do ((++i)); echo "GNU" >>/tmp/f; done;
real 0m0.080s
user 0m0.032s
sys 0m0.037s
と
$ i=0;time while [ $i -le 1000 ]; do ((++i)); echo "GNU"; done>>/tmp/f;
real 0m0.030s
user 0m0.019s
sys 0m0.011s