[英]What is causing the 2x slowdown in my Cython implementation of matrix vector multiplication?
我目前正在尝试在Cython中实现基本矩阵矢量乘法(这是一个更大的项目,以减少计算量 ),并发现我的代码比Numpy.dot
慢大约2 Numpy.dot
。
我想知道是否有我遗漏的东西导致速度下降。 我正在编写优化的Cython代码,声明变量类型,需要连续的数组,并避免缓存未命中。 我什至尝试使用Cython作为包装器并调用本机C代码(请参见下文)。
我想知道: 对于此基本操作,我还能做些什么来加快实现速度,使其与NumPy一样快地运行?
我正在使用的Cython代码是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
我正在使用以下脚本来编译此文件( seMatrixVectorExample.pyx
):
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
)
并使用以下测试脚本评估性能:
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)
对于n_rows = 10e6
和n_cols = 100
的测试矩阵,我得到:
cython runtime = 0.08852 seconds
numpy runtime = 0.04372 seconds
编辑:值得一提的是,即使我在本机C代码中实现矩阵乘法,并且仅使用Cython作为包装器,减速仍然持续。
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;
}
这是Cython包装器,它仅将指针发送到y
, A
和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
好吧,终于设法获得了比NumPy更好的运行时!
这是造成差异的原因(我认为):NumPy正在调用BLAS函数,这些函数是用Fortran而不是C编码的,从而导致速度差异。
我认为要注意这一点很重要,因为以前我的印象是BLAS函数是用C编码的,无法理解为什么它们的运行速度比我在问题中发布的第二个本机C实现要快得多。
无论哪种情况,我现在都可以使用scipy.linalg.cython_blas中的Cython + SciPy Cython BLAS函数指针来复制性能scipy.linalg.cython_blas.
为了完整起见,这是新的Cython代码blas_multiply.pyx
:
import cython
import numpy as np
cimport numpy as np
cimport scipy.linalg.cython_blas as blas
DTYPE = np.float64
ctypedef np.float64_t DTYPE_T
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def blas_multiply(np.ndarray[DTYPE_T, ndim=2, mode="fortran"] A, np.ndarray[DTYPE_T, ndim=1, mode="fortran"] x):
#calls dgemv from BLAS which computes y = alpha * trans(A) + beta * y
#see: http://www.nag.com/numeric/fl/nagdoc_fl22/xhtml/F06/f06paf.xml
cdef int N = A.shape[0]
cdef int D = A.shape[1]
cdef int lda = N
cdef int incx = 1 #increments of x
cdef int incy = 1 #increments of y
cdef double alpha = 1.0
cdef double beta = 0.0
cdef np.ndarray[DTYPE_T, ndim=1, mode = "fortran"] y = np.empty(N, dtype = DTYPE)
blas.dgemv("N", &N, &D, &alpha, &A[0,0], &lda, &x[0], &incx, &beta, &y[0], &incy)
return y
这是我用来构建的代码:
!/usr/bin/env python
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy
import scipy
ext_modules=[ Extension("blas_multiply",
sources=["blas_multiply.pyx"],
include_dirs=[numpy.get_include(), scipy.get_include()],
libraries=["m"],
extra_compile_args = ["-ffast-math"])]
setup(
cmdclass = {'build_ext': build_ext},
include_dirs = [numpy.get_include(), scipy.get_include()],
ext_modules = ext_modules,
)
这是测试代码(请注意,传递给BLAS函数的F_CONTIGUOUS
现在为F_CONTIGUOUS
)
import numpy as np
from blas_multiply import blas_multiply
import time
#np.__config__.show()
n_rows, n_cols = 1e6, 100
np.random.seed(seed = 0)
#initialize data matrix X and label vector Y
X = np.random.random(size=(n_rows, n_cols))
Y = np.random.randint(low=0, high=2, size=(n_rows, 1))
Y[Y==0] = -1
Z = X*Y
Z.flags
Z = np.require(Z, requirements = ['F'])
rho_test = np.random.randint(low=-10, high=10, size= n_cols)
set_to_zero = np.random.choice(range(0, n_cols), size =(np.floor(n_cols/2), 1), replace=False)
rho_test[set_to_zero] = 0.0
rho_test = np.require(rho_test, dtype=Z.dtype, requirements = ['F'])
start_time = time.time()
scores = blas_multiply(Z, rho_test)
print "Cython runtime = %1.5f seconds" % (time.time() - start_time)
Z = np.require(Z, requirements = ['C'])
rho_test = np.require(rho_test, requirements = ['C'])
start_time = time.time()
py_scores = np.exp(Z.dot(rho_test))
print "Python runtime = %1.5f seconds" % (time.time() - start_time)
在我的机器上进行此测试的结果是:
Cython runtime = 0.04556 seconds
Python runtime = 0.05110 seconds
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.