1

プログラムで行列データ構造を使用する必要があります.C++には2次元配列があり、非常に低レベルですが、Eigenなどの一部のライブラリは高レベルの行列データ構造を提供します. しかし、私の意見では、ライブラリが svd などの高度な操作でどれほど優れたパフォーマンスを発揮しても、読み取り (アクセス)、書き込み、合計、ドットなどの基本的な操作の高速化は、そのようなライブラリの前提条件であるべきです。 . 実際のアプリケーションでは、このような基本的な操作は高度な操作よりもはるかに頻繁に行われる可能性があるため、ライブラリでそのような操作が遅いと、システムの負担やボトルネックになる可能性があります。

そこで、2 次元配列と Eigen3 密行列 (MatrixXd) の両方を使用していくつかの非常に単純なプログラムを作成し、4 つの基本演算でのパフォーマンスを比較すると、ほとんどの場合、2 次元配列が Eigen3 に勝つことがわかりました。これは非常に残念です。テスト結果の一部を以下にリストします (コードは最後の付録にあります)。

10000X10000 行列、コンパイル コマンド: g++ -o test.o test.cpp -O0 -msse2

固有値:

[!COST] 初期化: 6.8 秒。

[!COST] 読み取り: 14.85 秒。

[!COST] 書き込み: 23.02 秒。

[!COST] 合計: 3.28 秒。

[!COST] ドット: 3.12 秒。

CPP:

[!COST] 初期化: 1.81 秒。

[!COST] 読み取り: 2.4 秒。

[!COST] 書き込み: 3.4 秒。

[!COST] 合計: 0.63 秒。

[!COST] ドット: 0.52 秒。

10000X10000 行列、コンパイル コマンド: g++ -o test.o test.cpp -O3 -msse2

固有値:

[!COST] 初期化: 2.44 秒。

[!COST] 読み取り: 2.16 秒。

[!COST] 書き込み: 2.18 秒。

[!COST] 合計: 0.26 秒。

[!COST] ドット: 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;
}
4

1 に答える 1

3

固有値テスト関数の場合、マトリックスを値で渡します。つまり、コピーする必要があります。これらの (大規模な) コピーの時間は、ベンチマークに含まれています。

代わりに、コピーのオーバーヘッドを回避し、配列バージョンと同じセマンティクスを取得するために、参照によって行列を渡す必要があります。この変更により、次のような結果が得られます。これはかなり高速に聞こえます。

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: 高速にしないようにコンパイラに明示的に指示します。)

于 2013-07-03T03:12:04.097 に答える