Ada numexpr , numba dan cython di sekitar, tujuan dari jawaban ini adalah untuk mempertimbangkan kemungkinan-kemungkinan ini.
Tapi pertama-tama mari kita nyatakan yang jelas: tidak peduli bagaimana Anda memetakan fungsi-Python ke array-numpy, tetap fungsi Python, yang berarti untuk setiap evaluasi:
- elemen numpy-array harus dikonversi ke objek-Python (misalnya a
Float
).
- semua perhitungan dilakukan dengan objek Python, yang berarti memiliki overhead juru bahasa, pengiriman dinamis, dan objek tidak berubah.
Jadi mesin mana yang digunakan untuk benar-benar loop melalui array tidak memainkan peran besar karena overhead yang disebutkan di atas - itu tetap jauh lebih lambat daripada menggunakan fungsi built-in numpy.
Mari kita lihat contoh berikut:
# 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"
np.vectorize
dipilih sebagai perwakilan dari kelas fungsi python murni dari pendekatan. Dengan menggunakan perfplot
(lihat kode pada lampiran jawaban ini) kami mendapatkan waktu berjalan berikut:
Kita bisa melihat, bahwa pendekatan numpy adalah 10x-100x lebih cepat daripada versi python murni. Penurunan kinerja untuk ukuran array yang lebih besar mungkin karena data tidak lagi sesuai dengan cache.
Perlu juga disebutkan, yang vectorize
juga menggunakan banyak memori, jadi sering penggunaan memori adalah botol-leher (lihat pertanyaan SO terkait ). Juga perhatikan, bahwa dokumentasi numpy np.vectorize
menyatakan bahwa itu "disediakan terutama untuk kenyamanan, bukan untuk kinerja".
Alat lain harus digunakan, ketika kinerja diinginkan, selain menulis ekstensi-C dari awal, ada beberapa kemungkinan berikut:
Orang sering mendengar, bahwa kinerja numpy sebagus yang didapat, karena murni C di bawah tenda. Namun ada banyak ruang untuk perbaikan!
Versi numpy vektor menggunakan banyak memori tambahan dan akses memori. Numexp-library mencoba untuk men-tile numpy-array dan karenanya mendapatkan pemanfaatan cache yang lebih baik:
# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
return ne.evaluate("x+2*x*x+4*x*x*x")
Mengarah ke perbandingan berikut:
Saya tidak bisa menjelaskan semuanya dalam plot di atas: kita bisa melihat overhead yang lebih besar untuk numexpr-library di awal, tetapi karena menggunakan cache lebih baik, ini sekitar 10 kali lebih cepat untuk array yang lebih besar!
Pendekatan lain adalah untuk mengkompilasi fungsi dan dengan demikian mendapatkan UFunc pure-C yang nyata. Ini adalah pendekatan numba:
# 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 10 kali lebih cepat dari pendekatan numpy asli:
Namun, tugasnya memaralelkan, sehingga kita juga bisa menggunakan prange
untuk menghitung loop secara paralel:
@nb.njit(parallel=True)
def nb_par_jitf(x):
y=np.empty(x.shape)
for i in nb.prange(len(x)):
y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y
Seperti yang diharapkan, fungsi paralel lebih lambat untuk input yang lebih kecil, tetapi lebih cepat (hampir faktor 2) untuk ukuran yang lebih besar:
Sementara numba berspesialisasi dalam mengoptimalkan operasi dengan numpy-array, Cython adalah alat yang lebih umum. Lebih rumit untuk mengekstraksi kinerja yang sama dengan numba - seringkali turun ke llvm (numba) vs kompiler lokal (gcc / MSVC):
%%cython -c=/openmp -a
import numpy as np
import cython
#single core:
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_f(double[::1] x):
y_out=np.empty(len(x))
cdef Py_ssize_t i
cdef double[::1] y=y_out
for i in range(len(x)):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
#parallel:
from cython.parallel import prange
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_par_f(double[::1] x):
y_out=np.empty(len(x))
cdef double[::1] y=y_out
cdef Py_ssize_t i
cdef Py_ssize_t n = len(x)
for i in prange(n, nogil=True):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
Cython menghasilkan fungsi yang agak lambat:
Kesimpulan
Jelas, pengujian hanya untuk satu fungsi tidak membuktikan apa-apa. Juga harus diingat, bahwa untuk fungsi-contoh yang dipilih, bandwidth memori adalah leher botol untuk ukuran lebih besar dari 10 ^ 5 elemen - sehingga kami memiliki kinerja yang sama untuk numba, numexpr dan cython di wilayah ini.
Pada akhirnya, jawaban ultimatif tergantung pada jenis fungsi, perangkat keras, distribusi Python dan faktor lainnya. Misalnya Anaconda-distribusi menggunakan Intel VML untuk fungsi numpy dan dengan demikian melebihi Numba (kecuali menggunakan SVML, melihat ini SO-posting ) mudah untuk fungsi-fungsi transendental seperti exp
, sin
, cos
dan sejenis - lihat misalnya berikut SO-post .
Namun dari penyelidikan ini dan dari pengalaman saya sejauh ini, saya akan menyatakan, bahwa numba tampaknya menjadi alat yang paling mudah dengan kinerja terbaik selama tidak ada fungsi transendental yang terlibat.
Merencanakan waktu berjalan dengan perfplot -paket:
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n),
n_range=[2**k for k in range(0,24)],
kernels=[
f,
vf,
ne_f,
nb_vf, nb_par_jitf,
cy_f, cy_par_f,
],
logx=True,
logy=True,
xlabel='len(x)'
)