摘要

对于选择 合适的块大小 为 Dask 数组感到困惑吗?

数组块不能太大(否则会耗尽内存),也不能太小(否则 Dask 引入的开销会变得压倒一切)。那么我们如何才能做到恰到好处呢?

这是一个两步过程

  1. 首先,从选择一个与您知道可以在内存中完全处理(即不使用 Dask)的数据大小相似的块大小开始,使用这些粗略的经验法则
  2. 然后,观察 Dask Dashboard 的任务流和工作节点内存图,并根据需要进行调整。这里有一些需要注意的迹象

目录

什么是 Dask 数组块?

Dask 数组是由许多小块构成的大型结构。通常,每个小块是一个独立的 numpy 数组,它们被组织在一起形成一个更大的 Dask 数组。

Diagram: Dask array chunks

您可以在文档的此页面上找到有关 Dask 数组块的更多信息: https://docs.dask.org.cn/en/latest/array-chunks.html

我如何知道我的数组有哪些块?

如果您有一个 Dask 数组,可以使用 chunksizechunks 属性来查看有关块的信息。您也可以使用 Dask 数组的 HTML 表示形式将其可视化。

Visualizating Dask array chunks with the HTML repr

arr.chunksize 显示最大的块大小。对于您期望块大小大致均匀的数组,这是总结块大小信息的好方法。

arr.chunks 显示 Dask 数组中沿所有维度所有块的完全显式大小(请参阅此处的第 3 项)。这更详细,对于具有不规则块的数组来说是个不错的选择。

太小是个问题

如果数组块太小,效率就会很低。为什么会这样?

使用 Dask 会为计算中的每个任务引入一定量的开销。这种开销是 Dask 最佳实践建议您避免过大的图的原因。这是因为如果每个任务的实际工作量非常小,那么开销时间与有用工作时间的比例就不高。

通常,Dask 调度器协调单个任务需要大约 1 毫秒。这意味着我们希望每个任务的计算时间相对更长,例如:以秒为单位而不是毫秒。

这可能很难直观地理解,所以这里有一个类比。想象一下我们正在建造一栋房子。这是一项相当大的工作,如果只有一个工人,建造时间会太长。所以我们有一个工人团队和一个工地工头。工地工头相当于 Dask 调度器:他们的工作是告诉工人需要完成什么任务。

假设我们有一大堆砖头要砌墙,堆在工地的角落。如果工头(Dask 调度器)让工人一次只去搬一块砖,然后把每一块都运到正在砌墙的地方,你可以看到这是多么缓慢和低效!工人们大部分时间都花在往返于墙壁和砖堆之间。用于实际将砖头砌到墙上的时间要少得多。

相反,我们可以用更聪明的方式来做。工头(Dask 调度器)可以告诉工人每次搬回一整车砖。现在工人们在墙壁和砖堆之间移动的时间大大减少,墙壁也会更快地完成。

太大也是个问题

如果 Dask 数组块太大,这也很糟糕。为什么?块太大不好,因为这样你很可能会耗尽工作内存。你可能会看到内存不足的错误发生,或者随着数据溢出到磁盘,性能会大幅下降。

当过多的数据加载到过少的工作节点的内存中时,Dask 会尝试将数据溢出到磁盘而不是崩溃。将数据溢出到磁盘会使事情运行得非常缓慢,因为所有额外的磁盘读/写操作。事情不仅仅是慢一点,而是慢很多,所以注意这一点是明智的。

为了注意这一点,请查看 Dask Dashboard 上的工作节点内存图。橙色条表示您已接近内存限制,灰色表示数据正在溢出到磁盘——这不好!更多提示,请参阅下方有关使用 Dask Dashboard 的部分。

选择初始块大小

粗略的经验法则

  • 如果您已经创建了一个原型,可能根本不涉及 Dask,只使用了您打算处理的数据的一小部分,那么您就会清楚地了解适合此工作流的数据大小。您可以使用这些知识在 Dask 中选择类似大小的块。
  • 有些人观察到小于 1MB 的块大小几乎总是很糟糕。100MB 到 1GB 之间的块大小通常很好,超过 1 或 2GB 意味着您有一个非常大的数据集和/或每个核心有很多可用内存,
  • 上限:避免过大的任务图。超过 10,000 或 100,000 个块可能会开始表现不佳。
  • 下限:为了获得并行化的优势,您需要的块数量至少等于可用工作节点核心的数量(或者更好,是工作节点核心数量的两倍)。否则,一些工作节点将处于空闲状态。
  • 计算每个任务所需的时间应远远大于调度任务所需的时间。Dask 调度器协调单个任务需要大约 1 毫秒,因此一个好的任务计算时间应该以秒(而不是毫秒)为单位。

