为啥在 Python 3 中未编译的、重复使用的正则表达式要慢得多?

Posted

技术标签:

【中文标题】为啥在 Python 3 中未编译的、重复使用的正则表达式要慢得多?【英文标题】:Why are uncompiled, repeatedly used regexes so much slower in Python 3?为什么在 Python 3 中未编译的、重复使用的正则表达式要慢得多? 【发布时间】:2013-01-23 06:53:57 【问题描述】:

在回答 this question 时(并阅读了 this answer 的类似问题),我认为我知道 Python 如何缓存正则表达式。

但后来我想我会测试一下,比较两个场景:

    一个简单正则表达式的单一编译,然后是该已编译正则表达式的 10 个应用程序。 10 个未编译正则表达式的应用程序(我预计性能会稍差,因为正则表达式必须编译一次,然后缓存,然后在缓存中查找 9 次)。

然而,结果却是惊人的(在 Python 3.3 中):

>>> import timeit
>>> timeit.timeit(setup="import re", 
... stmt='r=re.compile(r"\w+")\nfor i in range(10):\n r.search("  jkdhf  ")')
18.547793477671938
>>> timeit.timeit(setup="import re", 
... stmt='for i in range(10):\n re.search(r"\w+","  jkdhf  ")')
106.47892003890324

慢了超过 5.7 倍!在 Python 2.7 中,仍然增加了 2.5 倍,这也超出了我的预期。

在 Python 2 和 3 之间,正则表达式的缓存是否发生了变化? The docs 似乎不建议这样做。

【问题讨论】:

嗯,你为什么要这样使用timeit?为什么不stmt='re.search(...)'/stmt='r.search(...)' 并将re.compile 添加到setup 花哨的functools.lru_cache 是这里的问题,那就是您所追求的缓存更改。另见bugs.python.org/issue16389 @delnan:我希望正则表达式的编译成为时间的一部分。 这有点不公平(在技术意义上),因为 re.search 使用的缓存将在运行之间持续存在。另外,您可以单独测量它并获得更多信息。我非常厌倦timeit 的重要参数。 @delnan:我想你是对的,但这意味着不编译正则表达式的性能损失甚至比这些结果所暗示的还要糟糕...... 【参考方案1】:

代码已经改变了。

在 Python 2.7 中,缓存是一个简单的字典;如果其中存储了超过_MAXCACHE 个项目,则在存储新项目之前清除整个缓存。缓存查找只需要构建一个简单的键并测试字典,请参阅2.7 implementation of _compile()

在 Python 3.x 中,缓存已替换为 @functools.lru_cache(maxsize=500, typed=True) decorator。这个装饰器做了更多更多的工作,包括一个线程锁、调整缓存 LRU 队列和维护缓存统计信息(可通过re._compile.cache_info() 访问)。请参阅3.3.0 implementation of _compile()functools.lru_cache()

其他人也注意到了同样的减速,并在 Python 错误跟踪器中提交了issue 16389。我希望 3.4 会再次快很多。要么 lru_cache 的实现得到改进,要么 re 模块将再次移动到自定义缓存。

更新:revision 4b4dddd670d0 (hg) / 0f606a6 (git) 的缓存更改已恢复为 3.1 中的简单版本。 Python 版本 3.2.4 和 3.3.1 包含该修订版。

从那时起,在 Python 3.7 中,模式缓存已更新为基于常规 dict 的 custom FIFO cache implementation(依赖于插入顺序,与 LRU 不同,它不考虑缓存中已经存在的项目最近的时间)驱逐时使用)。

【讨论】:

嗯。谢谢。我想知道为什么 strptime 中的正则表达式缓存不使用 lru_cache。我希望 lru_cache 可以改进,因为理论上 lru 比擦除和重新启动更好。 @andrewcooke:提醒一下:从 Python 3.7 开始,re.compile() 使用 FIFO 队列缓存。这是一个很好的妥协。

以上是关于为啥在 Python 3 中未编译的、重复使用的正则表达式要慢得多?的主要内容,如果未能解决你的问题,请参考以下文章

带有devc ++的opencv中未定义的引用[重复]

python类中未定义函数名[重复]

为啥python列表在使用=运算符时会像这样[重复]

Python 3-为啥我在这个作业问题中的 try 和 except 函数没有编译? [关闭]

平时使用oracle时,为啥会锁表

为啥在使用 Promise 时在类方法中未定义“this”? [复制]