簡體   English   中英

為什么MATLAB / Octave在特征值問題中用C ++擦拭地板?

[英]Why does MATLAB/Octave wipe the floor with C++ in Eigenvalue Problems?

我希望標題中的問題的答案是我做了一些愚蠢的事情!

這是問題所在。 我想計算一個真實對稱矩陣的所有特征值和特征向量。 我已經使用GNU Scientific Library在MATLAB中實現了代碼(實際上,我使用Octave運行它)和C ++。 我在下面提供了完整的代碼用於兩種實現。

據我所知,GSL帶有自己的BLAS API實現,(以下我將其稱為GSLCBLAS)並使用我使用以下編譯的庫:

g++ -O3 -lgsl -lgslcblas

GSL表明這里使用替代BLAS庫,如自我優化ATLAS庫,以提高性能。 我正在運行Ubuntu 12.04,並已從Ubuntu存儲庫安裝了ATLAS軟件包。 在這種情況下,我編譯使用:

g++ -O3 -lgsl -lcblas -latlas -lm

對於所有三種情況,我已經使用隨機生成的大小為100到1000的矩陣進行了實驗,步長為100.對於每個大小,我執行10個具有不同矩陣的特征分解,並平均所花費的時間。 結果如下:

結果圖

性能上的差異是荒謬的。 對於大小為1000的矩陣,Octave在一秒鍾內執行分解; GSLCBLAS和ATLAS大約需要25秒。

我懷疑我可能錯誤地使用了ATLAS庫。 歡迎任何解釋; 提前致謝。

關於代碼的一些注意事項:

  • 在C ++實現中,不需要使矩陣對稱,因為該函數僅使用它的下三角部分

  • 在Octave中,行triu(A) + triu(A, 1)'強制矩陣是對稱的。

  • 如果您希望編譯自己的Linux機器的C ++代碼,還需要添加標志-lrt ,因為clock_gettime函數。

  • 不幸的是,我不認為clock_gettime在其他平台上退出。 考慮將其更改為gettimeofday

八度代碼

K = 10;

fileID = fopen('octave_out.txt','w');

for N = 100:100:1000
    AverageTime = 0.0;

    for k = 1:K
        A = randn(N, N);
        A = triu(A) + triu(A, 1)';
        tic;
        eig(A);
        AverageTime = AverageTime + toc/K;
    end

    disp([num2str(N), " ", num2str(AverageTime), "\n"]);
    fprintf(fileID, '%d %f\n', N, AverageTime);
end

fclose(fileID);

C ++代碼

#include <iostream>
#include <fstream>
#include <time.h>

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>

int main()
{
    const int K = 10;

    gsl_rng * RandomNumberGenerator = gsl_rng_alloc(gsl_rng_default);
    gsl_rng_set(RandomNumberGenerator, 0);

    std::ofstream OutputFile("atlas.txt", std::ios::trunc);

    for (int N = 100; N <= 1000; N += 100)
    {
        gsl_matrix* A = gsl_matrix_alloc(N, N);
        gsl_eigen_symmv_workspace* EigendecompositionWorkspace = gsl_eigen_symmv_alloc(N);
        gsl_vector* Eigenvalues = gsl_vector_alloc(N);
        gsl_matrix* Eigenvectors = gsl_matrix_alloc(N, N);

        double AverageTime = 0.0;
        for (int k = 0; k < K; k++)
        {   
            for (int i = 0; i < N; i++)
            {
                for (int j = 0; j < N; j++)
                {
                    gsl_matrix_set(A, i, j, gsl_ran_gaussian(RandomNumberGenerator, 1.0));
                }
            }

            timespec start, end;
            clock_gettime(CLOCK_MONOTONIC_RAW, &start);

            gsl_eigen_symmv(A, Eigenvalues, Eigenvectors, EigendecompositionWorkspace);

            clock_gettime(CLOCK_MONOTONIC_RAW, &end);
            double TimeElapsed = (double) ((1e9*end.tv_sec + end.tv_nsec) - (1e9*start.tv_sec + start.tv_nsec))/1.0e9;
            AverageTime += TimeElapsed/K;
            std::cout << "N = " << N << ", k = " << k << ", Time = " << TimeElapsed << std::endl;
        }
        OutputFile << N << " " << AverageTime << std::endl;

        gsl_matrix_free(A);
        gsl_eigen_symmv_free(EigendecompositionWorkspace);
        gsl_vector_free(Eigenvalues);
        gsl_matrix_free(Eigenvectors);
    }

    return 0;
}

我不同意上一篇文章。 這不是一個線程問題,這是一個算法問題。 matlab,R和octave用C ++庫擦拭地板的原因是因為他們的C ++庫使用更復雜,更好的算法。 如果您閱讀八度頁面,您可以找到他們做的事情[1]:

