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

尝试用Cython优化NumPy向量化运算性能未获提升的原因咨询

问题:Cython加速NumPy向量化运算未获预期性能提升

我尝试通过Cython化(或验证可行性)来加速一个基于NumPy的向量化运算。该代码根据两个距离矩阵(target_distances和由扁平化坐标向量计算得到的map_distances)以及距离类型信息(取值范围0-3,本次测试设为全0)计算某种应力值。测试后Cython版本与NumPy版本耗时相近(约4.47秒 vs 4.62秒),无法理解原因:NumPy和SciPy是否在后台进行了并行计算?查看核心使用率并未发现并行,通过设置export MKL_NUM_THREADS=1等环境变量关闭并行后也无变化。是我忽略了向量化运算的并行机制,还是这些库的例程已优化到极致?


NumPy版本代码(pycalculus.py)

import scipy
import numpy as np
from scipy.spatial import distance

def decay(x, s=10):
    return scipy.special.expit(s*x)

def stress(z, target_distances, dim, distance_types, step,
           nrows, ncols):
    row_coords = np.reshape(z[:dim*nrows],(nrows,dim))
    col_coords = np.reshape(z[dim*nrows:dim*(nrows+ncols)],(ncols,dim))
    
    map_distances = distance.cdist(row_coords, col_coords).copy()
    error = target_distances - map_distances
    
    I0 = (distance_types==0) | (distance_types==3)
    I2 = distance_types == 2

    return np.sum(error[I0]**2) + np.sum((error[I2] + step)**2*decay(error[I2] + step))

Cython化版本代码(calculus.pyx)

import cython
cimport cython
from libc.stdlib cimport malloc, free

cdef extern from "ctools.h":
    double stress (double*, double**, int**, double, int, int, int)

@cython.boundscheck(False) 
@cython.wraparound(False)
@cython.nonecheck(False)     
def stress_cython(double[:] z, double[:,:] target_distances, int [:,:]
           distance_types, double step, int dim, int nrows, int ncols):
    
    cdef int i
    cdef int j
    cdef int N = (nrows+ncols)*dim
    
    z_C = <double*>malloc(sizeof(double)*N)
    target_distances_C = <double **>malloc(sizeof(double*)*nrows)
    distance_types_C = <int **>malloc(sizeof(int*)*nrows)
    
    for i in range(N):
        z_C[i] = z[i]
    
    for i in range(nrows):
        target_distances_C[i] = <double *>malloc(sizeof(double)*ncols)
        distance_types_C[i] = <int *>malloc(sizeof(int)*ncols)
        
        for j in range(ncols):
            target_distances_C[i][j] = target_distances[i,j]
            distance_types_C[i][j] = distance_types[i,j]
        
   
    stress_val = stress(z_C, target_distances_C, 
                        distance_types_C, step, nrows, ncols, dim)
    
    for i in range(nrows):
        free(target_distances_C[i])
        free(distance_types_C[i])
        
    free(target_distances_C)
    free(distance_types_C)
    
    return stress_val

外部C实现文件(ctools.c)

#include<stdio.h>
#include<math.h>

double decay(double x){
    return 1/(1+exp(-10*x));
}

double** dist_pairs(double* z, int nrows, int ncols, int dims)
{
    int i,j,d;
    double coord1, coord2;
    double** dist;
    
    dist = (double **)malloc(sizeof(double*)*nrows);
    for (i=0; i<nrows; i++){
        dist[i] = (double *)malloc(sizeof(double)*ncols);
    }
    
    for (i=0; i<nrows; i++){
       for (j=0; j<ncols; j++){
            dist[i][j] = 0;
            for (d=0; d<dims; d++){
                coord1 = z[d*(nrows+ncols) + i];
                coord2 = z[d*(nrows+ncols) + nrows + j];
                dist[i][j] += pow(coord1-coord2,2.0) ;
            }
            dist[i][j] = sqrt(dist[i][j]);
        }
    }
                   
    return dist;
}

double stress(double* z, double **target_distances, int** distance_types, 
              double step, int nrows, int ncols, int dim){
    int i,j;
    double stress = 0.0;
    double err = 0.0;
    double **map_distances = dist_pairs(z, nrows, ncols, dim);

    for (i=0; i<nrows; i++){
        for (j=0; j<ncols; j++){
            if (distance_types[i][j]==0 || distance_types[i][j]==3){
                stress += pow(target_distances[i][j] - map_distances[i][j],2.0);
            }
            else if (distance_types[i][j]==2){
                err = target_distances[i][j] - map_distances[i][j] + step;
                stress += pow(step,2)*decay(step);
            }
        }
    }

    for (i=0; i<nrows; i++){
        free(map_distances[i]);
    }
    free(map_distances);

    return stress;
}

测试代码(test.py)

import numpy as np
import time 
from calculus import stress_cython
from pycalculus import stress

nrows = 3000
ncols = 2000
dim = 2
N = 100
is_discrete = 1.0

dt0 = 0
dt1 = 0
difs = []
for i in range(N):
    coordinates = np.random.rand(dim, nrows+ncols)
    coordinates_flat = coordinates.flatten()
    
    target_distances = np.random.rand(nrows, ncols)
    distance_types = np.zeros((nrows,ncols), dtype='i')
    
    t0 = time.time()
    stress1 = stress_cython(coordinates_flat, target_distances, distance_types, is_discrete, dim, nrows,
                   ncols)
    t1 = time.time()
    
    stress2 = stress(coordinates.T.flatten(), target_distances, dim, distance_types, is_discrete,
                     nrows,ncols)
    
    t2 = time.time()
    
    dt0 += t1-t0
    dt1 += t2-t1
    
    difs.append(stress1-stress2)

print(f'cython:{dt0:.2f} python:{dt1:.2f}')

补充说明

  • 编译Cython代码时使用了-ffast-math参数
  • 最终会用所有核心处理不同初始条件,因此NumPy自动并行对当前场景无帮助,这也是想弄清原因的重要因素

内容的提问来源于stack exchange,提问作者Sina

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 17:45:38