最佳实践
目录
最佳实践¶
开始使用 Dask 数组很容易,但要 用好 它需要一些经验。本页面包含最佳实践建议,并提供常见问题的解决方案。
使用 NumPy¶
如果你的数据能够轻松装入 RAM,并且你没有遇到性能瓶颈,那么使用 NumPy 可能是一个不错的选择。Dask 增加了一层额外的复杂性,可能会成为阻碍。
如果你只是寻求加速而不是可伸缩性,那么你可能需要考虑像 Numba 这样的项目。
选择合适的分块大小¶
Dask 数组用户常见的性能问题是选择了过小的分块大小(导致大量开销)或与数据对齐不佳的分块大小(导致读取效率低下)。
虽然最优的大小和形状高度依赖于具体问题,但很少见到小于 100 MB 的分块大小。如果你处理的是 float64 数据,那么对于 2D 数组,大小约为 (4000, 4000)
,对于 3D 数组,大小约为 (100, 400, 400)
。
你应该选择一个足够大的分块大小,以减少 Dask 需要处理的分块数量(这会影响开销),但也要足够小,以便许多分块可以同时装入内存。Dask 通常会在内存中保留相当于活动线程数两倍的分块。
对齐你的分块¶
读取数据时,你应该将分块与你的存储格式对齐。大多数数组存储格式本身就以分块形式存储数据。如果你的 Dask 数组分块不是这些存储分块形状的倍数,那么你就不得不重复读取相同的数据,这会很耗时。请注意,存储格式通常选择的分块大小远小于 Dask 的理想大小,更接近 1MB 而不是 100MB。在这种情况下,你应该选择一个与存储分块大小对齐的 Dask 分块大小,并且每个 Dask 分块维度都是存储分块维度的倍数。
例如,如果我们有一个 HDF 文件,其分块大小为 (128, 64)
,那么我们可以选择一个分块形状为 (1280, 6400)
。
>>> import h5py
>>> storage = h5py.File('myfile.hdf5')['x']
>>> storage.chunks
(128, 64)
>>> import dask.array as da
>>> x = da.from_array(storage, chunks=(1280, 6400))
请注意,如果你提供 chunks='auto'
,Dask 数组将查找 .chunks
属性并使用它来提供良好的分块。
避免线程过度订阅¶
提示
使用 distributed
调度器时,使用 Nanny 工作进程时,OMP_NUM_THREADS
、MKL_NUM_THREADS
和 OPENBLAS_NUM_THREADS
环境变量会自动设置为 1
。这有助于在常见情况下避免线程过度订阅。
默认情况下,Dask 会运行与你的逻辑核心数量相同的并发任务。它假设每个任务消耗大约一个核心。然而,许多数组计算库本身是多线程的,这可能导致资源争用和低性能。特别是支持 NumPy 大多数线性代数例程的 BLAS/LAPACK 库通常是多线程的,需要明确告知它们只使用一个线程。你可以使用以下环境变量来实现(下面使用的是 bash export
命令,但可能因你的操作系统而异)。
export OMP_NUM_THREADS=1
export MKL_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
你需要在启动 Python 进程之前运行此命令才能生效。
考虑使用 Xarray¶
Xarray 包封装了 Dask 数组,因此提供了相同的可伸缩性,同时在处理复杂数据集时增加了便利性。特别是 Xarray 可以帮助处理以下情况:
将多个数组作为一个一致的数据集进行管理
一次性读取堆叠的 HDF 或 NetCDF 文件
使用一致的 API 在 Dask 数组和 NumPy 之间切换
Xarray 应用于广泛的领域,包括物理、天文学、地球科学、显微镜学、生物信息学、工程、金融和深度学习。Xarray 还有一个活跃的用户社区,在提供支持方面表现出色。
构建你自己的操作¶
通常我们想执行一些 Dask 数组中没有精确对应函数的计算。在这种情况下,我们可以使用一些更通用的函数来构建我们自己的操作。这些函数包括:
|
张量运算:广义内积和外积 |
|
对 Dask 数组的所有分块应用一个函数。 |
|
对具有重叠的数组分块应用一个函数 |
|
归约的通用版本 |
这些函数可以帮助你将为 NumPy 函数编写的函数应用于更大的 Dask 数组。