【问题标题】:@lru_cache decorator excessive cache misses@lru_cache 装饰器过多的缓存未命中
【发布时间】:2021-03-28 15:18:15
【问题描述】:

如何配置lru_cache 以根据接收到的实际值而不是函数的调用方式为其缓存设置键?

>>> from functools import lru_cache
>>> @lru_cache
... def f(x=2):
...     print("reticulating splines...")
...     return x ** 2
...
>>> f()
reticulating splines...
4
>>> f(2)
reticulating splines...
4
>>> f(x=2)
reticulating splines...
4

换句话说,只有上面的第一个调用应该是缓存未命中,其他两个应该是缓存命中。

【问题讨论】:

  • docs do 状态:不同的参数模式可以被认为是具有不同缓存条目的不同调用。例如,f(a=1, b=2) 和 f(b=2, a=1) 的关键字参数顺序不同,可能有两个单独的缓存条目。看起来您可以使用f.cache_info() 方法查看实际的缓存命中/未命中。
  • 我知道,但我不认为这是一个明智的默认行为 - 这些调用对于所有实际目的都是相同的。我想以这样一种方式包装(或替换)lru_cache,以避免同一底层调用的所有各种不同拼写的缓存未命中。
  • @bnaecker:不,因为问题试图实现的行为取决于函数签名,make_key 不知道。
  • 为什么f()f(x=2) 的处理方式不同? args=()kwds={'x': 2} 在这两种情况下都不是吗?
  • @mkrieger1:不。默认值不是关键字参数。

标签: python function caching memoization functools


【解决方案1】:

为此,您必须完成将参数绑定到形式参数的过程。 实际过程是在没有公共接口的 C 代码中实现的,但在 inspect 中有一个(慢得多的)重新实现。这比通常使用functools.lru_cache 慢大约 100 倍:

import functools
import inspect

def mycache(f=None, /, **kwargs):
    def inner(f):
        sig = inspect.signature(f)
        f = functools.lru_cache(**kwargs)(f)
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            bound = sig.bind(*args, **kwargs)
            bound.apply_defaults()
            return f(*bound.args, **bound.kwargs)
        return wrapper
    if f:
        return inner(f)
    return inner

@mycache
def f(x):
    print("reticulating splines...")
    return x ** 2

如果这种方法的性能损失太大,您可以改用以下技巧,这需要更多的代码重复但运行速度要快得多,仅比通常使用 lru_cache 慢约 2 倍(有时更快,使用关键字参数):

@functools.lru_cache
def _f(x):
    print("reticulating splines...")
    return x ** 2

def f(x=2):
    return _f(x)

这使用更快的 C 级参数绑定来规范对记忆化辅助函数的调用,但需要将函数的参数复制 3 次:一次在外部函数的签名中,一次在辅助函数的签名中,一次在调用中给助手。

【讨论】:

  • 效果很好。你的C怎么样?有兴趣将 CPython PR 添加到 lru_cache 中直接添加“规范化”参数吗?
猜你喜欢
  • 2020-03-31
  • 1970-01-01
  • 2022-12-14
  • 2018-09-01
  • 2015-07-29
  • 2022-01-01
  • 2018-01-02
  • 2022-09-29
  • 2019-02-19
相关资源
最近更新 更多