<int> 与垫子<int>相比,犰狳SpMat非常慢

Armadillo SpMat<int> extremely slow compared to Mat<int>

本文关键字:int gt lt SpMat 非常 犰狳 相比      更新时间:2023-10-16

我正在尝试在犰狳中使用稀疏矩阵,并且注意到与使用Mat<int>的等效代码相比,SpMat<int>的访问时间存在显着差异。

描述:

下面是两种方法,除了Method_One使用规则矩阵和Method_Two使用稀疏矩阵之外,它们在各个方面都是相同的。

这两种方法都采用以下参数:

  • WS, DS:指向NN维数组的指针
  • WW: 13 K [max(WS)]
  • DD: 1.7 千米 [max(DS)]
  • NN: 2.3 米
  • TT: 50

我正在使用Visual Studio 2017将代码编译为可以从Matlab调用的.mexw64可执行文件。

法典:

void Method_One(int WW, int DD, int TT, int NN, double* WS, double* DS)
{
Mat<int> WP(WW, TT, fill::zeros); // (13000 x 50) matrix
Mat<int> DP(DD, TT, fill::zeros); // (1700  x 50) matrix
Col<int> ZZ(NN, fill::zeros);     // 2,300,000 column vector
for (int n = 0; n < NN; n++)
{
int w_n = (int) WS[n] - 1;
int d_n = (int) DS[n] - 1;
int t_n = rand() % TT;
WP(w_n, t_n)++;
DP(d_n, t_n)++;
ZZ(n) = t_n + 1;
}
return;
}
void Method_Two(int WW, int DD, int TT, int NN, double* WS, double* DS)
{
SpMat<int> WP(WW, TT);        // (13000 x 50) matrix
SpMat<int> DP(DD, TT);        // (1700  x 50) matrix
Col<int> ZZ(NN, fill::zeros); // 2,300,000 column vector
for (int n = 0; n < NN; n++)
{
int w_n = (int) WS[n] - 1;
int d_n = (int) DS[n] - 1;
int t_n = rand() % TT;
WP(w_n, t_n)++;
DP(d_n, t_n)++;
ZZ(n) = t_n + 1;
}
return;
}

定时:

我正在使用犰狳中的计时器对象对这两种方法进行计时wall_clock。例如

wall_clock timer;
timer.tic();
Method_One(WW, DD, TT, NN, WS, DS);
double t = timer.toc();

结果:

  • 使用Mat<int>进行Method_One的时间:0.091 sec
  • 使用SpMat<int>进行Method_Two的时间:30.227 sec(几乎慢300倍(
对此

的任何见解都非常感谢!



更新:

此问题已在较新版本 (8.100.1( 的犰狳中修复。

以下是新结果:

  • 使用Mat<int>进行Method_One的时间:0.141 sec
  • 使用SpMat<int>进行Method_Two的时间:2.127 sec(慢 15 倍,这是可以接受的!

感谢康拉德和瑞恩。

正如hbrerkere已经提到的,问题源于这样一个事实,即矩阵的值以打包格式(CSC(存储,这使得

  1. 查找现有条目的索引:根据列条目是按其行索引排序,您需要线性搜索还是二叉搜索。

  2. 插入
  3. 一个以前为零的值:在这里,您需要找到新值的插入点并在此之后移动所有元素,从而导致单次插入的最坏情况时间Ω(n(!

所有这些操作都是密集矩阵的常量时间操作,这主要解释了运行时的差异。

我通常的解决方案是根据坐标格式(存储三元组(i,j,值((使用单独的稀疏矩阵类型进行汇编(通常多次访问元素(,该坐标格式使用std::mapstd::unordered_map等映射来存储对应于矩阵中位置(i,j)的三元组索引。

在这个关于矩阵组装的问题中也讨论了一些类似的方法

我最近使用的例子:

class DynamicSparseMatrix {
using Number = double;
using Index = std::size_t;
using Entry = std::pair<Index, Index>;
std::vector<Number> values;
std::vector<Index> rows;
std::vector<Index> cols;
std::map<Entry, Index> map; // unordered_map might be faster,
// but you need a suitable hash function
// like boost::hash<Entry> for this.
Index num_rows;
Index num_cols;
...
Number& value(Index row, Index col) {
// just to prevent misuse
assert(row >= 0 && row < num_rows);
assert(col >= 0 && col < num_cols);
// Find the entry in the matrix
Entry e{row, col};
auto it = map.find(e);
// If the entry hasn't previously been stored
if (it == map.end()) {
// Add a new entry by adding its value and coordinates
// to the end of the storage vectors.
it = map.insert(make_pair(e, values.size())).first;
rows.push_back(row);
cols.push_back(col);
values.push_back(0);
}
// Return the value
return values[(*it).second];
}
...
};

组装后,您可以存储来自rowscolsvalues(实际上以坐标格式表示矩阵(中的所有值,可以对它们进行排序并批量插入到犰狳矩阵中。

稀疏矩阵以压缩格式(CSC(存储。每次将非零元素插入稀疏矩阵时,都必须更新整个内部表示。这很耗时。

使用批处理构造函数构造稀疏矩阵要快得多。