echoとcatの実行時になぜこれほど大きな違いがありますか?

echoとcatの実行時になぜこれほど大きな違いがありますか?

回答これ質問によって別の質問が生じました。
次のスクリプトは同じことをすると思います。最初のスクリプトは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番目のスクリプトでは、出力ファイルが予想より小さいなど、いくつかの問題が発生しました。

また、マニュアルページを確認しechocat比較しました。

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

(特にありがとうロエマ.)

しかし、これはポイントではありません。ループが一度だけ発生しても同じ問題があります。catecho

ベストアンサー1

ここで考慮すべきいくつかの点があります。

i=`cat input`

コストがかかり、ケースごとに違いが多い。

これはコマンド置換という機能です。アイデアは、コマンドの出力全体から末尾の改行文字を引いた値をiメモリ内の変数に格納することです。

この目的のために、シェルはサブシェルからコマンドを分岐し、パイプまたはソケットのペアを介して出力を読み込みます。ここから多くの変化が見られます。ここでは、50MiBファイルでbashがksh93より6倍遅いですが、zshより少し速いことがわかります。 yes yash

速度が遅くなる主な理由bashは、パイプから一度に128バイトを読み取り(他のシェルは一度に4KiBまたは8KiBを読み取る)、システムコールのオーバーヘッドによって困難になるためです。

zshNULバイトをエスケープするには、いくつかの後処理が必要です(他のシェルは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しかし、これはループで使用するよりも効率的ではありません(入力が非常に小さくない限り)。

おすすめ記事