如何在无GIL环境下用Cython手动创建Memoryview实现多视图访问
使用Cython Memoryview在无GIL环境下实现多视图访问numpy缓冲区
问题背景
我有一个作为缓冲区使用的连续一维numpy数组,通过多个numpy视图以不同形状、维度、偏移量访问数据,Python示例代码如下:
import numpy as np import math as m shape_0 =(2,3) shape_1 = (2,2,2) shifts = np.zeros((3),dtype=np.intp) shifts[1] = m.prod(shape_0) shifts[2] = shifts[1] + m.prod(shape_1) buf = np.random.random((shifts[2])) arr_0 = buf[shifts[0]:shifts[1]].reshape(shape_0) arr_1 = buf[shifts[1]:shifts[2]].reshape(shape_1) print(buf) print(arr_0) print(arr_1)
需要实现的是:在无GIL环境下,用Cython的Memoryview完成相同功能。
实现方案
Cython Memoryview可以直接映射numpy数组的缓冲区,支持指定偏移、形状和步幅,且能在释放GIL的代码块中操作,完全匹配需求。以下是具体实现步骤:
1. 编写Cython核心代码(buffer_views.pyx)
import numpy as np cimport numpy as np from cython cimport nogil from libc.stdlib cimport malloc, free from libc.math cimport prod # 对应numpy float64类型的内存视图类型定义 ctypedef np.float64_t DTYPE_t def create_and_operate_views(np.ndarray[DTYPE_t, ndim=1] buf): cdef: # 定义各视图的形状 int shape_0[2] = [2, 3] int shape_1[3] = [2, 2, 2] # 计算各段数据的元素偏移量 Py_ssize_t shift_1 = prod(shape_0, 2) Py_ssize_t shift_2 = shift_1 + prod(shape_1, 3) # 基于原缓冲区创建不同形状的内存视图,::1表示连续内存步幅 DTYPE_t[:, ::1] arr_0_view = buf[0:shift_1].reshape(shape_0) DTYPE_t[:, :, ::1] arr_1_view = buf[shift_1:shift_2].reshape(shape_1) # 释放GIL执行操作,此区域不受Python全局锁限制 with nogil: # 示例:遍历打印arr_0_view内容 print("arr_0_view (无GIL环境操作):") for i in range(shape_0[0]): for j in range(shape_0[1]): print(arr_0_view[i][j], end=" ") print() # 示例:遍历打印arr_1_view内容 print("\narr_1_view (无GIL环境操作):") for i in range(shape_1[0]): for j in range(shape_1[1]): for k in range(shape_1[2]): print(arr_1_view[i][j][k], end=" ") print() print() # 可选:将内存视图转回numpy数组,方便Python层验证 return np.asarray(arr_0_view), np.asarray(arr_1_view)
2. 编写编译脚本(setup.py)
from setuptools import setup from Cython.Build import cythonize import numpy as np setup( ext_modules=cythonize("buffer_views.pyx"), include_dirs=[np.get_include()] )
3. 编译与测试
执行编译命令生成扩展模块:
python setup.py build_ext --inplace
在Python中测试功能:
import numpy as np import math as m import buffer_views # 生成与原示例一致的缓冲区数组 shape_0 =(2,3) shape_1 = (2,2,2) shifts = np.zeros((3),dtype=np.intp) shifts[1] = m.prod(shape_0) shifts[2] = shifts[1] + m.prod(shape_1) buf = np.random.random((shifts[2])) # 调用Cython函数创建视图并执行无GIL操作 cy_arr_0, cy_arr_1 = buffer_views.create_and_operate_views(buf) # 验证结果与原Python代码一致 print("\n原Python代码生成的arr_0:") print(buf[0:shifts[1]].reshape(shape_0)) print("\nCython返回的arr_0:") print(cy_arr_0)
关键说明
- 无GIL特性:通过
with nogil:代码块释放GIL,此区域内的内存视图操作完全不受Python全局解释器锁限制,适合并行计算场景。 - 零拷贝视图:Memoryview直接映射原numpy数组的缓冲区,没有数据复制,内存效率与numpy原生视图一致。
- 连续内存适配:
::1指定步幅为单个元素大小,匹配原一维连续缓冲区的内存布局,若处理非连续数组可调整步幅参数。
内容的提问来源于stack exchange,提问作者G. Fougeron
相关产品推荐
相关产品推荐

