我刚刚意识到在一起使用jit
装饰器和range
函数有一种奇怪的行为。与冗长的演讲相比,最好考虑以下简单代码:
@nb.njit(['float64[:,:](float64[:,:], float64[:,:], int32, int32)'])
def range1(a, b, nx, nz):
for ix in range(5, nx-5):
for iz in range(5, nz-5):
b[ix, iz] = 0.5*(a[ix+1, iz+1] - a[ix-1, iz-1])
return b
@nb.njit(['float64[:,:](float64[:,:], float64[:,:], int32, int32, int32, int32)'])
def range2(a, b, ix1, ix2, iz1, iz2):
for ix in range(ix1, ix2):
for iz in range(iz1, iz2):
b[ix, iz] = 0.5*(a[ix+1, iz+1] - a[ix-1, iz-1])
return b
@nb.njit(['float64[:,:](float64[:,:], float64[:,:], int32, int32, int32, int32)'])
def range3(a, b, ix1, ix2, iz1, iz2):
for ix in range(ix1, ix2):
for iz in range(5, iz2):
b[ix, iz] = 0.5*(a[ix+1, iz+1] - a[ix-1, iz-1])
return b
if __name__ == "__main__":
print('Numba : {}'.format(nb.__version__))
print('Numpy : {}\n'.format(np.__version__))
nx, nz = 1024, 1024
a = np.random.rand(nx, nz)
b = np.zeros_like(a)
range1(a, b, nx, nz)
range2(a, b, 5, nx-5, 5, nz-5)
range3(a, b, 5, nx-5, 5, nz-5)
Nit = 1000
ti = time.time()
for i in range(Nit):
range1(a, b, nx, nz)
print('range1 : {:.3f}'.format(time.time() - ti))
ti = time.time()
for i in range(Nit):
range2(a, b, 5, nx-5, 5, nz-5)
print('range2 : {:.3f}'.format(time.time() - ti))
ti = time.time()
for i in range(Nit):
range3(a, b, 5, nx-5, 5, nz-5)
print('range3 : {:.3f}'.format(time.time() - ti))
在nopython
模式下编译的三个“jitted”函数几乎是相同的。。。除了范围参数。在我的笔记本电脑上,这个代码返回:
Numba : 0.37.0
Numpy : 1.14.2
range1 : 1.736 s.
range2 : 2.406 s.
range3 : 1.723 s.
如您所见,range1
和range2
执行时间之间有很大的差异!经过一些测试,我得出以下结论:
range
函数的参数在要编译的函数中直接作为常量提供时,或者是一个变量等于0(这是range1
和range3
函数的情况),性能是现有的,非常好!你知道吗range
函数的参数是变量时,函数运行速度会慢40%!你知道吗我认为这来自numba对range
函数的编译。这导致了两个主要问题:
这里的问题似乎是概括索引语义。如果将负数传递给
b[ix, iz]
,numpy将跟随python并从数组轴的末尾开始索引。你知道吗从LLVM IR可以看出这一点。有很多杂音需要修剪,我通过搜索
fmul
指令找到了每个函数的内环。你知道吗即使在那里,也有很多需要解析的地方,但是在
range1
中,只发生指针碰撞/查找/数学运算,而在range2中有边界检查(即icmp
指令),因为编译器可以证明iz
永远不会是负数。你知道吗据我所知,除了像您那样从编译时常量开始之外,目前还没有任何方法可以省略这一点。曾经有一个
wraparound
标志用于启用/禁用,但它是removed相关问题 更多 >
编程相关推荐