机会性缓存
内容
机会性缓存¶
Dask 通常会尽快删除中间值,以便为更多数据流过计算腾出空间。然而,在某些情况下,我们可能希望保留中间值,因为它们可能在交互式会话中的未来计算中很有用。
我们需要平衡以下关注点:
中间结果可能在未来的未知计算中有用
中间结果也会占用内存,减少当前计算所需的空间。
在这两个关注点之间进行协商有助于我们利用现有的内存来加速未来未预见的计算。我们应该保留哪些中间结果?
本文档解释了一种实验性的、机会主义的缓存机制,该机制自动挑选并存储有用的任务。
激励示例¶
考虑计算CSV文件中某一列的最大值:
>>> import dask.dataframe as dd
>>> df = dd.read_csv('myfile.csv')
>>> df.columns
['first-name', 'last-name', 'amount', 'id', 'timestamp']
>>> df.amount.max().compute()
1000
尽管我们的完整数据集可能太大而无法放入内存,但单个 df.amount
列可能足够小,可以在内存中保存,以防将来可能有用。这在数据探索期间通常是这种情况,因为我们在继续之前会反复调查数据的相同子集。
例如,我们现在可能想要找到数量列的最小值:
>>> df.amount.min().compute()
-1000
在正常操作下,这需要再次读取整个CSV文件。这有些浪费,并且阻碍了交互式数据探索。
两种简单解决方案¶
如果我们事先知道我们想要最大值和最小值,我们可以同时计算它们。Dask 会智能地共享中间结果,只需遍历数据集一次:
>>> dd.compute(df.amount.max(), df.amount.min())
(1000, -1000)
如果我们知道这一列适合内存,那么我们也可以显式地计算该列,然后继续使用纯 Pandas 进行操作:
>>> amount = df.amount.compute()
>>> amount.max()
1000
>>> amount.min()
-1000
如果这些解决方案中的任何一个对你有效,那就太好了。否则,请继续阅读第三种方法。
自动机会性缓存¶
另一种方法是观察 所有 中间计算,并 猜测 哪些计算可能在将来有价值。Dask 有一个 机会主义缓存机制,它会存储显示以下特性的中间任务:
计算成本高
存储成本低
常用
>>> from dask.cache import Cache
>>> cache = Cache(2e9) # Leverage two gigabytes of memory
>>> cache.register() # Turn cache on globally
现在,缓存将监视计算的每一个小部分,并根据上述三个特性(计算成本高、存储成本低、使用频繁)来判断该部分的价值。
Dask 将保留它所能找到的最好的中间结果中的 2GB,随着更好的结果出现,将驱逐较旧的结果。如果 df.amount
列适合 2GB,那么在继续处理时,可能所有内容都会被存储。
如果我们开始处理其他事情,那么 df.amount
列可能会被淘汰,以便为其他更及时的结果腾出空间:
>>> df.amount.max().compute() # slow the first time
1000
>>> df.amount.min().compute() # fast because df.amount is in the cache
-1000
>>> df.id.nunique().compute() # starts to push out df.amount from cache
缓存任务,而非表达式¶
这种缓存发生在低级调度层,而不是高级 Dask DataFrame 或 Dask Array 层。我们没有显式地缓存列 df.amount
。相反,我们缓存了构成 dask 图的那一列的数百个小片段。最终我们可能只缓存了该列的一部分。
这意味着上述机会性缓存机制适用于 所有 Dask 计算,只要这些计算采用一致的命名方案(正如 Dask DataFrame、Dask Array 和 Dask Delayed 所做的那样)。
你可以通过检查缓存对象的以下属性来查看哪些任务被缓存:
>>> cache.cache.data
<stored values>
>>> cache.cache.heap.heap
<scores of items in cache>
>>> cache.cache.nbytes
<number of bytes per item in cache>
缓存对象由 cachey 驱动,这是一个用于机会性缓存的小型库。
免责声明¶
在使用分布式调度器时,机会性缓存不可用。
将缓存限制为固定大小(如2GB)需要Dask准确计算内存中每个对象的大小。这可能会很棘手,特别是对于像列表和元组这样的Python对象,以及包含对象数据类型的DataFrame。
缓存机制完全有可能 少计 对象的大小,导致其使用比预期更多的内存,这可能导致RAM耗尽并使会话崩溃。