性能:Matlab与C ++矩阵向量乘法

时间:2017-10-12 16:07:37

标签: c++ matlab performance matrix eigen

序言

前段时间我问了一个关于Matlab与Python(Performance: Matlab vs Python)性能的问题。我很惊讶Matlab比Python更快,特别是在meshgrid中。在讨论这个问题时,我指出我应该使用Python中的包装器来调用我的C ++代码,因为我也可以使用C ++代码。我在C ++,Matlab和Python中使用相同的代码。

在这样做时,我再一次惊讶地发现Matlab在矩阵汇编计算中比C ++更快。我有一个稍微大一点的代码,我从中调查一段矩阵 - 矢量乘法。较大的代码在多个实例处执行这样的乘法。总体而言,C ++中的代码比Matlab快得多(因为Matlab中的函数调用有开销等),但Matlab似乎在矩阵向量乘法(底部的代码片段)中表现优于C ++。

结果

下表显示了组装内核矩阵所需的时间与将矩阵与向量相乘所需的时间的比较。结果汇编为矩阵大小NxN,其中N从10,000到40,000不等。哪个不是那么大。但有趣的是,Matlab优于C ++,N得到的越大。 Matlab的总​​时间快3.8到5.8倍。此外,它在矩阵汇编计算中也更快。

 ___________________________________________
|N=10,000   Assembly    Computation  Total  |
|MATLAB     0.3387      0.031        0.3697 |
|C++        1.15        0.24         1.4    |
|Times faster                        3.8    |
 ___________________________________________ 
|N=20,000   Assembly    Computation  Total  |
|MATLAB     1.089       0.0977       1.187  |
|C++        5.1         1.03         6.13   |
|Times faster                        5.2    |
 ___________________________________________
|N=40,000   Assembly    Computation  Total  |
|MATLAB     4.31        0.348        4.655  |
|C++        23.25       3.91         27.16  |
|Times faster                        5.8    |
 -------------------------------------------

问题

在C ++中有更快的方法吗?我错过了什么吗?我知道C ++正在使用for循环,但我的理解是Matlab也会在meshgrid中做类似的事情。

代码段

Matlab代码:

%% GET INPUT DATA FROM DATA FILES ------------------------------------------- %
% Read data from input file
Data       = load('Input/input.txt');
location   = Data(:,1:2);           
charges    = Data(:,3:end);         
N          = length(location);      
m          = size(charges,2);       

%% EXACT MATRIX VECTOR PRODUCT ---------------------------------------------- %
kex1=ex1; 
tic
Q = kex1.kernel_2D(location , location);
fprintf('\n Assembly time: %f ', toc);

tic
potential_exact = Q * charges;
fprintf('\n Computation time: %f \n', toc);

Class(使用meshgrid):

classdef ex1
    methods 
        function [kernel] = kernel_2D(obj, x,y) 
            [i1,j1] = meshgrid(y(:,1),x(:,1));
            [i2,j2] = meshgrid(y(:,2),x(:,2));
            kernel = sqrt( (i1 - j1) .^ 2 + (i2 - j2) .^2 );
        end
    end       
end

C ++代码:

修改

使用带有以下标志的make文件进行编译:

CC=g++ 
CFLAGS=-c -fopenmp -w -Wall -DNDEBUG -O3 -march=native -ffast-math -ffinite-math-only -I header/ -I /usr/include 
LDFLAGS= -g -fopenmp  
LIB_PATH= 

SOURCESTEXT= src/read_Location_Charges.cpp 
SOURCESF=examples/matvec.cpp
OBJECTSF= $(SOURCESF:.cpp=.o) $(SOURCESTEXT:.cpp=.o)
EXECUTABLEF=./exec/mykernel
mykernel: $(SOURCESF) $(SOURCESTEXT) $(EXECUTABLEF)
$(EXECUTABLEF): $(OBJECTSF)
    $(CC) $(LDFLAGS) $(KERNEL) $(INDEX) $(OBJECTSF) -o $@ $(LIB_PATH)
.cpp.o:
    $(CC) $(CFLAGS) $(KERNEL) $(INDEX) $< -o $@

