Эта часть np.einsum('i,j,k->ijk', wE, wE, wE)
не выполняет никаких суммирующих преобразований и, по существу, просто передается по элементарному умножению. Таким образом, мы можем заменить, что-то вроде этого -
wE[:,None,None] * wE[:,None] * wE
Продолжительность испытания -
In [9]: # Setup inputs at 1/5th of original dataset sizes
...: N = 20
...: n = 200
...: X = np.random.uniform(size = (N,n))
...: wE = np.mean(X,0)
...:
In [10]: %timeit np.einsum('i,j,k->ijk', wE, wE, wE)
10 loops, best of 3: 45.7 ms per loop
In [11]: %timeit wE[:,None,None] * wE[:,None] * wE
10 loops, best of 3: 26.1 ms per loop
Далее, мы имеем wE3 + T1
, где T1 = np.random.uniform(size = (n,n,n))
не выглядит, как можно было бы помочь в большой путь, поскольку мы должны создать T1
в любом случае, а затем это просто элементарное дополнение. Кажется, мы можем использовать np.add
, что позволяет нам записывать результаты в один из массивов: wE3
или T1
. Предположим, мы выберем T1
, если это нормально для изменения. Я предполагаю, что это принесет небольшую эффективность памяти, поскольку мы не будем добавлять другую переменную в рабочее пространство.
Таким образом, мы могли бы сделать -
np.add(wE3,T1,out=T1)
время выполнения теста -
In [58]: def func1(wE3):
...: T1 = np.random.uniform(size = (n,n,n))
...: return wE3 + T1
...:
...: def func2(wE3):
...: T1 = np.random.uniform(size = (n,n,n))
...: np.add(wE3,T1,out=T1)
...: return T1
...:
In [59]: # Setup inputs at 1/4th of original dataset sizes
...: N = 25
...: n = 250
...: X = np.random.uniform(size = (N,n))
...: wE = np.mean(X,0)
...: wE3 = np.einsum('i,j,k->ijk', wE, wE, wE)
...:
In [60]: %timeit func1(wE3)
1 loops, best of 3: 390 ms per loop
In [61]: %timeit func2(wE3)
1 loops, best of 3: 363 ms per loop
Использование @Aaron's suggestion
, мы можем использовать цикл и предполагая, что запись обратно результаты в wE3
в порядке, мы могли бы сделать -
wE3 = wE[:,None,None] * wE[:,None] * wE
for x in wE3:
np.add(x, np.random.uniform(size = (n,n)), out=x)
Конечные результаты
Таким образом, соединив назад все предложенные усовершенствования, наконец, результаты тестирования выполнения были -
In [97]: def func1(wE):
...: wE3 = np.einsum('i,j,k->ijk', wE, wE, wE)
...: T1 = np.random.uniform(size = (n,n,n))
...: return wE3 + T1
...:
...: def func2(wE):
...: wE3 = wE[:,None,None] * wE[:,None] * wE
...: for x in wE3:
...: np.add(x, np.random.uniform(size = (n,n)), out=x)
...: return wE3
...:
In [98]: # Setup inputs at 1/3rd of original dataset sizes
...: N = 33
...: n = 330
...: X = np.random.uniform(size = (N,n))
...: wE = np.mean(X,0)
...:
In [99]: %timeit func1(wE)
1 loops, best of 3: 1.09 s per loop
In [100]: %timeit func2(wE)
1 loops, best of 3: 879 ms per loop
а, с заранее выделить 'а = np.empty_like (T1) '? иначе вы вряд ли будете быстрее. numpy внутренне вызывает 'fork()' для параллельной работы и состоит из достаточно хорошо оптимизированного кода C. – Aaron
@Aaron - Numpy не выполняет потоковую передачу. Некоторые функции могут использовать несколько потоков, но это полностью зависит от базовых библиотек, таких как BLAS, LAPACK, FFTPACK и т. Д. В частности, ни одна из операций 'ufunc' (элементные операции) не имеет многопоточности. – user6758673
@ user6758673 вы правы, я знал только, что он иногда делал вилку в несколько потоков, но не специально, когда .. Я хотел бы указать, однако многократные операции почти всегда освобождают GIL, чтобы позволить использовать потоки для ускорения кода вместо многопроцессорной обработки. – Aaron