【问题标题】:Is there any good way to optimize the speed of this python code?有什么好的方法可以优化这个python代码的速度吗?
【发布时间】:2018-10-30 14:57:07
【问题描述】:

我有以下一段代码,它基本上评估一些数值表达式,并使用它来整合特定范围的值。当前的代码在8.6 s 内运行,但我只是使用模拟值,而我的实际数组要大得多。特别是,我的实际大小为freq_c= (3800, 101)number_bin = (3800, 100),这使得下面的代码效率非常低,因为实际数组的总执行时间将接近 9 分钟。相当慢的一部分代码是对k_one_thirdk_two_third 的评估,为此我还使用了numexpr.evaluate(".."),这将代码加速了大约10-20%。但是,我避免了下面的numexpr,这样任何人都可以运行它而无需安装包。还有其他方法可以提高此代码的速度吗?几个因素的改进也足够了。请注意for loop 几乎是不可避免的,由于内存问题,由于数组非常大,我通过循环一次操作每个轴。我也想知道numba jit是否可以在这里优化。

import numpy as np
import scipy 
from scipy.integrate import simps as simps
import time

def k_one_third(x):
    return (2.*np.exp(-x**2)/x**(1/3) + 4./x**(1/6)*np.exp(-x)/(1+x**(1/3)))**2

def k_two_third(x):
    return (np.exp(-x**2)/x**(2/3) + 2.*x**(5/2)*np.exp(-x)/(6.+x**3))**2

def spectrum(freq_c, number_bin, frequency, gamma, theta):
    theta_gamma_factor = np.einsum('i,j->ij', theta**2, gamma**2)
    theta_gamma_factor += 1.
    t_g_bessel_factor = 1.-1./theta_gamma_factor
    number = np.concatenate((number_bin, np.zeros((number_bin.shape[0], 1), dtype=number_bin.dtype)), axis=1)
    number_theta_gamma = np.einsum('jk, ik->ijk', theta_gamma_factor**2*1./gamma**3, number)
    final = np.zeros((np.size(freq_c[:,0]), np.size(theta), np.size(frequency)))
    for i in xrange(np.size(frequency)):
        b_n_omega_theta_gamma = frequency[i]**2*number_theta_gamma
        eta = theta_gamma_factor**(1.5)*frequency[i]/2.
        eta = np.einsum('jk, ik->ijk', eta, 1./freq_c)
        bessel_eta = np.einsum('jl, ijl->ijl',t_g_bessel_factor, k_one_third(eta))
        bessel_eta += k_two_third(eta)
        eta = None
        integrand = np.multiply(bessel_eta, b_n_omega_theta_gamma, out= bessel_eta)
        final[:,:, i] = simps(integrand, gamma)
        integrand = None
    return final

frequency = np.linspace(1, 100, 100)
theta = np.linspace(1, 3, 100)
gamma = np.linspace(2, 200, 101)
freq_c = np.random.randint(1, 200, size=(50, 101))
number_bin = np.random.randint(1, 100, size=(50, 100))
time1 = time.time()
spectra = spectrum(freq_c, number_bin, frequency, gamma, theta)
print(time.time()-time1)

【问题讨论】:

  • 代替 for 循环,您可以对数组执行 numpy 操作。这样可以节省一些时间
  • @Kalyan 我明确地使用了循环,因为我的数组太大而无法在没有得到MemoryError 的情况下一次执行所有numpy 操作。
  • 至少将你的内部循环体重写为简单的循环(没有 np.einsums,没有乘法,如果可能的话没有临时数组)。对于像 Numba 和 Cython 这样的编译器,这将更容易优化。

标签: python arrays performance numpy numba


【解决方案1】:

我分析了代码,发现k_one_third()k_two_third() 很慢。这两个函数有一些重复的计算。

通过将两个函数合并为一个函数,并用@numba.jit(parallel=True) 装饰它,我得到了 4 倍的加速。

@jit(parallel=True)
def k_one_two_third(x):
    x0 = x ** (1/3)
    x1 = np.exp(-x ** 2)
    x2 = np.exp(-x)
    one = (2*x1/x0 + 4*x2/(x**(1/6)*(x0 + 1)))**2
    two = (2*x**(5/2)*x2/(x**3 + 6) + x1/x**(2/3))**2
    return one, two

