Python是一个带有if语句的jit函数
我有一个包含3个部分的分段函数,我正试图用Python使用Numba@jit指令编写它。函数是通过数组计算的。该功能的定义如下:Python是一个带有if语句的jit函数,python,numpy,jit,numba,boolean-indexing,Python,Numpy,Jit,Numba,Boolean Indexing,我有一个包含3个部分的分段函数,我正试图用Python使用Numba@jit指令编写它。函数是通过数组计算的。该功能的定义如下: @njit(parallel=True) def f(x_vec): N=len(x_vec) y_vec=np.zeros(N) for i in prange(N): x=x_vec[i] if x<=2000: y=64/x elif x>=4000:
@njit(parallel=True)
def f(x_vec):
N=len(x_vec)
y_vec=np.zeros(N)
for i in prange(N):
x=x_vec[i]
if x<=2000:
y=64/x
elif x>=4000:
y=np.log(x)
else:
y=np.log(1.2*x)
y_vec[i]=y
return y_vec
对于完成论,称为以下库:
import numpy as np
from numba import njit, prange
因此,在这种情况下,功能将是:
def f1(x):
return 64/x
def f2(x):
return np.log(x)
def f3(x):
return np.log(1.2*x)
实际函数为以下函数,用于层流、过渡和湍流状态下的光滑管道摩擦系数:
@njit
def f1(x):
return 64/x
@njit
def f2(x):
#x is the Reynolds number(Re), y is the Darcy friction(f)
#for transition, we can assume Re=4000 (max possible friction)
y=0.02
y=(-2/np.log(10))*np.log(2.51/(4000*np.sqrt(y)))
return 1/(y*y)
@njit
def f3(x): #colebrook-white approximation
#x is the Reynolds number(Re), y is the Darcy friction(f)
y=0.02
y=(-2/np.log(10))*np.log(2.51/(x*np.sqrt(y)))
return 1/(y*y)
谢谢大家的贡献。这是numpy解决方案(由于某些原因,最后的树线速度较慢,但不需要预热):
y=np.empty\u like(x\u vec)
a1=np.其中(x_vec=4000,真,假)
a2=~(a1 | a3)
y[a1]=f1(x_向量[a1])
y[a2]=f2(x_向量[a2])
y[a3]=f3(x_vec[a3])
最快的Numba解决方案,允许传递函数名并利用prange(但受到jit预热的阻碍),可以与第一个解决方案一样快(问题之首):
@njit(parallel=True)
定义f(x_向量,f1,f2,f3):
N=len(x_vec)
y_vec=np.零(N)
对于prange中的i(N):
x=x_向量[i]
如果x=4000:
y=f3(x)
其他:
y=f2(x)
y_vec[i]=y
返回y_vec
这太慢了吗?这可以在纯numpy中完成,方法是避免循环并使用掩码进行索引:
def f(x):
y = np.empty_like(x)
mask = x <= 2000
y[mask] = 64 / x[mask]
mask = (x > 2000) & (x < 4000)
y[mask] = np.log(1.2 * x[mask])
mask = x >= 4000
y[mask] = np.log(x[mask])
return y
我得到(带6+6VT内核的i7-8850H笔记本电脑)
如果您预期的子功能主要是numpy操作,这仍然很快。您可以编写f()
来接受功能参数,例如:
@njit
def f(arr, f1, f2, f3):
N = len(arr)
y_vec = np.zeros(N)
for i in range(N):
x = x_vec[i]
if x <= 2000:
y = f1(x)
elif x >= 4000:
y = f2(x)
else:
y = f3(x)
y_vec[i] = y
return y_vec
@njit
def f(arr、f1、f2、f3):
N=长度(arr)
y_vec=np.零(N)
对于范围(N)中的i:
x=x_向量[i]
如果x=4000:
y=f2(x)
其他:
y=f3(x)
y_vec[i]=y
返回y_vec
确保您传递的函数与Numba兼容。如果您的子函数也被(并且可以)写入,它们仍然会很快。您可能还想使用
numba.prange
而不是range
。为了能够回答这个问题,我们可能需要查看您想要的子功能f1
、f2
和f3
。@JanChristopherasa the prange已经让它变得更快了。f1、f2和f3与问题中定义的相同。我在问题中添加了子功能。(我可以发布实际的函数,但它们可能很长,但它们基本上是由一堆乘法和日志调用组成的)。如果使用math.log(),可能会快一点在标量参数的NUBBA中,也考虑选项内联=“总是”来始终内嵌这些小函数和ErrRoMyMease=“NoMPY”,通过零校验来禁用除法,这也有一些开销。另外,在覆盖每个条目之前用零初始化y_vec
,没有多大意义。用np.empty
分配内存就足够了。谢谢Jan,这非常有帮助,事实上,你的回答回答了我很久以来一直在想的一个问题,即使用mask,我可以将函数实际应用到数组的一部分,并完全避免if条件(这是一种奇特的索引,对吧?)。这仍然比jit实现慢,但我可能会发现它的用处,特别是如果我用f1、f2和f3与jit一起尝试的话。可以使用ufuncs的where
参数,而不是布尔索引,以提高速度。另外,为其中两个条件定义mask1和mask2。我很确定~(mask1 | mask2)
是获取else掩码的一种更快的方法。也可以用单个buffer@dani. 布尔索引。奇特的索引是整数索引。如果使用最快的计算(即64/x
)作为基本情况,您可能会获得更快的f_else
。@Mad物理学家感谢where
提示,非常有用@也许是诺洛克。我并没有特别优化速度,我只是想证明,对于可以用纯numpy表述的问题,通常不需要NUBA。如果JIT的“预热”时间支配着您的运行时间,则尤其如此。此外,您可能还希望参数化阈值。谢谢,它也可以在不传递函数名的情况下工作。但是我想传递名称会给我以后更多的灵活性,因为我将能够传递不同的函数给它。
@njit(parallel=True)
def f(x_vec,f1,f2,f3):
N = len(x_vec)
y_vec = np.zeros(N)
for i in prange(N):
x=x_vec[i]
if x<=2000:
y=f1(x)
elif x>=4000:
y=f3(x)
else:
y=f2(x)
y_vec[i]=y
return y_vec
def f(x):
y = np.empty_like(x)
mask = x <= 2000
y[mask] = 64 / x[mask]
mask = (x > 2000) & (x < 4000)
y[mask] = np.log(1.2 * x[mask])
mask = x >= 4000
y[mask] = np.log(x[mask])
return y
def f_else(x):
y = np.log(1.2 * x)
mask = x <= 2000
y[mask] = 64 / x[mask]
mask = x >= 4000
y[mask] = np.log(x[mask])
return y
Np=10000000
x_vec=100*np.power(1e8/100,np.random.rand(Np))
f1: 1 loop, best of 5: 294 ms per loop
f_else: 1 loop, best of 5: 400 ms per loop
@njit
def f(arr, f1, f2, f3):
N = len(arr)
y_vec = np.zeros(N)
for i in range(N):
x = x_vec[i]
if x <= 2000:
y = f1(x)
elif x >= 4000:
y = f2(x)
else:
y = f3(x)
y_vec[i] = y
return y_vec