Obecnie próbuję wdrożyć podstawowy wektor mnożenie macierzy w Cython (jako część większego larger project to reduce computation) i stwierdzeniu, że mój kod jest około 2x wolniej niż Numpy.dot
.Co powoduje spowolnienie 2x w mojej implementacji Cython w mnożeniu wektorów macierzy?
Zastanawiam się, czy jest coś, co mi brakuje, że jest w wyniku spowolnienia. Piszę zoptymalizowany kod Cythona, deklarując typy zmiennych, wymagające sąsiadujących tablic i unikając chybionych pamięci podręcznych. Próbowałem nawet mieć Cythona jako opakowanie i wywoływanie natywnego kodu C (zobacz poniżej).
Zastanawiam się: co jeszcze mogę zrobić, aby przyspieszyć moją implementację, tak działa tak szybko jak NumPy dla tej podstawowej operacji?
Kod Cython że używam jest beow:
import numpy as np
cimport numpy as np
cimport cython
DTYPE = np.float64;
ctypedef np.float64_t DTYPE_T
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def matrix_vector_multiplication(np.ndarray[DTYPE_T, ndim=2] A, np.ndarray[DTYPE_T, ndim=1] x):
cdef Py_ssize_t i, j
cdef Py_ssize_t N = A.shape[0]
cdef Py_ssize_t D = A.shape[1]
cdef np.ndarray[DTYPE_T, ndim=1] y = np.empty(N, dtype = DTYPE)
cdef DTYPE_T val
for i in range(N):
val = 0.0
for j in range(D):
val += A[i,j] * x[j]
y[i] = val
return y
Mam kompilacji tego pliku (seMatrixVectorExample.pyx
) stosując następujący skrypt:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy as np
ext_modules=[ Extension("seMatrixVectorExample",
["seMatrixVectorExample.pyx"],
libraries=["m"],
extra_compile_args = ["-ffast-math"])]
setup(
name = "seMatrixVectorExample",
cmdclass = {"build_ext": build_ext},
include_dirs = [np.get_include()],
ext_modules = ext_modules
)
i przy użyciu następujących skrypt testowy ocenić wydajność:
import numpy as np
from seMatrixVectorExample import matrix_vector_multiplication
import time
n_rows, n_cols = 1e6, 100
np.random.seed(seed = 0)
#initialize data matrix X and label vector Y
A = np.random.random(size=(n_rows, n_cols))
np.require(A, requirements = ['C'])
x = np.random.random(size=n_cols)
x = np.require(x, requirements = ['C'])
start_time = time.time()
scores = matrix_vector_multiplication(A, x)
print "cython runtime = %1.5f seconds" % (time.time() - start_time)
start_time = time.time()
py_scores = np.exp(A.dot(x))
print "numpy runtime = %1.5f seconds" % (time.time() - start_time)
Na matrycy testowej z n_rows = 10e6
i n_cols = 100
uzyskać:
cython runtime = 0.08852 seconds
numpy runtime = 0.04372 seconds
Edit: Warto wspomnieć, że spowolnienie nie ustępuje nawet kiedy wdrożyć mnożenia macierzy w natywnym kodzie C, a tylko użycie Cython jako opakowanie.
void c_matrix_vector_multiplication(double* y, double* A, double* x, int N, int D) {
int i, j;
int index = 0;
double val;
for (i = 0; i < N; i++) {
val = 0.0;
for (j = 0; j < D; j++) {
val = val + A[index] * x[j];
index++;
}
y[i] = val;
}
return;
}
i tu jest owinięcie Cython, które po prostu wysyła wskaźnik do pierwszego elementu y
, A
i x
. :
import cython
import numpy as np
cimport numpy as np
DTYPE = np.float64;
ctypedef np.float64_t DTYPE_T
# declare the interface to the C code
cdef extern void c_multiply (double* y, double* A, double* x, int N, int D)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def multiply(np.ndarray[DTYPE_T, ndim=2, mode="c"] A, np.ndarray[DTYPE_T, ndim=1, mode="c"] x):
cdef int N = A.shape[0]
cdef int D = A.shape[1]
cdef np.ndarray[DTYPE_T, ndim=1, mode = "c"] y = np.empty(N, dtype = DTYPE)
c_multiply (&y[0], &A[0,0], &x[0], N, D)
return y
[To] (http://stackoverflow.com/questions/10442365/why-is-matrix-multiplication-faster-with-numpy-than-w-typach-in-python) pytanie/odpowiedź wydaje się być powiązane, z różnych powodów podanych w górnej odpowiedzi. Sprawdź to. – russianfool
@russianfool Dzięki! W rzeczywistości przeczytałem odpowiedzi na to pytanie, ale podane powody nie są tak istotne dla tego problemu, ponieważ mam do czynienia z rozmnażaniem macierzy-wektora zamiast mnożenia macierzy macierzy. Wyjaśnię to w moim pytaniu. –
Wydaje się być ze mną spokrewniony; mianowicie, przeczytaj bity dotyczące BLAS/rozwijanych pętli. Możesz znaleźć implementację multiplikacji macierzy-wektora [here] (http://www.netlib.org/clapack/cblas/cgemv.c) i wygląda na to, że mają różne zoptymalizowane wersje na podstawie danych, które posiadasz przekazanie. Na marginesie, nie jestem zbyt zaznajomiony z distutils ... czy mógłbyś przekazać -O2 jako jeden z extra_compile_args? Jeśli nie kompiluje się z -O2 pod maską, nie ma sensu porównywać osiągów. – russianfool