特征值是在幾步過程中計算的,該過程以Hessenberg分解開始,然后是Schur分解,特征值是明顯的。 當需要時,通過進一步操縱Schur分解來計算特征向量。

解決特征值/特征向量問題並非易事。 事實上,它是“C中的數字食譜”中為數不多的東西之一,建議你不要自己實現。 (P461)。 GSL通常很慢,這是我最初的回應。 ALGLIB的標准實現速度也很慢(我大約需要12秒!):

#include <iostream>
#include <iomanip>
#include <ctime>

#include <linalg.h>

using std::cout;
using std::setw;
using std::endl;

const int VERBOSE = false;

int main(int argc, char** argv)
{

    int size = 0;
    if(argc != 2) {
        cout << "Please provide a size of input" << endl;
        return -1;
    } else {
        size = atoi(argv[1]);
        cout << "Array Size: " << size << endl;
    }

    alglib::real_2d_array mat;
    alglib::hqrndstate state;
    alglib::hqrndrandomize(state);
    mat.setlength(size, size);
    for(int rr = 0 ; rr < mat.rows(); rr++) {
        for(int cc = 0 ; cc < mat.cols(); cc++) {
            mat[rr][cc] = mat[cc][rr] = alglib::hqrndnormal(state);
        }
    }

    if(VERBOSE) {
        cout << "Matrix: " << endl;
        for(int rr = 0 ; rr < mat.rows(); rr++) {
            for(int cc = 0 ; cc < mat.cols(); cc++) {
                cout << setw(10) << mat[rr][cc];
            }
            cout << endl;
        }
        cout << endl;
    }

    alglib::real_1d_array d;
    alglib::real_2d_array z;
    auto t = clock();
    alglib::smatrixevd(mat, mat.rows(), 1, 0, d, z);
    t = clock() - t;

    cout << (double)t/CLOCKS_PER_SEC << "s" << endl;

    if(VERBOSE) {
        for(int cc = 0 ; cc < mat.cols(); cc++) {
            cout << "lambda: " << d[cc] << endl;
            cout << "V: ";
            for(int rr = 0 ; rr < mat.rows(); rr++) {
                cout << setw(10) << z[rr][cc];
            }
            cout << endl;
        }
    }
}

如果你真的需要一個快速的庫,可能需要做一些真正的狩獵。

[1] http://www.gnu.org/software/octave/doc/interpreter/Basic-Matrix-Functions.html

我也遇到過這個問題。 真正的原因是matlab中的eig()不計算特征向量,但上面的C版本代碼確實如此。 所花費的時間差異可能大於一個數量級,如下圖所示。 所以比較不公平。

在Matlab中,根據返回值,調用的實際函數將是不同的。 要強制計算特征向量,應使用[V,D] = eig(A) (參見下面的代碼)。

計算特征值問題的實際時間在很大程度上取決於矩陣屬性和所需的結果,例如

  • 真實的或復雜的
  • Hermitian / Symmetric與否
  • 密集或稀疏
  • 僅特征值,特征向量,僅最大特征值等
  • 串行或並行

有針對上述每種情況優化的算法。 在gsl中,這些算法是手動選取的 ,因此錯誤的選擇會顯着降低性能。 某些C ++包裝類或某些語言(如matlab和mathematica)將通過某些方法選擇優化版本。

此外,Matlab和Mathematica使用了並行化。 根據機器的不同,這些進一步擴大了您看到的差距幾次。 可以合理地說,一般復數1000x1000的特征值和特征向量的計算大約是秒和10秒,沒有並行化。

比較Matlab和C. 圖。比較Matlab和C.“+ vec”表示代碼包括特征向量的計算。 CPU%是對N = 1000的CPU使用率的粗略觀察,其上限​​為800%,盡管它們應該完全使用所有8個核心。 Matlab和C之間的差距小於8倍。

比較Mathematica中的不同矩陣類型 圖。比較Mathematica中的不同矩陣類型。 算法自動選擇算法。

Matlab(與特征向量的計算)

K = 10;

fileID = fopen('octave_out.txt','w');

for N = 100:100:1000
    AverageTime = 0.0;

    for k = 1:K
        A = randn(N, N);
        A = triu(A) + triu(A, 1)';
        tic;
        [V,D] = eig(A);
        AverageTime = AverageTime + toc/K;
    end

    disp([num2str(N), ' ', num2str(AverageTime), '\n']);
    fprintf(fileID, '%d %f\n', N, AverageTime);
end

fclose(fileID);

C ++(沒有特征向量的計算)

#include <iostream>
#include <fstream>
#include <time.h>

#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>

