比较Python,Numpy,Numba和C++进行矩阵乘法

JD8*_*121 16 c++ python optimization numpy numba

在我正在研究的程序中,我需要重复乘以两个矩阵.由于其中一个矩阵的大小,这个操作需要一些时间,我想看看哪种方法最有效.矩阵的尺寸(m x n)*(n x p)在哪里m = n = 310^5 < p < 10^6.

除了我认为使用优化算法的Numpy之外,每个测试都包含矩阵乘法的简单实现:

矩阵乘法

以下是我的各种实现:

蟒蛇

def dot_py(A,B):
    m, n = A.shape
    p = B.shape[1]

    C = np.zeros((m,p))

    for i in range(0,m):
        for j in range(0,p):
            for k in range(0,n):
                C[i,j] += A[i,k]*B[k,j] 
    return C
Run Code Online (Sandbox Code Playgroud)

NumPy的

def dot_np(A,B):
    C = np.dot(A,B)
    return C
Run Code Online (Sandbox Code Playgroud)

Numba

代码与Python代码相同,但它在使用之前及时编译:

dot_nb = nb.jit(nb.float64[:,:](nb.float64[:,:], nb.float64[:,:]), nopython = True)(dot_py)
Run Code Online (Sandbox Code Playgroud)

到目前为止,每个方法调用已使用timeit模块定时10次​​.保持最好的结果.使用矩阵创建矩阵np.random.rand(n,m).

C++

mat2 dot(const mat2& m1, const mat2& m2)
{
    int m = m1.rows_;
    int n = m1.cols_;
    int p = m2.cols_;

    mat2 m3(m,p);

    for (int row = 0; row < m; row++) {
        for (int col = 0; col < p; col++) {
            for (int k = 0; k < n; k++) {
                m3.data_[p*row + col] += m1.data_[n*row + k]*m2.data_[p*k + col];
            }
        }
    }

    return m3;
}
Run Code Online (Sandbox Code Playgroud)

mat2是我定义的自定义类,dot(const mat2& m1, const mat2& m2)是此类的友元函数.它是使用QPFQPC来自定时的,Windows.h并且使用MinGW使用该g++命令编译程序.同样,保留了从10次执行中获得的最佳时间.

结果

结果

正如预期的那样,简单的Python代码速度较慢,但​​对于非常小的矩阵,它仍然胜过Numpy.对于最大的案例,Numba比Numpy快约30%.

我对C++结果感到惊讶,其中乘法比Numba多花了几乎一个数量级.事实上,我预计这些时间会相似.

这导致了我的主要问题:这是正常的,如果没有,为什么C++比Numba慢?我刚开始学习C++,所以我可能做错了.如果是这样,我的错误是什么,或者我可以做些什么来提高我的代码效率(除了选择更好的算法)?

编辑1

这是mat2班级的标题.

#ifndef MAT2_H
#define MAT2_H

#include <iostream>

class mat2
{
private:
    int rows_, cols_;
    float* data_;

public: 
    mat2() {}                                   // (default) constructor
    mat2(int rows, int cols, float value = 0);  // constructor
    mat2(const mat2& other);                    // copy constructor
    ~mat2();                                    // destructor

    // Operators
    mat2& operator=(mat2 other);                // assignment operator

    float operator()(int row, int col) const;
    float& operator() (int row, int col);

    mat2 operator*(const mat2& other);

    // Operations
    friend mat2 dot(const mat2& m1, const mat2& m2);

    // Other
    friend void swap(mat2& first, mat2& second);
    friend std::ostream& operator<<(std::ostream& os, const mat2& M);
};

#endif
Run Code Online (Sandbox Code Playgroud)

编辑2

正如许多人所建议的那样,使用优化标志是与Numba相匹配的缺失元素.以下是与之前的曲线相比的新曲线.v2通过切换两个内环获得标记的曲线,并显示另外30%至50%的改善.

结果v2

Mat*_*unn 8

我会推荐什么

如果您想要最高效率,您应该使用专用的线性代数库,其经典之作是BLAS/LAPACK库.有许多实现,例如.英特尔MKL.你写的东西不会超过超优化的库.

矩阵矩阵乘法将成为dgemm例程:d代表double,ge代表一般,mm代表矩阵矩阵乘法.如果您的问题具有其他结构,则可能会调用更具体的功能以进行额外的加速.

注意Numpy dot ALREADY调用dgemm!你可能不会做得更好.

为什么你的c ++很慢

与可能的情况相比,矩阵矩阵乘法的经典直观算法结果很慢.编写利用处理器缓存等方式的代码可以带来重要的性能提升.关键是,大量聪明人投入巨大的力量使矩阵矩阵快速增加,你应该使用他们的工作,而不是重新发明轮子.


zmb*_*mbq 7

绝对-O3用于优化.这会打开矢量化,这可以显着提高代码速度.

Numba应该已经这样做了.