Numba在非python模式下比纯python慢得多(没有print语句或指定的numpy函数)



我最近发现,即使在启用了parallel =True选项的非python模式下,Numba也可能比纯python慢得多。
重要如果你没有接触过Voronoi图表,请继续阅读,我的问题与它们没有直接关系。
目前,我正在解决一个问题,我有与Voronoi图的边缘和细胞区域相关的能量。scipy Vornoi返回一个数组,其中包含与每个Vornoi边相关的点对(vor.ridge_points)。对于我的代码,我希望在提供关联点的索引时能够获得边的索引,所以我定义了一种邻接矩阵,但它不是1和0,而是0和边的索引。
纯python在numpy数组上执行循环时比numba快10倍。这里是一个玩具示例(我只是随机生成数组,与我的模拟中相同数量的边和点)。
我猜这与内存分配有关。任何对这个主题的看法都将受到赞赏(为什么它如此慢或从点数中获得边缘数的更好方法的原因):)

# %%
from numba.np.ufunc import parallel
import numpy as np
from numba import njit
from numba import prange
# %% generating array that models array og ridges
points_number = 8802
ridges_number = 26379
np.random.seed(123)
ridge_points = np.random.randint(points_number, size=(ridges_number, 2))
# %% symmetric matrix containing indexes of all edges
# in space [original_point_1, original_point_2]
ridge_points = np.array(ridge_points, dtype=np.int32)

@njit(parallel=True, cache=True)
def jit_edges_matrix_op(r_p, r_n):
matrix = np.zeros((r_n, r_n), dtype=np.int32)
for i in prange(r_n):
e1 = r_p[i, 0]
e2 = r_p[i, 1]
matrix[e1, e2] = i
matrix[e2, e1] = i
return matrix

e_matrix_op = jit_edges_matrix_op(ridge_points, ridges_number)
# %% the same but not jitted

def edges_matrix_op(r_p, r_n):
matrix = np.zeros((r_n, r_n), dtype=np.int32)
for i in range(r_n):
e1 = r_p[i, 0]
e2 = r_p[i, 1]
matrix[e1, e2] = i
matrix[e2, e1] = i
return matrix

e_matrix_op = edges_matrix_op(ridge_points, ridges_number)
# %%
%%timeit
jit_edges_matrix_op(ridge_points, ridges_number)
# %%
%%timeit
edges_matrix_op(ridge_points, ridges_number)

更新确实并行化在这里不能正常工作,所以我用parallel=False运行测试。以下是结果
630毫秒±20.3毫秒每循环(平均±std. dev. 7次运行,每次1循环)- parallel=True

553 ms±4.22 ms/loop(平均±std. dev. of 7次运行,每次1个loop) - parallel=False

66.5 ms±3.12 ms/循环(平均±std. dev. 7次运行,每次10次循环)-纯python

更新2

感谢max9111分享链接https://github.com/numba/numba/issues/7259分配带有零的大数组(np.zeros)似乎存在问题。这个问题在几周前就被报告了,这个链接包含了一些解决方法的例子。

我尝试分配np.empty()

29.7 ms±1.38 ms/loop(平均±std. dev. of 7次运行,每次10次循环)- numba parallel=True

44.7 ms±2.34 ms/loop(平均±std. dev. of 7次运行,每次10次循环)- numba parallel=False

60.4 ms±1.47 ms/循环(平均±std. dev. 7次运行,每次10个循环)-纯python

可以看到并行化的numba效果最好,所以这个任务是并行的,开销也不是很大

我认为关键问题与您在这部分代码中执行的计算的性质有关:

for i in range(ridges_number):
e1 = r_p[i, 0]
e2 = r_p[i, 1]
matrix[e1, e2] = i
matrix[e2, e1] = i

如果循环计算是缓存本地的并且可并行化的(即每个循环中的计算是独立的),则循环计算的性能最好。

在您的例子中,这两个条件都被违反了。e1e2在所有循环中不取连续的值。同样,矩阵r_p可能会妨碍有效的并行化,因为它需要被每个循环中的所有线程访问,并且可能在被所有其他线程访问时被一个线程锁定)。

总而言之,您选择加速的函数可能会遭受并行化的开销,而实际上计算是顺序执行的。而且计算,至少就目前而言,很难通过并行化来加速。