据我了解,以下两个命令大致完成同样的事情:
命令 1:
find -name "filename.xml" -exec grep someString {} \;
命令2:
grep -r --include=filename.xml someString .
尽管如此,在相同环境下热身后计时,第一个比第二个快大约 3 倍(大约 4 秒对 12 秒)。
我测试的文件夹树中与文件名模式匹配的文件数量非常少,而且每个文件都非常小。这让我认为大部分时间都花在了查找与文件名模式匹配的文件中,而不是在查找这些匹配的文件上。
那么为什么这两条命令行的性能差异如此之大?
答案1
事实上恰恰相反;grep 命令通常更高效。
我将致力于研究 Gentoo 的 Portage 树快照,如果您想尝试的话,它是公开可用的。
$ time find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null
real 0m1.184s
user 0m0.033s
sys 0m0.130s
$ time grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null
real 0m0.017s
user 0m0.007s
sys 0m0.010s
让我们看一下每个函数被调用得最多的是哪些函数:
$ (strace find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null) |& sed 's/[({].*//g' | sort | uniq -c | sort -r | head -n 10
3574 fcntl
1597 close
794 newfstatat
794 getdents
689 wait4
689 clone
689 --- SIGCHLD
404 fstat
397 openat
20 mmap
$ (strace grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null) |& sed 's/[({].*//g' | sort | uniq -c | sort -r | head -n 10
2779 fcntl
1493 close
1382 read
1096 fstat
1087 openat
794 getdents
792 newfstatat
691 ioctl
689 lseek
25 write
再看看那些长时间的通话:
$ (strace -T find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} \; > /dev/null) |& sed 's/\(.*\)<\(.*\)>/\2 \1/g' | sort -nk1r | head -n10
exit_group(0) = ?
0.001884 wait4(29725, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29725
0.001879 wait4(29475, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29475
0.001813 wait4(29430, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29430
0.001812 wait4(30089, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30089
0.001807 wait4(29722, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29722
0.001795 wait4(29645, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29645
0.001794 wait4(29848, [{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0, NULL) = 29848
0.001759 wait4(30032, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30032
0.001754 wait4(30093, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30093
$ (strace -T grep -r --include '*.ebuild' DEPEND /usr/portage/sys-apps/ > /dev/null) |&
exit_group(0) = ?
0.002336 fcntl(3, F_SETFD, FD_CLOEXEC) = 0
0.000460 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30`C6\0\0\0"..., 832) = 832
0.000313 close(3) = 0
0.000295 execve("/bin/grep", ["grep", "-r", "--include", "*.ebuild", "DEPEND", "/usr/portage/sys-apps/"], [/* 75 vars */]) = 0
0.000276 fcntl(3, F_SETFD, FD_CLOEXEC) = 0
0.000265 getdents(3, /* 244 entries */, 32768) = 7856
0.000233 fstat(3, {st_mode=S_IFREG|0644, st_size=826, ...}) = 0
0.000162 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
0.000137 lseek(3, 1402, 0x4 /* SEEK_??? */) = -1 ENXIO (No such device or address)
非常有趣,在这个持续时间输出中,你会看到 find 等待了很长时间,而 grep 执行了一些启动和停止进程所需的操作。wait 调用花费了超过 0.001 秒的时间,而 find 调用则减少到稳定的 ~0.0002 秒。
如果你看一下 count 输出中的 wait4 调用,你会注意到发生了相等数量的 clone 调用和 SIGCHLD 信号;这是因为 find 会为遇到的每个文件调用 grep 进程,这就是它的效率受到影响的地方,因为克隆和等待的成本很高。
有些时候它不会受到影响;您可能会得到一组足够小的文件,因此启动多个 grep 进程的开销不会太大,但您的磁盘也可能非常慢,以至于忽略了启动新进程的开销,而且可能还有其他原因。不过,在比较速度时,我们通常会考虑一种或另一种方法的扩展性,而不是考虑特殊的极端情况。
在您的案例中,您提到“这就是为什么我觉得“grep”访问目录树的方式与“find”相比效率低下。",情况可能确实如此;如您所见,已经进行了 1382 次读取调用,而 find 并没有这样做,这会使 grep 的 I/O 消耗更大。
总结:了解原因你的时间效率低下,尝试再次进行此分析,并找出您案例中的问题,以便您知道为什么您的特定数据和任务在 grep 中效率低下;您会发现不同的 grep 在您的极端情况下会有怎样的行为......
因此,正如其他人建议的那样,您需要确保它不会为每个文件调用 grep,这可以通过\;
在+
结尾附近替换来完成。
$ time find /usr/portage/sys-apps/ -name '*.ebuild' -exec grep DEPEND {} + > /dev/null
real 0m0.027s
user 0m0.010s
sys 0m0.013s
如您所见,0.027 秒与 0.017 秒非常接近;差异主要归因于它仍然必须调用 find 和 grep,而不是仅调用 grep。或者如评论中所示,在某些系统上,它+
允许您改进 grep。
答案2
用 Wakizashi 而不是 Katana 来削土豆皮可能更好,但这两种工具都不是适合这项工作的好工具。数字工具也是如此,请明智地使用它们。
这听起来可能像是一个空洞的建议,但在这种情况下,例如,对于 find 示例中的每个文件,grep 都会执行一次。从性能角度来看,这并不明智。如果您用 '+' 而不是 '\;' 替换 find 的结束参数,grep 将只对找到的所有文件运行一次。
在这种情况下,要确切回答这个问题,必须比较 grep 和 find 源代码的相关部分,看看哪个在匹配(查找)文件名方面更快。坦率地说,这超出了我的技能范围。
直观地说,find 被优化为在目录中查找文件,而 grep 被优化为在文件中查找字符串。此外,该--include
选项应该适用于大写和小写文件,而 `-name
编辑:(我的发现是错误的)
对包含约 35,000 个文件的 doc 文件夹进行一些基本调查:
$ strace find . -name "moo" -exec grep a {} \+ 2>&1 |grep ^open |wc -l
4448
$ strace grep -r --include=moo a . 2>&1 | grep ^open | wc -l
2289
find 组合打开了更多文件。这表明结果与您的发现相反。我做了一些基本的计时(像 Tom Wijsman 一样)。
DIR=imagemagick-6.7.8.7
$ findhtml $DIR |& top10 $ grephtml $DIR |& top10
1617 mmap2 316 read
1176 fstat64 173 close
1176 close 164 fstat64
735 open 157 openat
608 read 148 ioctl
588 mprotect 63 fcntl64
441 brk 25 getdents64
294 munmap 16 fstatat64
294 ioctl 11 mmap2
147 write 5 write
time: Real 0m2.0s time: Real 0m0.3s
我发现 find strace 指向 /usr/lib/locale/locale-archive,但我并不确定其含义是什么。