2

假设我有一个引用透明的功能。很容易记住它;例如

def memoize(obj):
  memo = {}
  @functools.wraps(obj)
  def memoizer(*args, **kwargs):
    combined_args = args + (kwd_mark,) + tuple(sorted(kwargs.items()))
    if combined_args not in memo:
      memo[combined_args] = obj(*args, **kwargs)
    return cache[combined_args]
  return memoizer

@memoize
def my_function(data, alpha, beta):
  # ...

现在假设data论点my_function很大;说,这是一个frozenset有数百万个元素的。在这种情况下,记忆的成本是令人望而却步的:每次,我们都必须将计算hash(data)作为字典查找的一部分。

我可以使memo字典成为一个属性,而不是装饰器data内的一个对象。memoize这样我可以data在进行缓存查找时完全跳过参数,因为另一个巨大frozenset的相同的机会可以忽略不计。但是,这种方法最终会污染传递给my_function. 更糟糕的是,如果我有两个或更多的大论点,这根本无济于事(我只能附加memo一个论点)。

还有什么可以做的吗?

4

2 回答 2

1

好吧,您可以在那里毫无顾忌地使用“哈希”。Python 不会多次计算 freezeset 的哈希值——就在它创建时——检查时间:

>>> timeit("frozenset(a)", "a=range(100)")
3.26825213432312
>>> timeit("hash(a)", "a=frozenset(range(100))")
0.08160710334777832
>>> timeit("(lambda x:x)(a)", "a=hash(frozenset(range(100)))")
0.1994171142578125

不要忘记 Python 的“hash”内置调用对象的__hash__方法,该方法的返回值在创建时为内置的可散列对象定义。上面你可以看到调用一个标识 lambda 函数比调用“hash(a)”慢两倍以上

因此,如果您的所有参数都是可散列的,则只需在创建“combined_args”时添加它们的散列 - 否则,只需编写它的创建,以便您将散列用于frozenset(可能还有其他)类型,并带有条件。

于 2012-10-04T11:59:17.517 回答
1

事实证明,内置__hash__并没有那么糟糕,因为它在第一次计算后缓存了自己的值。真正的性能损失来自内置的__eq__,因为它不会在相同的对象上短路,并且实际上每次都会进行完整的比较,这使得它非常昂贵。

我想到的一种方法是对所有大参数的内置类进行子类化:

class MyFrozenSet(frozenset):
  __eq__ = lambda self, other : id(self) == id(other)
  __hash__ = lambda self : id(self)

这样,字典查找将是即时的。但是新阶级的平等将被打破。

一个更好的解决方案可能是这样的:只有在执行字典查找时,才能将大参数包装在一个特殊的类中,该类重新定义__eq____hash__返回被包装对象的id(). 包装器的明显实现有点烦人,因为它需要复制所有标准frozenset方法。也许从相关ABC类派生它可能会更容易。

于 2012-10-04T12:02:08.827 回答