提交新活动

谢谢!您的提交已收到!
哎呀!提交表单时出现问题。

提交新闻报道

谢谢!您的提交已收到!
哎呀!提交表单时出现问题。

订阅新闻通讯

谢谢!您的提交已收到!
哎呀!提交表单时出现问题。
2021年11月2日

在 Dask 中选择合适的块大小

作者

摘要

对于为 Dask 数组选择一个好的块大小感到困惑吗?

数组块不能太大(会内存不足),也不能太小(Dask 引入的开销会变得难以承受)。那么我们如何才能做到正确呢?

这是一个两步过程

     
  1. 首先,根据你知道可以在内存中完全处理(即不使用 Dask)的数据,使用这些粗略的经验法则来选择一个相似的块大小。
  2.  
  3. 然后,观察 Dask 仪表盘的任务流和工作进程内存图,并根据需要进行调整。以下是需要注意的迹象

目录

什么是 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 仪表盘上的 工作进程内存图。橙色条表示您已接近内存限制,而灰色表示数据正在溢出到磁盘 - 不妙!有关更多提示,请参阅下面关于使用 Dask 仪表盘的部分。

选择初始块大小

粗略的经验法则

     
  • 如果您已经创建了一个原型,可能根本不涉及 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 仪表盘

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

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

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

在仪表盘上要注意什么

需要注意的不良迹象包括

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

这是一个良好计算期间 Dask 仪表盘的示例(此视频中的时间 6:12)。

Visualizating Dask array chunks with the HTML repr

作为对比,这是一个不良计算期间 Dask 仪表盘的示例(此视频中的时间 6:57)。

在此示例中,效率低下是因为块太小,所以在任务流图中看到大量空白区域和红色工作进程通信。

Visualizating Dask array chunks with the HTML repr

重新分块数组

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

rechunked_array = original_array.rechunk(new_shape)

警告:重新分块 Dask 数组是有代价的。

     
  • 必须重新安排 Dask 图以适应新的块结构。这会立即发生,并且在 Dask 重新安排任务图之前会阻止与 Python 的任何其他交互。
  •  
  • 这也会在 Dask 图中插入新任务。在计算时,现在有更多任务需要执行。

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

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

rechunker 库在这些情况下可能很有用

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

非托管内存

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

 “非托管内存是指 Dask 调度器不直接知道的 RAM,它可能导致工作进程内存不足,并导致计算挂起和崩溃。”——Guido Imperiale

以下是处理非托管内存的一些技巧

感谢阅读

我们希望这篇博客文章能帮助您弄清楚如何在 Dask 中选择好的块大小。这篇博客文章的灵感来自于这条 Twitter 推文。如果您想在 Twitter 上关注 Dask,可以访问 https://twitter.com/dask_dev