Как ускорить вложенный цикл python?
Я выполняю вложенный цикл в python, который включен ниже. Это служит основным способом поиска по существующим финансовым временным рядам и поиска периодов во временных рядах, которые соответствуют определенным характеристикам. В этом случае существуют два отдельных массива одинакового размера, представляющих собой "закрытие" (т. е. цену актива) и "объем" (т. е. сумму актива, который был обменен за период). Для каждого периода времени я хотел бы смотреть вперед вообще будущие интервалы с длинами от 1 до INTERVAL_LENGTH и посмотреть, если любой из этих интервалов имеют характеристики, которые соответствуют моему поиску (в этом случае отношение близких значений больше, чем 1.0001 и меньше, чем 1.5 и суммированный объем больше, чем 100).
Я понимаю, что одна из основных причин ускорения при использовании NumPy заключается в том, что интерпретатору не нужно проверять операнды каждый раз, когда он оценивает что-то, пока вы работаете на массив в целом (например, numpy_array * 2), но, очевидно, приведенный ниже код не использует это. Есть ли способ заменить внутренний цикл какой-то оконной функцией, которая может привести к ускорению, или любым другим способом, используя numpy/scipy, чтобы существенно ускорить это в родном python?
альтернативно, есть ли лучший способ сделать это в целом (например, будет ли намного быстрее написать этот цикл на C++ и использовать weave)?
ARRAY_LENGTH = 500000
INTERVAL_LENGTH = 15
close = np.array( xrange(ARRAY_LENGTH) )
volume = np.array( xrange(ARRAY_LENGTH) )
close, volume = close.astype('float64'), volume.astype('float64')
results = []
for i in xrange(len(close) - INTERVAL_LENGTH):
for j in xrange(i+1, i+INTERVAL_LENGTH):
ret = close[j] / close[i]
vol = sum( volume[i+1:j+1] )
if ret > 1.0001 and ret < 1.5 and vol > 100:
results.append( [i, j, ret, vol] )
print results
3 ответов
Update: (почти) полностью векторизованная версия ниже в "new_function2"...
Я добавлю комментарии, чтобы объяснить вещи немного.
это дает ускорение ~50x, и большее ускорение возможно, если вы в порядке с выходом, являющимся массивами numpy вместо списков. Как есть:
In [86]: %timeit new_function2(close, volume, INTERVAL_LENGTH)
1 loops, best of 3: 1.15 s per loop
вы можете заменить свой внутренний цикл вызовом np.cumsum()... См. мою функцию" new_function " ниже. Это дает значительное ускорение...
In [61]: %timeit new_function(close, volume, INTERVAL_LENGTH)
1 loops, best of 3: 15.7 s per loop
vs
In [62]: %timeit old_function(close, volume, INTERVAL_LENGTH)
1 loops, best of 3: 53.1 s per loop
должно быть возможно векторизовать все это и полностью избежать циклов... Дай мне минуту, и я посмотрю, что можно сделать...
import numpy as np
ARRAY_LENGTH = 500000
INTERVAL_LENGTH = 15
close = np.arange(ARRAY_LENGTH, dtype=np.float)
volume = np.arange(ARRAY_LENGTH, dtype=np.float)
def old_function(close, volume, INTERVAL_LENGTH):
results = []
for i in xrange(len(close) - INTERVAL_LENGTH):
for j in xrange(i+1, i+INTERVAL_LENGTH):
ret = close[j] / close[i]
vol = sum( volume[i+1:j+1] )
if (ret > 1.0001) and (ret < 1.5) and (vol > 100):
results.append( (i, j, ret, vol) )
return results
def new_function(close, volume, INTERVAL_LENGTH):
results = []
for i in xrange(close.size - INTERVAL_LENGTH):
vol = volume[i+1:i+INTERVAL_LENGTH].cumsum()
ret = close[i+1:i+INTERVAL_LENGTH] / close[i]
filter = (ret > 1.0001) & (ret < 1.5) & (vol > 100)
j = np.arange(i+1, i+INTERVAL_LENGTH)[filter]
tmp_results = zip(j.size * [i], j, ret[filter], vol[filter])
results.extend(tmp_results)
return results
def new_function2(close, volume, INTERVAL_LENGTH):
vol, ret = [], []
I, J = [], []
for k in xrange(1, INTERVAL_LENGTH):
start = k
end = volume.size - INTERVAL_LENGTH + k
vol.append(volume[start:end])
ret.append(close[start:end])
J.append(np.arange(start, end))
I.append(np.arange(volume.size - INTERVAL_LENGTH))
vol = np.vstack(vol)
ret = np.vstack(ret)
J = np.vstack(J)
I = np.vstack(I)
vol = vol.cumsum(axis=0)
ret = ret / close[:-INTERVAL_LENGTH]
filter = (ret > 1.0001) & (ret < 1.5) & (vol > 100)
vol = vol[filter]
ret = ret[filter]
I = I[filter]
J = J[filter]
output = zip(I.flat,J.flat,ret.flat,vol.flat)
return output
results = old_function(close, volume, INTERVAL_LENGTH)
results2 = new_function(close, volume, INTERVAL_LENGTH)
results3 = new_function(close, volume, INTERVAL_LENGTH)
# Using sets to compare, as the output
# is in a different order than the original function
print set(results) == set(results2)
print set(results) == set(results3)
одним ускорением было бы удалить sum
часть, так как в этой реализации он суммирует список длины от 2 до INTERVAL_LENGTH
. Вместо этого, просто добавьте volume[j+1]
к предыдущему результату vol от последней итерации цикла. Таким образом, вы просто добавляете два целых числа каждый раз, а не суммируете весь список и нарезаете его каждый раз. Кроме того, вместо того, чтобы начать делать sum(volume[i+1:j+1])
, просто vol = volume[i+1] + volume[j+1]
, как вы знаете, начальный случай здесь всегда будет только два индекса.
другой ускорение будет использовать .extend
вместо .append
, поскольку реализация python имеет extend
значительно быстрее работает.
вы также можете разбить финал if
оператор, чтобы выполнить только определенные вычисления, если это необходимо. Например, вы знаете if vol <= 100
, вам не нужно вычислить ret
.
это не отвечает на вашу проблему точно, но я думаю, особенно с проблемой суммы, что вы должны увидеть значительные ускорения с этими изменения.
Edit-вам также не нужно len
, так как вы уже знаете конкретно длину списка (если это не было только для примера). Определение его как числа, а не len(something)
всегда быстрее.
Edit-реализация (это непроверено):
ARRAY_LENGTH = 500000
INTERVAL_LENGTH = 15
close = np.array( xrange(ARRAY_LENGTH) )
volume = np.array( xrange(ARRAY_LENGTH) )
close, volume = close.astype('float64'), volume.astype('float64')
results = []
ex = results.extend
for i in xrange(ARRAY_LENGTH - INTERVAL_LENGTH):
vol = volume[i+1]
for j in xrange(i+1, i+INTERVAL_LENGTH):
vol += volume[j+1]
if vol > 100:
ret = close[j] / close[i]
if 1.0001 < ret < 1.5:
ex( [i, j, ret, vol] )
print results
почему бы вам не попытаться создать результат в виде одного списка (намного быстрее, чем добавление или расширение), что-то вроде:
results = [ t for t in ( (i, j, close[j]/close[i], sum(volume[i+1:j+1]))
for i in xrange(len(close)-INT_LEN)
for j in xrange(i+1, i+INT_LEN)
)
if t[3] > 100 and 1.0001 < t[2] < 1.5
]