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

Gab*_*iel 6 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
Run Code Online (Sandbox Code Playgroud)

的怪异格式list1list2是因为这是这个代码块如何接收他们.

大部分时间花费的明显部分是递归计算AB术语.

有没有什么方法可以加快这段代码而不必并行化它(我之前尝试过,它给了我很多麻烦)?我愿意使用任何包装,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
Run Code Online (Sandbox Code Playgroud)

aba*_*ert 7

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

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

array1 = np.array(list1)
array2 = np.array(zip(*list2))
# …
for elem in array1:
    # …
    for elem2 in array2:
Run Code Online (Sandbox Code Playgroud)

这不会加速我的机器,它从14.1秒到12.9 - 但它给了我们一个开始工作的地方.

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

sum_list3 = sum(list3)
sum_list3 = sum_list3 if sum_list3>0. else 1e-06
Run Code Online (Sandbox Code Playgroud)

同时,这是一个有点奇怪,你想要value <= 01e-6的,但0 < value < 1e-6一个人呆着.这真的是故意的吗?如果没有,你可以解决这个问题,同时简化代码,只需这样做:

sum_list3 = max(array3.sum(), 1e-06)
Run Code Online (Sandbox Code Playgroud)

现在,让我们播放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)
Run Code Online (Sandbox Code Playgroud)

这让我们从12.9秒降到0.12秒.您可以更进一步,也可以通过广播来array1替换list4预先分配的数组,等等,但这可能已经足够快了.

  • 指数是昂贵的:不是两次取'np.exp`再乘以,将两个值加在一起,然后取'np.exp`. (4认同)
  • 如果你向你提供额外20%的全部内容,那就是,我必须同意,在前99%中可以忽略不计.但这是我的一个小小的烦恼,就像没有采取距离的平方根,如果它的平方足够你正在做的事情,我无法抗拒,对不起噪音...... (2认同)
  • @Gabriel:人们常常不假思索地说,但考虑一下:加速你的代码是否值得花2个小时,比如每次运行增加180微秒?做了10000次,你最多总共保存了3598.2秒的负数 - 并且你增加了错误的可能性,以及调试它的时间(特别是如果你发现新的版本更难理解). (2认同)