You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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代码块中使用,具体疑问:

  1. 如何转换为支持存储object的memoryview?提供的尝试代码是否可行?
  2. 还有哪些实现方案?
  3. 不同方案的速度与效率权衡是什么?

尝试的代码及报错:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 22:25:30