为什么在Python 3中未编译的重复使用的正则表达式这么慢?
在回答这个问题时(并且看过这个类似问题的回答),我以为我了解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之间有变化吗?文档似乎没有提到这一点。
1 个回答
这段代码已经发生了变化。
在Python 2.7中,缓存是一个简单的字典。如果存储的项目超过了_MAXCACHE
的数量,缓存会在存储新项目之前被清空。查找缓存的过程只需要构建一个简单的键,然后测试这个字典,具体可以参考2.7版本的_compile()
。
在Python 3.x中,缓存被替换成了@functools.lru_cache(maxsize=500, typed=True)
装饰器。这个装饰器做了很多额外的工作,包括线程锁、调整缓存的LRU队列以及维护缓存统计信息(可以通过re._compile.cache_info()
访问)。具体可以查看3.3.0版本的_compile()
和functools.lru_cache()
。
其他人也注意到了同样的速度变慢,并在Python的bug跟踪系统中提交了问题16389。我预计3.4版本会更快,要么是lru_cache
的实现得到了改善,要么是re
模块会再次使用自定义缓存。
更新:在修订版4b4dddd670d0 (hg) / 0f606a6 (git)中,缓存的变化被恢复到了3.1中的简单版本。Python版本3.2.4和3.3.1包含了这个修订。
从那时起,在Python 3.7中,模式缓存被更新为基于普通dict
的自定义FIFO缓存实现(依赖于插入顺序,与LRU不同的是,在驱逐项目时不考虑缓存中已有项目的使用频率)。