В программе, над которой я работаю, мне нужно многократно умножать две матрицы. Из-за размера одной из матриц эта операция занимает некоторое время, и я хотел бы узнать, какой метод будет наиболее эффективным. Матрицы имеют размеры (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)
.
С++
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)
является функцией друга для этого класса. Он запускается с использованием QPF
и QPC
из Windows.h
, и программа скомпилируется с помощью MinGW с помощью команды g++
. Опять же, лучшее время, полученное от 10 исполнений, сохраняется.
Результаты
Как и ожидалось, простой код Python работает медленнее, но он по-прежнему превосходит Numpy для очень маленьких матриц. Для большинства случаев Numba оказывается на 30% быстрее, чем Numpy.
Я удивлен результатами С++, где умножение занимает почти на порядок больше времени, чем с Numba. Фактически, я ожидал, что они возьмут такое же количество времени.
Это приводит к моему основному вопросу: это нормально, а если нет, то почему С++ медленнее, чем Numba? Я только начал изучать С++, чтобы я мог что-то делать неправильно. Если да, то какая будет моя ошибка или что я могу сделать для повышения эффективности моего кода (кроме выбора лучшего алгоритма)?
РЕДАКТИРОВАТЬ 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%.