块应与磁盘上的数组存储对齐

如果您从磁盘读取数据,存储结构将决定您的 Dask 数组块应该是什么形状。为了获得最佳性能,请选择与数据存储方式良好对齐的块。

来自 Dask 最佳实践中关于如何定向您的块的信息

读取数据时,应使块与存储格式对齐。大多数数组存储格式本身就以块的形式存储数据。如果您的 Dask 数组块不是这些块形状的倍数,那么您将不得不重复读取相同的数据,这可能会很昂贵。但请注意,存储格式通常选择的块大小比 Dask 的理想大小小得多,更接近 1MB 而不是 100MB。在这些情况下,您应该选择与存储块大小对齐的 Dask 块大小,并且每个 Dask 块维度都是存储块维度的倍数。

磁盘上的数据存储结构的一些例子包括

  • 一个 HDF5 或 Zarr 数组。存储在磁盘上的块/块的大小和形状应与您选择的 Dask 数组块良好对齐。
  • 包含 tiff 文件的文件夹。您可以决定每个 tiff 文件应成为 Dask 数组中的单个块(或者多个 tiff 文件应组合成一个块)。

使用 Dask Dashboard

选择合适块大小的第二部分是监控 Dask Dashboard,看看是否需要进行任何调整。

如果您对 Dask Dashboard 不太熟悉,或者有时忘记某些 Dashboard 图表(如工作节点内存图)在哪里,那么您可能会喜欢这些快速视频教程

我们建议在使用 Dask 时始终打开 Dashboard。这是了解哪些地方运行良好或不佳的绝佳方式,以便您可以进行调整。

在 Dashboard 上需要注意什么

需要注意的不良迹象包括

  • 任务流图中有大量空白是糟糕的迹象。空白意味着什么都没发生。块可能太小。
  • 任务流图中有大量红色是糟糕的迹象。红色表示工作节点通信。Dask 工作节点需要一些通信,但如果它们几乎除了通信什么都不做,那么就没有多少生产性工作在进行。
  • 在工作节点内存图上,注意橙色条,它们表示您正接近内存限制。块可能太大。
  • 在工作节点内存图上,注意灰色条,它们表示数据正在溢出到磁盘。块可能太大。

这里是 Dask Dashboard 在一次良好计算期间的示例(此视频的 6:12)。Visualizating Dask array chunks with the HTML repr

作为比较,这里是 Dask Dashboard 在一次糟糕计算期间的示例(此视频的 6:57)。

在这个例子中,由于块太小,效率很低,因此我们在任务流图中看到了大量空白和红色工作节点通信。Visualizating Dask array chunks with the HTML repr

重新分块数组

如果您需要在计算过程中更改 Dask 数组的分块,可以使用 rechunk 方法完成。

rechunked_array = original_array.rechunk(new_shape)

警告:重新分块 Dask 数组需要付出代价。

  • 必须重新排列 Dask 图以适应新的块结构。这会立即发生,并将阻塞与 python 的任何其他交互,直到 Dask 重新排列好任务图。
  • 这也会在 Dask 图中插入新的任务。在计算时,现在有更多的任务需要执行。

由于这些原因,最好选择一个良好的初始块大小并避免重新分块。

然而,有时磁盘上存储的数据对齐不佳,可能需要重新分块。例如,这里是 Draga Doncila Pop 谈论块对齐与卫星图像数据。

在这些情况下,rechunker 库会很有用

Rechunker 接收存储在持久性存储设备(如文件系统或云存储桶)中的输入数组(或数组组),并将具有相同数据但不同分块方案的数组(或数组组)写入新位置。Rechunker 被设计用于像 Dask 这样的并行执行框架中。

非托管内存

最后,请记住,您不仅需要考虑内存中数组块的大小,还需要考虑分析函数所消耗的工作内存。在 Dask 中,这有时被称为“非托管内存”。

“非托管内存是 Dask 调度器未直接感知的 RAM,可能导致工作节点耗尽内存并导致计算挂起和崩溃。”——Guido Imperiale

这里有一些处理非托管内存的技巧

感谢阅读

我们希望这篇文章能帮助您弄清楚如何在 Dask 中选择合适的块大小。这篇博文的灵感来自这个推特帖子。如果您想在 Twitter 上关注 Dask,可以在https://twitter.com/dask_dev进行:


博客评论由 Disqus 提供支持