Cython如何初始化全零C风格数组 及np.zeros性能差异问题
问题描述
我需要创建一个大小为rows x cols、所有元素填充为0的矩阵。我日常使用numpy,最初认为按照官方文档说明使用np.zeros是最简单的实现方案,对应代码如下:
DTYPE = np.int ctypedef np.int_t DTYPE_t def f1(): cdef: int dim = 40000 int i, j np.ndarray[DTYPE_t, ndim=2] mat = np.zeros([40000, 40000], dtype=DTYPE) for i in range(dim): for j in range(dim): mat[i, j] = 1
随后我使用C风格数组实现了相同逻辑做性能对比,代码如下:
def f2(): cdef: int dim = 40000 int[40000][40000] mat int i, j for i in range(dim): for j in range(dim): mat[i][j] = 1
在我的设备上测试,numpy版本运行耗时3秒,而C数组版本仅耗时2.4e-5秒。但我发现从f2()返回数组时,数组并未被零填充:上述示例代码中所有元素都被赋值为1自然无法体现该问题,但若不对数组做全量赋值,返回的就不是全零数组。我了解标准C中可以通过int arr[n][m] = {};的方式实现全零初始化,想知道Cython中该如何实现这一操作?如果我的numpy版本代码存在明显的可优化问题,我也可以选择使用numpy方案。
核心疑问
- Cython中如何将C风格数组填充为全
0? - 若现有numpy实现存在明显错误也请指出。
回答
先澄清性能测试的误区
你测得的C数组版本2.4e-5秒耗时是编译器优化无效代码导致的错误结果,没有参考价值:
- 40000×40000的int数组单份占内存约6.4GB,远超过进程默认栈空间大小(通常仅8MB左右),正常在栈上分配这种规模的数组会直接触发段错误崩溃。
- 你在
f2中声明的mat数组既没有返回给外部,也没有被任何后续逻辑使用,开启编译优化时GCC会直接把数组分配、两层循环的代码全部删除,根本没有实际执行,才会出现违背常识的超短耗时。
Cython中C数组零初始化方案
根据数组的分配场景,有两种标准实现:
- 小体积栈上固定长度数组
直接沿用标准C的初始化语法,声明时赋值{0}即可将所有元素填充为0:
# 仅适合总大小在MB级以内的小数组,绝对不要用于大矩阵分配 cdef int small_mat[100][100] = {0}
- 大体积堆分配数组
超过栈容量的大数组必须走堆内存分配,分配完成后用memset批量填充0即可,参考实现:
from libc.stdlib cimport malloc, free from libc.string cimport memset def c_array_demo(): cdef: int dim = 40000 int** mat int i, j # 分配行指针数组 mat = <int**>malloc(dim * sizeof(int*)) if not mat: raise MemoryError("内存分配失败") # 逐行分配内存并零初始化 for i in range(dim): mat[i] = <int*>malloc(dim * sizeof(int)) if not mat[i]: # 回滚释放已分配内存,避免泄漏 for j in range(i): free(mat[j]) free(mat) raise MemoryError("内存分配失败") memset(mat[i], 0, dim * sizeof(int)) # 此处编写矩阵操作逻辑 for i in range(dim): for j in range(dim): mat[i][j] = 1 # 使用完成后必须手动释放所有内存 for i in range(dim): free(mat[i]) free(mat)
注意:纯C堆分配的数组不能直接返回给Python层使用,如果需要对接Python生态,手动转numpy数组的开销会抵消手写C数组的大部分性能收益。
numpy版本的优化方法
你写的numpy版本耗时3秒完全是写法问题,和np.zeros本身性能无关:
- 手写的两层循环没有关闭Cython默认的边界检查、负索引包装等保护逻辑,单元素索引访问开销很高;
- 给全矩阵赋统一值不需要手写循环,numpy内置的
fill方法是原生C实现,性能远高于手写逐元素循环。
优化后的numpy版本参考:
import numpy as np cimport numpy as np cimport cython # 注意:np.int 是numpy已弃用的别名,推荐使用明确位宽的类型 DTYPE = np.intc ctypedef np.intc_t DTYPE_t @cython.boundscheck(False) # 关闭数组边界检查 @cython.wraparound(False) # 关闭Python风格负索引支持 def numpy_optimized(): cdef int dim = 40000 cdef np.ndarray[DTYPE_t, ndim=2] mat = np.zeros((dim, dim), dtype=DTYPE) # 调用内置方法批量赋值,不需要手写双循环 mat.fill(1) return mat
这个版本不需要手动管理内存,可直接返回给Python层使用,实际运行性能和正确实现的C数组版本基本持平,是这类场景下的首选方案。
内容的提问来源于stack exchange,提问作者CodeNoob
相关产品推荐
相关产品推荐

