犰狳 SpMat<int> 与 Mat<int> 相比非常慢

Posted

技术标签:

【中文标题】犰狳 SpMat<int> 与 Mat<int> 相比非常慢【英文标题】:Armadillo SpMat<int> extremely slow compared to Mat<int> 【发布时间】:2017-08-02 19:19:39 【问题描述】:

我正在尝试在 Armadillo 中使用稀疏矩阵,并注意到与使用 Mat&lt;int&gt; 的等效代码相比,使用 SpMat&lt;int&gt; 的访问时间存在显着差异。

说明:

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

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

WS, DS:指向NN 维数组的指针 WW: 13 K [max(WS)] DD: 1.7 K [max(DS)] NN:230 万 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();

结果:

Method_One 使用 Mat&lt;int&gt; 的时间已过:0.091 sec Method_Two 使用 SpMat&lt;int&gt;: 30.227 sec 的时间已过(慢了将近 300 倍)

高度赞赏对此的任何见解!



更新:

这个问题已经用较新的犰狳version (8.100.1) 解决了。

以下是新结果:

Method_One 使用 Mat&lt;int&gt; 的时间已过:0.141 sec Method_Two 使用 SpMat&lt;int&gt; 的时间已过:2.127 sec(慢 15 倍,这是可以接受的!)

感谢康拉德和瑞恩。

【问题讨论】:

【参考方案1】:

正如 hbrerkere 已经提到的,问题源于矩阵的值以压缩格式 (CSC) 存储的事实,这使得它耗时

    查找已存在条目的索引:根据列条目是否按其行索引排序,您需要线性搜索或二进制搜索。

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

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

我通常的解决方案是使用单独的稀疏矩阵类型进行组装(您通常会多次访问一个元素)基于坐标格式(存储三元组 (i, j, value))使用std::mapstd::unordered_map 之类的映射来存储矩阵中与位置(i,j) 对应的三元组索引。

this question about matrix assembly中也讨论了一些类似的方法

我最近使用的示例:

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 erow, 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(实际上以坐标格式表示矩阵)的所有值,可能对它们进行排序并在您的犰狳矩阵中执行batch insertion。

【讨论】:

感谢您分享此代码。使用std::map 似乎是填充稀疏矩阵的最佳方式。我希望犰狳在内部做一些更聪明的事情。 @kedarps Armadillo 是开源的,你可以随时提交代码【参考方案2】:

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

使用batch constructors 构造稀疏矩阵要快得多。

【讨论】:

批处理构造函数的行为甚至比在我的 mac 上直接插入还要慢。

以上是关于犰狳 SpMat<int> 与 Mat<int> 相比非常慢的主要内容,如果未能解决你的问题,请参考以下文章

将犰狳中的矩阵从稀疏转换为密集(spmat 到 mat)

C++犰狳稀疏矩阵类型转换

编译犰狳时出错

犰狳 - 矩阵乘法错误

如何将犰狳库修复为 C++

向量到矩阵犰狳