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


116

我正在尝试(递归地)将目录中所有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

为什么回声比触摸此处要快得多?


20
就在方一句话:你知道,这两个命令是不等价的,不是吗?至少对于Unix / Linux,echo >> $file它将在其中添加换行符$file并对其进行修改。我认为对于OS / X它将是相同的。如果您不想这样做,请使用echo -n >> $file
Dubu 2014年

2
还不会touch `find . -name "*.xml"` 比以上两者都快吗?
Elmo 2014年

4
或者考虑一下>>$file
gerrit 2014年

8
这不是对明确问题的答案,但是为什么要调用touch这么多次?find . -name '*.xml' -print0 | xargs -0 touch调用touch次数更少(可能只有一次)。在Linux上的作品,应在OS X上运行
迈克·伦弗洛

3
@elmo参数列表太长(很容易,包含300.000个文件...)
Rmano 2014年

Answers:


161

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

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

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

echo但是,它是Shell内置的,而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
[...]

使用回声

$ 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
[...]

1
您是在OS X上编译strace还是在另一个OS上运行测试?
bmike 2014年

1
@bmike我的测试是在Linux上进行的,但是原理是相同的。
克里斯·

我完全同意-看到我对主要问题的评论,即/ bin / echo与/ bin / touch一样慢,因此推理是合理的。我只是想重现strace的时序,但使用dtruss / dtrace失败了,bash -c语法在OS X上也无法正常工作。
bmike 2014年

71

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

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

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

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

这种方法的另一个优点是,它对于包含所有空格字符的文件名表现出良好的性能,而原始循环则不然。


17
+1指出find +参数。我认为很多人都没有意识到这一点(我不是)。
gerrit 2014年

7
并非所有版本find都有该+参数。通过管道连接到,可以得到类似的效果xargs
2014年

5
@Barmar,该+部分是POSIX必需的,因此应该是可移植的。-print0不是。
Graeme 2014年

1
我仍然偶尔会遇到没有它的实现。YMMV。
Barmar 2014年

1
@ChrisDown,我发现的是Busybox find有可用的选项,但只将其视为;表面之下。
Graeme 2014年

29

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

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

shell内建的速度更快,因为没有开销参与加载程序,即不存在fork/ exec参与。这样,当您多次执行内置命令与外部命令时,会观察到明显的时差。

这就是类似time的实用程序可以作为shell内置程序使用的原因。

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

enable -p

如上所述,使用实用程序而不是内置实用程序会导致性能显着下降。以下是使用内置 工具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

而且我认为echo大多数系统上都有一个二进制文件(对我来说是/bin/echo),因此您可以使用该二进制文件而不是内置文件来重试时序测试
Michael Mrozek

@MichaelMrozek添加了针对内置和二进制文件的计时测试。
devnull 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.