回答これ質問によって別の質問が生じました。
次のスクリプトは同じことをすると思います。最初のスクリプトはcat
ファイルを開いたままにしておく必要があるため、2番目のスクリプトはより速くなければなりませんが、2番目のスクリプトはファイルを一度開いてからEchoだけを開きます。変数:
(正しいコードについては更新されたセクションを参照してください。)
最初:
#!/bin/sh
for j in seq 10; do
cat input
done >> output
第二:
#!/bin/sh
i=`cat input`
for j in seq 10; do
echo $i
done >> output
そして入力は約50MBです。
ところで、2回目の試みをしてみると、変数をエコーする作業が途方もないi
作業なので、速度が遅すぎました。また、2番目のスクリプトでは、出力ファイルが予想より小さいなど、いくつかの問題が発生しました。
また、マニュアルページを確認しecho
てcat
比較しました。
echo - テキスト 1 行を表示します。
cat - ファイルをリンクして標準出力として印刷
しかし、私はその違いを理解していませんでした。
だから:
- 2番目のスクリプトでは、catはなぜそんなに速く、echoはそんなに遅いのですか?
- それとも変数に問題があるのでしょうか
i
? (マンページにecho
次のように出ているからです。「テキスト一行」だから私はそれが短い変数に対してのみ最適化しますi
。 ) - なぜ使用に問題がありますか
echo
?
修正する
私が使ったのは間違ってseq 10
いませんでした。`seq 10`
編集されたコードは次のとおりです。
最初:
#!/bin/sh
for j in `seq 10`; do
cat input
done >> output
第二:
#!/bin/sh
i=`cat input`
for j in `seq 10`; do
echo $i
done >> output
(特にありがとうロエマ.)
しかし、これはポイントではありません。ループが一度だけ発生しても同じ問題があります。cat
。echo
答え1
ここで考慮すべきいくつかの点があります。
i=`cat input`
コストがかかり、ケースごとに違いが多い。
これはコマンド置換という機能です。アイデアは、コマンドの出力全体から末尾の改行文字を引いた値をi
メモリ内の変数に格納することです。
この目的のために、シェルはサブシェルからコマンドを分岐し、パイプまたはソケットのペアを介して出力を読み込みます。ここから多くの変化が見られます。ここでは、50MiBファイルでbashがksh93より6倍遅いですが、zshより少し速いことがわかります。 yes yash
。
速度が遅くなる主な理由bash
は、パイプから一度に128バイトを読み取り(他のシェルは一度に4KiBまたは8KiBを読み取る)、システムコールのオーバーヘッドによって困難になるためです。
zsh
NULバイトをエスケープするには、いくつかの後処理が必要です(他のシェルはNULバイトで中断されます)、yash
マルチバイト文字を解析してより強力な処理を実行します。
すべてのシェルは末尾の改行文字を削除する必要があり、やや効率的です。
いくつかはNULバイトを処理し、他のものよりもエレガントにその存在を確認したいと思うかもしれません。
その後、メモリにこの大きな変数が含まれている場合、そのすべての操作には通常、より多くのメモリを割り当て、データ全体を処理することが含まれます。
ここで変数の内容をecho
。
幸いにもecho
シェルに組み込まれています。そうしないと、実行が失敗する可能性があります。パラメータリストが長すぎます。間違い。それでもパラメータリストの配列を構築するには、変数の内容をコピーする必要があります。
コマンド代替アプローチのもう一つの主な問題は、以下を呼び出すことです。分割+グローバル 演算子(変数を引用するのを忘れました)。
これを行うには、シェルは文字列を文字列として扱う必要があります。数値(一部のシェルはこれを行わずにこの領域に欠陥がありますが)、したがってUTF-8ロケールでは、これはUTF-8シーケンスを解析し(まだ以前のように実行されていない場合)、yash
文字列内の文字を見つけることを意味します。スペース、タブ、または改行が含まれている$IFS
場合$IFS
(デフォルトで含まれている)、アルゴリズムははるかに複雑で費用がかかります。この分割によって生成された単語は割り当ててコピーする必要があります。
地球の部分はより高価です。これらの単語の1つにグローバル文字(*
、、、)が含まれている場合、シェルはいくつかのディレクトリの内容を読み取り、高価なパターンマッチングを実行する必要があります(?
実装が非常に悪い)。[
bash
入力にこのような内容が含まれていると、/*/*/*/../../../*/*/*/../../../*/*/*
数千のディレクトリを一覧表示して数百MiBまで拡張できるため、非常に費用がかかります。
その後、echo
通常、いくつかの追加処理が実行される。一部の実装\x
では、受け取る引数のシーケンスを拡張します。これは、コンテンツを解析し、データの別の割り当てとコピーを解析することを意味します。
一方、ほとんどのシェルにはcat
組み込まれていないので、プロセスを分岐して実行することを意味します(したがって、コードとライブラリのロード)。ただし、最初の呼び出しの後、コードと入力ファイルの内容はメモリにキャッシュされます。一方、ブローカーはいないでしょう。cat
大量のデータを一度に読み込んで別途の処理なしで直書きする方式で、大容量メモリを割り当てる必要なくバッファだけを再利用すればよい。
これはまた、NULバイトをブロックせず、後続の改行を切り捨てないため、より安定していることを意味します。変数を引用してこれを回避できますが、キャストシーケンスを拡張しませんが、分割+グローブを実行しません。 、printf
代わりに)を使用すると、echo
これを防ぐことができます。
もっと最適化したい場合は、cat
何度も呼び出すのではなく、input
数回だけ渡してくださいcat
。
yes input | head -n 100 | xargs cat
100の代わりに3つのコマンドが実行されます。
変数バージョンをより信頼性の高いものにするには、次を使用しzsh
(他のシェルはNULバイトを処理できません)、次のことを行う必要があります。
zmodload zsh/mapfile
var=$mapfile[input]
repeat 10 print -rn -- "$var"
入力にNULバイトが含まれていないことがわかっている場合は、POSIXlyを介してこれを確実に実行できます(printf
組み込まれていないと機能しない可能性があります)。
i=$(cat input && echo .) || exit # add an extra .\n to avoid trimming newlines
i=${i%.} # remove that trailing dot (the \n was removed by cmdsubst)
n=10
while [ "$n" -gt 10 ]; do
printf %s "$i"
n=$((n - 1))
done
cat
しかし、これはループで使用するよりも効率的ではありません(入力が非常に小さくない限り)。
答え2
問題はcat
andではecho
なく、忘れられた引用変数です$i
。
Bourneなどのシェルスクリプト(除外zsh
)で変数を引用符で囲まないと、glob+split
変数に演算子が使用されます。
$var
実際には:
glob(split($var))
したがって、ループが繰り返されるたびに、内容全体input
(末尾の改行を除く)が拡張、分割、およびワイルドカードとして指定されます。プロセス全体では、シェルはメモリを割り当て、文字列を解析し続ける必要があります。そのため、業績が低下します。
回避するために変数を引用することができますが、glob+split
シェルはまだ大きな文字列引数を作成してその内容をスキャンする必要があるため、あまり役に立ちません。 (echo
組み込みを外部に置き換えると、引数リストが長すぎるか、メモリが不足します。サイズによって)。ほとんどの実装はPOSIXと互換性がなく、受信した引数でバックスラッシュシーケンスを拡張します。echo
/bin/echo
$i
echo
\x
使用するには、cat
シェルはループごとにプロセスを作成し、cat
コピーI / Oを実行するだけです。システムはまた、ファイルの内容をキャッシュすることによって猫の処理速度を向上させることができる。
答え3
電話すると
i=`cat input`
これにより、シェルプロセスを50MBから最大200MBまで増やすことができます(内部ワイド文字の実装によって異なります)。これはシェルの速度を遅くするかもしれませんが、それは大きな問題ではありません。
主な問題は、上記のコマンドがファイル全体をシェルメモリに読み込み、echo $i
ファイルの内容をフィールドに分割する必要があることです$i
。フィールド分割を実行するには、ファイル内のすべてのテキストをワイド文字に変換する必要があります。ここでほとんどの時間がかかります。
遅い場合にいくつかのテストを実行した結果、次のようになります。
- 最速はksh93です
- 以下はBourne Shellです(ksh93より2倍遅い)。
- 以下はbashです(ksh93より3倍遅い)。
- 最後にksh88(ksh93より7倍遅い)
ksh93が最も速い理由は、ksh93がmbtowc()
libcを使用せずに独自の実装を使用するためです。
注:Stephaneは、読み取りサイズがある程度影響を及ぼすと誤って信じていました。 Bourne Shellをコンパイルして128バイトの代わりに4096バイトのブロックを読み込み、どちらの場合も同じパフォーマンスを得ました。
答え4
これはecho
、画面に1行を配置することを意味します。 2番目の例では、ファイルの内容を変数に入れてから、その変数を印刷します。まず、コンテンツはすぐに画面に表示されます。
cat
この用途に最適化されています。echo
いいえ。また、環境変数に50Mbを入れるのも良い考えではありません。