假设我有一个引用透明的功能。记忆它很容易; 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
附加到一个参数上)。
还有什么可以做的吗?
答案 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
类派生它可能会更容易。