`

# include"environment.hpp"
using namespace std;
using namespace Eigen;

class ex1 
{
public:
    void kernel_2D(const unsigned long M, double*& x, const unsigned long N,  double*&  y, MatrixXd& kernel)    {   
        kernel  =   MatrixXd::Zero(M,N);
        for(unsigned long i=0;i<M;++i)  {
            for(unsigned long j=0;j<N;++j)  {
                        double X =   (x[0*N+i] - y[0*N+j]) ;
                        double Y =   (x[1*N+i] - y[1*N+j]) ;
                        kernel(i,j) = sqrt((X*X) + (Y*Y));
            }
        }
    }
};

int main()
{
    /* Input ----------------------------------------------------------------------------- */
    unsigned long N = 40000;          unsigned m=1;                   
    double* charges;                  double* location;
    charges =   new double[N * m]();  location =    new double[N * 2]();
    clock_t start;                    clock_t end;
    double exactAssemblyTime;         double exactComputationTime;

    read_Location_Charges ("input/test_input.txt", N, location, m, charges);

    MatrixXd charges_           =   Map<MatrixXd>(charges, N, m);
    MatrixXd Q;
    ex1 Kex1;

    /* Process ------------------------------------------------------------------------ */
    // Matrix assembly
    start = clock();
        Kex1.kernel_2D(N, location, N, location, Q);
    end = clock();
    exactAssemblyTime = double(end-start)/double(CLOCKS_PER_SEC);

    //Computation
    start = clock();
        MatrixXd QH = Q * charges_;
    end = clock();
    exactComputationTime = double(end-start)/double(CLOCKS_PER_SEC);

    cout << endl << "Assembly     time: " << exactAssemblyTime << endl;
    cout << endl << "Computation time: " << exactComputationTime << endl;


    // Clean up
    delete []charges;
    delete []location;
    return 0;
}

2 个答案:

答案 0 :(得分:9)

正如评论中所述,MatLab依赖于英特尔的MKL库来处理矩阵产品,这是用于此类操作的最快库。尽管如此,Eigen本身应该能够提供类似的性能。为此,请确保使用最新的Eigen(例如3.4)和正确的编译标志来启用AVX / FMA(如果可用)和多线程:

-O3 -DNDEBUG -march=native

由于charges_是一个向量,最好使用VectorXd来表示你想要一个矩阵向量积而不是矩阵向量积。{/ p>

如果您拥有英特尔的MKL,那么您也可以让Eigen uses it获得与MatLab完全相同的性能,以实现这一精确操作。

关于程序集,更好地反转两个循环以启用向量化,然后使用OpenMP启用多线程(添加-fopenmp作为编译器标志)以使最外层循环并行运行,最后您可以使用Eigen简化代码:

void kernel_2D(const unsigned long M, double* x, const unsigned long N,  double*  y, MatrixXd& kernel)    {
    kernel.resize(M,N);
    auto x0 = ArrayXd::Map(x,M);
    auto x1 = ArrayXd::Map(x+M,M);
    auto y0 = ArrayXd::Map(y,N);
    auto y1 = ArrayXd::Map(y+N,N);
    #pragma omp parallel for
    for(unsigned long j=0;j<N;++j)
      kernel.col(j) = sqrt((x0-y0(j)).abs2() + (x1-y1(j)).abs2());
}

使用多线程,您需要测量挂钟时间。在这里(Haswell有4个物理内核运行在2.6GHz),组装时间下降到0.36s,N = 20000,矩阵矢量产品需要0.24s,所以总共0.6s比MatLab快,而我的CPU似乎更慢比你的。

答案 1 :(得分:0)

您可能有兴趣查看MATLAB Central贡献mtimesx

Mtimesx是一个mex函数,它使用BLAS库,openMP和其他方法优化矩阵乘法。根据我的经验,当它最初发布时,在某些情况下可能会超过3个数量级的库存MATLAB。 (我认为,MATHWORKS有点尴尬。)这些天MATLAB已经改进了自己的方法(我怀疑从中借用。)并且差异不那么严重。 MATLAB有时会胜过它。