Python 使用元素求幂加速嵌套for循环
我正在编写一个大型代码,我发现自己需要加快一个特定的速度。我创建了一个Python 使用元素求幂加速嵌套for循环,python,performance,numpy,scipy,Python,Performance,Numpy,Scipy,我正在编写一个大型代码,我发现自己需要加快一个特定的速度。我创建了一个MWE,如下所示: import numpy as np import time def random_data(N): # Generate some random data. return np.random.uniform(0., 10., N).tolist() # Lists that contain all the data. list1 = [random_data(10) for _ in
MWE
,如下所示:
import numpy as np
import time
def random_data(N):
# Generate some random data.
return np.random.uniform(0., 10., N).tolist()
# Lists that contain all the data.
list1 = [random_data(10) for _ in range(1000)]
list2 = [random_data(1000), random_data(1000)]
# Start taking the time.
tik = time.time()
list4 = []
# Loop through all elements in list1.
for elem in list1:
list3 = []
# Loop through elements in list2.
for elem2 in zip(*list2):
A = np.exp(-0.5*((elem[0]-elem2[0])/elem[3])**2)
B = np.exp(-0.5*((elem[1]-elem2[1])/elem[3])**2)
list3.append(A*B)
# Sum elements in list3 and append result to list4.
sum_list3 = sum(list3) if sum(list3)>0. else 1e-06
list4.append(sum_list3)
# Print the elapsed time.
print time.time()-tik
list1
和list2
的奇怪格式是因为这段代码就是这样接收它们的
显然,大部分时间都花在了A
和B
项的递归计算上
有没有什么方法可以在不需要并行化的情况下加速这段代码(我以前尝试过,但它给了我很多好处)?我愿意使用任何软件包,numpy
,scipy
,等等
添加 这是应用abarnert优化的结果,也是Jaime建议只进行一次幂运算的结果。优化后的功能在我的系统上平均快约60倍
import numpy as np
import timeit
def random_data(N):
return np.random.uniform(0., 10., N).tolist()
# Lists that contain all the data.
list1 = [random_data(10) for _ in range(1000)]
list2 = [random_data(1000), random_data(1000)]
array1 = np.array(list1)
array2 = np.array(zip(*list2))
# Old non-optimezed function.
def func1():
list4 = []
# Process all elements in list1.
for elem in list1:
# Process all elements in list2.
list3 = []
for elem2 in zip(*list2):
A = np.exp(-0.5*((elem[0]-elem2[0])/elem[3])**2)
B = np.exp(-0.5*((elem[1]-elem2[1])/elem[3])**2)
list3.append(A*B)
# Sum elements in list3 and append result to list4.
sum_list3 = sum(list3) if sum(list3)>0. else 1e-06
list4.append(sum_list3)
# New optimized function.
def func2():
list4 = []
# Process all elements in list1.
for elem in array1:
# Broadcast over elements in array2.
A = -0.5*((elem[0]-array2[:,0])/elem[3])**2
B = -0.5*((elem[1]-array2[:,1])/elem[3])**2
array3 = np.exp(A+B)
# Sum elements in array3 and append result to list4.
sum_list3 = max(array3.sum(), 1e-10)
list4.append(sum_list3)
# Get time for both functions.
func1_time = timeit.timeit(func1, number=10)
func2_time = timeit.timeit(func2, number=10)
# Print hom many times faster func2 is versus func1.
print func1_time/func2_time
您希望逐渐将其从使用列表和循环转换为使用阵列和广播,首先获取最简单和/或时间最关键的部分,直到足够快 第一步是不要一次又一次地使用
zip(*list2)
(尤其是在使用Python2.x时)。当我们使用它时,我们不妨将其存储在一个数组中,并对list1执行相同的操作-您现在仍然可以对它们进行迭代。因此:
array1 = np.array(list1)
array2 = np.array(zip(*list2))
# …
for elem in array1:
# …
for elem2 in array2:
这不会在我的机器上加快很多速度,从14.1秒到12.9秒,但它给了我们开始工作的地方
您还应该删除双重计算总和(列表3)
:
同时,有点奇怪的是,您想要值,为什么当您在顶部有一个NumPy依赖项时,此代码都是基于列表的?没有特别的原因,这就是此代码从另一段代码接收list1
和list2
的方式。关于list3
和list4
,这是我能找出如何填写它们的最佳方法。如果你认为这会有所不同,它们都可以转换成numpy数组。@Gabriel:当然会有所不同。这就是使用numpy
-如果可以通过数组广播计算,则可以用C循环替换Python循环,并删除围绕每个算术计算的所有装箱/拆箱,这意味着您的代码通常会快4-400倍。发布好的MWE需要+1。这是一个很好的例子,可以让你投入工作,提出一个问题,然后得到一个精彩的答案。书签,以便我将来可以链接到它。最后一个旁注:你不应该尝试自己使用time.time
。该模块(或者,如果您使用的是IPython,%timeit
magic语句)确保选择正确的计时器,处理您甚至没有想到的一系列问题,允许您重复测试并正确总结它们,并使启动更简单。(当你的代码比你预期的要长100倍时,这通常没什么大不了的,但要养成经常使用timeit
的习惯是值得的)求幂是昂贵的:而不是取np.exp
两次,然后相乘,将这两个值相加,然后取np.exp
@Jaime:我们已经有了99%的加速,我怀疑他还需要10%。(我估计快速跳过exp
会将时间缩短到.79x,因此用一个而不是两个大概是.9x。)但我敢打赌,如果将其移出一个级别,您可以获得更大的改进(一次在所有行上广播exp
,然后对所有行进行sum
而不是每行执行一次),因此,我认为,如果现有的改进还不够,那么通过array1
和array4
进行广播应该是第一步,然后寻求优化它们内部的数学。如果你将全部内容矢量化,你将获得额外的20%,我不得不同意,在最初的99%中可以忽略不计。但这是我最讨厌的事情之一,比如如果你所做的事情已经足够让距离平方了,就不去求距离的平方根,我就是忍不住,为噪音感到抱歉…@Jaime:这不是噪音;在评论中进行额外的优化是值得的,以防OP需要它们,也只是为了让OP从中学习。我刚才解释了为什么我不认为它需要回答,这可能是一个很好的评论。加布里埃尔:人们经常这样说,而不考虑,但考虑到:这是值得2个小时的工作,以加快您的代码,例如,每运行180微秒的增益?这样做10000次,您最多节省了3598.2秒的时间,并且增加了错误的可能性,以及调试它所需的时间(特别是当您发现新版本更难理解时)。
sum_list3 = sum(list3)
sum_list3 = sum_list3 if sum_list3>0. else 1e-06
sum_list3 = max(array3.sum(), 1e-06)
# Broadcast over elements in list2.
A = np.exp(-0.5*((elem[0]-array2[:,0])/elem[3])**2)
B = np.exp(-0.5*((elem[1]-array2[:, 1])/elem[3])**2)
array3 = A*B
# Sum elements in list3 and append result to list4.
sum_list3 = max(array3.sum(), 1e-06)
list4.append(sum_list3)