【问题标题】:why the performance different if change the order of compile and findall in python如果在python中更改编译和findall的顺序,为什么性能会有所不同
【发布时间】:2019-04-14 22:11:37
【问题描述】:

我注意到通过编译模式进行预处理会加快匹配操作,就像下面的例子一样。

python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "t.findall('abc eft123&aaa123')"

1000000 loops, best of 3: 1.42 usec per loop

python3 -m timeit -s "import re;" "re.findall(r'[\w+][\d]+', 'abc eft123&aaa123')"

100000 loops, best of 3: 2.45 usec per loop

但是如果我改变编译模式和重新模块的顺序,结果就不一样了,现在似乎慢了很多,为什么会这样?

python3 -m timeit -s "import re; t = re.compile(r'[\w+][\d]+')" "re.findall(t, 'abc eft123&aaa123')"

100000 loops, best of 3: 3.66 usec per loop

【问题讨论】:

    标签: python regex compilation findall


    【解决方案1】:

    通过“更改顺序”,您实际上是在以“静态”形式使用 findall,几乎相当于调用 str.lower('ABC') 而不是 'ABC'.lower()

    根据您使用的 Python 解释器的具体实现,这可能会导致一些开销(例如,用于方法查找)。

    换句话说,这与 Python 的工作方式更相关,而不是专门针对正则表达式或 re 模块。

    from timeit import Timer
    
    def a():
        str.lower('ABC')
    
    def b():
        'ABC'.lower()
    
    print(min(Timer(a).repeat(5000, 5000)))
    print(min(Timer(b).repeat(5000, 5000)))
    

    输出

    0.001060427000000086    # str.lower('ABC')
    0.0008686820000001205   # 'ABC'.lower()
    

    【讨论】:

    • 感谢您的回复。
    • 我按照您的描述做了一个测试,结果表明它可能不是根本原因。 python3 -m timeit -s "'AAAAAAAAAAAAABBBBBBBBBBBBBA'.lower()" 100000000 个循环,3 个中最好的:每个循环 0.00995 微秒 python3 -m timeit -s "str.lower('AAAAAAAAAAAAAAABBBBBBBBBBBBBA')" 100000000 个循环,3 个中最好的:0.00979每个循环使用秒
    【解决方案2】:

    假设 word1, word2 ... 是正则表达式:

    让我们重写这些部分:

    allWords = [re.compile(m) for m in ["word1", "word2", "word3"]]
    

    我将为所有模式创建一个单一的正则表达式:

    allWords = re.compile("|".join(["word1", "word2", "word3"])
    

    使用 | 支持正则表达式在它们中,您必须将表达式括起来:

    allWords = re.compile("|".join("({})".format(x) for x in ["word1", "word2", "word3"])
    

    (当然也适用于标准词,由于 | 部分,它仍然值得使用正则表达式)

    现在这是一个变相的循环,每个术语都是硬编码的:

    def bar(data, allWords):
       if allWords[0].search(data) != None:
          temp = data.split("word1", 1)[1]  # that works only on non-regexes BTW
          return(temp)
    
       elif allWords[1].search(data) != None:
          temp = data.split("word2", 1)[1]
          return(temp)
    

    可以简单地改写为

    def bar(data, allWords):
       return allWords.split(data,maxsplit=1)[1]
    

    在性能方面:

    正则表达式是在启动时编译的,所以它尽可能快 没有循环或粘贴表达式,“或”部分由正则表达式引擎完成,这在大多数情况下是一些编译代码:在纯 python 中无法击败它。 匹配和拆分在一个操作中完成 最后一个问题是正则表达式引擎在内部搜索循环中的所有表达式,这使得它成为一个 O(n) 算法。为了让它更快,你必须预测哪个模式是最频繁的,然后把它放在第一位(我的假设是正则表达式是“不相交的”,这意味着一个文本不能被几个匹配,否则最长的将不得不在较短的之前出现)

    【讨论】:

      【解决方案3】:

      我花了一些时间研究了re.findallre.match的实现,这里复制了标准库源码。

      def findall(pattern, string, flags=0):
          """Return a list of all non-overlapping matches in the string.
      
          If one or more capturing groups are present in the pattern, return
          a list of groups; this will be a list of tuples if the pattern
          has more than one group.
      
          Empty matches are included in the result."""
          return _compile(pattern, flags).findall(string)
      
      
      def match(pattern, string, flags=0):
          """Try to apply the pattern at the start of the string, returning
          a match object, or None if no match was found."""
          return _compile(pattern, flags).match(string)
      
      
      def _compile(pattern, flags):
          # internal: compile pattern
          try:
              p, loc = _cache[type(pattern), pattern, flags]
              if loc is None or loc == _locale.setlocale(_locale.LC_CTYPE):
                  return p
          except KeyError:
              pass
          if isinstance(pattern, _pattern_type):
              if flags:
                  raise ValueError(
                      "cannot process flags argument with a compiled pattern")
              return pattern
          if not sre_compile.isstring(pattern):
              raise TypeError("first argument must be string or compiled pattern")
          p = sre_compile.compile(pattern, flags)
          if not (flags & DEBUG):
              if len(_cache) >= _MAXCACHE:
                  _cache.clear()
              if p.flags & LOCALE:
                  if not _locale:
                      return p
                  loc = _locale.setlocale(_locale.LC_CTYPE)
              else:
                  loc = None
              _cache[type(pattern), pattern, flags] = p, loc
          return p
      

      这说明如果我们直接执行re.findall(compiled_pa​​ttern, string),会触发额外调用_compile(pattern, flags),在这个函数中会做一些检查和查找缓存字典中的模式。但是,如果我们改为调用compile_pattern.findall(string),则该“附加操作”将不存在。所以compile_pattern.findall(string) 会比 re.findall(compile_pattern, string) 快

      【讨论】:

        猜你喜欢
        • 2013-11-18
        • 2023-03-24
        • 1970-01-01
        • 2013-06-07
        • 1970-01-01
        • 1970-01-01
        • 2019-11-18
        • 2010-09-25
        • 1970-01-01
        相关资源
        最近更新 更多