与Mat <int>相比,Armadillo SpMat <int>非常慢

时间:2017-08-02 19:19:40

标签: c++ sparse-matrix mex armadillo

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

说明

以下两种方法在各个方面都相同,只是Method_One使用常规矩阵而Method_Two使用稀疏矩阵。

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

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

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

代码:

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;
}

定时:

我在Armadillo中使用wall_clock计时器对象计时两种方法。例如,

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

结果:

  • 使用Method_OneMat<int>
  • 0.091 sec的时间已过去
  • 使用Method_Two SpMat<int> 30.227 sec的时间已经过去了Method_One(差不多300倍)

对此有任何见解都非常感谢!

更新:

此问题已通过较新的version (8.100.1)犰狳修复。

以下是新结果:

  • 使用Mat<int>0.141 sec
  • Method_Two的时间已过去
  • 使用SpMat<int>2.127 sec {{1}}的时间已经过去了(慢了15倍,这是可以接受的!)

感谢康拉德和瑞安。

2 个答案:

答案 0 :(得分:2)

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

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

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

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

    我通常的解决方案是使用单独的稀疏矩阵类型进行装配(通常多次访问元素)基于坐标格式(存储三元组(i,j,值))使用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 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的所有值(实际上代表协调格式的矩阵),可能会对它们进行排序并执行{{3}进入你的犰狳矩阵。

答案 1 :(得分:1)

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

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