1. 稀疏矩阵存储格式选型与内存评估

问题界定与评估口径

给定一个大型稀疏矩阵,格式选型的核心不是“哪个格式最好”,而是“在什么操作模式下、以什么内存与构建代价,换取什么样的访问效率”。本例以三个可量化指标作为选型依据:

  • 存储内存:索引数组与数值数组的总字节数,按各格式真实字段计算。
  • 构建开销:从三元组(COO 式输入)构造目标格式所需的排序、计数、前缀和与拷贝工作量。
  • 访问适配:按行遍历(如 SpMV)偏好 CSR,按列遍历偏好 CSC,纯装配/转换阶段偏好 COO。

下面用一个具体算例贯穿:矩阵规模 m = 2,000,000 行、n = 2,000,000 列、非零元 nnz = 50,000,000,索引使用 32 位整数(4 字节),数值使用双精度(8 字节)。

三种格式的字段结构与内存公式

设 nnz 为非零元个数,m 为行数,n 为列数,s 为索引字节数,v 为数值字节数:

  • COO:row[nnz] + col[nnz] + val[nnz],内存 = nnz·(2s + v)。无压缩,允许重复索引与乱序。
  • CSR:row_ptr[m+1] + col_idx[nnz] + val[nnz],内存 = (m+1)·s + nnz·(s + v)。
  • CSC:col_ptr[n+1] + row_idx[nnz] + val[nnz],内存 = (n+1)·s + nnz·(s + v)。

代入算例数据:

格式索引数组数值数组总内存相对 COO
COO2 × 50,000,000 × 4 = 400 MB400 MB800 MB100%
CSR(2,000,001 + 50,000,000) × 4 ≈ 208 MB400 MB≈ 608 MB76%
CSC(2,000,001 + 50,000,000) × 4 ≈ 208 MB400 MB≈ 608 MB76%

可见 CSR 与 CSC 内存相同(当 m = n 时),比 COO 节省约 24%,节省量正好等于一份行/列索引数组。若矩阵极度“高瘦”或“矮胖”,m 与 n 差异会打破这一对称性,此时指针数组长度成为次要但可测的差异项。

三种格式的字段布局与内存构成(nnz = 5×10⁷,索引 4B,数值 8B) COO row[nnz] 200 MB col[nnz] 200 MB val[nnz] 400 MB 合计 800 MB CSR row_ptr 8 MB col_idx[nnz] 200 MB val[nnz] 400 MB 合计 ≈ 608 MB CSC col_ptr 8 MB row_idx[nnz] 200 MB val[nnz] 400 MB 合计 ≈ 608 MB 构建开销(从三元组输入出发) COO:直接写入三数组,O(nnz),无排序、无前缀和 → 构建最快 CSR:按行计数 O(nnz) → 前缀和 O(m) → 按行散射拷贝 O(nnz),需一次行排序或计数排序 CSC:按列计数 O(nnz) → 前缀和 O(n) → 按列散射拷贝 O(nnz),与 CSR 对称 结论:装配阶段用 COO 收集,求解阶段转 CSR/CSC;转换成本约 2 次 O(nnz) 遍历

构建开销的量化估算

构建开销不能只看渐近阶,还要看实际遍历次数与临时内存。以 CSR 为例,从 COO 三元组构造的典型路径是:

  1. 统计每行非零元个数:遍历 row 数组一次,O(nnz),写 row_ptr 计数区。
  2. 对 row_ptr 做前缀和:O(m),得到每行起始偏移。
  3. 按行散射:再遍历一次三元组,把 col 与 val 写入 CSR 的 col_idx 与 val,O(nnz)。

因此 CSR 构建约需 2 次 nnz 遍历 + 1 次 m 遍历,峰值临时内存为 row_ptr 计数区((m+1)·s)加上目标数组本身。COO 构建只需 1 次 nnz 遍历直接写入,但若输入本身乱序且后续需要按行访问,则每次访问都要额外承担随机寻址代价。

用算例估算:nnz = 5×10⁷,两次遍历共 10⁸ 次元素访问;若单次随机访问约 100 ns(受缓存未命中影响),仅散射阶段就约 5 s 量级。这是示例性量级估算,实际取决于内存带宽、是否多线程与输入有序程度,不能当作实测值。

选型决策与量化依据

把上述指标整理成可执行的判断规则:

场景首选格式量化依据
仅装配、暂不求解,或需保留重复索引COO构建 1 次遍历,无前缀和;内存 800 MB 可接受
按行 SpMV、行遍历为主的迭代求解CSR内存 ≈ 608 MB,行内列索引连续,缓存友好
按列访问、列压缩类算法CSC内存 ≈ 608 MB,列内行索引连续
内存极度紧张且 m、n 很大CSR/CSC + 更窄索引若 nnz < 2³¹,索引可降为 4B;指针数组占比小

本例的量化结论:在 m = n = 2×10⁶、nnz = 5×10⁷ 且以按行迭代求解为主时,选 CSR。理由是内存从 800 MB 降到约 608 MB(节省 24%),构建仅多出一次前缀和与一次散射遍历,而按行访问的局部性收益在迭代求解中会被反复放大。若后续算法以按列访问为主,则对称地选 CSC,内存结论不变。

选型时先固定三个量:nnz、m、n,再套用上面的内存公式。很多“内存爆掉”的问题其实来自误用 COO 长期驻留,而不是矩阵本身太大。
索引宽度必须与 nnz 和 m 同时匹配:若 nnz 或指针上界超过 2³¹−1,32 位索引会溢出,必须改用 64 位索引,此时内存公式中的 s 变为 8,CSR 内存会显著上升,需重新评估。