【问题标题】: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

【问题讨论】:

    标签: performance bash time grep


    【解决方案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

        【讨论】:

        • 您能提供一个摘要吗?
        猜你喜欢
        • 2012-01-19
        • 1970-01-01
        • 1970-01-01
        • 2011-11-19
        • 1970-01-01
        • 2014-12-23
        • 1970-01-01
        • 1970-01-01
        • 2012-09-19
        相关资源
        最近更新 更多