Коли 2d-масив (або nd-масив) є C- або F-суміжним, то це завдання відображення функції на 2d-масив практично те саме, що завдання відображення функції на 1d-масив - ми просто повинні бачити це таким чином, наприклад, через np.ravel(A,'K')
.
Наприклад, тут обговорювались можливі рішення для 1d-масиву .
Однак, коли пам'ять 2d-масиву не є суміжним, тоді ситуація трохи складніша, тому що хотілося б уникнути можливих пропусків кешу, якщо вісь обробляється в неправильному порядку.
Numpy вже має механізм для обробки осей у найкращому порядку. Одна з можливостей використовувати цю техніку - це np.vectorize
. Однак документація numpy про np.vectorize
те, що він "надається в першу чергу для зручності, а не для продуктивності" - повільна функція пітона залишається повільною функцією пітона з усіма пов'язаними накладними! Інше питання полягає в його величезному споживанні пам'яті - див., Наприклад, цей SO-пост .
Коли хочеться виконати функцію C, але використовувати машину numpy, хорошим рішенням є використання numba для створення ufuncs, наприклад:
# 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-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 є хорошим компромісом між зручністю та продуктивністю, він все ще не найкращий, що ми можемо зробити. Але немає жодної срібної кулі чи підходу, який найкраще підходить для будь-якого завдання - треба розуміти, що таке обмеження та як їх можна пом'якшити.
Наприклад, для трансцендентних функцій (наприклад exp
, sin
, cos
) Numba не дає жодних - яких переваг по порівнянні з NumPy - х np.exp
(немає тимчасових масивів , створених - основне джерело прискорення). Однак моя установка Anaconda використовує VML Intel для векторів більше 8192 - він просто не може це зробити, якщо пам'ять не є суміжною. Тому може бути краще скопіювати елементи в суміжну пам'ять, щоб мати можливість використовувати VML від Intel:
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 також може використовувати SVML від Intel, як це пояснено у цій публікації :
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, навіть незважаючи на додаткові накладні копії - що не є чимось сюрпризом, оскільки уфункції numba не паралельні.
Список:
А. Порівняння поліномної функції:
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)',
)