在没有内存开销的情况下设置 Eigen::SparseMatrix 的稀疏模式

Set sparsity pattern of Eigen::SparseMatrix without memory overhead

我需要设置 Eigen::SparseMatrix 的稀疏模式, 我已经知道 (我有唯一的排序列索引和行偏移量)。显然可以通过 setFromTriplets 但不幸的是 setFromTriplets 需要大量额外的内存(至少在我的情况下)

我写了个小例子

const long nRows = 5000000;
const long nCols = 100000;
const long nCols2Skip = 1000;
//It's quite big!
const long nTriplets2Reserve = nRows * (nCols / nCols2Skip) * 1.1;
Eigen::SparseMatrix<double, Eigen::RowMajor, long> mat(nRows, nCols);

std::vector<Eigen::Triplet<double, long>> triplets;

triplets.reserve(nTriplets2Reserve);
for(long row = 0; row < nRows; ++row){
    for(long col = 0; col < nCols; col += nCols2Skip){
        triplets.push_back(Eigen::Triplet<double, long>(row, col, 1));
    }
}
std::cout << "filling mat" << std::endl << std::flush;
mat.setFromTriplets(triplets.begin(), triplets.end());

std::cout << "Finished! nnz " << mat.nonZeros() << std::endl;
//Stupid way to check memory consumption
std::cin.get();

在我的例子中,这个例子在峰值时消耗了大约 26Gb(在行 "filling mat" 和 "Finished" 之间),最终消耗了 18Gb。 (我通过 htop 进行了所有检查)。 ~8Gb 开销对我来说相当大(在我的 "real world" 任务中我有更大的开销)。

所以我有两个问题:

  1. 如何以尽可能少的开销填充 Eigen::SparseMatrix 的稀疏模式
  2. 为什么 setFromTriplets 需要这么多内存?

如果我的示例有误,请告诉我。

我的 Eigen 版本是 3.3.2

PS对不起我的英语

编辑: 看起来 inserting (with preallocation) 每个三元组手动工作得更快,并且在峰值时需要更少的内存。但我还是想知道是否可以手动设置稀疏模式

广告 1:通过使用内部函数 startVec and insertBack,您甚至可以比普通 insert 更高效一点,前提是您可以保证按字典顺序插入元素。

广告 2:如果您使用 setFromTriplets,您需要矩阵最终大小的大约两倍(加上 Triplet 容器的大小),因为元素首先插入到矩阵的转置版本中,然后将其转置为最终矩阵,以确保对所有内部向量进行排序。如果你提前知道矩阵的结构,这显然是相当浪费内存,但它旨在处理任意输入数据。

在您的示例中,您有 5000000 * 100000 / 1000 = 5e8 个元素。一个 Triplet 需要 8+8+8 = 24 字节(vector 大约需要 12Gb),稀疏矩阵的每个元素需要 8+8=16 字节(一个 double 用于值,一个 long 用于内部索引),即每个矩阵大约 8Gb,因此总共需要大约 28Gb,即大约 26 Gib。

奖金: 如果你的矩阵有一些特殊的结构,可以更有效地存储,并且你愿意深入挖掘 Eigen 的内部结构,你也可以考虑实现一个继承自 Eigen::SparseBase<> 的新类型(但我不推荐这个,除非 memory/performance 对您来说非常关键,并且您愿意通过大量 "sparsely" 记录的内部特征代码...)。但是,在那种情况下,考虑您打算对矩阵做什么并尝试仅为此实现特殊操作可能更容易。