Terkadang Anda perlu menulis kode numpy non-idiomatis jika Anda benar - benar ingin mempercepat perhitungan Anda yang tidak dapat Anda lakukan dengan numpy asli.
numba
mengkompilasi kode python Anda ke level C. rendah. Karena banyak numpy itu sendiri biasanya secepat C, ini sebagian besar akhirnya berguna jika masalah Anda tidak cocok dengan vektorisasi asli dengan numpy. Ini adalah salah satu contoh (di mana saya berasumsi bahwa indeks berdekatan dan diurutkan, yang juga tercermin dalam contoh data):
import numpy as np
import numba
# use the inflated example of roganjosh https://stackoverflow.com/a/58788534
data = [1.00, 1.05, 1.30, 1.20, 1.06, 1.54, 1.33, 1.87, 1.67]
index = [0, 0, 1, 1, 1, 1, 2, 3, 3]
data = np.array(data * 500) # using arrays is important for numba!
index = np.sort(np.random.randint(0, 30, 4500))
# jit-decorate; original is available as .py_func attribute
@numba.njit('f8[:](f8[:], i8[:])') # explicit signature implies ahead-of-time compile
def diffmedian_jit(data, index):
res = np.empty_like(data)
i_start = 0
for i in range(1, index.size):
if index[i] == index[i_start]:
continue
# here: i is the first _next_ index
inds = slice(i_start, i) # i_start:i slice
res[inds] = data[inds] - np.median(data[inds])
i_start = i
# also fix last label
res[i_start:] = data[i_start:] - np.median(data[i_start:])
return res
Dan berikut adalah beberapa timing menggunakan %timeit
keajaiban IPython :
>>> %timeit diffmedian_jit.py_func(data, index) # non-jitted function
... %timeit diffmedian_jit(data, index) # jitted function
...
4.27 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
65.2 µs ± 1.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Menggunakan contoh data yang diperbarui dalam pertanyaan angka-angka ini (yaitu runtime dari fungsi python vs runtime dari JT-accelerated functio) adalah
>>> %timeit diffmedian_jit.py_func(data, groups)
... %timeit diffmedian_jit(data, groups)
2.45 s ± 34.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
93.6 ms ± 518 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Ini berarti 65x speedup dalam case yang lebih kecil dan 26x speedup dalam case yang lebih besar (dibandingkan dengan kode gila, tentu saja) menggunakan kode akselerasi. Kelebihan lainnya adalah bahwa (tidak seperti vektorisasi khas dengan numpy asli) kami tidak memerlukan memori tambahan untuk mencapai kecepatan ini, ini semua tentang kode tingkat rendah yang dioptimalkan dan disusun yang akhirnya dijalankan.
Fungsi di atas mengasumsikan bahwa array int numpy secara int64
default, yang sebenarnya tidak terjadi pada Windows. Jadi alternatifnya adalah menghapus tanda tangan dari panggilan ke numba.njit
, memicu kompilasi tepat waktu yang tepat. Tetapi ini berarti bahwa fungsi tersebut akan dikompilasi selama eksekusi pertama, yang dapat mencampuri hasil pengaturan waktu (kita dapat mengeksekusi fungsi tersebut secara manual, menggunakan tipe data yang representatif, atau hanya menerima bahwa eksekusi pengaturan waktu pertama akan jauh lebih lambat, yang seharusnya diabaikan). Ini persis apa yang saya coba cegah dengan menentukan tanda tangan, yang memicu kompilasi sebelumnya.
Bagaimanapun, dalam kasus JIT yang tepat dekorator yang kita butuhkan adalah adil
@numba.njit
def diffmedian_jit(...):
Perhatikan bahwa timing di atas yang saya perlihatkan untuk fungsi yang dikompilasi jit hanya berlaku setelah fungsi dikompilasi. Ini bisa terjadi pada definisi (dengan kompilasi bersemangat, ketika tanda tangan eksplisit diteruskan ke numba.njit
), atau selama panggilan fungsi pertama (dengan kompilasi malas, ketika tidak ada tanda tangan dilewatkan ke numba.njit
). Jika fungsi ini hanya akan dieksekusi sekali maka waktu kompilasi juga harus dipertimbangkan untuk kecepatan metode ini. Biasanya hanya layak mengkompilasi fungsi jika total waktu kompilasi + eksekusi kurang dari runtime yang tidak dikompilasi (yang sebenarnya benar dalam kasus di atas, di mana fungsi python asli sangat lambat). Ini sebagian besar terjadi ketika Anda sering memanggil fungsi yang dikompilasi.
Seperti yang dicatat oleh max9111 dalam komentar, salah satu fitur penting numba
adalah cache
kata kunci untuk jit
. Passing cache=True
to numba.jit
akan menyimpan fungsi yang dikompilasi ke disk, sehingga selama eksekusi berikutnya dari modul python yang diberikan fungsi akan dimuat dari sana daripada dikompilasi ulang, yang lagi-lagi dapat membantu Anda menjalankan runtime dalam jangka panjang.
scipy.ndimage.median
saran dalam jawaban yang ditautkan? Bagi saya tampaknya tidak membutuhkan jumlah elemen yang sama per label. Atau apakah saya melewatkan sesuatu?