【问题标题】:How does Python's regex pattern caching work?Python 的正则表达式模式缓存如何工作?
【发布时间】:2012-09-12 21:50:09
【问题描述】:

来自re.compile() 的 Python 文档:

注意传递给的最新模式的编译版本 re.match()、re.search() 或 re.compile() 被缓存,所以程序 一次只使用几个正则表达式 不用担心 编译正则表达式。

但是,在我的测试中,这个断言似乎站不住脚。当重复使用相同模式的以下 sn-ps 计时时,编译后的版本仍然比未编译的版本快得多(应该被缓存)。

我在这里遗漏了什么来解释时差吗?

import timeit

setup = """
import re
pattern = "p.a.t.t.e.r.n"
target = "p1a2t3t4e5r6n"
r = re.compile(pattern)
"""

print "compiled:", \
    min(timeit.Timer("r.search(target)", setup).repeat(3, 5000000))
print "uncompiled:", \
    min(timeit.Timer("re.search(pattern, target)", setup).repeat(3, 5000000))

结果:

compiled: 2.26673030059
uncompiled: 6.15612802627

【问题讨论】:

  • 请不要这样使用timeit。只需使用标准参数。即使结果有效,也很难判断方法是否合理。
  • 抱歉,标准方法是什么样的?我基本上只是复制了this question
  • 不,你没有。那里的答案有运行一次的设置代码(在你的情况下,编译)和一个 single 语句。通过在repeat(通常只是默认值,31000000)调用中指定较大的数字,或者使用循环足够长但不会太长的命令行界面(python -m timeit),循环留给定时自动长。 from __main__ import ... 技巧确实很有用。
  • @delnan 更新了,这是更好的方法吗? (注意在这种情况下结果是一样的,幸运的是我。)
  • 是的,更好。现在讨论基准测试中的下一个潜在缺陷;)

标签: python regex performance python-2.7


【解决方案1】:

这是re.search 的(CPython)实现:

def search(pattern, string, flags=0):
    """Scan through string looking for a match to the pattern, returning
    a match object, or None if no match was found."""
    return _compile(pattern, flags).search(string)

这里是re.compile:

def compile(pattern, flags=0):
    "Compile a regular expression pattern, returning a pattern object."
    return _compile(pattern, flags)

依赖于re._compile:

def _compile(*key):
    # internal: compile pattern
    cachekey = (type(key[0]),) + key
    p = _cache.get(cachekey)            #_cache is a dict.   
    if p is not None:
        return p
    pattern, flags = key
    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"
    try:
        p = sre_compile.compile(pattern, flags)
    except error, v:
        raise error, v # invalid expression
    if len(_cache) >= _MAXCACHE:
        _cache.clear()
    _cache[cachekey] = p
    return p

所以你可以看到,只要正则表达式已经在字典中,唯一涉及的额外工作就是在字典中查找(这涉及创建一些临时元组,一些额外的函数调用......)。

更新 在过去的好日子(上面复制的代码),缓存过大时会完全失效。这些天来,缓存循环——首先丢弃最旧的项目。此实现依赖于 python 字典的顺序(这是在 python3.7 之前的实现细节)。在 python3.6 之前的 Cpython 中,这会从缓存中删除一个任意值(可以说这仍然比使整个缓存无效)

【讨论】:

  • 暗示这都是额外函数调用和cachekey = (type(key[0]),) + key, p = _cache.get(cachekey)?的开销
  • re.search 必须这样做,外加一个额外的函数调用 (re._compile)。但这几乎是全部......也许您的正则表达式很简单(并且通过_compile 进行了足够优化),python 中的那些小东西成为您计算的重要部分。另请注意,如果您有多个 100 正则表达式 (_MAXCACHE),您的缓存将被清除,您需要重新开始,但在这种情况下不应该打击您。
  • 似乎只是一些小事加起来。尝试使用更复杂的模式/更长的输入字符串,.search 仍然较慢,但差异保持不变,在 5M 调用后约 4 秒。所以我猜想得到的结果是开销很小,如果你在做 大量搜索(但大多数时候不值得)。
  • 注意,在最新版本的python中,这个缓存变成了最近最少添加的512项缓存:github.com/python/cpython/blob/…
猜你喜欢
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 2012-07-24
  • 1970-01-01
相关资源
最近更新 更多