稀疏数组

通过将内存中的 NumPy 数组替换为内存中的稀疏数组,我们可以重用 Dask 数组的块算法来实现并行和分布式的稀疏数组。

Dask Array 中的阻塞算法通常围绕内存中的 NumPy 数组进行并行化。然而,如果另一个内存数组库支持 NumPy 接口,那么它也可以利用 Dask Array 的并行算法。特别是 sparse 数组库满足 NumPy API 的一个子集,并且与 Dask Array 配合良好(并且针对其进行了测试)。

示例

假设我们有一个大部分为零的Dask数组:

rng = da.random.default_rng()
x = rng.random((100000, 100000), chunks=(1000, 1000))
x[x < 0.95] = 0

我们可以将这些 NumPy 数组的每个块转换为 sparse.COO 数组:

import sparse
s = x.map_blocks(sparse.COO)

现在,我们的数组不是由许多 NumPy 数组组成,而是由许多稀疏数组组成。从语义上讲,这并没有改变什么。能够工作的操作将继续以相同的方式工作(假设 numpysparse 的行为相同),但性能特征和存储成本可能会发生显著变化:

>>> s.sum(axis=0)[:100].compute()
<COO: shape=(100,), dtype=float64, nnz=100>

>>> _.todense()
array([ 4803.06859272,  4913.94964525,  4877.13266438,  4860.7470773 ,
        4938.94446802,  4849.51326473,  4858.83977856,  4847.81468485,
        ... ])

要求

任何复制了 NumPy ndarray 接口的内存库都应该在这里工作。sparse 库是一个最小的例子。特别是,一个内存库至少应该实现以下操作:

  1. 使用切片、列表和元素进行简单切片(用于切片、重组、重塑等)

  2. 一个与 np.concatenate 接口匹配的 concatenate 函数。这必须在 dask.array.core.concatenate_lookup 中注册。

  3. 所有ufuncs必须支持完整的ufunc接口,包括 dtype=out= 参数(即使它们不能正常工作)

  4. 所有归约操作必须支持完整的 axis=keepdims= 关键字,并且在此方面行为应与 NumPy 一致。

  5. 数组类应遵循 __array_priority__ 协议,并准备好响应优先级较低的其他数组。

  6. 如果需要 dot 支持,应在 dask.array.core.tensordot_lookup 中注册一个与 np.tensordot 接口匹配的 tensordot 函数。

其他操作如 reshape、transpose 等的实现,应遵循关于形状和数据类型的标准 NumPy 约定。未实现这些操作是可以的;如果在尝试这些操作时,并行的 dask.array 将在运行时出错。

混合数组

Dask 的数组支持混合不同类型的内存数组。这依赖于内存数组在必要时知道如何相互交互。当两个数组交互时,具有最高 __array_priority__ 的数组的函数将优先执行(例如,对于连接、张量点积等)。