Python re模块的缓存清理
在阅读关于Python re
模块的文档时,我决定看看re.py
的源代码。
当我打开它时,我发现了这个:
_cache = {}
_MAXCACHE = 100
def _compile(*key):
cachekey = (type(key[0]),) + key
p = _cache.get(cachekey)
if p is not None:
return p
#...Here I skip some part of irrelevant to the question code...
if len(_cache) >= _MAXCACHE:
_cache.clear()
_cache[cachekey] = p
return p
为什么当缓存的条目达到_MAXCACHE
时要使用_cache.clear()
来清空缓存呢?
这样完全清空缓存然后重新开始是常见的做法吗?
为什么不直接删除最久之前缓存的值呢?
3 个回答
缓存的目的就是为了减少函数调用的平均时间。为了存储更多的信息在_cache
里,以及在需要的时候去删除这些信息,而不是直接清空它,这样会导致平均调用时间变长。调用_cache.clear()
这个方法会很快完成,虽然这样会丢失缓存,但相比于保持缓存状态并在达到限制时逐个删除缓存里的元素,这样做更好。
在计算缓存效率时,有几个方面需要考虑:
- 缓存命中时的平均调用时间(非常短)
- 缓存未命中时的平均调用时间(比较长)
- 缓存命中的频率(相对不常见)
- 清空或修剪缓存时的调用时间(也比较不常见)
问题是,如果增加第3点的频率会导致第2点和第4点的时间增加,这样做是否合理。我猜可能不合理,或者说这种差别微不足道,保持代码简单更好。
这里有一段来自一个新 regex
模块开发者的引用,这个模块计划在3.3版本中推出,提到了一些缓存的内容。这些内容是新模块与当前 re
模块的不同之处。
7) 修改
re
编译表达式的缓存,以更好地处理频繁的缓存清空情况。目前,当正则表达式被编译时,结果会被缓存,这样如果再次编译相同的表达式,就可以直接从缓存中取出,不需要再做额外的工作。这个缓存最多支持100个条目。一旦达到第100个条目,缓存就会被清空,必须重新编译。虽然这种情况比较少见,但有可能在编译第100个表达式时,发现又需要重新编译,这样就得重复之前的工作,可能是3个表达式前的工作。通过稍微修改这个逻辑,可以建立一个任意的计数器,为每个编译的条目打上时间戳,而不是在缓存满了的时候清空整个缓存,而是只删除最旧的一半,保留更新的那一半。这样可以减少频繁清空缓存的情况,尤其是在有大量正则表达式被不断重新编译的情况下。此外,我还会把缓存的限制提高到256个条目,这样可以保留最近的128个。
http://bugs.python.org/issue2636
这似乎表明,当前的缓存行为可能更多是由于开发者的懒惰或者“强调可读性”所导致的。
如果让我猜的话,我觉得这样做是为了避免需要记录每个值在缓存中存储了多久,这样会增加内存和处理的负担。因为使用的缓存对象是一个字典,而字典本身是无序的,所以没有好的办法知道里面的项目是按什么顺序添加的,除非使用其他的缓存对象。如果你使用的是Python 2.7及以上版本,可以用OrderedDict来替代普通字典,这样就能解决这个问题。否则,你就需要大幅度重新设计缓存的实现方式,才能去掉对clear()
的需求。