Cython中object类型高效memoryview在nogil函数中的使用问题
Cython中object类型ndarray转memoryview的解决方案
当前使用NUMPY_1_7 C API与Cython 0.29+,cnp.ndarray类型已被弃用,官方推荐使用Cython memoryview。但代码中存在存储object类型的ndarray,原代码示例如下:
cdef class A: cdef cnp.ndarray buff cdef void A_func(self): self.buff = np.empty(10, dtype='object') cdef int idx for idx in range(10): self.buff[idx] = CythonClass(5) cdef void nogil_func(self) nogil: cdef void** buff = <void**> self.buff cdef int idx for idx in range(10): (<CythonClass> buff[idx]).do_something_nogil() cdef class CythonClass: def __cinit__(self, int n): self.n = n cdef int do_something_nogil(self) nogil: cdef int result = 0 cdef int idx for idx in range(5): result += idx return result
需要将cdef cnp.ndarray buff转换为Cython memoryview并在nogil代码块中使用,具体疑问:
- 如何转换为支持存储
object的memoryview?提供的尝试代码是否可行? - 还有哪些实现方案?
- 不同方案的速度与效率权衡是什么?
尝试的代码及报错:
cdef class A: cdef object[:] buff cdef void A_func(self): self.buff = np.empty(10, dtype='object') cdef int idx for idx in range(10): self.buff[idx] = CythonClass(5) cdef void nogil_func(self) nogil: cdef void** buff = <void**> self.buff cdef int idx for idx in range(10): (<CythonClass> buff[idx]).do_something_nogil()
报错翻译:需要算术或指针类型,但操作数类型为__Pyx_memviewslice
问题1:转换方案及尝试代码的可行性
你提供的尝试代码不可行,错误原因是memoryview对象不能直接强制转换为void**指针。要在nogil代码中操作object类型memoryview的底层数据,需要通过memoryview的data属性获取底层指针,再转换为void**。修正后的代码如下:
cdef class A: cdef object[:] buff cdef void A_func(self): self.buff = np.empty(10, dtype='object') cdef int idx for idx in range(10): self.buff[idx] = CythonClass(5) cdef void nogil_func(self) nogil: # 通过data属性获取底层指针,转换为void** cdef void** buff_ptr = <void**> self.buff.data cdef int idx for idx in range(10): (<CythonClass> buff_ptr[idx]).do_something_nogil()
这样就能正确在nogil块中访问存储的CythonClass实例并调用nogil方法。
问题2:其他实现方案
除了上述memoryview方案,还有以下几种可选方案:
- 保留
cnp.ndarray(不推荐但可用):虽然官方标记弃用,但当前版本的Cython仍支持cnp.ndarray[object, ndim=1]类型声明,原代码无需大幅修改就能继续运行。但长期来看可能会被移除,不建议新代码使用。 - 使用C级数组存储指针:直接声明
cdef CythonClass** buff,在A_func中手动分配内存(buff = <CythonClass**>malloc(10 * sizeof(CythonClass*))),逐个存储CythonClass实例指针。这种方式完全脱离Python对象管理,需要手动在类的__dealloc__方法中释放内存,避免泄漏。 - 使用Python List(nogil块不可用):如果不需要在nogil块中操作,可以用
cdef list buff,但list的操作无法脱离GIL,因此不能在nogil_func中使用,仅适用于不需要释放GIL的场景。
问题3:不同方案的速度与效率权衡
- memoryview方案:与原
cnp.ndarray方案速度几乎一致,因为都是直接操作numpy数组的底层内存,同时符合官方推荐的未来兼容方向。内存由numpy自动管理,无需手动处理,兼顾性能与安全性。 - C级数组方案:速度最快,因为完全绕过Python对象的间接层,直接操作原生指针。但需要手动管理内存,容易出现内存泄漏或野指针问题,仅适合对性能极致要求且能保证内存安全的场景。
- 保留
cnp.ndarray方案:性能与memoryview相当,但存在版本兼容风险,未来可能无法使用。 - Python List方案:速度最慢,因为每次访问都需要Python对象的属性查找,且无法在nogil块中使用,仅适合简单场景。
内容的提问来源于stack exchange,提问作者ajl123
相关产品推荐
相关产品推荐

