针对超大稀疏矩阵的高效逐元素访问结构(Python/Cython)

15 投票
1 回答
2126 浏览
提问于 2025-04-17 07:21

我正在寻找一种高效的数据结构,用于在Python/Cython中表示一个非常大的整数矩阵,重点是元素级的操作。

我现在正在构建一个模型,需要对一个非常大的、稀疏的矩阵进行大量的元素级操作(大约在一个2百万乘50万的矩阵上进行500亿次读写)。之前我在较小的数据上进行过实验,使用了Python、Cython和Numpy数组,理想情况下我希望继续使用现有的一些基础设施。

我已经查看和实现了一些选项。虽然它们可能没有完全优化,但所有的实现都足够好,可以给出每种方法潜力的真实想法。我通过创建一个2百万乘50万的矩阵,添加2500万元素,然后再将它们移除来进行测试。这反映了我需要的操作类型。

29 minutes: Cython lists to fill scipy.sparse.coo -> sparse.dok 
10 minutes: Cython lists to fill scipy.sparse.coo -> sparse.lil 
 3 minutes: Dict, s.t. A["%d_%d" % (i,j)] contains M[i][j]
 3 minutes: Dict, s.t. A[(i,j)] contains M[i][j]
<1 minute:  Dict, s.t. A[i*N,j] contains M[i][j]
<1 minute:  <std::map> using Cython

目前,拼凑一个字典的方式表现最好,但仍然相当慢。而且这感觉像是一个临时解决方案,所以我认为一定有更高效的方法,特别是考虑到字典的解决方案并没有真正利用Cython的潜力。不知道有没有更符合Cython风格的解决方案?可惜谷歌没有提供太多帮助(或者我没有找到正确的搜索关键词)。

如果有任何建议,我将非常感激!

编辑 1

这两种字典解决方案的区别在于,A["%d_%d" % (i,j)]这种方式在访问时更快,而A[(i,j)]这种方式在设置时更快。

                                                  Setup    Execution
Dict, s.t. A["%d_%d" % (i,j)] contains M[i][j]    180s      30s
Dict, s.t. A[(i,j)] contains M[i][j]               66s     104s
Dict, s.t. Dict, s.t. A[i*N,j] contains M[i][j]    40s       8s

虽然在当前测试中A["%d_%d" % (i,j)]稍微慢一些,但从长远来看,它会更可取,因为设置成本只会增加10倍,而执行成本会增加10,000倍,这对我的实际实验来说。

编辑 2

我可以通过去掉字符串操作,改用一个大的整数表示法来进一步加速字典版本,方法是将两个索引用一个合适的大乘数连接在一起,以避免冲突。乘法是用cdef类型定义的:

cdef unsigned int key(int a, int b): return a * 10000000 + b

通过优化字典或将数据结构移到C中,可能仍然有进一步提高速度的可能,但这对于我的目的来说应该已经足够快了。如果还有其他建议,我仍然非常欢迎!如果我找到使用stl map或类似数据结构的更高效的解决方案,会再回来报告。

编辑 3

根据同事的建议,我还通过Cython接口实现了使用<std::map>的系统,将数据存储在<int,int>映射中。实际上,一旦数据量大了,这种实现的速度比dict实现稍慢,主要的区别在于访问速度:

                  Small data (25MM elements)         Large data (250MM elements)
          Time    total    setup    read/write       total    setup    read/write
Dict[int keys]      40s      25s       8s             369s     269s      72s
<std::map>          26s      11s      12s             376s     169s     177s

1 个回答

3

如果你不需要按行或按列来访问数据,可以使用一个字典,字典的键是一个元组,比如 A[(i,j)]

撰写回答