【问题标题】:Speed of many regular expressions in pythonpython中许多正则表达式的速度
【发布时间】:2009-11-23 11:32:22
【问题描述】:

我正在编写一个处理大量字符串/文件的 python 程序。我的问题是我将看到一段相当短的文本,我需要在其中搜索范围相当广泛的单词/短语的实例。

我想我需要编译正则表达式来匹配文本中的这些单词/短语。然而,我担心的是这会花费很多时间。

我的问题是重复编译正则表达式,然后搜索一小段文本以找到匹配项的过程有多快?使用一些字符串方法会更好吗?

编辑:所以,我想我的问题的一个例子是:用一个正则表达式编译和搜索与说,迭代'如果字符串中的“单词”'说,5 次,会有多昂贵?

【问题讨论】:

  • 你应该提供文本样本,并使用样本清楚地描述你想找到什么

标签: python regex performance


【解决方案1】:

您应该尝试使用| 运算符将所有正则表达式编译为一个。这样,正则表达式引擎将为您完成大部分优化。使用分组运算符() 来确定匹配的正则表达式。

【讨论】:

  • Python 用什么方式优化你的正则表达式?
  • 例如,r'axxx|byyy' 将比单独检查 r'axxx'r'byyy' 快得多,因为正则表达式引擎将为您执行“切换”(它将检查第一个字符然后忽略其中一种模式)。所以更复杂的正则表达式不一定意味着“更慢”。
  • @Aaron Digulla:“它会检查第一个字符”哪个字符串满足什么条件?您能否指出它在源代码中执行此操作的位置。对于 T 字节大小的文本和 N 个查询字符串,您是说最佳情况时间是 O(T) 还是 O(NT)?
  • 正则表达式引擎将首先尝试查找各种​​子表达式的前导字符之一。如果找不到它们,它将是 O(TN)(正则表达式引擎将检查每个文本位置的每个正则表达式的几个字符)。如果您使用单独的正则表达式,它将是 sum(O(T*Rx)),其中 Rx 是每个正则表达式的大小。
  • 使用 |操作员。请参阅snowplow.org/martin/rebench“这样做的一个非常明显的结果是,如果您在 perl 中有一个依赖于在一段文本中查找多个正则表达式中的一个的应用程序,那么单独测试每个正则表达式比测试更有效使用备用正则表达式。”
【解决方案2】:

如果速度至关重要,您最好在决定如何编写生产应用程序之前运行一些测试。

首先,您说您正在搜索表明您可以使用 split() 来分解空格上的字符串的单词。然后使用简单的字符串比较来进行搜索。

一定要编译您的正则表达式并进行时间测试,将其与纯字符串函数进行比较。查看字符串类的文档以获取完整列表。

【讨论】:

  • 搜索字符串列表将比在文本块上进行 re.search 慢几个数量级。如果它是简单的字符串,则组合正则表达式会更快。
【解决方案3】:

您的要求似乎是在文本中搜索第一次出现的字符串集合中的任何一个。大概您希望重新开始搜索以找到下一个匹配项,依此类推,直到搜索到的字符串用尽。只涉及普通的旧字符串比较。

此任务的经典算法是Aho-Corasick,其中有一个Python extension(用C 编写)。这应该可以击败任何使用 re 模块的替代方案。

