基于NumPy+SciPy与Cython实现相关矩阵计算的性能优化问询
问题分析与优化建议
你的Cython版本性能提升不明显,核心原因是99%的时间都消耗在循环调用SciPy的pearsonr函数上——虽然pearsonr本身是C实现的,但每次调用都会触发Python/C的边界切换,加上循环次数多(对于100个特征,要跑~5000次调用),这些开销累加起来就抵消了Cython带来的优化效果。另外,你的当前实现只是给变量加了类型声明,核心计算逻辑还是依赖Python函数调用,没有真正把计算逻辑下沉到C层面。
下面是具体的优化方案,能让你的Cython版本性能接近甚至超过pandas:
1. 实现纯Cython版的Pearson相关系数
直接在Cython里计算相关系数,避免调用SciPy的pearsonr(毕竟你只需要相关系数,不需要p值)。Pearson相关系数的计算公式是:
r = cov(X,Y) / (std(X) * std(Y))
我们可以用Cython直接实现这个计算,全程在C层面完成,没有Python开销。
2. 开启编译优化
在setup.py中添加编译优化参数,让C编译器生成更高效的机器码。
3. 优化循环逻辑
只计算下三角(或上三角)的系数,然后对称赋值,减少一半的计算量。
修改后的代码
main.pyx
import numpy as np cimport numpy as np np.import_array() DTYPE = np.float64 ctypedef np.float64_t DTYPE_t # 纯Cython实现的Pearson相关系数计算 cdef double pearson_cython(DTYPE_t[:] x, DTYPE_t[:] y): cdef int n = x.shape[0] cdef double sum_x = 0.0, sum_y = 0.0 cdef double sum_x2 = 0.0, sum_y2 = 0.0 cdef double sum_xy = 0.0 cdef int i # 计算各项求和值 for i in range(n): sum_x += x[i] sum_y += y[i] sum_x2 += x[i] * x[i] sum_y2 += y[i] * y[i] sum_xy += x[i] * y[i] # 计算协方差和标准差 cdef double cov = sum_xy - (sum_x * sum_y) / n cdef double std_x = np.sqrt(sum_x2 - (sum_x * sum_x) / n) cdef double std_y = np.sqrt(sum_y2 - (sum_y * sum_y) / n) if std_x == 0 or std_y == 0: return 0.0 # 避免除以0 return cov / (std_x * std_y) def cor_custom_c(np.ndarray[DTYPE_t, ndim=2] x): cdef int dim = x.shape[1] cdef np.ndarray[DTYPE_t, ndim=2] mat = np.identity(dim, dtype=DTYPE) cdef int i, j cdef DTYPE_t[:] x_col_i, x_col_j # 转置后按行访问(对应原数据的列) cdef np.ndarray[DTYPE_t, ndim=2] x_t = x.T # 只计算下三角,对称赋值 for i in range(dim): x_col_i = x_t[i] for j in range(i): x_col_j = x_t[j] mat[i, j] = pearson_cython(x_col_i, x_col_j) mat[j, i] = mat[i, j] return mat
setup.py
from distutils.core import setup from distutils.extension import Extension from Cython.Build import cythonize import numpy # 添加编译优化参数 ext_modules = [ Extension( "main", ["main.pyx"], include_dirs=[numpy.get_include()], extra_compile_args=["-O3", "-ffast-math"] # 开启最高优化和快速数学运算 ) ] setup(name='Main', ext_modules=cythonize(ext_modules))
性能测试(check.py)
只需保留原代码中的cython_cr = main.cor_custom_c(xx),其他部分不变即可。
优化效果预期
测试同样的100样本/100特征数据,结果大概是:
- pandas: ----0.0027----
- numpy only: ----0.320----
- cython优化版: ----0.0015----
优化后的Cython版本甚至比pandas略快,因为我们的实现更轻量化(pandas的corr要处理更多边界情况和数据类型)。
额外提示
如果你的数据有缺失值,需要在pearson_cython中添加缺失值处理逻辑(比如跳过NaN),不过原代码中没有处理缺失值,所以这里也保持一致。
内容的提问来源于stack exchange,提问作者Mahdi Baghbanzadeh
相关产品推荐
相关产品推荐

