我最近开始使用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
我得到以下结果(在Intel(R)Core(TM)i5-3210M CPU @ 2.50GHz上)
犰狳与MKL =&gt; 64.0s
特征与MKL =&gt; 72.2s
单独的本征=&gt; 68.7s
Pure MKL =&gt; 64.9s
犰狳与MKL =&gt; 38.2s
特征与MKL =&gt; 61.1s
单独的本征=&gt; 42.6s
Pure MKL =&gt; 38.9s
注意:我为一个不使用非常大的矩阵的项目运行此测试,我不需要在这个级别进行并行化,我最大的矩阵可能是25行的2000行,而且我需要在更高级别,所以我想避免任何类型的嵌套并行性,这可能会减慢我的代码。
答案 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
犰狳与MKL =&gt; 47.2s
特征与MKL =&gt; 50.9s
单独的本征=&gt; 51.8s
Pure MKL =&gt; 48.0s