【讨论】:

    【解决方案4】:

    如果您想知道在编译正则表达式模式时它的速度如何,您需要对其进行基准测试。

    这是我的做法。每个模式编译 100 万次。

    import time,re
    
    def taken(f):
     def wrap(*arg):
      t1,r,t2=time.time(),f(*arg),time.time()
      print t2-t1,"s taken"
      return r
     return wrap
    
    @taken
    def regex_compile_test(x):
     for i in range(1000000):
      re.compile(x)
     print "for",x,
    
    #sample tests
    regex_compile_test("a")
    regex_compile_test("[a-z]")
    regex_compile_test("[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}")
    

    我的计算机中的每个模式大约需要 5 分钟。

    for a 4.88999986649 s taken
    for [a-z] 4.70300006866 s taken
    for [A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4} 4.78200006485 s taken
    

    真正的瓶颈不在于编译模式,它在于提取像 re.findall 这样的文本,替换 re.sub。如果你对几个 MB 文本使用它,它会很慢。

    如果您的文本是固定的,请使用普通的 str.find,它比正则表达式更快。

    实际上,如果您提供文本示例和正则表达式模式示例,我们可以为您提供更好的想法,那里有很多很棒的正则表达式和 python 专家。

    希望对您有所帮助,如果我的回答对您没有帮助,请见谅。

    【讨论】:

    • 您可能想看看timeit 模块。
    • 感谢您提供有关timeit 模块的信息,我不知道。
    【解决方案5】:

    当您编译正则表达式时,它会被转换为状态机表示。如果正则表达式被有效地表达,它仍然应该非常快地匹配。不过,编译正则表达式可能会很昂贵,因此您需要预先进行,并且尽可能不频繁地进行。但最终,只有您能回答是否足够快以满足您的要求。

    还有其他字符串搜索方法,例如Boyer-Moore algorithm。但我敢打赌,搜索多个单独字符串的复杂性远高于可以关闭每个连续字符的正则表达式。

    【讨论】:

    • 你几乎在这里回答了我的问题,正则表达式的编译成本很高......使用一个正则表达式编译和搜索与迭代'if "word" in string'相比会有多昂贵说,5次?
    • @gavinb:(1)“状态机表示”和“关闭每个连续字符”听起来您认为 Python 的 RE 引擎使用 DFA。它没有。 (2) 您似乎不知道正则表达式的编译被缓存了。如果调用者没有预编译正则表达式,则几乎没有惩罚。
    • @John Machin,我确实假设 RE 引擎使用了 DFA。我得仔细看看实现。
    • @Wilduck:我怀疑 5 次迭代是否足以作为性能比较的基础。正如罗伯特的回答所示,正则表达式很容易比使用“in”运算符快得多。最好先编译正则表达式,但由于它们是缓存的,因此成本仅在第一次使用时承担。
    【解决方案6】:

    这是一个只要尝试一下就可以很容易回答的问题。

    >>> import re
    >>> import timeit
    >>> find = ['foo', 'bar', 'baz']
    >>> pattern = re.compile("|".join(find))
    >>> with open('c:\\temp\\words.txt', 'r') as f:
            words = f.readlines()
    
    >>> len(words)
    235882
    >>> timeit.timeit('r = filter(lambda w: any(s for s in find if w.find(s) >= 0), words)', 'from __main__ import find, words', number=30)
    18.404569854548527
    >>> timeit.timeit('r = filter(lambda w: any(s for s in find if s in w), words)', 'from __main__ import find, words', number=30)
    10.953313759150944
    >>> timeit.timeit('r = filter(lambda w: pattern.search(w), words)', 'from __main__ import pattern, words', number=30)
    6.8793022576891758
    

    看起来您可以合理地期望正则表达式比使用findin 更快。但如果我是你,我会用一个更像你的真实数据的案例重复这个测试。

    【讨论】:

      【解决方案7】:

      如果您只是搜索特定的子字符串,请改用 str.find()

      【讨论】:

        【解决方案8】:

        根据您的操作,最好使用标记器并遍历标记以查找匹配项。

        但是,当涉及到短文本正则表达式时,它的性能非常好。就我个人而言,我记得只有当文本大小变得像 100k 字或类似的东西时才会出现问题。

        此外,如果您担心实际正则表达式编译而不是匹配的速度,您可能会受益于创建一个编译所有正则表达式然后在一个大循环中遍历所有文本片段或作为服务运行的守护进程。这样,您只需编译一次正则表达式。

        【讨论】:

        • (1) Python/Perl/etc 中使用的大多数正则表达式引擎比它们可能的速度要慢得多(如果进行了大量工作进行优化),即不是“非常好”(2 ) 在许多地方,100K 字的文本会被认为小得离谱 (3) 像 "foo|bar|baz|zot|tox" 这样的正则表达式很可能导致文本被搜索 5 次,而不是在其上运行一次自动机。
        【解决方案9】:

        一般情况下,您可以使用“in”关键字

        for line in open("file"):
            if "word" in line:
                print line.rstrip()
        

        使用 Python 时通常不需要正则表达式 :)

        【讨论】:

          猜你喜欢
          • 2011-05-18
          • 1970-01-01
          • 1970-01-01
          • 2018-04-02
          • 1970-01-01
          • 2021-12-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多