使用MKL编译时,Eigen C ++运行速度较慢

时间:2016-12-11 18:02:57

标签: c++ performance armadillo intel-mkl eigen3

我最近开始使用Eigen(版本3.3.1),在OLS回归的核心运行基于简单矩阵运算的Armadillo基准,即计算矩阵乘积的逆矩阵,I注意到,当使用MKL库编译时,Eigen的运行速度比没有它的情况要慢。我想知道我的编译说明是否错误。我也试图实现这个操作直接调用MKL BLAS和LAPACK例程,得到了更快的结果,和Armadillo一样快。我无法解释这种糟糕的表现,特别是浮动型。

我编写了下面的代码来实现这个基准:

#define ARMA_DONT_USE_WRAPPER
#define ARMA_NO_DEBUG
#include <armadillo>

#define EIGEN_NO_DEBUG
#define EIGEN_NO_STATIC_ASSERT
#define EIGEN_USE_MKL_ALL
#include <Eigen/Dense>

template <typename T>
using Matrix = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;

#ifdef USE_FLOAT
using T = float;
#else
using T = double;
#endif

int main()
{
    arma::wall_clock timer;

    int niter = 1000000;
    int n = 1000;
    int k = 20;

    arma::Mat<T> Xa = arma::cumsum(arma::randn<arma::Mat<T>>(n, k));
    Matrix<T> Xe = Matrix<T>::Map(Xa.memptr(), Xa.n_rows, Xa.n_cols);

    // Armadillo compiled with MKL
    timer.tic();
    for (int i = 0; i < niter; ++i) {
        arma::Mat<T> iX2a = (Xa.t() * Xa).i();
    }
    std::cout << "...Elapsed time: " << timer.toc() << "\n";

    // Eigen compiled with MKL
    timer.tic();
    for (int i = 0; i < niter; ++i) {
        Matrix<T> iX2e = (Xe.transpose() * Xe).inverse();
    }
    std::cout << "...Elapsed time: " << timer.toc() << "\n";*/

    // Eigen Matrix with MKL routines
    timer.tic();
    for (int i = 0; i < niter; ++i) {
        Matrix<T> iX2e =  Matrix<T>::Zero(k, k);
        // first stage => computing square matrix trans(X) * X
        #ifdef USE_FLOAT
        cblas_ssyrk(CblasColMajor, CblasLower, CblasTrans, k, n, 1.0, &Xe(0,0), n, 0.0, &iX2e(0,0), k);
        #else
        cblas_dsyrk(CblasColMajor, CblasLower, CblasTrans, k, n, 1.0, &Xe(0,0), n, 0.0, &iX2e(0,0), k);
        #endif
        // getting upper part  
        for (int i = 0; i < k; ++i)
            for (int j = i + 1; j < k; ++j)
                iX2e(i, j) = iX2e(j, i);
        // second stage => inverting square matrix
        // initializing pivots
        int* ipiv = new int[k];
        // factorizing matrix
        #ifdef USE_FLOAT 
        LAPACKE_sgetrf(LAPACK_COL_MAJOR, k, k, &iX2e(0,0), k, ipiv);
        #else
        LAPACKE_dgetrf(LAPACK_COL_MAJOR, k, k, &iX2e(0,0), k, ipiv); 
        #endif
        // computing the matrix inverse
        #ifdef USE_FLOAT 
        LAPACKE_sgetri(LAPACK_COL_MAJOR, k, &iX2e(0,0), k, ipiv);
        #else
        LAPACKE_dgetri(LAPACK_COL_MAJOR, k, &iX2e(0,0), k, ipiv);
        #endif
        delete[] ipiv;
    }
    std::cout << "...Elapsed time: " << timer.toc() << "\n";
}

我用以下代码编译了名为test.cpp的文件:

g ++ -std = c ++ 14 -Wall -O3 -march = native -DUSE_FLOAT test.cpp -o run -L $ {MKLROOT} / lib / intel64 -Wl, - no-as-needed -lmkl_gf_lp64 - lmkl_sequential -lmkl_core

我得到以下结果(在I​​ntel(R)Core(TM)i5-3210M CPU @ 2.50GHz上)

  • 表示双重类型:

犰狳与MKL =&gt; 64.0s

特征与MKL =&gt; 72.2s

单独的本征=&gt; 68.7s

Pure MKL =&gt; 64.9s

  • for float type:

犰狳与MKL =&gt; 38.2s

特征与MKL =&gt; 61.1s

单独的本征=&gt; 42.6s

Pure MKL =&gt; 38.9s

注意:我为一个不使用非常大的矩阵的项目运行此测试,我不需要在这个级别进行并行化,我最大的矩阵可能是25行的2000行,而且我需要在更高级别,所以我想避免任何类型的嵌套并行性,这可能会减慢我的代码。

2 个答案:

答案 0 :(得分:2)

正如我在评论中所说,确保在基准测试时禁用turbo-boost。

作为附注并供将来参考,您当前的Eigen代码将调用gemm而不是syrk。你可以明确地要求后者:

Matrix<T> tmp = Matrix<T>::Zero(k, k);
tmp.selfadjointView<Eigen::Lower>().rankUpdate(Xe.transpose());
tmp.triangularView<Eigen::Upper>() = tmp.transpose().triangularView<Eigen::Lower>();
iX2e = tmp.inverse();

对于这样的小矩阵,我真的看不出太多差异。

答案 1 :(得分:0)

我只是想添加以防万一有些人可能会遇到这样的问题,ggael给出的表达必须写成如下,以防它是模板函数/类的一部分,否则编译器将难以进行类型推导

ft_min_word_len

通过此修改并关闭涡轮增压,我得到以下结果:

  • 表示双重类型:

犰狳与MKL =&gt; 79.9s

特征与MKL =&gt; 79.8s

单独的本征=&gt; 71.1s

Pure MKL =&gt; 81.1s

  • for float type:

犰狳与MKL =&gt; 47.2s

特征与MKL =&gt; 50.9s

单独的本征=&gt; 51.8s

Pure MKL =&gt; 48.0s