Ускорить вложенный цикл for с элементами возведения в степень
Я работаю над большим кодом, и мне нужно ускорить определенный бит. Я создал 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
, etc..
добавить
это результат применения оптимизаций abarnert, а также Хайме советую сделать только одно возведение в степень. Оптимизированная функция в среднем ~60x быстрее в моей системе.
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 ответов
вы хотите постепенно преобразовать это из использования списков и циклов в использование массивов и вещания, захватывая самые простые и/или наиболее важные для времени части, пока это не будет достаточно быстро.
первый шаг-не делать zip(*list2)
снова и снова (особенно если это Python 2.икс.) Пока мы на нем, мы могли бы также сохранить его в массиве и сделать то же самое с 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)
теперь давайте транслировать A
и B
расчет:
# 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
С предварительно выделенным массивом и так далее, но это, вероятно, уже достаточно быстро.