【问题标题】:Python grep code much slower than command line's grepPython grep 代码比命令行的 grep 慢得多
【发布时间】:2016-08-12 11:16:13
【问题描述】:

我只是为approved="no" 模式寻找一些 Xliff 文件。我有一个 Shell 脚本和一个 Python 脚本,性能差异很大(一组 393 个文件,总共 3,686,329 行,Shell 脚本的用户时间为 0.1s,Python 脚本为 6.6s)。

壳牌:grep 'approved="no"' FILE
蟒蛇:

def grep(pattern, file_path):
    ret = False

    with codecs.open(file_path, "r", encoding="utf-8") as f:
        while 1 and not ret:
            lines = f.readlines(100000)
            if not lines:
                break
            for line in lines:
                if re.search(pattern, line):
                    ret = True
                    break
    return ret

有什么想法可以通过多平台解决方案提高性能?

结果

以下是应用一些建议的解决方案后的几个结果。
测试在 RHEL6 Linux 机器上运行,使用 Python 2.6.6。
工作集:393 个 Xliff 文件,共 3,686,329 行。
数字是以秒为单位的用户时间。

grep_1(io,加入 100,000 个文件行):50 秒
grep_3(mmap):0.7 秒
Shell 版本 (Linux grep): 0.130s

【问题讨论】:

    标签: python performance grep


    【解决方案1】:

    Python 是一种解释语言,而 grep 的编译 C 版本总是会更慢。

    除此之外,您的 Python 实现与您的 grep 示例相同。它不会返回匹配的行,它只是测试模式是否与任何一行上的字符匹配。更仔细的比较是:

    grep -q 'approved="no"' FILE
    

    一旦找到匹配项就会返回,并且不会产生任何输出。

    您可以通过更有效地编写grep() 函数来显着加快代码速度:

    def grep_1(pattern, file_path):
        with io.open(file_path, "r", encoding="utf-8") as f:
            while True:
                lines = f.readlines(100000)
                if not lines:
                    return False
                if re.search(pattern, ''.join(lines)):
                    return True
    

    这使用io 而不是codecs,我发现它要快一点。 while 循环条件不需要检查ret,只要知道结果就可以从函数中返回。无需为每个单独的 ilne 运行 re.search() - 只需加入行并执行单个搜索。

    以内存使用为代价,您可以试试这个:

    import io
    
    def grep_2(pattern, file_path):
        with io.open(file_path, "r", encoding="utf-8") as f:
            return re.search(pattern, f.read())
    

    如果内存有问题,您可以mmap 文件并在mmap 上运行正则表达式搜索:

    import io
    import mmap
    
    def grep_3(pattern, file_path):
        with io.open(file_path, "r", encoding="utf-8") as f:
            return re.search(pattern, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ))
    

    mmap 将有效地从页面中读取文件中的数据,而不会消耗大量内存。此外,您可能会发现mmap 的运行速度比其他解决方案快。


    对这些函数中的每一个使用timeit 表明情况就是这样:

    10 个循环,最好的 3 个:每个循环 639 毫秒 # grep() 10 个循环,最好的 3 个:每个循环 78.7 毫秒 # grep_1() 10 个循环,3 个循环中的最佳:每个循环 19.4 毫秒 # grep_2() 100 个循环,最好的 3 个:每个循环 5.32 毫秒 # grep_3()

    文件为/usr/share/dict/words,包含大约480,000 行,搜索模式为zymurgies,出现在文件末尾附近。为了比较,当模式接近文件开头时,例如abaciscus,时间是:

    10 个循环,最好的 3 个:每个循环 62.6 毫秒 # grep() 1000 个循环,3 个循环中的最佳:每个循环 1.6 毫秒 # grep_1() 100 个循环,3 个循环中的最佳:每个循环 14.2 毫秒 # grep_2() 10000 次循环,3 次中的最佳:每个循环 37.2 微秒 # grep_3()

    这再次表明mmap 版本是最快的。


    现在比较 grep 命令与 Python mmap 版本:

    $ time grep -q zymurgies /usr/share/dict/words
    
    real    0m0.010s
    user    0m0.007s
    sys 0m0.003s
    
    $ time python x.py grep_3    # uses mmap
    
    real    0m0.023s
    user    0m0.019s
    sys 0m0.004s
    

    考虑到grep 的优势,这还不错。

    【讨论】:

    • 太棒了!非常感谢您提供所有详细信息和示例。
    • 要让re.search(pattern, mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) 在 Windows 上工作,您必须将其更改为 re.search(pattern, mmap.mmap(f.fileno(), 0)),然后,我仍然收到拒绝访问错误。关于如何克服这个问题的任何想法?
    • access=ACCESS_READ 被记录为可在 Windows 和 Unix 中工作。尝试将length 设置为您可以使用size = os.path.getsize(filename) 获得的文件的实际大小。
    • mhawke 谢谢,那行得通。由于TypeError: cannot use a string pattern on a bytes-like object(使用Python 3.5.2),我还需要将r'approved="no"' 模式更改为b'approved="no"'(使用Python 3.5.2)
    • 这是一个很好的答案。很想看看在 python 中实现的 boyer-moore 算法与这些算法相比如何。
    【解决方案2】:

    Grep 实际上是一个非常聪明的软件,它不只是对每行进行正则表达式搜索。它利用Boyer-Moore 算法。请参阅here 了解更多信息。

    请参阅here 了解 grep 的 python 实现以获取更多指针。

    【讨论】:

      【解决方案3】:

      这里缓慢的另一个原因是在循环中调用re.search。这将重新编译每一行的正则表达式。

      试试吧:

      pattern = re.compile(pattern)
      while True:
          ...
          if pattern.search(line):
          ...
      

      【讨论】:

      • 美丽。谢谢!
      • 在循环内调用re.search() 确实会减慢它的速度,但是说每次迭代都会重新编译模式是不正确的。事实上,编译后的正则表达式模式实际上会被缓存,并且不会每次都重新编译。请参阅文档中有关 re.compile()` 的注释。
      猜你喜欢
      • 2020-02-12
      • 1970-01-01
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      • 2016-10-28
      • 2016-10-23
      • 1970-01-01
      • 2012-05-12
      相关资源
      最近更新 更多