使用元素取幂加速嵌套for循环

时间:2014-01-21 21:51:15

标签: 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 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

奇怪的list1list2格式是因为这就是这段代码接收它们的方式。

花费大部分时间的明显部分是递归计算AB项。

有没有什么方法可以加速这段代码而不必将其并行化(我以前尝过它并且它给了我a lot of troubles)?我愿意使用任何软件包,numpyscipy等。


添加

这是应用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

1 个答案:

答案 0 :(得分:7)

您希望逐渐将其从使用列表和循环转换为使用数组和广播,首先抓取最简单和/或时间最关键的部分,直到它足够快。

第一步是不要反复执行zip(*list2)(特别是如果这是Python 2.x)。虽然我们正在使用它,但我们也可以将它存储在一个数组中,并对list1执行相同的操作 - 您现在仍然可以迭代它们。所以:

array1 = np.array(list1)
array2 = np.array(zip(*list2))
# …
for elem in array1:
    # …
    for elem2 in array2:

这不会加速我的机器,它从14.1秒到12.9 - 但它让我们开始工作。

您还应该删除sum(list3)的双重计算:

sum_list3 = sum(list3)
sum_list3 = sum_list3 if sum_list3>0. else 1e-06

与此同时,您希望value <= 0转到1e-6,但0 < value < 1e-6保持独立,这有点奇怪。这真的是故意的吗?如果没有,你可以解决这个问题,同时简化代码,只需这样做:

sum_list3 = max(array3.sum(), 1e-06)

现在,让我们播放AB计算:

# 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)

这让我们从12.9秒降到0.12秒。您可以更进一步,也可以通过array1进行广播,并用预先分配的数组替换list4,依此类推,但这可能已经足够快了。