python - 在 Cython 中优化代码的技巧

标签 python optimization cython

我有一个相对简单的问题(我认为)。我正在研究一段 Cython 代码,它在给定应变和特定方向时计算应变椭圆的半径(即,对于一定量的应变,半径平行于给定方向)。这个函数在每次程序运行期间被调用数百万次,分析表明这个函数是性能方面的限制因素。这是代码:

# importing math functions from a C-library (faster than numpy)
from libc.math cimport sin, cos, acos, exp, sqrt, fabs, M_PI

cdef class funcs:

    cdef inline double get_r(self, double g, double omega):
        # amount of strain: g, angle: omega
        cdef double l1, l2, A, r, g2, gs   # defining some variables
        if g == 0: return 1   # no strain means the strain ellipse is a circle
        omega = omega*M_PI/180   # converting angle omega to radians
        g2 = g*g
        gs = g*sqrt(4 + g2)
        l1 = 0.5*(2 + g2 + gs)   # l1 and l2: eigenvalues of the Cauchy strain tensor
        l2 = 0.5*(2 + g2 - gs)
        A = acos(g/sqrt(g2 + (1 - l2)**2))   # orientation of the long axis of the ellipse
        r = 1./sqrt(sqrt(l2)*(cos(omega - A)**2) + sqrt(l1)*(sin(omega - A)**2))   # the radius parallel to omega
        return r   # return of the jedi

运行此代码每次调用大约需要 0.18 微秒,我认为对于这样一个简单的函数来说有点长。另外,math.h 有一个 square(x) 函数,但我无法从 libc.math 库中导入它,有人知道怎么做吗?对于进一步改进这一小段代码的性能还有其他建议吗?

2013 年 9 月 4 日更新:

似乎有更多的东西在起作用。当我分析一个调用 get_r 1000 万次的函数时,我得到的性能与调用另一个函数的性能不同。我已经添加了我的部分代码的更新版本。当我使用 get_r_profile 进行分析时,每次调用 get_r 我得到 0.073 微秒,而 MC_criterion_profile 给我大约 0.164 微秒/调用 get_r,50% 的差异似乎与 return r 的开销成本有关。

from libc.math cimport sin, cos, acos, exp, sqrt, fabs, M_PI

cdef class thesis_funcs:

    cdef inline double get_r(self, double g, double omega):
        cdef double l1, l2, A, r, g2, gs, cos_oa2, sin_oa2
        if g == 0: return 1
        omega = omega*SCALEDPI
        g2 = g*g
        gs = g*sqrt(4 + g2)
        l1 = 0.5*(2 + g2 + gs)
        l2 = l1 - gs
        A = acos(g/sqrt(g2 + square(1 - l2)))
        cos_oa2 = square(cos(omega - A))
        sin_oa2 = 1 - cos_oa2
        r = 1.0/sqrt(sqrt(l2)*cos_oa2 + sqrt(l1)*sin_oa2)
        return r

    @cython.profile(False)
    cdef inline double get_mu(self, double r, double mu0, double mu1):
        return mu0*exp(-mu1*(r - 1))

    def get_r_profile(self): # Profiling through this guy gives me 0.073 microsec/call
        cdef unsigned int i
        for i from 0 <= i < 10000000:
            self.get_r(3.0, 165)

    def MC_criterion(self, double g, double omega, double mu0, double mu1, double C = 0.0):
        cdef double r, mu, theta, res
        r = self.get_r(g, omega)
        mu = self.get_mu(r, mu0, mu1)
        theta = 45 - omega
        theta = theta*SCALEDPI
        res = fabs(g*sin(2.0*theta)) - mu*(1 + g*cos(2.0*theta)) - C
        return res

    def MC_criterion_profile(self): # Profiling through this one gives 0.164 microsec/call
        cdef double g, omega, mu0, mu1
        cdef unsigned int i
        omega = 165
        mu0 = 0.6
        mu1 = 2.0
        g = 3.0
        for i from 1 <= i < 10000000:
            self.MC_criterion(g, omega, mu0, mu1)

我认为 get_r_profileMC_criterion 之间可能存在根本差异,这会导致额外的开销成本。你能发现吗?

最佳答案

根据您的评论,计算 r 的行是最昂贵的。如果是这种情况,那么我怀疑是触发函数调用导致了性能下降。

根据毕达哥拉斯,cos(x)**2 + sin(x)**2 == 1 因此您可以通过计算跳过其中一个调用

cos_oa2 = cos(omega - A)**2
sin_oa2 = 1 - cos_oa2
r = 1. / sqrt(sqrt(l2) * cos_oa2 + sqrt(l1) * sin_oa2)

(或者翻转它们:在我的机器上,sin 似乎比 cos 快。不过可能是 NumPy 的故障。)

关于python - 在 Cython 中优化代码的技巧,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18593308/

相关文章:

python - 为类的 __init__ 方法编写单元测试

c++ - float 学矢量化,但整数数学不

c++ - MPI_ALLgather 将局部最大值发送到所有进程,进程终止错误

javascript - 检测 Javascript 内存泄漏和优化代码

python - 通过 Cython 将 numpy 数组传递和返回给 C++ 方法

python - 如果在 python 中声明的实例方法没有 self 会发生什么

python:使用文件句柄打印文件内容

python - 一键对具有多个键的字典求和的最有效方法是什么?

exception - Cython:返回类型化内存 View 的函数的异常类型

python - 在cython中声明一个类的实例