Python Dask在计算具有共同依赖关系的两个值时内存使用率高

Python Dask在计算具有共同依赖关系的两个值时内存使用率高,python,dask,dask-distributed,dask-delayed,Python,Dask,Dask Distributed,Dask Delayed,我在一台机器上使用Dask(LocalCluster,具有4个进程、16个线程、68.56GB内存),并且在尝试同时计算共享依赖项的两个结果时遇到工作内存问题 在下面所示的示例中,仅使用一次计算即可计算result,运行良好且快速,工作人员的组合内存使用量最大约为1GB。然而,当通过两次计算计算结果时,工作人员会快速使用所有内存,并在总内存使用量约为40GB时开始写入磁盘。计算最终将完成,但一旦开始写入磁盘,就会出现预期的大幅减速 直观地说,如果读入一个块,然后立即计算它的两个和,那么就可以丢弃

我在一台机器上使用Dask(
LocalCluster
,具有4个进程、16个线程、68.56GB内存),并且在尝试同时计算共享依赖项的两个结果时遇到工作内存问题

在下面所示的示例中,仅使用一次计算即可计算
result
,运行良好且快速,工作人员的组合内存使用量最大约为1GB。然而,当通过两次计算计算
结果时,工作人员会快速使用所有内存,并在总内存使用量约为40GB时开始写入磁盘。计算最终将完成,但一旦开始写入磁盘,就会出现预期的大幅减速

直观地说,如果读入一个块,然后立即计算它的两个和,那么就可以丢弃该块,并且内存使用率保持较低。然而,Dask似乎正在优先考虑数据的加载,而不是稍后清除内存的聚合计算

如果您能帮助了解这里发生的一切,我们将不胜感激。如何计算具有公共依赖关系的两个结果,而不需要读取基础数据两次或将其完全读入内存

导入dask
将dask.dataframe作为dd导入
将dask.array导入为da
从dask.distributed导入客户端
client=client(“localhost:8786”)
数组=da.random.normal(大小=(int(1e9),10),块=(int(1e6),10))
df=dd.from_数组(数组,列=[str(i)表示范围(10)中的i)])
#不会占用工作内存,总使用量保持在1GB以下
结果=dask.compute(df[“0”].sum())
#这会破坏工人的记忆吗
结果=dask.compute([df[“0”].sum(),df[“1”].sum())

按照数组的构造方式,每次创建块时,它都必须生成数组的每一列。所以优化的一个机会(如果可能的话)是以一种允许按列处理的方式生成/加载数组。这将减少单个任务的内存负载

另一种优化方法是显式指定公共依赖项,例如
dask.compute(df[['0','1']]].sum())
将高效运行

然而,更重要的一点是,默认情况下,
dask
遵循一些经验法则来确定工作的优先级。您有几个可干预的选项(不确定此列表是否详尽):自定义优先级、资源约束、修改计算图(允许工作人员从中间任务释放内存,而无需等待最终任务完成)

修改图表的一种简单方法是,通过手动计算中间和,打破最终和数字与所有中间任务之间的依赖关系:

[results]=dask.compute([df[“0”].map_分区(sum),df[“1”].map_分区(sum)])

请注意,
results
将是两个子列表的列表,但计算每个子列表的总和很简单(尝试在延迟对象上运行
sum
将触发计算,因此在计算
结果之后运行
sum
更有效)。

非常感谢您的帮助!你的建议使我解决了我的问题。我的实际计算比求和要复杂一些,我使用内置的
map
函数对延迟
pd.DataFrame
对象列表的每一项应用一个函数。就像上面示例中的
sum
方法一样,
map
函数似乎阻止了任务的有效分解。从
map
功能切换到for循环或等效的列表理解后,系统能够正确地分割任务并处理计算,而不会占用内存。这太棒了!使用
.visualize()
可以查看优化工作流是否有进一步的好处。