web-dev-qa-db-ja.com

「エコー」が「タッチ」よりもはるかに速いのはなぜですか。

私のディレクトリにあるすべてのxmlファイルのタイムスタンプを現在の時刻に更新しようとしています(再帰的に)。 Mac OSX 10.8.5を使用しています。

約300,000ファイルで、次のechoコマンドは10秒かかります。

for file in `find . -name "*.xml"`; do echo >> $file; done

ただし、次のtouchコマンドは10分かかります。 :

for file in `find . -name "*.xml"`; do touch $file; done

エコーがここで触れるよりもはるかに速いのはなぜですか?

116
Casey Patton

Bashでは、touchは外部バイナリですが、echoShell builtin

$ type echo
echo is a Shell builtin
$ type touch
touch is /usr/bin/touch

touchは外部バイナリであり、ファイルごとにtouchを呼び出すため、シェルはtouchのインスタンスを300,000作成する必要があり、これには長い時間がかかります。

ただし、echoはShellビルトインであり、Shellビルトインの実行にはforkはまったく必要ありません。代わりに、現在のシェルがすべての操作を実行し、外部プロセスは作成されません。これが非常に高速である理由です。

シェルの操作の2つのプロファイルを次に示します。 touchを使用すると、新しいプロセスの複製に多くの時間が費やされていることがわかります。 Shellビルトインの代わりに/bin/echoを使用すると、はるかに比較可能な結果が表示されます。


タッチを使用する

$ strace -c -- bash -c 'for file in a{1..10000}; do touch "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 56.20    0.030925           2     20000     10000 wait4
 38.12    0.020972           2     10000           clone
  4.67    0.002569           0     80006           rt_sigprocmask
  0.71    0.000388           0     20008           rt_sigaction
  0.27    0.000150           0     10000           rt_sigreturn
[...]

エコーの使用

$ strace -c -- bash -c 'for file in b{1..10000}; do echo >> "$file"; done'
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 34.32    0.000685           0     50000           fcntl
 22.14    0.000442           0     10000           write
 19.59    0.000391           0     10011           open
 14.58    0.000291           0     20000           dup2
  8.37    0.000167           0     20013           close
[...]
160
Chris Down

他の人が答えたように、echoは一般に(必須ではありませんが)シェルに組み込まれているコマンドであるため、touchを使用するとechoよりも高速になります。これを使用すると、touchで取得する各ファイルに対して新しいプロセスの開始を実行することに関連するカーネルオーバーヘッドが不要になります。

ただし、この効果を実現する最も速い方法は、touchを使用することですが、ファイルごとに1回プログラムを実行するのではなく、find-execオプションを使用して、それは数回だけ実行されます。このアプローチは、シェルループに関連するオーバーヘッドを回避するため、通常は高速になります。

find . -name "*.xml" -exec touch {} +

+\;を(find ... -execとは対照的に)使用すると、各ファイルを引数として可能な場合はコマンドが1回だけ実行されます。引数リストが非常に長い場合(300,000ファイルの場合のように)、制限(ほとんどのシステムではARG_MAX)に近い長さの引数リストで複数の実行が行われます。

このアプローチの別の利点は、元のループの場合とは異なり、すべての空白文字を含むファイル名で確実に動作することです。

71
Graeme

echoは組み込みのシェルです。一方、touchは外部バイナリです。

$ type echo
echo is a Shell builtin
$ type touch
touch is hashed (/usr/bin/touch)

Shell builtins は、プログラムのロードに伴うオーバーヘッドがないため、つまりfork/execが関与しないため、はるかに高速です。そのため、組み込みコマンドと外部コマンドを何度も実行すると、大きな時間差が見られます。

これが、timeのようなユーティリティがシェル組み込みとして使用できる理由です。

Shellビルトインの完全なリストは、次のようにして取得できます。

enable -p

上記のように、utilitybuiltinではなく使用すると、パフォーマンスが大幅に低下します。以下は、builtinechoおよびutilityechoを使用して〜9000ファイルを作成するのにかかる時間の統計です。

# Using builtin
$ time bash -c 'for i in {1000..9999}; do echo > $i; done'

real    0m0.283s
user    0m0.100s
sys 0m0.184s

# Using utility /bin/echo
$ time bash -c 'for i in {1000..9999}; do /bin/echo > $i; done'

real    0m8.683s
user    0m0.360s
sys 0m1.428s
29
devnull