【发布时间】:2013-09-05 01:43:08
【问题描述】:
我有一个相对简单的问题(我认为)。我正在研究一段 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_profile 和MC_criterion 之间可能存在根本区别,这会导致额外的间接费用。你能看出来吗?
【问题讨论】:
-
您的
<math.h>有square功能?这不是标准功能,您在哪个平台上?在任何情况下,您都可以定义自己的cdef inline square(x): return x * x以摆脱对pow的所有调用。 -
如果您将
get_r从类中解耦,函数call 可能会更快。不需要的话就不用self了。 -
@larsmans 尝试了这两个建议,但没有明显的收获。倒数第二行
r = 1./...是迄今为止最贵的,所以我需要先降低那一行的成本。 -
为了比较速度,我用纯 C 编译了你的
get_r和get_r_profile函数。纯 Cget_r_profile似乎比 Cython 快 2.5-3,所以也许它是一个编译选项get_r单独并从 Cython 链接到它?有趣的是,根据优化标志,编译器似乎完全跳过了对get_r的调用!不知道这如何与 Cython 生成的 C 代码一起发挥作用,但也许这样的事情会导致您看到的时间差异。
标签: python optimization cython