Python timeit.timeit 在语句之间执行不定时的操作



我有一个递归函数,上面有一个functools.lru_cache()装饰器。我想多次从空缓存中对它的执行进行计时。一种方法是每次都清除缓存,但这包括缓存清除时间。

这是我目前拥有的:

@functools.lru_cache(maxsize=None)
def recursive_function(n):
# ...
return sum(recursive_function(n - i) * b for i, b in enumerate(other_list))
def time(number, n):
return timeit.timeit(
'f({}); f.cache_clear()'.format(n),
'from {} import recursive_function as f'.format(__name__),
number=number
)

我仍然想用缓存来计时,因为缓存会成倍地减少时间,但是在调用一次之后,再次调用它需要 ~0ms,因为它只是获取缓存值。

有没有办法在语句之间做一些事情timeit.timeit而不计入时间?还是在cache_clear之前暂停计时器?

您可以生成函数的number副本,每个副本都有其独立的lru_cache包装器:

setup = '''
from {name} import recursive_function as f
f = iter([
functools.lru_cache(maxsize=None)(recursive_function.__wrapped__)
for _ in range({number})])
n = {n}
next_ = next
'''.format(name=__name__, number=number, n=n)
test = '''
recursive_function = next_(f)
recursive_funcion.__globals__['recursive_funcion'] = recursive_funcion
recursive_function(n)
'''
return timeit.timeit(test, setup, number=number)

安装程序预先创建number单独修饰的函数对象,每个对象都有不同的 LRU 缓存,并为此创建一个迭代器。然后,测试使用next()函数获取下一个可用的函数对象,并将其用于测试。

但是,您每次都必须recursive_function替换当前的全局名称,否则递归调用将找不到新的修饰版本。这有点缺点,不要运行计时赛并期望缓存之后为空(它将包含最后一次测试运行的结果)。

这是有效的,因为:

  1. 原始的未缓存函数仍可用作recursive_function.__wrapped__
  2. 装饰器语法只是调用装饰器对象以生成新函数对象的语法糖。

这为每个单独的测试提供了一个干净的缓存,开销最小(只有一个next()调用,它已绑定到本地以避免全局名称查找损失)。

最新更新