我正在尝试定义一个包含内部循环的函数来模拟积分。
问题在于速度。在我的机器上评估该功能一次最多可能需要30秒。由于我的最终目标是最小化这个功能,所以一些额外的速度会很好。
因此,我试图让Cython工作,但我必须犯一个严重的错误(可能很多人!)。在Cython文档之后,我尝试输入我的变量。这样做之后,代码就像纯Python一样慢。这看起来很奇怪。
这是我的代码:
import numpy as np
cimport cython
cimport numpy as np
import minuit
data = np.genfromtxt('q6data.csv', usecols = np.arange(1, 24, 1), delimiter = ',')
cdef int ns = 1000 # Number of simulation draws
cdef int K = 5 # Number of observed characteristics, including constant
cdef int J = len(data[:,1]) # Number of products, including outside
cdef double tol = 0.0001 # Inner GMM loop tolerance
nu = np.random.normal(0, 1, (6, ns)) # ns random deviates
@cython.boundscheck(False)
@cython.wraparound(False)
def S(np.ndarray[double, ndim=1] delta, double s1, double s2, double s3, double s4, double s5, double a):
"""Computes the simulated integrals, one for each good.
Parameters: delta is an array of length J containing mean product specific utility levels
Returns: Numpy array with length J."""
cdef np.ndarray[double, ndim=2] mu_ij = np.dot((data[:,2:7]*np.array([s1, s2, s3, s4, s5])), nu[1:K+1,:])
cdef np.ndarray[double, ndim=2] mu_y = a * np.log(np.exp(data[:,21].reshape(J,1) + data[:,22].reshape(J,1)*nu[0,:].reshape(1, ns)) - data[:,7].reshape(J,1))
cdef np.ndarray[double, ndim=2] V = delta.reshape(J,1) + mu_ij + mu_y
cdef np.ndarray[double, ndim=2] exp_vi = np.exp(V)
cdef np.ndarray[double, ndim=2] P_i = (1.0 / np.sum(exp_vi[np.where(data[:,1] == 71)], 0)) * exp_vi[np.where(data[:,1] == 71)]
cdef int yrs = 19
cdef int yr
for yr in xrange(yrs):
P_yr = (1.0 / np.sum(exp_vi[np.where(data[:,1]== (yr + 72))], 0)) * exp_vi[np.where(data[:,1] == (yr + 72))]
P_i = np.concatenate((P_i, P_yr))
cdef np.ndarray[double, ndim=1] S = np.zeros(dtype = "d", shape = J)
cdef int j
for j in xrange(ns):
S += P_i[:,j]
return (1.0 / ns) * S
def d_infty(np.ndarray[double, ndim=1] x, np.ndarray[double, ndim=1] y):
"""Sup norm."""
return np.max(np.abs(x - y))
def T(np.ndarray[double, ndim=1] delta_exp, double s1, double s2, double s3, double s4, double s5, double a):
"""The contraction operator. This function takes the parameters and the exponential
of the starting value of delta and returns the fixed point."""
cdef int iter = 0
cdef int maxiter = 200
cdef int i
for i in xrange(maxiter):
delta1_exp = delta_exp * (data[:, 8] / S(np.log(delta_exp), s1, s2, s3, s4, s5, a))
print i
if d_infty(delta_exp, delta1_exp) < tol:
break
delta_exp = delta1_exp
return np.log(delta1_exp)
def Q(double s1, double s2, double s3, double s4, double s5, double a):
"""GMM objective function."""
cdef np.ndarray[double, ndim=1] delta0_exp = np.exp(data[:,10])
cdef np.ndarray[double, ndim=1] delta1 = T(delta0_exp, s1, s2, s3, s4, s5, a)
delta1[np.where(data[:,10]==0)] = np.zeros(len(np.where(data[:,10]==0)))
cdef np.ndarray[double, ndim=1] xi = delta1 - (np.dot(data[:,2:7], np.linalg.lstsq(data[:,2:7], delta1)[0]))
cdef np.ndarray[double, ndim=2] g_J = xi.reshape(J,1) * data[:,11:21]
cdef np.ndarray[double, ndim=1] G_J = (1.0 / J) * np.sum(g_J, 0)
return np.sqrt(np.dot(G_J, G_J))
我已经分析了代码,它似乎是功能S,即整体模拟器,它正在扼杀性能。无论如何,我希望通过键入我的变量至少可以获得一些速度提升。由于它没有产生任何收益,我被引导相信我犯了一些根本性的错误。
是否有人在Cython代码中看到可能导致此结果的明显错误?
哦,因为我对编程很陌生,所以肯定会有很多不好的风格和减慢代码的速度。如果你有时间,请随时让我直截了当。
答案 0 :(得分:29)
Cython可以生成一个html文件来帮助解决这个问题:
cython -a MODULE.py
这显示每行源代码通过各种黄色阴影着色为白色。黄色越深,仍然在该行上执行的动态Python行为越多。对于包含黄色的每一行,您需要添加更多静态类型声明。
当我这样做时,我喜欢将我遇到的部分源代码分成许多单独的行,每个表达式或运算符一行,以获得最精细的视图。
如果没有这个,很容易忽略变量,函数调用或运算符的一些静态类型声明。 (例如,索引运算符x [y]仍然是一个完全动态的Python操作,除非你另外声明)
答案 1 :(得分:16)
Cython不提供自动性能提升,你必须知道它的内部结构并检查生成的C代码。
特别是如果你想改善循环性能,你必须避免在其中调用Python函数,在这种情况下你碰巧做了很多(所有np.
调用都是Python调用,切片,并且可能其他事情)。
有关使用Cython进行性能优化的一般指导原则(请参阅-a开关在优化时非常方便)和this page优化numpy代码时的特性,请参阅this one。
答案 2 :(得分:11)
通过使用Numpy的更多功能,你绝对可以加速你的代码。
例如:
cdef np.ndarray[double, ndim=1] S = np.zeros(dtype = "d", shape = J)
cdef int j
for j in xrange(ns):
S += P_i[:,j]
会更快,更清晰。
S = P_i.sum(axis=1)
您还会重复一些计算,因此需要的时间比必要时间长两倍。例如
np.where(data[:,1]==(yr + 72))
只能计算一次并存储在您可以重复使用的变量中。
您还执行了大量的整形和切片:从一开始就可以让您的变量采用更简单的格式。如果可能,您的代码将更加清晰,优化可能会更加明显。
答案 3 :(得分:6)
分析器会帮助您找出哪个部分很慢吗?我喜欢使用标准库分析器运行程序:
python -O -m cProfile -o profile.out MYAPP.py
然后在'RunSnakeRun'GUI中查看输出:
runsnake profile.out
可以从这里安装RunSnakeRun: http://www.vrplumber.com/programming/runsnakerun/
答案 4 :(得分:1)
根据这里给出的建议,我花了更多的时间来分析上面的代码。为了希望清理一些我定义的东西
我已经对代码进行了更多的分析,并且更好地了解哪些代码是最慢的。我另外定义了
X = data[:, 2:7]
m_y = data[:, 21].reshape(J,1)
sigma_y = 1.0
price = data[:, 7].reshape(J, 1)
shares_data = data[:,8]
然后是以下几行占用了总时间的大部分时间。
mu_ij = np.dot((X*np.array([s1, s2, s3, s4, s5])), nu[1:K+1,:])
mu_y = a * np.log(np.exp(m_y + sigma_y*nu[0,:].reshape(1,ns)) - price)
V = delta.reshape(J,1) + mu_ij + mu_y
exp_vi = np.exp(V)
P_i = (1.0 / np.sum(exp_vi[np.where(data[:,1]==71)], 0)) * exp_vi[np.where(data[:,1]==71)]
for yr in xarange(19):
P_yr = (1.0 / np.sum(exp_vi[np.where(data[:,1]==yr)], 0)) * exp_vi[np.where(data[:,1]==yr)]
P_i = np.concatenate((P_i, P_yr))
我认为这是实现目标的过于繁琐的方式。我希望有人可以提供一些关于如何加快这些阵容的建议。也许我缺少Numpy功能?如果没有充分说明这个问题对您有所帮助,我很乐意提供有关问题背景的更多详细信息。谢谢!
答案 5 :(得分:1)
仅拆分数据
import sys
import time
import numpy as np
def splitdata( data, n, start=1971 ):
""" split data into n pieces, where col 1 == start .. start + n """
# not fancy, fast enough for small n
split = n * [None]
for j in range(n):
split[j] = data[ data[:,1] == start + j ]
return split # [ arrays: col1 0, col1 1 ... ]
#...........................................................................
N = 2237
ncol = 21
start = 1971
n = 20
seed = 1
exec "\n".join( sys.argv[1:] ) # run this.py N= ...
np.set_printoptions( 2, threshold=100, suppress=True ) # .2f
np.random.seed(seed)
print "N=%d ncol=%d n=%d" % (N, ncol, n)
data = np.random.uniform( start, start + n, (N,ncol) )
data[:,1] = data[:,1].round()
t0 = time.time()
split = splitdata( data, n, start ) # n pieces
print "time: %.0f us splitdata" % ((time.time() - t0) * 1e6)
for y, yeardata in enumerate(split):
print "%d %d %g" % (start + y, len(yeardata), yeardata[:,0].sum())
- &GT;
time: 27632 us splitdata # old mac ppc
1971 69 136638
1972 138 273292
...
答案 6 :(得分:-6)
“基本错误”是你期望从Python的长循环中获得良好的性能。它是一种解释型语言,实现和ctyping之间的切换对此没有任何作用。有一些用于快速计算的数字Python库,用C语言编写。例如,如果你已经使用numpy
数组,为什么不进一步使用scipy
进行高级数学运算?它将提高可读性和的速度。