在没有内存开销的情况下设置 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" 任务中我有更大的开销)。
所以我有两个问题:
- 如何以尽可能少的开销填充 Eigen::SparseMatrix 的稀疏模式
- 为什么 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" 记录的内部特征代码...)。但是,在那种情况下,考虑您打算对矩阵做什么并尝试仅为此实现特殊操作可能更容易。
我需要设置 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" 任务中我有更大的开销)。
所以我有两个问题:
- 如何以尽可能少的开销填充 Eigen::SparseMatrix 的稀疏模式
- 为什么 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" 记录的内部特征代码...)。但是,在那种情况下,考虑您打算对矩阵做什么并尝试仅为此实现特殊操作可能更容易。