为什么使用 "grep --ignore-case" 会慢50倍?

16

当您在grep命令中添加--ignore-case选项时,很惊讶地发现搜索速度会降低50倍。我已经在两台不同的机器上进行了测试,并得到了相同的结果。我很想知道这种巨大性能差异背后的原因。

我还想找到一种替代grep命令的方法,用于不区分大小写的搜索。我不需要正则表达式,只需要固定字符串搜索。首先,测试文件将是一个50MB的纯文本文件,其中包含一些虚拟数据,您可以使用以下代码生成:

创建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

可能的解释

在Google中搜索后,我发现有人讨论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
4个回答

8

3
可以,请提供摘要。 - Peter Mortensen

8
这种缓慢是由于grep(在UTF-8环境中)不断访问文件“/usr/lib/locale/locale-archive”和“/usr/lib/gconv/gconv-modules.cache”所致。可以使用strace工具来展示。这两个文件都来自glibc。

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

0
为了进行不区分大小写的搜索,grep 首先必须将整个 50MB 的文件转换为其中一种大小写形式。这需要时间。不仅如此,还会有内存复制的问题...
在您的测试用例中,您首先生成文件。这意味着它将被缓存在内存中。第一次 grep 运行只需 mmap 缓存页面;它甚至不需要访问磁盘。
不区分大小写的 grep 做同样的事情,但然后它试图修改那些数据。这意味着内核将对每个修改的 4kB 页面进行异常处理,并最终不得不逐页地将整个 50MB 复制到新的内存中。
基本上,我会期望这会更慢。也许不是 57 倍慢,但肯定会更慢。

我不认为你对此是正确的。这个文件很小,只有50MB。更重要的是,请看我的更新,CentOS执行两个搜索几乎在同样的时间内完成。 - Marwan Alsabbagh
50MB相当于12500个内存页面,大约可以存放50分钟的MP3音乐,是Hotmail附件限制的5倍……我不确定是否应该称其为“微小”。 - ams
无论如何,就像我说的那样。57倍的减速似乎有点过分。 - ams
经过我自己的测试,将大小写转换后再运行正常的grep命令与运行grep -i命令相比,速度要快得多。 - Yablargo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接