【问题标题】:Python timeit.timeit do untimed actions in between statementsPython timeit.timeit 在语句之间执行不定时的操作
【发布时间】:2017-08-28 20:56:43
【问题描述】:

我有一个递归函数,上面有一个 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
    )

我仍然想用 a 缓存来计时,因为缓存会以指数方式减少时间,但是在调用一次之后,再次调用它需要大约 0 毫秒,因为它只是获取缓存的值.

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

【问题讨论】:

  • 我的错,我读错了

标签: python performance caching time


【解决方案1】:

您可以生成函数的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() 调用,它已绑定到本地以避免全局名称查找损失)。

【讨论】:

  • @Artyer:递归调用仍然会找到错误的函数对象,因为在函数本身内部引用的名称 recursive_function 在原始全局变量中查找。
猜你喜欢
  • 1970-01-01
  • 2011-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 2016-05-04
相关资源
最近更新 更多