为啥“grep --ignore-case”慢了 50 倍?
Posted
技术标签:
【中文标题】为啥“grep --ignore-case”慢了 50 倍?【英文标题】:Why is "grep --ignore-case" 50 times slower?为什么“grep --ignore-case”慢了 50 倍? 【发布时间】:2012-11-28 23:15:46 【问题描述】:我很惊讶地发现,当您将 --ignore-case
选项添加到 grep
时,它会使搜索速度减慢 50 倍。我已经在两台不同的机器上进行了测试,结果相同。我很想知道巨大的性能差异的解释。
我还希望看到 grep 的替代命令用于不区分大小写的搜索。我不需要正则表达式,只需要固定字符串搜索。首先,测试文件将是一个 50 MB 的纯文本文件,其中包含一些虚拟数据,您可以使用以下代码生成它:
创建 test.txt
yes all work and no play makes Jack a dull boy | head -c 50M > test.txt
echo "Jack is no fun" >> test.txt
echo "Jack is no Fun" >> test.txt
演示
下面是缓慢的演示。通过添加 --ignore-case
选项,命令会变慢 57 倍。
$ time grep fun test.txt
all work and no plJack is no fun
real 0m0.061s
$ time grep --ignore-case fun test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m3.498s
可能的解释
谷歌搜索我发现一个关于 grep 在 UTF-8 语言环境中速度慢的讨论。所以我进行了以下测试,它确实加快了速度。我机器上的默认语言环境是en_US.UTF-8
,因此将其设置为POSIX
似乎可以提高性能,但现在我当然无法正确搜索不受欢迎的Unicode 文本。它仍然慢了 2.5 倍。
$ time LANG=POSIX grep --ignore-case fun test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m0.142s
替代品
我们可以改用 Perl,它更快,但仍然比区分大小写的 grep 快 5.5 倍。上面的 POSIX grep 大约快两倍。
$ time perl -ne '/fun/i && print' test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m0.388s
因此,如果有人有的话,我很想找到一个快速正确的替代方案和解释。
更新 - CentOS
上面测试的两台机器都运行 Ubuntu,一台 11.04(Natty Narwhal),另一台 12.04(Precise Pangolin)。在 CentOS 5.3 机器上运行相同的测试会产生以下有趣的结果。两种情况的性能结果几乎相同。现在 CentOS 5.3 于 2009 年 1 月发布,运行 grep 2.5.1,而 Ubuntu 12.04 运行 grep 2.10。所以新版本可能会有变化,两个发行版也有区别。
$ time grep fun test.txt
Jack is no fun
real 0m0.026s
$ time grep --ignore-case fun test.txt
Jack is no fun
Jack is no Fun
real 0m0.027s
【问题讨论】:
【参考方案1】:原因是它需要对当前语言环境进行 Unicode 感知比较,从 Marat 的回答来看,这样做效率不高。
这显示了不考虑 Unicode 时它的速度有多快:
$ time LC_CTYPE=C grep -i fun test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m0.192s
当然,这种替代方法不适用于其他语言中的字符,例如 Ñ/ñ、Ø/ø、Ð/ð、Æ/æ 等。
另一种选择是修改正则表达式,使其不区分大小写:
$ time grep '[Ff][Uu][Nn]' test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m0.193s
这相当快,但是将每个字符转换为一个类当然很痛苦,而且与上面的不同,将其转换为别名或sh
脚本并不容易。
为了比较,在我的系统中:
$ time grep fun test.txt
all work and no plJack is no fun
real 0m0.085s
$ time grep -i fun test.txt
all work and no plJack is no fun
Jack is no Fun
real 0m3.810s
【讨论】:
【参考方案2】:这种缓慢是由于 grep(在 UTF-8 语言环境上)不断访问文件“/usr/lib/locale/locale-archive”和“/usr/lib/gconv/gconv-modules.cache”。
可以使用strace 实用程序显示。这两个文件都来自 glibc。
【讨论】:
【参考方案3】:要进行不区分大小写的搜索,grep 首先必须将整个 50 MB 文件转换为一种或另一种。这需要时间。不仅如此,还有内存副本……
在您的测试用例中,您首先生成文件。这意味着它将被内存缓存。第一次 grep 运行只需 mmap 缓存页面;它甚至不必访问磁盘。
不区分大小写的 grep 执行相同的操作,但随后它会尝试修改该数据。这意味着内核将对每个修改过的 4 kB 页面进行异常处理,最终不得不将整个 50 MB 复制到新内存中,一次一页。
基本上,我预计这会慢一些。也许不会慢 57 倍,但肯定更慢。
【讨论】:
我不认为你是对的。这个文件很小,只有 50MB。更重要的是看看我的更新,centos 在几乎相同的执行时间执行这两个搜索。 50MB 是 12500 个内存页,大约 50 分钟的 MP3,是 hotmail 附件限制的 5 倍……我不确定我会称它为“小”。 无论如何,就像我说的。慢 57 倍似乎有点过分了。 做我自己的测试,转换大小写然后运行常规 grep vs grep -i 快得多。【参考方案4】:我认为这个错误报告有助于理解为什么它很慢:
bug report grep, slow on ignore-case
【讨论】:
您能提供一个摘要吗?以上是关于为啥“grep --ignore-case”慢了 50 倍?的主要内容,如果未能解决你的问题,请参考以下文章