计算阶段

本页描述了计算的所有部分、一些常见的慢速原因以及如何有效地进行性能分析。本页面向在较大计算中遇到速度问题的更高级用户。

图构建

对 Dask 集合(array、dataframe、bag、delayed)执行的操作会构建任务图。这些是 Python 函数的字典,每次某个函数需要在某些数据块上运行时,都会包含一个条目。当这些字典变得非常大(数百万个任务)时,构建它们的开销可能会相当大。此外,构建图的代码本身也可能效率低下。

幸运的是,这个计算完全在您自己的计算机上以普通 Python 方式进行,因此您可以像分析计算机上的任何其他 Python 代码一样对其进行性能分析,使用 cProfile 模块,或 %prun%snakeviz 等 IPython magic 命令。

假设在性能分析时没有出现明显原因,解决此问题的一个常见方法是尽可能通过增加块大小来减小图的大小,或手动将多个操作批量合并到更少的函数中。

图优化

在提交图进行执行之前,Dask 会检查是否可以稍微清理一下图。这有助于去除不必要的工作,有时还会替换为更高效的操作。然而,如前所述,如果您的图非常大(数百万个任务),这可能会花费一些时间。

同样如前所述,这一切都在您本地机器上的 Python 中进行。您可以使用 dask.optimize 函数将优化与计算分开进行性能分析。

# x, y = dask.compute(x, y)
x, y = dask.optimize(x, y)

人们很少需要更改优化设置。这也很少是导致速度下降的主要原因。

图序列化

当您使用分布式调度器时,图必须发送到调度器进程,然后从调度器发送到工作节点。为了发送这些数据,它们必须首先转换为字节。如果传递的对象非常复杂或非常大,这个序列化过程有时会非常耗时。

最简单的性能分析方法是使用分布式调度器分析 persist 调用。这将包括上述的优化阶段,以及下面的序列化和部分通信阶段(序列化通常是最大的组成部分)。幸运的是,persist 会立即返回,而不等待计算实际完成。

序列化时间长的最常见原因是将大型对象(如 NumPy 数组或 Pandas dataframes)重复地放入图中。Dask 通常在注意到这种情况时会发出警告。通常最好的解决方案是将其作为任务读取数据而不是直接包含它,预先scatter大型数据,或将其包装在 dask.delayed 中。有时序列化是由复杂对象的其他问题引起的。这些问题往往与特定库高度相关,因此很难提供一般性指导。

图通信

然后图必须发送到调度器。您可以查看仪表盘的 /system 选项卡来监视与调度器之间的网络通信。目前没有好的方法对此进行性能分析。

调度

调度器现在接收到图,并且必须填充其内部数据结构,以便能够有效地将这些任务调度到各个工作节点。

只有在填充完这些数据结构后,仪表盘才会显示任何活动。从按下 compute/persist 到看到活动之间的所有时间都花费在上述阶段。

您可以使用仪表盘的 /profile-server 页面对调度成本进行性能分析。然而,这对用户很少有用,因为除非您愿意深入研究调度代码,否则很难在此基础上采取行动。不过,感兴趣的用户可能会对调度器内部工作原理的性能分析报告感兴趣。

如果调度成本很高,那么最好的办法是减小图的大小,通常通过增加块大小来实现。

执行

最后,您的工作节点会被分配一些任务并开始运行。您的代码在工作节点的一个线程上运行,并执行被告知要做的事情。

Dask 的仪表盘是分析和调查此处性能的好工具,特别是 /status/profile 页面。

加速这个阶段通常取决于您提交的任务的作者。如果您使用自定义代码,那可能就是您,或者就是 NumPy 或 Pandas 的开发者。我们鼓励您考虑使用 Cython、Numba 或其他任何常用于加速 Python 代码的高效库。