机会性缓存
目录
机会性缓存¶
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
列可能足够小,可以保存在内存中,以防将来有用。这在数据探索期间经常发生,因为我们在继续之前会重复调查数据的同一子集。
例如,我们现在可能想找到 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 对象,以及包含对象 dtypes 的 DataFrames。
缓存机制完全可能低估对象的大小,导致其使用的内存超出预期,这可能导致 RAM 溢出并使您的会话崩溃。