int main()
{
    const int K = 10;

    gsl_rng * RandomNumberGenerator = gsl_rng_alloc(gsl_rng_default);
    gsl_rng_set(RandomNumberGenerator, 0);

    std::ofstream OutputFile("atlas.txt", std::ios::trunc);

    for (int N = 100; N <= 1000; N += 100)
    {
        gsl_matrix* A = gsl_matrix_alloc(N, N);
        gsl_eigen_symm_workspace* EigendecompositionWorkspace = gsl_eigen_symm_alloc(N);
        gsl_vector* Eigenvalues = gsl_vector_alloc(N);

        double AverageTime = 0.0;
        for (int k = 0; k < K; k++)
        {   
            for (int i = 0; i < N; i++)
            {
                for (int j = i; j < N; j++)
                {
                    double rn = gsl_ran_gaussian(RandomNumberGenerator, 1.0);
                    gsl_matrix_set(A, i, j, rn);
                    gsl_matrix_set(A, j, i, rn);
                }
            }

            timespec start, end;
            clock_gettime(CLOCK_MONOTONIC_RAW, &start);

            gsl_eigen_symm(A, Eigenvalues, EigendecompositionWorkspace);

            clock_gettime(CLOCK_MONOTONIC_RAW, &end);
            double TimeElapsed = (double) ((1e9*end.tv_sec + end.tv_nsec) - (1e9*start.tv_sec + start.tv_nsec))/1.0e9;
            AverageTime += TimeElapsed/K;
            std::cout << "N = " << N << ", k = " << k << ", Time = " << TimeElapsed << std::endl;
        }
        OutputFile << N << " " << AverageTime << std::endl;

        gsl_matrix_free(A);
        gsl_eigen_symm_free(EigendecompositionWorkspace);
        gsl_vector_free(Eigenvalues);
    }

    return 0;
}

數學

(* Symmetric real matrix + eigenvectors *)
Table[{NN, Mean[Table[(
     M = Table[Random[], {i, NN}, {j, NN}];
     M = M + Transpose[Conjugate[M]];
     AbsoluteTiming[Eigensystem[M]][[1]]
     ), {K, 10}]]
  }, {NN, Range[100, 1000, 100]}]

(* Symmetric real matrix *)
Table[{NN, Mean[Table[(
     M = Table[Random[], {i, NN}, {j, NN}];
     M = M + Transpose[Conjugate[M]];
     AbsoluteTiming[Eigenvalues[M]][[1]]
     ), {K, 10}]]
  }, {NN, Range[100, 1000, 100]}]

(* Asymmetric real matrix *)
Table[{NN, Mean[Table[(
     M = Table[Random[], {i, NN}, {j, NN}];
     AbsoluteTiming[Eigenvalues[M]][[1]]
     ), {K, 10}]]
  }, {NN, Range[100, 1000, 100]}]

(* Hermitian matrix *)
Table[{NN, Mean[Table[(
     M = Table[Random[] + I Random[], {i, NN}, {j, NN}];
     M = M + Transpose[Conjugate[M]];
     AbsoluteTiming[Eigenvalues[M]][[1]]
     ), {K, 10}]]
  }, {NN, Range[100, 1000, 100]}]

(* Random complex matrix *)
Table[{NN, Mean[Table[(
     M = Table[Random[] + I Random[], {i, NN}, {j, NN}];
     AbsoluteTiming[Eigenvalues[M]][[1]]
     ), {K, 10}]]
  }, {NN, Range[100, 1000, 100]}]

在C ++實現中,不需要使矩陣對稱,因為該函數僅使用它的下三角部分。

情況可能並非如此。 參考文獻中 ,聲明:

int gsl_eigen_symmv(gsl_matrix * A,gsl_vector * eval,gsl_matrix * evec,gsl_eigen_symmv_workspace * w)

該函數計算實對稱矩陣A的特征值和特征向量。 必須在w中提供適當大小的附加工作空間。 A的對角線和下三角形部分在計算過程中被破壞,但沒有參考嚴格的上三角形部分。 特征值存儲在向量eval中並且是無序的。 相應的特征向量存儲在矩陣evec的列中。 例如,第一列中的特征向量對應於第一特征值。 保證特征向量相互正交並歸一化為單位幅度。

您似乎還需要在C ++中應用類似的對稱化操作,以便獲得至少正確的結果,盡管您可以獲得相同的性能。

在MATLAB方面,由於本參考文獻中所述的多線程執行,特征值分解可能更快:

內置多線程

線性代數和數值函數,如fft,\\(mldivide),eig,svd和sort在MATLAB中是多線程的。 自Release 2008a以來,MATLAB中默認啟用了多線程計算。 這些函數在單個MATLAB會話中自動在多個計算線程上執行,從而允許它們在支持多核的機器上執行得更快。 此外,Image Processing Toolbox™中的許多功能都是多線程的。

為了測試MATLAB對單核的性能,可以通過以下方式禁用多線程

文件>首選項>常規>多線程

在R2007a或更高版本的說明這里

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM