Python/Cython: numpy数组中存储类的开销
这段慢慢的代码可以通过改变结构来改进,但有时候这样做会比较困难。我觉得问题的原因在于把类存储在数组里。我听说内存视图可以用来连接Python和C语言的数组,但我对这些还不太熟悉(只懂一点Python)。
有没有什么高效的方法来做到以下这些呢?
一个示例类:
cdef class ClassWithAdditionFunction:
cdef double value
def __init__(self, double value):
self.value = value
cpdef add_one(self):
self.value += 1
一个慢慢的函数:
cdef unsigned long int i, ii
cdef unsigned long int loops = pow(10, 8)
cdef double value
addition_classes = np.array([None] * 10)
for i in range(len(addition_classes)):
addition_classes[i] = ClassWithAdditionFunction(value=0)
for i in range(loops/10):
for ii in range(10):
addition_classes[ii].add_one()
非常感谢任何建议!
1 个回答
7
有一些小技巧可以帮助你提高代码的运行速度。你想要加速的代码行是 addition_classes[ii].add_one()
。如果你使用 cython -a
来查看代码的底层运行情况,你会发现它实际上调用了 Pyx_GetItemInt、PyObject_GetAttr 和 PyObject_Call 这三个步骤。你需要调整你的代码,尽量避免这三次调用。
为了避免 GetItem 调用,你可以使用 numpy 的缓冲区接口或者内存视图。这可以告诉 cython 数组的结构,从而更高效地从数组中提取元素。在下面的例子中,我使用了内存视图。如果你做类似的事情,请确保这个数组确实是由 ClassWithAdditionFunction 实例组成的数组,否则你可能会遇到段错误(segfault)。
为了避免 GetAttr 调用,你可以声明一个 ClassWithAdditionFunction 类型的变量,并在这个变量上调用方法。这样,cython 就知道这个变量有一个编译过的方法版本,可以用来加快调用速度。
最后,你已经用 cpdef 方法定义了 add_one,但我建议你也添加一个返回类型。通常我们可以用 void,但因为这是一个 cpdef 函数,而不是 cdef 函数,所以你可以用 int 作为返回类型。
把这些结合起来,代码应该看起来像这样:
import numpy as np
cimport cython
cdef class ClassWithAdditionFunction:
cdef double value
def __init__(self, double value):
self.value = value
cpdef int add_one(self):
self.value += 1
return 0
@cython.boundscheck(False)
@cython.wraparound(False)
def main():
cdef:
unsigned long int i, ii, loops = 10 ** 6
ClassWithAdditionFunction addInstance
double value, y
addition_classes = np.array([None] * 10)
cdef ClassWithAdditionFunction[:] arrayview = addition_classes
for i in range(len(addition_classes)):
addition_classes[i] = ClassWithAdditionFunction(value=0)
for i in range(loops/10):
for ii in range(10):
addInstance = arrayview[ii]
addInstance.add_one()
return None