使用内部数组定义提高 Cython Lapack 性能?
Improving Cython Lapack performance with internal array definitions?
我正在尝试加快我正在构建的模型中的一些矩阵求逆,特别是通过在 Cython 中实现一些线性代数例程。我有代码工作,但正在尝试优化。目前我的 Cython 看起来像这样:
import cython
import numpy as np
cimport numpy as cnp
cimport scipy.linalg.cython_lapack as cython_lapack
cdef int ZERO = 0
ctypedef cnp.float64_t REAL_t
def inverse(mat,identity,pivots,k):
cdef int K = k
cdef REAL_t* mat_pointer = <REAL_t *>cnp.PyArray_DATA(mat)
cdef REAL_t* iden_pointer = <REAL_t *>cnp.PyArray_DATA(identity)
cdef int* piv_pointer = <int *>cnp.PyArray_DATA(pivots)
cython_lapack.dgesv(&K,&K,mat_pointer,&K,piv_pointer,iden_pointer,&K,&ZERO)
一旦我编译(如 linalg.pyx
),我可以 运行 这很好,但是因为 dgesv
修改了一些输入变量,我必须使用包装函数和做一些复制:
import linalg
import numpy as np
identity = np.eye(K) # K is the dimensionality of the square matrix
def lapack_inverse(a):
b = identity.copy()
linalg2.inverse(a,b,np.zeros(K,dtype=np.intc),K)
return b
对于小 K,我们看到相当可靠的速度改进 (2-3x):
In [29]: K=10
In [30]: x = np.random.random((K,K))
In [31]: identity = np.eye(K)
In [32]: print np.allclose(np.linalg.inv(x),lapack_inverse(x))
True
In [33]: %timeit np.linalg.inv(x)
10000 loops, best of 3: 28 µs per loop
In [34]: %timeit lapack_inverse(x)
100000 loops, best of 3: 10.3 µs per loop
但对于较大的 K,returns 明显减小(例如对于 K=200):
In [8]: %timeit np.linalg.inv(x)
100 loops, best of 3: 10.1 ms per loop
In [9]: %timeit lapack_inverse(x)
100 loops, best of 3: 8.1 ms per loop
我想我可以通过在 C 中定义标识和主元数组来获得更好的性能,而不是像我目前正在做的那样进行复制和传递。换句话说,我想修改 Cython 函数,让它只需要我传递要反转的数组。
我的问题是 (a):这会导致速度提高(即使是很小的提高)吗?并且,假设它会,(b) 我如何着手直接在 Cython 中定义枢轴和恒等数组?
在 Cython 中有很多分配内存的方法——基本上你可以创建任何 Python 对象或使用标准的 C 分配技术。 This previous question 列出了很多(答案中给出了时间)并且几乎肯定比我的答案更有用。不过,它处理的用例略有不同。
在您的情况下,您必须 将单位矩阵分配为一个 numpy 数组,因为答案已写入其中并且您想从函数中 return (你显然可以分配它,否则复制,但这似乎有点毫无意义)。我怀疑最好是调用 eye
而不是每次都复制它。
您可以随心所欲地分配枢轴。在下面的示例中,我使用 C 分配机制作为分配大块内存的最直接方式(因此希望最快)。这样做的缺点是您必须使用 free
.
解除分配它
cimport numpy as cnp
import numpy as np
#cimport scipy.linalg.cython_lapack as cython_lapack
from libc.stdlib cimport malloc, free
ctypedef cnp.float64_t REAL_t
def inverse(mat):
# you should probably check that mat.shape[0]==mat.shape[1]
# and that mat is actually a float64 array
cdef int k = mat.shape[0]
cdef int info = 0
cdef REAL_t* mat_pointer = <REAL_t*>cnp.PyArray_DATA(mat)
# I suspect you should be doing a check that mat_pointer has been assigned
cdef REAL_t* iden_pointer
cdef int* piv_pointer = <int*>malloc(sizeof(int)*k)
# this is uninitialised (the contents are arbitrary)
# but that's OK because it's used as an output
try:
identity = np.eye(k)
iden_pointer = <REAL_t*>cnp.PyArray_DATA(identity)
cython_lapack.dgesv(&k,&k,mat_pointer,&K,
piv_pointer,iden_pointer,&K,&info)
# you should check info to ensure it's worked
return identity
finally:
free(piv_pointer) # the "try ... finally" ensures that this is freed
有几个地方我注意到您应该添加检查以确保一切都有效...这会减慢速度但会更安全。您可能还可以添加一些 Cython 类型定义以获得额外的速度,但我还没有真正研究这个例子。
最后 - 想一想为什么要使用逆函数。如果您只是将另一个矩阵乘以逆矩阵,那么直接对该矩阵使用 dgsev
会更好(更快更准确)。很少有充分的理由直接计算逆。
我正在尝试加快我正在构建的模型中的一些矩阵求逆,特别是通过在 Cython 中实现一些线性代数例程。我有代码工作,但正在尝试优化。目前我的 Cython 看起来像这样:
import cython
import numpy as np
cimport numpy as cnp
cimport scipy.linalg.cython_lapack as cython_lapack
cdef int ZERO = 0
ctypedef cnp.float64_t REAL_t
def inverse(mat,identity,pivots,k):
cdef int K = k
cdef REAL_t* mat_pointer = <REAL_t *>cnp.PyArray_DATA(mat)
cdef REAL_t* iden_pointer = <REAL_t *>cnp.PyArray_DATA(identity)
cdef int* piv_pointer = <int *>cnp.PyArray_DATA(pivots)
cython_lapack.dgesv(&K,&K,mat_pointer,&K,piv_pointer,iden_pointer,&K,&ZERO)
一旦我编译(如 linalg.pyx
),我可以 运行 这很好,但是因为 dgesv
修改了一些输入变量,我必须使用包装函数和做一些复制:
import linalg
import numpy as np
identity = np.eye(K) # K is the dimensionality of the square matrix
def lapack_inverse(a):
b = identity.copy()
linalg2.inverse(a,b,np.zeros(K,dtype=np.intc),K)
return b
对于小 K,我们看到相当可靠的速度改进 (2-3x):
In [29]: K=10
In [30]: x = np.random.random((K,K))
In [31]: identity = np.eye(K)
In [32]: print np.allclose(np.linalg.inv(x),lapack_inverse(x))
True
In [33]: %timeit np.linalg.inv(x)
10000 loops, best of 3: 28 µs per loop
In [34]: %timeit lapack_inverse(x)
100000 loops, best of 3: 10.3 µs per loop
但对于较大的 K,returns 明显减小(例如对于 K=200):
In [8]: %timeit np.linalg.inv(x)
100 loops, best of 3: 10.1 ms per loop
In [9]: %timeit lapack_inverse(x)
100 loops, best of 3: 8.1 ms per loop
我想我可以通过在 C 中定义标识和主元数组来获得更好的性能,而不是像我目前正在做的那样进行复制和传递。换句话说,我想修改 Cython 函数,让它只需要我传递要反转的数组。
我的问题是 (a):这会导致速度提高(即使是很小的提高)吗?并且,假设它会,(b) 我如何着手直接在 Cython 中定义枢轴和恒等数组?
在 Cython 中有很多分配内存的方法——基本上你可以创建任何 Python 对象或使用标准的 C 分配技术。 This previous question 列出了很多(答案中给出了时间)并且几乎肯定比我的答案更有用。不过,它处理的用例略有不同。
在您的情况下,您必须 将单位矩阵分配为一个 numpy 数组,因为答案已写入其中并且您想从函数中 return (你显然可以分配它,否则复制,但这似乎有点毫无意义)。我怀疑最好是调用 eye
而不是每次都复制它。
您可以随心所欲地分配枢轴。在下面的示例中,我使用 C 分配机制作为分配大块内存的最直接方式(因此希望最快)。这样做的缺点是您必须使用 free
.
cimport numpy as cnp
import numpy as np
#cimport scipy.linalg.cython_lapack as cython_lapack
from libc.stdlib cimport malloc, free
ctypedef cnp.float64_t REAL_t
def inverse(mat):
# you should probably check that mat.shape[0]==mat.shape[1]
# and that mat is actually a float64 array
cdef int k = mat.shape[0]
cdef int info = 0
cdef REAL_t* mat_pointer = <REAL_t*>cnp.PyArray_DATA(mat)
# I suspect you should be doing a check that mat_pointer has been assigned
cdef REAL_t* iden_pointer
cdef int* piv_pointer = <int*>malloc(sizeof(int)*k)
# this is uninitialised (the contents are arbitrary)
# but that's OK because it's used as an output
try:
identity = np.eye(k)
iden_pointer = <REAL_t*>cnp.PyArray_DATA(identity)
cython_lapack.dgesv(&k,&k,mat_pointer,&K,
piv_pointer,iden_pointer,&K,&info)
# you should check info to ensure it's worked
return identity
finally:
free(piv_pointer) # the "try ... finally" ensures that this is freed
有几个地方我注意到您应该添加检查以确保一切都有效...这会减慢速度但会更安全。您可能还可以添加一些 Cython 类型定义以获得额外的速度,但我还没有真正研究这个例子。
最后 - 想一想为什么要使用逆函数。如果您只是将另一个矩阵乘以逆矩阵,那么直接对该矩阵使用 dgsev
会更好(更快更准确)。很少有充分的理由直接计算逆。