【讨论】:

  • 我用numexpr计算了同样的表达式,速度比jit快。所以,对我来说仍然没有整体提升。
  • interpolation 会加快计算速度吗?
  • 一般不要使用parallel=True。这仍然是实验性的,可能会减慢某些计算机上的计算速度。试试@njit@jit(nopython=True)。 HYRY提供的解决方案应该比numexpr快很多。
  • @Scotty1 最好在安装了英特尔 SVML 的情况下引入 fastmath=True 和 error_model='numpy'。但通常建议在外循环上使用显式并行化,这也比向量化方法快 20% 左右。
【解决方案2】:

正如 cmets 中所说,应该重写大部分代码以获得最佳性能。

我只修改了 simpson 集成并稍微修改了@HYRY 的答案。根据您提供的测试数据,这将计算速度从 26.15s 加快到 1.76s (15x)。通过用简单的循环替换 np.einsums,这应该会在不到一秒的时间内结束。 (改进后的集成大约需要 0.4 秒,k_one_two_third(x) 大约需要 24 秒)

用于使用 Numba read 获得性能。最新的 Numba 版本 (0.39)、英特尔 SVML 包和 fastmath=True 之类的东西对您的示例产生了相当大的影响。

代码

#a bit faster than HYRY's version
@nb.njit(parallel=True,fastmath=True,error_model='numpy')
def k_one_two_third(x):
  one=np.empty(x.shape,dtype=x.dtype)
  two=np.empty(x.shape,dtype=x.dtype)
  for i in nb.prange(x.shape[0]):
    for j in range(x.shape[1]):
      for k in range(x.shape[2]):
        x0 = x[i,j,k] ** (1/3)
        x1 = np.exp(-x[i,j,k] ** 2)
        x2 = np.exp(-x[i,j,k])
        one[i,j,k] = (2*x1/x0 + 4*x2/(x[i,j,k]**(1/6)*(x0 + 1)))**2
        two[i,j,k] = (2*x[i,j,k]**(5/2)*x2/(x[i,j,k]**3 + 6) + x1/x[i,j,k]**(2/3))**2
  return one, two

#improved integration
@nb.njit(fastmath=True)
def simpson_nb(y_in,dx):
  s = y[0]+y[-1]

  n=y.shape[0]//2
  for i in range(n-1):
    s += 4.*y[i*2+1]
    s += 2.*y[i*2+2]

  s += 4*y[(n-1)*2+1]
  return(dx/ 3.)*s

@nb.jit(fastmath=True)
def spectrum(freq_c, number_bin, frequency, gamma, theta):
    theta_gamma_factor = np.einsum('i,j->ij', theta**2, gamma**2)
    theta_gamma_factor += 1.
    t_g_bessel_factor = 1.-1./theta_gamma_factor
    number = np.concatenate((number_bin, np.zeros((number_bin.shape[0], 1), dtype=number_bin.dtype)), axis=1)
    number_theta_gamma = np.einsum('jk, ik->ijk', theta_gamma_factor**2*1./gamma**3, number)
    final = np.empty((np.size(frequency),np.size(freq_c[:,0]), np.size(theta)))

    #assume that dx is const. on integration
    #speedimprovement of the scipy.simps is about 4x
    #numba version to scipy.simps(y,x) is about 60x
    dx=gamma[1]-gamma[0]

    for i in range(np.size(frequency)):
        b_n_omega_theta_gamma = frequency[i]**2*number_theta_gamma
        eta = theta_gamma_factor**(1.5)*frequency[i]/2.
        eta = np.einsum('jk, ik->ijk', eta, 1./freq_c)

        one,two=k_one_two_third(eta)

        bessel_eta = np.einsum('jl, ijl->ijl',t_g_bessel_factor, one)
        bessel_eta += two

        integrand = np.multiply(bessel_eta, b_n_omega_theta_gamma, out= bessel_eta)

        #reorder array
        for j in range(integrand.shape[0]):
          for k in range(integrand.shape[1]):
            final[i,j, k] = simpson_nb(integrand[j,k,:],dx)
    return final

【讨论】:

  • 代码看起来不错,但我无法执行。用jit 替换njit 我得到:NameError: global name 'y' is not defined。使用njit 产生:TypingError: cannot determine Numba type of <class 'object'>。错误源于simpson_nb()njit
  • 抱歉,def simpson_nb(y_in,dx) 行有错误。这应该是 def simpson_nb(y,dx)。我希望这对性能没有影响,我在测试时显然在我的全局命名空间中有一个数组 y....
  • 我纠正了错误,现在它正在工作。它比 HYRY 的解决方案快约 20%。好的!我猜不使用np.einsum 重写它会使其更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2012-03-13
相关资源
最近更新 更多