numba 中的性能嵌套循环
Performance nested loop in numba
出于性能原因,除了 NumPy 之外,我已经开始使用 Numba。我的 Numba 算法正在运行,但我感觉它应该更快。有一点正在减慢它的速度。这是代码片段:
@nb.njit
def rfunc1(ws, a, l):
gn = a**l
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and
numpy.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and
numpy.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
在我看来,if
命令正在减慢它的速度。有没有更好的办法? (我在这里尝试实现的与之前发布的问题有关:Count possibilites for single crossovers)ws
是一个大小为 (gn, l)
的 NumPy 数组,包含 0
和 1
的
考虑到希望确保所有项目都相等的逻辑,您可以利用这一事实,如果有任何项目不相等,您可以短路(即停止比较)计算。我稍微修改了您的原始函数,以便 (1) 您不会重复相同的比较两次,以及 (2) 对所有嵌套循环求和 y 所以有一个 return 可以比较:
@nb.njit
def rfunc1(ws, a, l):
gn = a**l
ysum = 0
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
if np.all(ws[x1][0:i] == ws[x2][0:i]) and np.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
ysum += 1
return ysum
@nb.njit
def rfunc2(ws, a, l):
gn = a**l
ysum = 0
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
incr_y = True
for j in range(i):
if ws[x1,j] != ws[x2,j]:
incr_y = False
break
if incr_y is True:
for j in range(i,l):
if ws[x1,j] != ws[x3,j]:
incr_y = False
break
if incr_y is True:
y += 1
ysum += 1
return ysum
我不知道完整的功能是什么样的,但希望这能帮助您走上正确的道路。
现在一些时间:
l = 7
a = 2
gn = a**l
ws = np.random.randint(0,2,size=(gn,l))
In [23]:
%timeit rfunc1(ws, a , l)
1 loop, best of 3: 2.11 s per loop
%timeit rfunc2(ws, a , l)
1 loop, best of 3: 39.9 ms per loop
In [27]: rfunc1(ws, a , l)
Out[27]: 131919
In [30]: rfunc2(ws, a , l)
Out[30]: 131919
这使您的速度提高了 50 倍。
而不是 "having a feeling" 你的瓶颈在哪里,为什么不 剖析 你的代码并找到 确切 在哪里?
分析的第一个目的是测试一个有代表性的系统以确定什么是慢的(或使用过多的 RAM,或导致过多的磁盘 I/O 或网络 I/O)。
分析通常会增加开销(通常会导致 10 到 100 倍的减速),并且您仍然希望您的代码的使用方式尽可能接近真实情况。提取测试用例并隔离您需要测试的系统部分。最好,它已经被编写成自己的一组模块。
基本技巧包括 IPython、time.time(),
和 timing decorator
中的 %timeit
魔法(参见下面的示例)。您可以使用这些技术来理解语句和函数的行为。
然后你有 cProfile
这会给你一个问题的高级视图,这样你就可以将注意力集中在关键功能上。
接下来,查看 line_profiler,
,它将逐行分析您选择的函数。结果将包括每行被调用的次数和每行所花费时间的百分比。这正是您了解什么是 运行 缓慢及其原因所需的信息。
perf stat
帮助您了解最终在 CPU 上执行的指令数以及 CPU 的缓存的使用效率。这允许对矩阵运算进行高级调整。
heapy
可以追踪Python内存中的所有对象。这对于寻找奇怪的内存泄漏非常有用。如果您使用的是 long-运行 系统,
那么 dowser
会让您感兴趣:它允许您通过 Web 浏览器界面在一个漫长的 运行 过程中内省活动对象。
为了帮助您了解 RAM 使用率高的原因,请查看 memory_profiler.
它对于在带标签的图表上随时间跟踪 RAM 使用率特别有用,因此您可以向同事(或您自己)解释某些原因函数使用的 RAM 多于预期。
示例:定义装饰器以自动执行计时测量
from functools import wraps
def timefn(fn):
@wraps(fn)
def measure_time(*args, **kwargs):
t1 = time.time()
result = fn(*args, **kwargs)
t2 = time.time()
print ("@timefn:" + fn.func_name + " took " + str(t2 - t1) + " seconds")
return result
return measure_time
@timefn
def your_func(var1, var2):
...
有关更多信息,我建议阅读 High performance Python(Micha Gorelick;Ian Ozsvald),上面的内容出自该文章。
出于性能原因,除了 NumPy 之外,我已经开始使用 Numba。我的 Numba 算法正在运行,但我感觉它应该更快。有一点正在减慢它的速度。这是代码片段:
@nb.njit
def rfunc1(ws, a, l):
gn = a**l
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and
numpy.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
if numpy.all(ws[x1][0:i] == ws[x2][0:i]) and
numpy.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
在我看来,if
命令正在减慢它的速度。有没有更好的办法? (我在这里尝试实现的与之前发布的问题有关:Count possibilites for single crossovers)ws
是一个大小为 (gn, l)
的 NumPy 数组,包含 0
和 1
的
考虑到希望确保所有项目都相等的逻辑,您可以利用这一事实,如果有任何项目不相等,您可以短路(即停止比较)计算。我稍微修改了您的原始函数,以便 (1) 您不会重复相同的比较两次,以及 (2) 对所有嵌套循环求和 y 所以有一个 return 可以比较:
@nb.njit
def rfunc1(ws, a, l):
gn = a**l
ysum = 0
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
if np.all(ws[x1][0:i] == ws[x2][0:i]) and np.all(ws[x1][i:l] == ws[x3][i:l]):
y += 1
ysum += 1
return ysum
@nb.njit
def rfunc2(ws, a, l):
gn = a**l
ysum = 0
for x1 in range(gn):
for x2 in range(gn):
for x3 in range(gn):
y = 0.0
for i in range(1, l):
incr_y = True
for j in range(i):
if ws[x1,j] != ws[x2,j]:
incr_y = False
break
if incr_y is True:
for j in range(i,l):
if ws[x1,j] != ws[x3,j]:
incr_y = False
break
if incr_y is True:
y += 1
ysum += 1
return ysum
我不知道完整的功能是什么样的,但希望这能帮助您走上正确的道路。
现在一些时间:
l = 7
a = 2
gn = a**l
ws = np.random.randint(0,2,size=(gn,l))
In [23]:
%timeit rfunc1(ws, a , l)
1 loop, best of 3: 2.11 s per loop
%timeit rfunc2(ws, a , l)
1 loop, best of 3: 39.9 ms per loop
In [27]: rfunc1(ws, a , l)
Out[27]: 131919
In [30]: rfunc2(ws, a , l)
Out[30]: 131919
这使您的速度提高了 50 倍。
而不是 "having a feeling" 你的瓶颈在哪里,为什么不 剖析 你的代码并找到 确切 在哪里?
分析的第一个目的是测试一个有代表性的系统以确定什么是慢的(或使用过多的 RAM,或导致过多的磁盘 I/O 或网络 I/O)。
分析通常会增加开销(通常会导致 10 到 100 倍的减速),并且您仍然希望您的代码的使用方式尽可能接近真实情况。提取测试用例并隔离您需要测试的系统部分。最好,它已经被编写成自己的一组模块。
基本技巧包括 IPython、time.time(),
和 timing decorator
中的 %timeit
魔法(参见下面的示例)。您可以使用这些技术来理解语句和函数的行为。
然后你有 cProfile
这会给你一个问题的高级视图,这样你就可以将注意力集中在关键功能上。
接下来,查看 line_profiler,
,它将逐行分析您选择的函数。结果将包括每行被调用的次数和每行所花费时间的百分比。这正是您了解什么是 运行 缓慢及其原因所需的信息。
perf stat
帮助您了解最终在 CPU 上执行的指令数以及 CPU 的缓存的使用效率。这允许对矩阵运算进行高级调整。
heapy
可以追踪Python内存中的所有对象。这对于寻找奇怪的内存泄漏非常有用。如果您使用的是 long-运行 系统,
那么 dowser
会让您感兴趣:它允许您通过 Web 浏览器界面在一个漫长的 运行 过程中内省活动对象。
为了帮助您了解 RAM 使用率高的原因,请查看 memory_profiler.
它对于在带标签的图表上随时间跟踪 RAM 使用率特别有用,因此您可以向同事(或您自己)解释某些原因函数使用的 RAM 多于预期。
示例:定义装饰器以自动执行计时测量
from functools import wraps
def timefn(fn):
@wraps(fn)
def measure_time(*args, **kwargs):
t1 = time.time()
result = fn(*args, **kwargs)
t2 = time.time()
print ("@timefn:" + fn.func_name + " took " + str(t2 - t1) + " seconds")
return result
return measure_time
@timefn
def your_func(var1, var2):
...
有关更多信息,我建议阅读 High performance Python(Micha Gorelick;Ian Ozsvald),上面的内容出自该文章。