为什么“回声”比“触摸”快这么多?

121 shell echo command

我正在尝试将我目录中所有 xml 文件的时间戳更新为当前时间(递归)。我使用的是 Mac OSX 10.8.5。

在大约 300,000 个文件上,以下echo命令需要10 秒

for file in `find . -name "*.xml"`; do echo >> $file; done
Run Code Online (Sandbox Code Playgroud)

但是,以下touch命令需要10 分钟!:

for file in `find . -name "*.xml"`; do touch $file; done
Run Code Online (Sandbox Code Playgroud)

为什么这里的回声比触摸快得多?

Chr*_*own 164

在 bash 中,touch是一个外部二进制文件,但它echo是一个内置的 shell

$ type echo
echo is a shell builtin
$ type touch
touch is /usr/bin/touch
Run Code Online (Sandbox Code Playgroud)

由于touch是外部二进制文件,并且您touch每个文件调用一次,因此 shell 必须创建 300,000 个 实例touch,这需要很长时间。

echo但是,它是一个 shell 内置函数,shell 内置函数的执行根本不需要 fork。相反,当前的 shell 会执行所有操作,并且不会创建任何外部进程;这就是为什么它要快得多的原因。

下面是 shell 操作的两个配置文件。您可以看到,在使用touch. 使用/bin/echo而不是 shell 内置应该显示更具可比性的结果。


使用触摸

$ 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
[...]
Run Code Online (Sandbox Code Playgroud)

使用回声

$ 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
[...]
Run Code Online (Sandbox Code Playgroud)


Gra*_*eme 74

正如其他人回答,使用echo会比更快touchecho是内置到shell命令它通常是(但并非规定)。使用它可以省去与为您获得的每个文件启动一个新进程相关的内核开销touch

但是,请注意,实现此效果的最快方法仍然是使用touch,而不是为每个文件运行一次程序,而是可以使用-execwith 选项find来确保仅运行几次。这种方法通常会更快,因为它避免了与 shell 循环相关的开销:

find . -name "*.xml" -exec touch {} +
Run Code Online (Sandbox Code Playgroud)

如果可能,使用+(而不是\;) withfind ... -exec将每个文件作为参数仅运行一次命令。如果参数列表很长(如 300,000 个文件的情况),将使用长度接近限制的参数列表进行多次运行(ARG_MAX在大多数系统上)。

这种方法的另一个优点是它对包含所有空白字符的文件名表现得很好,而原始循环的情况并非如此。

  • `+1` 用于指出 find `+` 参数。我想很多人都没有意识到这一点(我没有)。 (17认同)
  • 并非所有版本的 `find` 都有 `+` 参数。您可以通过管道连接到 `xargs` 来获得类似的效果。 (7认同)
  • @Barmar,POSIX 需要`+` 部分,所以应该是可移植的。`-print0` 不是。 (5认同)

dev*_*ull 29

echo是一个内置的shell。另一方面,touch是外部二进制文件。

$ type echo
echo is a shell builtin
$ type touch
touch is hashed (/usr/bin/touch)
Run Code Online (Sandbox Code Playgroud)

Shell 内置函数要快得多,因为在加载程序时不涉及任何开销,即不涉及fork/ exec。因此,在大量执行内置命令与外部命令时,您会观察到显着的时间差异。

这就是诸如此类time的实用程序可用作 shell 内置函数的原因。

您可以通过以下方式获取 shell 内置函数的完整列表:

enable -p
Run Code Online (Sandbox Code Playgroud)

如上所述,使用实用程序而不是内置程序会导致显着的性能下降。以下是使用内置 echo实用程序 创建约 9000 个文件所花费的时间的统计信息echo

# 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
Run Code Online (Sandbox Code Playgroud)