参数可能非常大时的记忆

时间:2012-10-04 11:03:17

标签: python design-patterns python-3.x memoization

假设我有一个引用透明的功能。记忆它很容易; for example

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的{​​{1}}参数很大;比方说,它是一个拥有数百万个元素的my_function。在这种情况下,记忆的成本是高昂的:每次,我们都必须计算frozenset作为字典查找的一部分。

我可以将hash(data)字典赋予memo的属性,而不是data装饰器内的对象。这样我在进行缓存查找时可以完全跳过memoize参数,因为另一个巨大的data相同的可能性是微不足道的。但是,这种方法最终会污染传递给frozenset的参数。更糟糕的是,如果我有两个或更多大的参数,这根本没有帮助(我只能将my_function附加到一个参数上)。

还有什么可以做的吗?

2 个答案:

答案 0 :(得分:1)

好吧,你可以在那里使用“哈希”而不用担心。 Python中不会多次计算冻结集的散列 - 只要它被创建 - 检查时间:

>>> 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__方法,该方法在创建时为内置的hasheable对象定义了返回值。上面你可以看到调用一个身份lambda函数比调用“hash(a)”

慢两倍多

因此,如果你的所有参数都是hasheable,只需在创建“combined_args”时添加它们的哈希值 - 否则,只需编写它的创建,以便使用条件的firset(也许是其他)类型的哈希值。

答案 1 :(得分:1)

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

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

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

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

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