【问题标题】:Improve performance of a python function提高python函数的性能
【发布时间】:2021-06-06 17:42:30
【问题描述】:

我是 python 新手,我正在寻找一种方法来加速我编写的以下函数:

def incoming_Wave_Vel(geom,t,phi):

    x = geom[:,0].reshape(1,geom.shape[0])
    y = geom[:,1].reshape(1,geom.shape[0])
    z = geom[:,2].reshape(1,geom.shape[0])
    
    q1 = k*(h+z)
    q2 = omega*t-Kx*x-Ky*y+phi[:,0].reshape(phi.shape[0],1)

    u = AOmega.T @ (np.cosh(q1)/np.sinh(k*h)*np.sin(q2))
    w = AOmega.T @ (np.sinh(q1)/np.sinh(k*h)*np.cos(q2))
        
    return np.vstack((u*np.cos(th),u*np.sin(th),w))*RampFun(t)

AOmega, k, Kx, Ky, omegaphi 是维度为 [n,1] 的数组,而 x,y,z 的维度是 [1,m]。我使用点积@ 来避免求和函数,但性能几乎相同。 我尝试使用 numba 但到目前为止所做的所有测试都失败了。 有什么改进代码的建议吗?谢谢

我在下面发布了尝试将函数与 numba 并行化:

from numba import njit, prange

@njit(fastmath=True, parallel=True)
def incoming_Wave_Vel_parallel(x,y,z,t,phi):

    u = np.zeros_like(x)
    w = np.zeros_like(x)
    n = len(omega)
    for i in prange(n):
        q1 = k[i]*(h+z)
        q2 = omega[i]*t-Kx[i]*x-Ky[i]*y+phi[i]
        u += AOmega[i]*np.cosh(q1)/np.sinh(k[i]*h)*np.sin(q2)
        w += AOmega[i]*np.sinh(q1)/np.sinh(k[i]*h)*np.cos(q2)
    
    return np.vstack((u*np.cos(th),u*np.sin(th),w))

但是这个版本比串行版本慢。

【问题讨论】:

  • t, h, omega 和 th 是常量吗?
  • 抱歉,t,h 和 th 是常量浮点数,omega 是一个数组,如 k
  • AOmega 的维度是 (n,1),但 q1 和 q2 的维度是 (n,m),这使得 u 和 v 无法计算。我错过了什么吗?
  • 再次抱歉,我错过了一个 AOmega.T,在脚本中我发布的部分上方几行......所以 AOmega 是 [1,n]
  • 好的,我已经开始运行了!有没有机会,您是否有任何数量级的 n 和 m,以便我们可以运行更具代表性的测试?

标签: python performance numba


【解决方案1】:

我们可以通过去除 sin 和 sinh 的冗余计算来优化 incoming_Wave_Vel,将性能提高约 28%。


def incoming_Wave_Vel(t,x,y,z):
    q1 = k*(h+z)
    q2 = omega*t-Kx*x-Ky*y+phi_shaped
    sin = np.sin(q2) 
    cos = np.cos(q2)
    cste = (np.cosh(q1)/np.sinh(k*h))*sin
    u = AOmega.T @ cste*sin
    w = AOmega.T @ cste*cos

    return np.vstack((u*np.cos(th),u*np.sin(th),w))*t

通过与multiprocessing并行化操作可以进一步提升性能,与原来相比实现了52%的性能提升:

from multiprocessing import ThreadPool

times = np.linspace(0,1,200)
pool = ThreadPool()
res = pool.map(lambda i: incoming_Wave_Vel(times[i], x[i],y[i],z[i]), range(len(times)))

用于时间比较的代码:

import numpy as np
from multiprocessing.pool import ThreadPool

n, m = 237, 420
time_steps = 200
times = np.linspace(0,1,200)
AOmega, k, Kx, Ky, omega, phi = [np.random.random((n,1)) for _ in range(6)]
h, th = 1, 1
x = np.random.random((time_steps,1,m))
y = np.random.random((time_steps,1,m))
z = np.random.random((time_steps,1,m))
phi_shaped = phi[:,0].reshape(phi.shape[0],1)


def incoming_Wave_Vel(t,x,y,z):
    q1 = k*(h+z)
    q2 = omega*t-Kx*x-Ky*y+phi_shaped
    sin = np.sin(q2) 
    cos = np.cos(q2)
    cste = (np.cosh(q1)/np.sinh(k*h))*sin
    u = AOmega.T @ cste*sin
    w = AOmega.T @ cste*cos

    return np.vstack((u*np.cos(th),u*np.sin(th),w))*t

def incoming_Wave_Vel_original(t,x,y,z,phi):
    
    q1 = k*(h+z)
    q2 = omega*t-Kx*x-Ky*y+phi[:,0].reshape(phi.shape[0],1)

    u = AOmega.T @ (np.cosh(q1)/np.sinh(k*h)*np.sin(q2))
    w = AOmega.T @ (np.sinh(q1)/np.sinh(k*h)*np.cos(q2))
    
    return np.vstack((u*np.cos(th),u*np.sin(th),w))*t

原文:

%%timeit
res = []
for i,t in enumerate(times):
    incoming_Wave_Vel_original(t, x[i], y[i], z[i], phi)

并行和优化:

%%timeit
pool = ThreadPool()
res = pool.map(lambda i: incoming_Wave_Vel(times[i], x[i],y[i],z[i]), range(len(times)))

【讨论】:

  • 感谢您的建议...关于多进程的第二个很有趣,但我必须看看如何实现它。我不确定我能做到这一点,因为该函数不是在 for cylce 中直接调用的,而是在其他函数内部调用,此外,我之前引用的 Rotation 和 traslation 取决于函数本身的输出
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-11
  • 2016-10-12
  • 2022-07-13
  • 1970-01-01
相关资源
最近更新 更多