【问题标题】:Parallel Construction of Compressed Sparse Column Matrix压缩稀疏列矩阵的并行构造
【发布时间】:2016-10-04 11:29:15
【问题描述】:

具有 m 列的压缩稀疏列矩阵 (csc),定义为 here。

它有三个数组:Data、Indices 和 Indptr。

  • indptr 是一维数组,大小为 m+1
  • indices 是矩阵中所有非零项的行索引。
  • indices[indptr[i]:indptr[i + 1]] 是第 i 列的所有非零条目的行索引。这些非零条目的值定义在 data[indptr[i]:indptr[i + 1]]

具有 n 行的压缩稀疏行 (csr) 矩阵具有类似的definition。

问题:

Cpu 处理 n 个作业。第 p 个作业为具有 n 行和 m 列的稀疏矩阵(csc 或 csr)的第 q 行生成数据。 p 和 q 不必相等,只要记录哪个作业对应哪个行即可。

如何有效地从作业中收集数据并生成 csc 矩阵?

目前的解决方案:

方法 A。

  1. 创建了名为 shared_data、shared_indices、shared_indptr(它们定义了一个 csr 矩阵)、shared_ordering 的空数组。 shared_data 和 shared_indices 的大小取决于对非零条目数量的估计。设置 shared_indptr[0] = 0。这些数组由作业共享。
  2. 非零条目数 (count_nz) 和输入到 csr 矩阵 (count_row) 的行数的计数器也由作业共享。
  3. 访问 1 和 2 中描述的资源需要获取锁。
  4. 每个作业都知道它尝试放入 csr 矩阵的非零条目 (job_indices) 的列索引。它还知道非零条目的值 (job_data) 和非零条目的数量 (job_nz)。
  5. 每个作业都尝试获取锁。
  6. 获取锁后,作业会执行以下操作。然后释放锁

.

