Когда 2d-массив (или nd-массив) является C- или F-смежным, тогда эта задача отображения функции на 2d-массив практически такая же, как задача отображения функции на 1d-массив - мы просто нужно рассматривать это таким образом, например, через np.ravel(A,'K')
.
Возможное решение для 1d-массива обсуждалось, например, здесь .
Однако, когда память 2d-массива не является непрерывной, тогда ситуация немного усложняется, потому что хотелось бы избежать возможных промахов кеша, если оси обрабатываются в неправильном порядке.
У Numpy уже есть оборудование для обработки топоров в наилучшем возможном порядке. Одна из возможностей использовать эту технику np.vectorize
. Однако в документации numpy np.vectorize
указано, что она «предоставляется в первую очередь для удобства, а не для производительности» - медленная функция python остается медленной функцией python со всеми связанными накладными расходами! Другая проблема - это огромное потребление памяти - см., Например, этот SO-пост .
Когда кто-то хочет иметь производительность C-функции, но использовать механизм numpy, хорошим решением является использование numba для создания ufunc, например:
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
Он легко превосходит, np.vectorize
но также, когда та же функция будет выполняться как умножение / сложение массива numpy, т.е.
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
См. Приложение к этому ответу для кода измерения времени:
Версия Numba (зеленая) примерно в 100 раз быстрее, чем функция python (т.е. np.vectorize
), что неудивительно. Но это также примерно в 10 раз быстрее, чем numpy-функциональность, потому что версия numbas не требует промежуточных массивов и, таким образом, использует кеш более эффективно.
Хотя подход numba к ufunc - это хороший компромисс между удобством использования и производительностью, это все еще не лучшее, что мы можем сделать. Тем не менее, не существует серебряной пули или наилучшего подхода для любой задачи - нужно понимать, в чем заключаются ограничения и как их можно уменьшить.
Например, для трансцендентных функций (например exp
, sin
, cos
) Numba не дает каких - либо преимуществ по сравнению с NumPy - х np.exp
(нет временных массивов , созданных - основной источник ускорения). Однако моя установка Anaconda использует Intel VML для векторов больше 8192 - он просто не может этого сделать, если память не является непрерывной. Поэтому, возможно, лучше скопировать элементы в непрерывную память, чтобы иметь возможность использовать Intel VML:
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp(x):
return np.exp(x)
def np_copy_exp(x):
copy = np.ravel(x, 'K')
return np.exp(copy).reshape(x.shape)
Для справедливости сравнения я отключил распараллеливание VML (см. Код в приложении):
Как можно видеть, как только VML начинает работать, накладные расходы на копирование более чем компенсируются. Однако, когда объем данных становится слишком большим для кэша L3, преимущество становится минимальным, поскольку задача снова становится ограниченной пропускной способностью памяти.
С другой стороны, numba также может использовать Intel SVML, как объясняется в этом посте :
from llvmlite import binding
# set before import
binding.set_option('SVML', '-vector-library=SVML')
import numba as nb
@nb.vectorize(target="cpu")
def nb_vexp_svml(x):
return np.exp(x)
а использование VML с распараллеливанием дает:
Версия numba имеет меньше накладных расходов, но для некоторых размеров VML превосходит SVML, даже несмотря на дополнительные накладные расходы на копирование, что неудивительно, поскольку ufuncs numba не распараллеливаются.
Тэг:
A. сравнение полиномиальной функции:
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
f,
vf,
nb_vf
],
logx=True,
logy=True,
xlabel='len(x)'
)
Б. сравнение exp
:
import perfplot
import numexpr as ne # using ne is the easiest way to set vml_num_threads
ne.set_vml_num_threads(1)
perfplot.show(
setup=lambda n: np.random.rand(n,n)[::2,::2],
n_range=[2**k for k in range(0,12)],
kernels=[
nb_vexp,
np.exp,
np_copy_exp,
],
logx=True,
logy=True,
xlabel='len(x)',
)