在我正在开发的程序中,我需要反复相乘两个矩阵。由于其中一个矩阵的大小,这个操作需要一些时间,因此我想知道哪种方法是最有效的。这些矩阵的维度为
除了我认为会使用优化算法的Numpy之外,每个测试都包括对矩阵乘法的简单实现。 以下是我的各种实现:
Python
到目前为止,每个方法调用都使用
我对C++的结果感到惊讶,其中乘法所需的时间几乎比Numba长一个数量级。事实上,我原以为它们需要相似的时间。
这引出了我的主要问题:这是正常现象吗?如果不是,为什么C++比Numba慢?我刚开始学习C++,所以可能做错了什么。如果是这样,我的错误是什么,或者我能做些什么来提高代码的效率(除了选择更好的算法)?
编辑1:
这是mat2类的头文件。
(m x n)*(n x p)
,其中 m = n = 3
,且 10^5 < p < 10^6
。除了我认为会使用优化算法的Numpy之外,每个测试都包括对矩阵乘法的简单实现。 以下是我的各种实现:
Python
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
Numpy
def dot_np(A,B):
C = np.dot(A,B)
return C
Numba
这段代码与Python代码相同,但在使用之前会即时编译:
dot_nb = nb.jit(nb.float64[:,:](nb.float64[:,:], nb.float64[:,:]), nopython = True)(dot_py)
到目前为止,每个方法调用都使用
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;
}
这里,mat2
是我定义的自定义类,dot(const mat2& m1, const mat2& m2)
是该类的友元函数。使用Windows.h
中的QPF
和QPC
进行计时,程序使用g++
命令在MinGW下编译。同样,从10次执行中获得的最佳时间被保留。
结果
正如预期的那样,简单的Python代码虽然比Numpy慢,但对于非常小的矩阵仍然胜过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
编辑2
正如许多人建议的那样,使用优化标志是与Numba匹配的缺失元素。以下是新曲线与先前曲线的比较。标记为v2
的曲线通过交换两个内部循环获得,并显示出另外30%至50%的改进。