Давайте посмотрим на лес сначала, прежде чем смотреть на деревья.
Здесь есть много информативных ответов с большими подробностями, я не буду повторять ни одного из них. Ключ к программированию в JavaScript имеет сначала правильную ментальную модель общего исполнения.
Хорошие новости заключается в том, что, если вы хорошо понимаете этот момент, вам никогда не придется беспокоиться о гоночных условиях. Прежде всего вы должны понимать, как вы хотите упорядочить свой код как по существу ответ на разные дискретные события, и как вы хотите объединить их в логическую последовательность. Вы можете использовать обещания или новые асинхронные / ожидающие более высокие уровни в качестве инструментов для этой цели, или вы можете откатывать свои собственные.
Но вы не должны использовать какие-либо тактические инструменты для решения проблемы, пока вам не понравится актуальная проблемная область. Нарисуйте карту этих зависимостей, чтобы знать, что нужно запускать, когда. Попытка ad-hoc подхода ко всем этим обратным вызовам просто не поможет вам.
На самом деле это не получается быстрее, это ваши варианты:
numpy.outer
>>> %timeit np.outer(a,b)
100 loops, best of 3: 9.79 ms per loop
numpy.einsum
>>> %timeit np.einsum('i,j->ij', a, b)
100 loops, best of 3: 16.6 ms per loop
numba
from numba.decorators import autojit
@autojit
def outer_numba(a, b):
m = a.shape[0]
n = b.shape[0]
result = np.empty((m, n), dtype=np.float)
for i in range(m):
for j in range(n):
result[i, j] = a[i]*b[j]
return result
>>> %timeit outer_numba(a,b)
100 loops, best of 3: 9.77 ms per loop
попугай
from parakeet import jit
@jit
def outer_parakeet(a, b):
... same as numba
>>> %timeit outer_parakeet(a, b)
100 loops, best of 3: 11.6 ms per loop
cython
cimport numpy as np
import numpy as np
cimport cython
ctypedef np.float64_t DTYPE_t
@cython.boundscheck(False)
@cython.wraparound(False)
def outer_cython(np.ndarray[DTYPE_t, ndim=1] a, np.ndarray[DTYPE_t, ndim=1] b):
cdef int m = a.shape[0]
cdef int n = b.shape[0]
cdef np.ndarray[DTYPE_t, ndim=2] result = np.empty((m, n), dtype=np.float64)
for i in range(m):
for j in range(n):
result[i, j] = a[i]*b[j]
return result
>>> %timeit outer_cython(a, b)
100 loops, best of 3: 10.1 ms per loop
theano
from theano import tensor as T
from theano import function
x = T.vector()
y = T.vector()
outer_theano = function([x, y], T.outer(x, y))
>>> %timeit outer_theano(a, b)
100 loops, best of 3: 17.4 ms per loop
pypy
# Same code as the `outer_numba` function
>>> timeit.timeit("outer_pypy(a,b)", number=100, setup="import numpy as np;a = np.random.rand(128,);b = np.random.rand(32000,);from test import outer_pypy;outer_pypy(a,b)")*1000 / 100.0
16.36 # ms
╔═══════════╦═══════════╦═════════╗
║ method ║ time(ms)* ║ version ║
╠═══════════╬═══════════╬═════════╣
║ numba ║ 9.77 ║ 0.16.0 ║
║ np.outer ║ 9.79 ║ 1.9.1 ║
║ cython ║ 10.1 ║ 0.21.2 ║
║ parakeet ║ 11.6 ║ 0.23.2 ║
║ pypy ║ 16.36 ║ 2.4.0 ║
║ np.einsum ║ 16.6 ║ 1.9.1 ║
║ theano ║ 17.4 ║ 0.6.0 ║
╚═══════════╩═══════════╩═════════╝
* less time = faster
Это должно быть так же просто, как использовать numpy.outer()
: один вызов функции, который будет реализован в C для высокой производительности.
@ elyase велик и справедливо принят. Вот еще одно предположение о том, что если вы можете использовать его, вы можете сделать вызов np.outer
еще быстрее.
Вы говорите: «Я должен делать эту операцию несколько раз», поэтому возможно, что вы можете повторно использовать массив, который содержит внешний продукт, вместо того, чтобы каждый раз назначать новый. Это может дать хорошее повышение производительности.
Во-первых, некоторые случайные данные для работы с:
In [32]: a = np.random.randn(128)
In [33]: b = np.random.randn(32000)
Вот базовое время для np.outer (a, b) на моем компьютер:
In [34]: %timeit np.outer(a, b)
100 loops, best of 3: 5.52 ms per loop
Предположим, что мы будем повторять эту операцию несколько раз с массивами одинаковой формы. Создайте массив out
, чтобы сохранить результат:
In [35]: out = np.empty((128, 32000))
Теперь используйте out
в качестве третьего аргумента np.outer
:
In [36]: %timeit np.outer(a, b, out)
100 loops, best of 3: 2.38 ms per loop
Итак, вы получаете приятное повышение производительности, если вы можете повторно использовать массив, который содержит внешний продукт.
Вы получаете аналогичную выгоду, если вы используете аргумент out
в einsum
и в функции cython, если вы добавляете третий аргумент для вывода вместо выделения его в функции с помощью np.empty
. (Другие компилированные / закодированные коды в ответе @ elyase, вероятно, также выиграют от этого, но я попробовал только версию cython.)
Nota bene! Вышеприведенное преимущество может не реализуются на практике. Массив out
подходит для кэша L3 моего процессора, и когда он используется в цикле, выполняемом командой timeit
, он, вероятно, остается в кеше. На практике массив может быть перемещен из кеша между вызовами np.outer
. В этом случае улучшение не так драматично, но по-прежнему должно быть по меньшей мере стоимость вызова np.empty()
, то есть
In [53]: %timeit np.empty((128, 32000))
1000 loops, best of 3: 1.29 ms per loop