「はい」を使用すると、ファイルにどのようにすばやく書き込むことができますか?

「はい」を使用すると、ファイルにどのようにすばやく書き込むことができますか?

たとえば、見てみましょう。

$ 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を使用して115046405秒で行を書くことができることがわかります。1953forecho

コメントで提案されているように、効率を向上させるさまざまな方法がありますが、その速度に近い方法はありません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はい。echowrite()GNU。それはたくさん~のモード切替 (もちろんそうではありませんが、コンテキストスイッチ)

言うまでもなく、初期最適化ループ以外はyes非常にシンプルで小さなコンパイルされたCループなので、シェルループはコンパイラオプティマイザと比較できません。


しかし私は間違っていた:

私が以前yesuseと言ったとき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


費用は次のとおりです。

リクエストに応じて、単純なコードコメントよりも包括的な説明をここで見ることができます。このリンク

おすすめ記事