Іноді вам потрібно написати недіотичний код numpy, якщо ви дійсно хочете пришвидшити свій розрахунок, чого ви не можете зробити з рідною нумією.
numba
компілює ваш python-код на низькому рівні C. Оскільки велика кількість онімінь, як правило, так само швидко, як і C, це здебільшого стає корисним, якщо ваша проблема не піддається нативній векторизації з numpy. Це один із прикладів (де я припускав, що індекси є суміжними та сортованими, що також відображається в даних прикладу):
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
Ось кілька таймінгів із використанням %timeit
магії 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)
Використовуючи оновлені приклади даних у питанні, ці числа (тобто час виконання функції python порівняно з часом виконання функціонування, прискореного JIT):
>>> %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)
Це становить 65-кратну швидкість у меншому випадку та 26-кратну прискорення у більшому випадку (порівняно з кодом повільного циклу), звичайно) з використанням прискореного коду. Інша перевага полягає в тому, що (на відміну від типової векторизації з власним нумером) нам не була потрібна додаткова пам'ять для досягнення цієї швидкості, це все про оптимізований і складений код низького рівня, який в кінцевому підсумку запускається.
Вищенаведена функція передбачає, що numpy int масиви int64
за замовчуванням, що насправді не так у Windows. Отже, альтернативою є видалення підпису з виклику до numba.njit
, запускаючи належну своєчасну компіляцію. Але це означає, що функція буде скомпільована під час першого виконання, що може поєднуватися з результатами часу (ми можемо виконати функцію один раз вручну, використовуючи представницькі типи даних, або просто прийняти, що перше виконання часу буде набагато повільніше, що повинно ігнорувати). Це саме те, що я намагався запобігти, вказавши підпис, що запускає дострокову компіляцію.
У будь-якому випадку, в належному випадку JIT декоратор, який нам потрібен, є просто
@numba.njit
def diffmedian_jit(...):
Зауважте, що вищезазначені таймінги, які я показав для функції, складеної jit, застосовуються лише після того, як функція була скомпільована. Це або відбувається при визначенні (з нетерплячим складанням, коли явний підпис передається доnumba.njit
), або під час першого виклику функції (при лінивій компіляції, коли підпис не передається numba.njit
). Якщо функція буде виконуватися лише один раз, час компіляції також слід враховувати для швидкості цього методу. Як правило, варто лише компілювати функції, якщо загальний час компіляції + виконання менший, ніж некомпільований час виконання (що насправді вірно у вищенаведеному випадку, коли нативна функція пітона дуже повільна). В основному це відбувається, коли ви багато разів викликаєте свою компільовану функцію.
Як max9111 відзначена в коментарях, одна важлива особливістю numba
є cache
ключовим словом , щоб jit
. Перехід cache=True
до numba.jit
збереже складену функцію на диск, так що під час наступного виконання даного модуля python функція буде завантажена звідти, а не перекомпільована, що знову може позбавити вас часу виконання.
scipy.ndimage.median
пропозицію у відповіді? Мені не здається, що йому потрібно однакова кількість елементів на етикетці. Або я щось пропустив?