Ketika 2d-array (atau nd-array) adalah C- atau F-contiguous, maka tugas memetakan sebuah fungsi ke dalam array 2d secara praktis sama dengan tugas memetakan fungsi ke dalam array 1d - kita hanya harus melihatnya seperti itu, mis np.ravel(A,'K')
. via .
Solusi yang memungkinkan untuk 1d-array telah dibahas misalnya di sini .
Namun, ketika memori dari 2d-array tidak bersebelahan, maka situasinya sedikit lebih rumit, karena seseorang ingin menghindari kemungkinan cache miss jika sumbu ditangani dalam urutan yang salah.
Numpy sudah memiliki mesin untuk memproses sumbu dalam urutan terbaik. Salah satu kemungkinan untuk menggunakan mesin ini adalah np.vectorize
. Namun, dokumentasi numpy np.vectorize
menyatakan bahwa itu "disediakan terutama untuk kenyamanan, bukan untuk kinerja" - fungsi python yang lambat tetap menjadi fungsi python yang lambat dengan seluruh overhead terkait! Masalah lainnya adalah konsumsi memori yang besar - lihat contoh SO-post ini .
Ketika seseorang ingin memiliki kinerja fungsi-C tetapi menggunakan mesin numpy, solusi yang baik adalah menggunakan numba untuk pembuatan ufunc, misalnya:
# 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
Ini mudah berdetak np.vectorize
tetapi juga ketika fungsi yang sama akan dilakukan sebagai perkalian / penambahan numpy-array, yaitu
# 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"
Lihat lampiran jawaban ini untuk kode pengukuran waktu:
Versi Numba (hijau) sekitar 100 kali lebih cepat daripada fungsi python (yaitu np.vectorize
), yang tidak mengherankan. Tetapi juga sekitar 10 kali lebih cepat daripada fungsionalitas numpy, karena versi numbas tidak memerlukan array perantara dan karenanya menggunakan cache lebih efisien.
Meskipun pendekatan ufunc numba adalah pertukaran yang baik antara kegunaan dan kinerja, itu masih bukan yang terbaik yang bisa kami lakukan. Namun tidak ada solusi terbaik atau pendekatan terbaik untuk tugas apa pun - orang harus memahami apa saja batasannya dan bagaimana hal itu dapat dikurangi.
Misalnya, untuk fungsi transendental (misalnya exp
, sin
, cos
) Numba tidak memberikan keuntungan apa pun atas numpy ini np.exp
(tidak ada array sementara yang dibuat - sumber utama kecepatan-up). Namun, instalasi Anaconda saya menggunakan VML Intel untuk vektor yang lebih besar dari 8192 - tidak dapat melakukannya jika memori tidak berdekatan. Jadi mungkin lebih baik untuk menyalin elemen ke memori yang berdekatan agar dapat menggunakan 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)
Untuk keadilan perbandingan, saya telah mematikan paralelisasi VML (lihat kode di lampiran):
Seperti yang bisa dilihat, begitu VML dijalankan, overhead penyalinan lebih dari kompensasi. Namun begitu data menjadi terlalu besar untuk cache L3, keuntungannya minimal karena tugas sekali lagi terikat pada memori-bandwidth.
Di sisi lain, numba juga dapat menggunakan SVML Intel, seperti yang dijelaskan dalam posting ini :
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)
dan menggunakan VML dengan hasil paralelisasi:
Versi numba memiliki overhead yang lebih sedikit, tetapi untuk beberapa ukuran VML mengalahkan SVML meskipun ada overhead penyalinan tambahan - yang tidak mengejutkan karena ufunc numba tidak diparalelkan.
Daftar:
A. perbandingan fungsi polinom:
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)'
)
B. perbandingan dari 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)',
)