如何使用Cython基于Shape、Strides和Double*指针表示非连续内存视图?
在Cython中用Shape、Strides和指针创建对应NumPy切片的内存视图
好问题!NumPy的切片(比如你例子里的arr_slice和arr_tile)本质上是共享原始数组的数据,只改变了形状(shape)和步长(strides)——而Cython的内存视图完全支持这种“零拷贝”的视图操作,只要你能拿到原始数据的指针、目标视图的形状和步长,就能直接创建对应的内存视图。下面我结合你的示例一步步说明:
先明确你的示例对应的参数值
先把你NumPy代码里的两个切片对应的参数列出来,方便后续对应:
- 原始数组
arr:shape=(10,30),dtype=float64(每个元素8字节),行步长是30*8字节,列步长是8字节 arr_slice = arr[::2]:取所有偶数行,所以shape=(5,30),行步长变成30*8*2(每次跳2行),列步长还是8字节,数据指针和原始数组一致arr_tile = arr[:3, :5]:取前3行前5列,shape=(3,5),行步长还是30*8(因为还是按原始行的间隔取),列步长8字节,数据指针指向原始数组的第一个元素
方法1:借助NumPy数组中转(简单直观)
如果你在Cython里已经能拿到原始数组的指针、目标shape和strides,可以先创建一个不拷贝数据的NumPy数组,再转成Cython内存视图:
import numpy as np cimport numpy as np # 假设我们已经获取到以下参数(对应arr_slice的情况) cdef double* data_ptr = <double*>arr.data # arr是原始NumPy数组 cdef Py_ssize_t shape[2] = [5, 30] cdef Py_ssize_t strides[2] = [30*8*2, 8] # float64是8字节,行步长翻倍 # 创建不拷贝数据的NumPy数组 cdef np.ndarray slice_np = np.ndarray( shape=shape, dtype=np.float64, buffer=data_ptr, strides=strides ) # 转成Cython内存视图 cdef double[:, :] slice_view = slice_np # 测试:修改视图的值,原始数组也会改变(验证零拷贝) slice_view[0, 0] = 99.9 print(arr[0, 0]) # 会输出99.9,说明共享数据
对应arr_tile的话,只需要修改shape和strides:
cdef Py_ssize_t tile_shape[2] = [3, 5] cdef Py_ssize_t tile_strides[2] = [30*8, 8] # 行步长和原始数组一致 cdef np.ndarray tile_np = np.ndarray( shape=tile_shape, dtype=np.float64, buffer=data_ptr, strides=tile_strides ) cdef double[:, :] tile_view = tile_np
方法2:纯Cython方式(不依赖NumPy中转)
如果你不想依赖NumPy的数组构造,可以直接用Cython内置的cython.view.array来创建内存视图,更底层也更高效:
from cython.view cimport array as cython_array from libc.stddef cimport Py_ssize_t # 对应arr_slice的情况 cdef double* data_ptr = <double*>arr.data cdef Py_ssize_t slice_shape[2] = [5, 30] cdef Py_ssize_t slice_strides[2] = [30*8*2, 8] # 直接创建内存视图 cdef double[:, :] slice_view = cython_array( shape=slice_shape, itemsize=sizeof(double), # float64的字节数 format="d", # 格式符,对应double strides=slice_strides, ptr=data_ptr ) # 同样验证共享数据 slice_view[1, 1] = 88.8 print(arr[2, 1]) # 输出88.8,因为arr_slice的第1行对应arr的第2行 # 对应arr_tile的情况 cdef Py_ssize_t tile_shape[2] = [3, 5] cdef Py_ssize_t tile_strides[2] = [30*8, 8] cdef double[:, :] tile_view = cython_array( shape=tile_shape, itemsize=sizeof(double), format="d", strides=tile_strides, ptr=data_ptr )
注意事项
- 确保strides的单位是字节:NumPy的
arr.strides返回的就是字节数,所以直接用就行;如果是自己计算的话,要记得乘以元素的字节长度(比如float64是8) - 内存视图的生命周期要和原始数据一致:如果原始数据被释放了,内存视图就会变成野指针,所以要保证原始数组在视图使用期间一直存在
- 如果你需要更灵活的步长(比如非连续的列),只需要修改对应的strides值就行,原理是一样的
内容的提问来源于stack exchange,提问作者user11488411
相关产品推荐
相关产品推荐

