Python 是什么导致我的Cython矩阵向量乘法实现速度下降了2倍?

Python 是什么导致我的Cython矩阵向量乘法实现速度下降了2倍?,python,numpy,matrix,cython,linear-algebra,Python,Numpy,Matrix,Cython,Linear Algebra,我目前正试图在Cython中实现基本的矩阵向量乘法(作为MULTE的一部分),发现我的代码比Numpy.dot慢2倍左右 我想知道是否有什么东西我错过了,这是导致经济放缓。我正在编写优化的Cython代码,声明变量类型,需要连续数组,并避免缓存未命中。我甚至尝试使用Cython作为包装器并调用本机C代码(见下文) 我想知道:我还能做些什么来加快我的实现速度,以便在这个基本操作中运行得像NumPy一样快? 我使用的Cython代码如下: import numpy as np cimport nu

我目前正试图在Cython中实现基本的矩阵向量乘法(作为MULTE的一部分),发现我的代码比
Numpy.dot
慢2倍左右

我想知道是否有什么东西我错过了,这是导致经济放缓。我正在编写优化的Cython代码,声明变量类型,需要连续数组,并避免缓存未命中。我甚至尝试使用Cython作为包装器并调用本机C代码(见下文)

我想知道:我还能做些什么来加快我的实现速度,以便在这个基本操作中运行得像NumPy一样快?


我使用的Cython代码如下:

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

Edit:值得一提的是,即使我在本机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; 
}

OK最终获得了比NumPy更好的运行时

这里是(我认为)造成差异的原因:NumPy调用BLAS函数,这些函数是用Fortran而不是C编写的,导致速度差异

我认为这一点很重要,因为我之前的印象是BLAS函数是用C编写的,不明白为什么它们会比我在问题中发布的第二个本机C实现运行得快得多

在任何一种情况下,我现在都可以通过使用Cython+SciPy.linalg.Cython\u BLAS中的SciPy 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_连续的

在我的机器上进行此测试的结果是:

Cython runtime = 0.04556 seconds
Python runtime = 0.05110 seconds

问题/答案似乎是相关的,在顶部答案中给出了各种原因。看看。@russianfool谢谢!我实际上已经通读了这个问题的答案,但给出的理由与这个问题并不完全相关,因为我处理的是矩阵向量乘法,而不是矩阵乘法。我将在我的问题中澄清这一点。似乎与我有关;也就是说,阅读有关BLAS/展开循环的位。你可以找到一个矩阵向量乘法的实现,而且看起来它们有各种基于你传递的数据的优化版本。顺便说一句,我对distutils不太熟悉。。。你能把-O2作为一个额外的参数传入吗?如果不是在引擎盖下使用-O2编译,那么比较性能是没有意义的。将优化的
dot
的系数控制在2以内听起来很不错。在
cython
如何将代码翻译成
c
方面,您没有太多发言权。矩阵向量乘法和矩阵之间的区别并不显著。向量只是一个大小为1维的矩阵。BLAS代码没有做任何不必要的计算。与比较。我不得不问。。。为10%的性能提升付出的所有努力真的值得吗?numpy/Python开销的大小相对于数组维度来说大致是恒定的,因此,当您将其应用于越来越大的数据集时,我预计回报会迅速减少。如果您正在计算大量小矩阵的矩阵积,那么从Cython调用BLAS可能是有意义的(但即使在这种情况下,您也可以使用
np.dot
np.matmul
的内置向量化功能做得很好…)。对于一个大的矩阵积,它可能几乎没有什么区别。@ali_m哈哈,仅仅是矩阵向量乘法绝对不值得。这就是说,对我来说,让它正确运行/理解是什么导致了减速是很重要的,因为这是一个更大的例程的子例程,我打算使用Cython优化它(也只是对人们指向BLAS像一些神奇的黑匣子而不解释它到底在做什么感到失望)。当我第一次实现它时,它的速度太慢了,以至于我认为我在Cython中做了一些非常错误的事情。BLAS没有什么神奇之处,但它确实代表了一群熟练的Fortran程序员的努力,他们准备手工制作这些例程的优化版本,从某种特定的处理器模型中挤出最后一点性能。老实说,我有点惊讶,你甚至可以用简单的矩阵向量乘法得到2的因子。对优化BLAS例程的调用可能是对密集矩阵向量乘法所能做的最好的,除了可能在GPU上做之外。。。
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