Python 参数可能非常大时的记忆

Python 参数可能非常大时的记忆,python,design-patterns,python-3.x,memoization,Python,Design Patterns,Python 3.x,Memoization,假设我有一个引用透明的函数。记住它很容易;: 现在假设my_函数的data参数很大;比如说,这是一个包含数百万元素的冻结集。在这种情况下,记忆的成本是很高的:每次,我们都必须计算散列(数据),作为字典查找的一部分 我可以将memo字典作为data的属性,而不是memoize装饰器中的对象。通过这种方式,我可以在执行缓存查找时完全跳过data参数,因为另一个巨大的冻结集的可能性可以忽略不计。但是,这种方法最终会污染传递给myu函数的参数。更糟糕的是,如果我有两个或更多的大参数,这将毫无帮助(我只能

假设我有一个引用透明的函数。记住它很容易;:

现在假设
my_函数的
data
参数很大;比如说,这是一个包含数百万元素的
冻结集。在这种情况下,记忆的成本是很高的:每次,我们都必须计算
散列(数据)
,作为字典查找的一部分

我可以将
memo
字典作为
data
的属性,而不是
memoize
装饰器中的对象。通过这种方式,我可以在执行缓存查找时完全跳过
data
参数,因为另一个巨大的
冻结集
的可能性可以忽略不计。但是,这种方法最终会污染传递给
myu函数的参数。更糟糕的是,如果我有两个或更多的大参数,这将毫无帮助(我只能将
memo
附加到一个参数)

还有什么可以做的吗?

好吧,你可以毫无顾虑地使用“散列”。Python不会多次计算frozenset的哈希值—仅在创建frozenset时—请检查计时:

>>> 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”内置调用对象的
\uuuuu hash\uuuu
方法,该方法的返回值在创建内置可散列对象时定义。在上面,您可以看到调用identity lambda函数比调用“hash(a)”慢两倍多


因此,如果您的所有参数都是可散列的,那么在创建“组合参数”时只需添加它们的散列即可。否则,只需编写其创建,这样您就可以使用散列对frozenset(可能还有其他)类型使用有条件的。

事实证明,内置的
\uuuuuuuuuu散列>/code>并没有那么糟糕,因为它在第一次计算后缓存了自己的值。真正的性能冲击来自内置的
\uuuuu eq\uuuu
,因为它不会在相同的对象上短路,而且实际上每次都要进行完整的比较,这使得成本非常高

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

class MyFrozenSet(frozenset):
  __eq__ = lambda self, other : id(self) == id(other)
  __hash__ = lambda self : id(self)
这样,字典查找将是即时的。但新阶级的平等性将被打破


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

当您定义
my\u函数时,您知道您总是想跳过
数据的记忆吗?我不想跳过它。但是如果我将
memo
对象作为传递给我的
data
参数的属性,我就不再需要将它包含在键中:毕竟,通过构造一个单独的
memo
将用于每个
data
对象!虽然哈希值实际上不会计算多次,但在创建过程中不会计算。对于一个大的对象,它几乎和创建对象一样昂贵。但我同意,
散列
不是问题所在。似乎
\uuuu eq\uuuu
是一个更大的问题。只有在尝试
frozenset(范围(100000000))
或类似操作时,性能影响才可见。嗯,如果哈希值没有计算多次,为什么python自己的
lru\u cache
实现只是为了缓存元组的哈希值呢?为什么不存储参数的哈希值呢?因为这样你就必须绝对确保不会有两个不同的对象使用相同的哈希值。我同意这种可能性几乎可以忽略不计,但严格来说是不安全的。子类化应该(到第一近似值)遵守,而您的版本肯定不遵守。即使在它确实适用的情况下,使用共享API包装通常也会更好。另外,使用
def\uuuuu hash\uuuuu(self):返回hash(id(self.value))
:额外的
hash
对于分发非常重要。@Veedrac使用wrapped方法,我需要在包装类中定义哪些方法?@max
\uuuuu eq\uuuuu
\uuuuuuu hash
class MyFrozenSet(frozenset):
  __eq__ = lambda self, other : id(self) == id(other)
  __hash__ = lambda self : id(self)