たとえば、見てみましょう。
$ 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
ここでは、コマンドは1秒以内に行を書きますが、私はbashを使用して11504640
5秒で行を書くことができることがわかります。1953
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
毎秒最大20,000行を書き込むことができます。次のようにさらに改善することができます。
$ 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
これにより、毎秒40,000行を得ることができます。より良いですが、yes
それでも毎秒1,100万行を書くのは遠いです!
だから、yes
なぜファイルはそんなに速く書かれますか?
ベストアンサー1
簡単に言うと:
yes
通常、他のほとんどの標準ユーティリティと同様の動作を示します。書くにファイルストリーム出力は以下を介してlibCによってバッファリングされます。stdio
。これらはシステムコールのみを実行します。write()
それぞれ約4kb(16kbまたは64kb)または出力ブロックBUFSIZ
はい。echo
はwrite()
GNU
。それはたくさん~のモード切替 (もちろんそうではありませんが、コンテキストスイッチ)。
言うまでもなく、初期最適化ループ以外はyes
非常にシンプルで小さなコンパイルされたCループなので、シェルループはコンパイラオプティマイザと比較できません。
しかし私は間違っていた:
私が以前yes
useと言ったときstdio
、私はそれがそうする人と非常によく似ているので、そうだったと思いました。それは本当ではありません。ただ彼らの行動をそのように真似するだけです。実際に行うことは、シェルを使用して以下で行うことと非常によく似ています。まず、引数のマージを繰り返します。(またはy
そうでない場合)もう成長しなくなるまでそうではありませんBUFSIZ
。
さんのコメント源泉関連ループ状態の直前for
:
/* Buffer data locally once, rather than having the
large overhead of stdio buffering each item. */
yes
その後、自分のwrite()
作業を行います。
余談:
(元の質問に含まれており、ここで作成された潜在的に有益な説明の文脈のままです。):
試しましたが、
timeout 1 $(while true; do echo "GNU">>file2; done;)
ループを停止できませんでした。
コマンドの代替の問題timeout
- 理解し、なぜ停止しないのかを説明できるようです。timeout
コマンドラインが実行されていないため起動しません。シェルはサブシェルを分岐し、標準出力でパイプを開き、それを読み取ります。サブプロセスが終了したら、読み取りを停止し、再構成および$IFS
グローバル拡張のために作成されたすべてのサブプロセスを解釈し、結果に従って一致$(
するすべての項目を置き換えます)
。
ただし、子プロセスがパイプに書き込まれない無限ループの場合、子プロセスはループを停止せず、timeout
コマンドラインも決して停止しません。(私が推測した通り)Ctrl+を実行しCてサブループを終了します。だからtimeout
確認 いいえ始める前に完了する必要があるループを終了します。
その他timeout
:
...シェルプログラムが出力を処理するためにユーザーモードとカーネルモードを切り替えるのに必要な時間ほど、パフォーマンスの問題とは関係ありません。timeout
しかし、シェルほど柔軟ではありません。シェルの利点は、パラメータを処理し、他のプロセスを管理する能力にあります。
他の場所で指摘したように[fd-num] >> named_file
単にループコマンドの出力を指示するのではなく、ループの出力先にリダイレクトすると、パフォーマンスが大幅に向上する可能性があります。open()
システムコールは一度だけ完了できます。|
これは、ターゲットが内部ループの出力であるパイプを使用して以下でも実行されます。
直接比較:
あなたは次のようになります:
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
これはタイプ前述のコマンドとサブ関係に似ていますが、パイプと子プロセスがない場合は、親プロセスが終了するまでバックグラウンドにあります。この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
。
費用は次のとおりです。
リクエストに応じて、単純なコードコメントよりも包括的な説明をここで見ることができます。このリンク。