【问题标题】:Further optimization of simple cython code进一步优化简单的cython代码
【发布时间】:2018-11-13 01:12:04
【问题描述】:

我有一个用 cython 编写的函数,它通过双 for 循环计算一定的相关性度量(距离相关性):

%%cython -a
import numpy as np

def distances_Matrix(X):
    return np.array([[np.linalg.norm(xi-xj) for xi in X] for xj in X])

def c_dCov(double[:, :] a, double[:, :] b, int n):
    cdef int i
    cdef int j
    cdef double U       =  0
    cdef double W1      =  n/(n-1)
    cdef double W2      =  2/(n-2)
    cdef double[:] a_M  =  np.mean(a,axis=1)
    cdef double    a_   =  np.mean(a)
    cdef double[:] b_M  =  np.mean(b,axis=1)
    cdef double    b_   =  np.mean(b)

    for i in range(n):
        for j in range(n):
            if i != j:
                U = U + (a[i][j] + W1*(-a_M[i]-a_M[j]+a_)) * (b[i][j] +   W1*(-b_M[i]-b_M[j]+b_))
            else:
                U = U - W2*(W1**2)*(a_M[i] - a_) * (b_M[i] - b_)
    return U/(n*(n-3))

def c_dCor(X,Y):
    n     =  len(X)
    a     =  distances_Matrix(X)
    b     =  distances_Matrix(Y)
    V_XX  =  c_dCov(a,a,n) 
    V_YY  =  c_dCov(b,b,n)
    V_XY  =  c_dCov(a,b,n)
    return V_XY/np.sqrt(V_XX*V_YY)

当我编译这段代码时,我得到编译器的以下优化报告:

第 23 行还是很黄的,这表明 python 交互很重要,如何使该行进一步优化?

在那条线上的操作很简单,只是乘积和求和,因为我确实指定了该函数中使用的每个数组和变量的类型,为什么我在那条线上的表现如此糟糕?

提前致谢。

【问题讨论】:

  • 我可以通过将 @cython.boundscheck(False) 装饰器添加到 c_dCov 来优化第 23 行。仅此一项就足以将黄色变为白色。虽然@cython.boundscheck(False) 不在您的代码 sn-p 中,但它看起来确实在优化报告中。您是否已经尝试使用boundscheck,但它不起作用?如果是这样,您可能只需要升级 Cython 包。您目前使用的是什么版本?
  • a[i][j]a[i,j] 也可能有用
  • 最明显的优化是distances_Matrix函数。例如。 stackoverflow.com/q/50675705/4045774(别忘了给 euclidean_distance_square_einsum 添加一个 sqrt)。
  • @max9111 我现在将尝试优化 distances_Matrix。您是否认为使用纯 python 循环来完成这一切以对函数进行 cythonize 是一种好方法?提前感谢您的洞察力。
  • 至少对于稍微大一点的问题,除了快速的 BLAS 后端(例如 Intel MKL)之外,没有太多的优化潜力。您可以简单地使用 euclidean_distance_square_einsum 函数并添加一个 np.sqrt 。

标签: python numpy optimization cython


【解决方案1】:

简答:disable bounds checkingc_dCov 函数中,在其前面的行中添加以下装饰器:

cimport cython
@cython.boundscheck(False)  # Deactivate bounds checking
def c_dCov(double[:, :] a, double[:, :] b, int n):

或者,您可以在代码顶部添加编译器指令。在你的 Cython 魔法线之后,你会放:

%%cython -a
#cython: language_level=3, boundscheck=False

如果你有一个setup.py 文件,你也可以在那里全局关闭边界检查:

from distutils.core import setup
from Cython.Build import cythonize

setup(
    name="foo",
    ext_modules=cythonize('foo.pyx', compiler_directives={'boundscheck': False}),
)

不管它是如何完成的,禁用边界检查本身就足以获得以下优化报告:

其他一些optimizations suggested by the Cython docs 正在关闭负数索引,并声明您的数组保证在内存中具有连续布局。通过所有这些优化,c_dCov 的签名将变为:

cimport cython
@cython.boundscheck(False)  # Deactivate bounds checking
@cython.wraparound(False)   # Deactivate negative indexing.
def c_dCov(double[:, ::1] a, double[:, ::1] b, int n):

但只需要@cython.boundscheck(False) 即可获得更好的优化报告。

现在我仔细观察,即使您的代码 sn-p 中没有这些优化,您的优化报告中的代码中确实有 boundscheck(False)wraparound(False) 装饰器。您是否已经尝试过这些并且它们不起作用?你运行的是什么版本的 Cython?也许您需要升级。

说明

每次按索引访问数组时,都会进行边界检查。这样,当您有一个形状为(5,5) 的数组arr 并尝试访问arr[19,27] 时,您的程序将吐出一个错误,而不是让您访问越界数据。然而,为了速度,一些语言不对数组访问进行边界检查(例如 C/C++)。 Cython 允许您选择关闭边界检查以优化性能。使用 Cython,您可以使用 boundscheck compiler directive 全局禁用整个程序的边界检查,或者使用 the @cython.boundscheck(False) decorator 禁用单个函数的边界检查。

【讨论】:

  • 非常感谢!确实我有那些装饰器,但它们似乎对优化没有任何影响,所以我最终删除了它们。我更新了我的 cython 版本,现在报告看起来像你的。
  • @Ettore Majorana 声明 c_dCov(double[:, ::1] 之类的连续数组或禁用边界检查可能会产生非常显着的效果,如果您通过此操作进行 SIMD 矢量化。这也很大程度上取决于编译器标志。然而,距离矩阵的创建应该比您首先要优化的简单循环更耗时。
猜你喜欢
  • 2023-02-09
  • 1970-01-01
  • 2015-09-27
  • 2015-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多