【发布时间】: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。
- 创建了名为 shared_data、shared_indices、shared_indptr(它们定义了一个 csr 矩阵)、shared_ordering 的空数组。 shared_data 和 shared_indices 的大小取决于对非零条目数量的估计。设置 shared_indptr[0] = 0。这些数组由作业共享。
- 非零条目数 (count_nz) 和输入到 csr 矩阵 (count_row) 的行数的计数器也由作业共享。
- 访问 1 和 2 中描述的资源需要获取锁。
- 每个作业都知道它尝试放入 csr 矩阵的非零条目 (job_indices) 的列索引。它还知道非零条目的值 (job_data) 和非零条目的数量 (job_nz)。
- 每个作业都尝试获取锁。
- 获取锁后,作业会执行以下操作。然后释放锁
.
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.
通过包装共享数组(如 scipy.sparse.csr_matrix((shared_data, shared_indices, shared_indptr)) 创建一个 csr 矩阵对象。
将 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