grep 怎么跑得这么快?
Posted
技术标签:
【中文标题】grep 怎么跑得这么快?【英文标题】:How does grep run so fast? 【发布时间】:2012-09-19 17:35:28 【问题描述】:我真的很惊讶 GREP 在 shell 中的功能,之前我在 java 中使用 substring 方法,但现在我使用 GREP 并且它在几秒钟内执行,它比我使用的 java 代码快得多写。(根据我的经验,我可能是错的)
话虽如此,我还无法弄清楚它是如何发生的?网络上也没有太多可用的。
谁能帮我解决这个问题?
【问题讨论】:
它是开源的,所以你可以自己看看。 gnu.org/software/grep/devel.html Ridiculous Fish 有一篇很棒的文章准确地回答了你的问题:ridiculousfish.com/blog/posts/old-age-and-treachery.html @WilliamPursell 当执行时间以秒为单位时,JIT 可能已经热身,而令人麻木的差异是由于 (1) grep 对它所做的事情非常聪明和 (2) Java 代码为 grep 关注的特定问题做出了非常糟糕的算法选择。 您的 Java 实现在启动 JVM 上花费了多少时间,实际执行代码花费了多少时间?或者这可能与您在 Java 代码中使用的算法有关; O(N^2) 算法在任何语言中都可能很慢。 【参考方案1】:假设您的问题专门针对GNU grep
。以下是作者 Mike Haertel 的注释:
GNU grep 速度很快,因为它避免查看每个输入字节。
GNU grep 速度很快,因为它只执行很少的指令 字节,它 确实在看。
GNU grep 使用著名的 Boyer-Moore 算法,它看起来首先 对于目标字符串的最后一个字母,并使用查找表 告诉它每当它找到一个输入时它可以跳过多远 不匹配的字符。
GNU grep 还展开 Boyer-Moore 的内部循环,并设置 Boyer-Moore 增量表条目以不需要的方式 在每个展开的步骤中执行循环退出测试。结果是 在极限情况下,GNU grep 平均少于 3 个 x86 指令 为它实际查看的每个输入字节执行(并且它跳过了许多 字节)。
GNU grep 使用原始 Unix 输入系统调用并避免复制数据 读完之后。此外,GNU grep 避免将输入中断为 线。寻找换行符会使 grep 减慢一个因素 几次,因为要找到换行符,它必须查看 每个字节!
因此,GNU grep 不是使用面向行的输入,而是将原始数据读入 一个大缓冲区,使用 Boyer-Moore 搜索缓冲区,并且仅当 它找到匹配项并寻找边界换行符 (某些命令行选项,如 -n 禁用此优化。)
此答案是从here 获取的信息的子集。
【讨论】:
【参考方案2】:补充史蒂夫的出色答案。
它可能并不广为人知,但当 grep 查找 longer 模式时,grep 几乎总是 faster -字符串而不是短字符串,因为在较长的模式中,Boyer-Moore 可以以更长的步幅向前跳跃,以实现更好的 sublinear 速度:
例子:
# after running these twice to ensure apples-to-apples comparison
# (everything is in the buffer cache)
$ time grep -c 'tg=f_c' 20140910.log
28
0.168u 0.068s 0:00.26
$ time grep -c ' /cc/merchant.json tg=f_c' 20140910.log
28
0.100u 0.056s 0:00.17
更长的形式快 35%!
怎么会? Boyer-Moore 从模式字符串中构造一个向前跳过的表,每当出现不匹配时,它会在将输入中的单个字符与跳过表中的字符。
这里是a video explaining Boyer Moore(归功于 kommradHomer)
另一个常见的误解(对于 GNU grep)是 fgrep
比 grep
快。 fgrep
中的 f
不代表“快速”,它代表“固定”(参见手册页),因为两者都是同一个程序,并且都使用 Boyer-Moore ,在搜索没有正则表达式特殊字符的固定字符串时,它们之间的速度没有差异。我使用fgrep
的唯一原因是当有一个正则表达式特殊字符(如.
、[]
或*
)时,我不希望它被这样解释。即使这样,grep -F
的更便携/标准形式也比fgrep
更受欢迎。
【讨论】:
很明显,更长的模式更快。如果模式是一个字节,那么 grep 将不得不检查每个字节。如果模式是 4 字节,那么它可以跳过 4 字节。如果模式和文本一样长,那么 grep 只会做一步。 是的,它很直观——如果您了解 Boyer-Moore 的工作原理。 即便如此,它也很直观。在大海捞针中找到一根长针比找到一根短针更容易 “越长越快”的反例是您必须在失败之前进行大量测试,但无论如何您都无法继续前进。假设文件xs.txt
包含 100000000 个“x”,而您执行 grep yx xs.txt
,那么它实际上比您执行 grep yxxxxxxxxxxxxxxxxxxx xs.txt
更快地找不到匹配项。 Boyer-Moore-Horspool 对 Boyer-Moore 的改进在这种情况下改进了前跳,但在一般情况下可能不会只有三个机器指令。
@Tino 谢谢。是的,似乎 (GNU) grep/fgrep/egrep
成为同一个可执行文件的所有硬链接的日子已经一去不复返了。它们(以及其他扩展,如即时解压缩的 z*grep
bz*grep
实用程序)现在是 grep
周围的小型外壳包装器。可以在此提交中找到有关在单个可执行文件和 shell 包装器之间切换的一些有趣的历史 cmets:git.savannah.gnu.org/cgit/grep.git/commit/…以上是关于grep 怎么跑得这么快?的主要内容,如果未能解决你的问题,请参考以下文章