after acquiring lock
shared_data[counter_nz:(counter_nz + job_nz)] = job_data
shared_indices[counter_nz:(counter_nz + job_nz)] = job_indices
shared_indptr[count_row + 1] = shared_indptr[count_row] + job_nz
shared_ordering[counter_row] = job_id
counter_nz  += job_nz
counter_row += 1
release lock.
  1. 通过包装共享数组(如 scipy.sparse.csr_matrix((shared_data, shared_indices, shared_indptr)) 创建一个 csr 矩阵对象。

  2. 将 csr 转换为 csc 矩阵(目标是制作 csc 矩阵而不是 csr 矩阵...)

这种方法的问题是锁确实会影响性能。这就像十几个人试图抓住同一个工具来处理项目的不同部分。

方法 B

每个 cpu 处理 m 个作业,并使用 A 中描述的方法构建一个 csc 矩阵。这种方式使用更多内存,但生成矩阵更快。

方法 C

一个 cpu 处理 m 个工作并构建一个 csc 矩阵。这比在 12 cpu 机器上接近 A 所花费的时间大约多两倍。

方法 D(2016 年 6 月 5 日更新)

有 s 个 cpu 核心。最终 csc 矩阵的 shared_data、shared_indptr 和 shared_indices 数组和 shared_ordering 数组由所有 cpu 内核共享。不需要锁。每个 cpu 核心运行一个子进程(而不是一直启动和停止子进程) 每个 cpu 核心进程 1/s 的 m 个作业和每个 cpu 用 ​​A 中描述的方法构造一个小的 csc 矩阵。在一个 cpu 完成所有作业后,它将三种大小的数据广播到其他 cpu 中,并将其放入其小型 csc 矩阵的三个数组中的每个数组中。一旦一个 cpu 从所有其他 cpu 接收到此信息(有 s - 1 个信息要从其他 cpu 接收),它就会计算其小 csc 矩阵的三个数组在最终 csc 矩阵的三个数组中的位置。然后它将其小的 csc 矩阵复制到共享的 csc 矩阵。

之后,cpu向其他cpu发送信号。一旦 CPU 接收到 s - 1 个信号,它就会开始下一个周期。 (可能不需要第二次同步。)

希望同步所浪费的时间比 A 中的锁定要少得多。而且这种方法从 1 个 cpu 核心线性扩展到 12 个 cpu 核心。如果有人有这方面的经验,请随时提出建议。

方法 D 的一个缺点是它使用的内存量是保存最终 csc 矩阵所需内存量的两倍。一半的内存用于共享数组。另一半被小 csc 矩阵的所有数组使用......这似乎没问题。在方法 B 中,每个 cpu 只有大约 1/s 的整个内存可供使用。方法 D 更好。

可以将小矩阵的数组保留为缓冲区,因此无需浪费时间重新创建 numpy 数组。

我正在尝试与 B 合作并优化每个作业的每个 cpu 核心的内存使用情况。但是,如果方法 A 可以通过更智能的锁设计避免扼杀性能,我会使用 A ......我想我会选择 D。 相关问题: Efficiently populate SciPy sparse matrix from subset of dictionary

【问题讨论】:

  • 不分析你的设置:你确定,不是迭代填充(改变稀疏结构)这些矩阵的概念是这里的性能杀手吗?通常使用 lil 或 dok 矩阵来增量构建稀疏矩阵(然后在之后转换;这很快)。 csr_matrix 增量填充的个人经验也很糟糕,我不得不切换 dok_matrix 作为中间步骤。 (您参考的文档中也提到了类似的内容)
  • 十几个人共享同一个工具是一个更大的问题。我使用python的日志记录模块来做计时。 A 使用 12 个 cpu,C 使用 1 个 cpu,但 A 的速度只有 C 的两倍左右。Lil 会使用更多内存……我现在正试图将内存优化到死。我还没有检查过。哦,我编写了一个自定义 python 函数,用于首先为三个数组定义一个缓冲区,然后填充数据,而不是使用 scipy 中的那个。除了共享一个工具的问题仍然存在之外,这要好得多。
  • 就我的经验而言,我永远无法直接构建 csr_matrix(对于像 csr_matrix 是高效的。
  • from_buffer 是如何工作的?
  • 本文的灵感来自this blog

标签: python matrix parallel-processing scipy sparse-matrix


【解决方案1】:

听起来您已经创建了一个csr 矩阵,然后直接修改了 3 个属性数组。并且您可以通过附加到现有数组来做到这一点。虽然我操纵了 csr 的 data 属性,但我没有直接更改其他属性的经验。如果没有稀疏代码对象,似乎很难可靠地做到这一点。

另一种方法是先构造3个数组,然后构造csr。而不是对每一行使用np.append,而是附加到列表,然后使用np.concatenate 加入它们。

如果您使用coo 输入样式,您可以按任意顺序附加行。一项工作不必知道前几行中有多少项。

coo 风格的输入特别适合有限元刚度矩阵,其中元素的矩阵重叠。而coo输入可以直接到csc。

我最近在这里讨论了从块构建稀疏矩阵: https://stackoverflow.com/a/37040831/901925

和

tile operation to create a csr_matrix from one row of another csr_matrix

lil 适合增量工作,因为属性是两个列表对象数组 - 每行一个列表。所以更新只需要用新列表替换两个空列表 - 一个指针操作。但我认为lil 到csc 比coo 到csc 慢(但我没有测试过)。

【讨论】:

  • 抱歉在 A 的描述中使用了单词 append。我编辑了我的问题。
  • 非常感谢您的建议。 Coo 和 lil 的内存效率似乎低于 csc/csr。虽然我还没有真正尝试过。遗憾的是,数据不是稀疏块矩阵。只有当矩阵的所有行都相同时,平铺操作似乎才有效。为了改进方法 A,更智能的锁和并行性是主要问题。对于改进方法 B,内存是主要问题。 C 比较浪费cpu 功率,但是很容易编程。
  • 使用从每个作业返回的条目的链接列表可能不需要锁定。数组比链表更节省内存,尤其是在 python 中。如果内存使用情况不是太糟糕,那可以适用于方法 A。让我尝试一下。谢谢。
  • @rxu 你有没有让并行稀疏矩阵工作?当我看到你六月的笔记时,我也在制定并行计划。
猜你喜欢
  • 2015-12-05
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
  • 2019-03-12
  • 2015-12-30
  • 2023-03-25
  • 2017-07-07
  • 2020-07-01
相关资源
最近更新 更多