当我使用本征密集矩阵数据结构时,我应该期待什么

what should I expect when I use Eigen dense matrix data structure?

本文关键字:数据结构 我应该 什么 期待      更新时间:2023-10-16

我需要在程序中使用矩阵数据结构,而C++有2d数组,这是非常低的级别,而像Eigen这样的一些库提供了更高级别的矩阵数据结构。但在我看来,无论一个库在一些高技能的操作(如svd)中表现得多么好,在读(访问)、写、求和、点等基本操作上的快速都应该是此类库的先决条件。因为在实际应用中,这样的基本操作可能比那些高技能的操作频繁得多,如果库在这样的操作上很慢,它可能会成为系统的负担甚至瓶颈。

因此,我使用2d阵列和Eigen3稠密矩阵(MatrixXd)编写了一些非常简单的程序,并比较了它们在4种基本运算上的性能,结果发现,大多数情况下,2d阵列都赢得了Eigen3,这非常令人失望。我在下面列出了我的一些测试结果(代码在最后的附录中):

10000X10000矩阵,编译命令:g++-o test.otest.cpp-O0-msse2

特征:

[!COST]init:6.8秒

[!成本]读数:14.85秒

[!COST]写入:23.02秒

[!成本]总计:3.28秒

[!COST]dot:3.12秒

CPP:

[!COST]初始化:1.81秒

[!COST]读数:2.4秒

[!COST]写入:3.4秒

[!成本]总和:0.63秒

[!COST]点:0.52秒

10000X10000矩阵,编译命令:g++-o test.otest.cpp-O3-msse2

特征:

[!COST]初始化:2.44秒

[!COST]读数:2.16秒

[!COST]写入:2.18秒

[!COST]总和:0.26秒

[!COST]dot:0.26秒

CPP:

[!COST]初始化:1.71秒

[!COST]读数:2.06秒

[!COST]写入:2.24秒

[!COST]总和:0.15秒

[!COST]点:0.06秒

然而,我对此仍然有一些疑问,也许我不应该期望矩阵结构的更高级别抽象应该像它的原始版本一样快,如果是这样,我应该使用像Eigen这样的库来期望什么?请注意,在我的程序中,有一些SVD的高技能操作,而还有一些更基本的操作,如访问矩阵和写入矩阵。

附录,test.cpp:

#include <iostream>
#include <Eigen/Dense>
#include <ctime>
using Eigen::MatrixXf;
inline int cpp_testor_read(float **m, const int M, const int N)
{
    float randomTmp = 0;
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            randomTmp += m[i][j];
            randomTmp -= m[j][i];
        }
    return randomTmp;
}
inline int eigen_testor_read(MatrixXf m, const int M, const int N)
{
    float randomTmp = 0;
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            randomTmp += m(i, j);
            randomTmp -= m(j, i);
        }
    return randomTmp;
}
inline int cpp_testor_write(float **m, const int M, const int N)
{
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            m[i][j] += m[j][i];
            m[j][i] -= m[i][j];
        }
    return m[rand()%10000][rand()%10000];
}
inline int eigen_testor_write(MatrixXf m, const int M, const int N)
{
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            m(i, j) += m(j, i);
            m(j, i) -= m(i, j);
        }
    return m(rand()%10000, rand()%10000);
}
inline int cpp_testor_sum(float **m, const int M, const int N, float val)
{
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            m[i][i] += m[i][j];
        }
    return m[rand()%1000][rand()%1000];
}
inline int eigen_testor_sum(MatrixXf m, const int M, const int N, float val)
{
    m += m;
    return m(0, 0);
}
inline int cpp_testor_dot(float **m, const int M, const int N, float val)
{
    float randomTmp = 0;
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
        {
            m[i][j] *= val;
        }
    return m[rand()%1000][rand()%1000];
}
inline int eigen_testor_dot(MatrixXf m, const int M, const int N, float val)
{
    m *= val;
    return m(0, 0);
}
float** cpp_generator_mtarix(const int M, const int N)
{
    float **m = new float*[M];
    for (int i = 0; i < M; i ++)
        m[i] = new float[N];
    return m;
}
MatrixXf& eigen_generator_matrix(const int M, const int N)
{
    static MatrixXf m(M,N);
    return m;
}
int main()
{
    const int M = 10000;
    const int N = M;
    int antiopt = 0;
    srand(time(NULL));
    float val1 = rand()%10000 + 1;
    float val2 = rand()%10000 + 1;
    std::cout<< M << " " << N << std::endl;
    std::cout<<"Eigen:" << std::endl;
    size_t t = clock();
    //MatrixXf m = eigen_generator_matrix(M, N);
    MatrixXf m(M,N);
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
            m(i,j) = rand()%1000 + 1;
    t = clock() - t;
    std::cout<< "[!COST] init: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += eigen_testor_read(m,M,N);
    t = clock() - t;
    std::cout<< "[!COST] read: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += eigen_testor_write(m,M,N);
    t = clock() - t;
    std::cout<< "[!COST] write: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += eigen_testor_sum(m,M,N, val1);
    t = clock() - t;
    std::cout<< "[!COST] sum: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += eigen_testor_dot(m,M,N, val2);
    t = clock() - t;
    std::cout<< "[!COST] dot: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    std::cout<<"CPP:" << std::endl;
    t = clock();
    //float **mm = cpp_generator_mtarix(M, N);
    float **mm = new float*[M];
    for (int i = 0; i < M; i ++)
        mm[i] = new float[N];
    for (int i = 0; i < M; i ++)
        for (int j = 0; j < N; j ++)
            mm[i][j] = rand()%1000 + 1;
    t = clock() - t;
    std::cout<< "[!COST] init: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += cpp_testor_read(mm,M,N);
    t = clock() - t;
    std::cout<< "[!COST] read: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += cpp_testor_write(mm,M,N);
    t = clock() - t;
    std::cout<< "[!COST] write: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += cpp_testor_sum(mm,M,N, val1);
    t = clock() - t;
    std::cout<< "[!COST] sum: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    t = clock();
    antiopt += cpp_testor_dot(mm,M,N, val2);
    t = clock() - t;
    std::cout<< "[!COST] dot: " << t/float(CLOCKS_PER_SEC) << " sec." <<std::endl;
    std::cout<<antiopt<<std::endl;
}

对于特征测试函数,您通过值传递矩阵,这意味着必须复制它。这些(大)拷贝的时间包含在基准测试中。

相反,您应该通过引用传递矩阵,以避免复制开销,并获得与数组版本相同的语义。有了这个变化,我得到了如下结果,听起来相当快:

10000 10000
Eigen:
[!COST] init: 3.5 sec.
[!COST] read: 2.98 sec.
[!COST] write: 3.03 sec.
[!COST] sum: 0.06 sec.
[!COST] dot: 0.07 sec.
CPP:
[!COST] init: 1.46 sec.
[!COST] read: 3.41 sec.
[!COST] write: 3.57 sec.
[!COST] sum: 0.14 sec.
[!COST] dot: 0.05 sec.

(还要注意,使用-O0进行基准测试是毫无意义的:您明确地告诉编译器不要让它变快。)