由於我的程序快速索引Numpy
陣列是非常必要的,花式索引考慮到性能沒有良好的聲譽,我決定做一些測試。特別是因爲Numba
發展得相當快,我嘗試了哪些方法適用於numba。性能的各種numpy花式索引方法,也與numba
正如我一直在使用下面的陣列爲我輸入的小型陣列測試:
import numpy as np
import numba as nb
x = np.arange(0, 100, dtype=np.float64) # array to be indexed
idx = np.array((0, 4, 55, -1), dtype=np.int32) # fancy indexing array
bool_mask = np.zeros(x.shape, dtype=np.bool) # boolean indexing mask
bool_mask[idx] = True # set same elements as in idx True
y = np.zeros(idx.shape, dtype=np.float64) # output array
y_bool = np.zeros(bool_mask[bool_mask == True].shape, dtype=np.float64) #bool output array (only for convenience)
而且我的大陣列測試以下陣列(這裏以應對從重複數據刪除的數字需要y_bool
randint
):
x = np.arange(0, 1000000, dtype=np.float64)
idx = np.random.randint(0, 1000000, size=int(1000000/50))
bool_mask = np.zeros(x.shape, dtype=np.bool)
bool_mask[idx] = True
y = np.zeros(idx.shape, dtype=np.float64)
y_bool = np.zeros(bool_mask[bool_mask == True].shape, dtype=np.float64)
這將產生以下的定時,而無需使用numba:
%timeit x[idx]
#1.08 µs ± 21 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
#large arrays: 129 µs ± 3.45 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit x[bool_mask]
#482 ns ± 18.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
#large arrays: 621 µs ± 15.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.take(x, idx)
#2.27 µs ± 104 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# large arrays: 112 µs ± 5.76 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.take(x, idx, out=y)
#2.65 µs ± 134 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# large arrays: 134 µs ± 4.47 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit x.take(idx)
#919 ns ± 21.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 108 µs ± 1.71 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit x.take(idx, out=y)
#1.79 µs ± 40.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# larg arrays: 131 µs ± 2.92 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.compress(bool_mask, x)
#1.93 µs ± 95.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 618 µs ± 15.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.compress(bool_mask, x, out=y_bool)
#2.58 µs ± 167 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# large arrays: 637 µs ± 9.88 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit x.compress(bool_mask)
#900 ns ± 82.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 628 µs ± 17.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit x.compress(bool_mask, out=y_bool)
#1.78 µs ± 59.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 628 µs ± 13.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit np.extract(bool_mask, x)
#5.29 µs ± 194 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# large arrays: 641 µs ± 13 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
並與numba
,在nopython
- 模式,cach
ING和nogil
使用jitting我飾索引的方式,這是由numba
支持:
@nb.jit(nopython=True, cache=True, nogil=True)
def fancy(x, idx):
x[idx]
@nb.jit(nopython=True, cache=True, nogil=True)
def fancy_bool(x, bool_mask):
x[bool_mask]
@nb.jit(nopython=True, cache=True, nogil=True)
def taker(x, idx):
np.take(x, idx)
@nb.jit(nopython=True, cache=True, nogil=True)
def ndtaker(x, idx):
x.take(idx)
我們得到以下結果爲小型和大型陣列:
%timeit fancy(x, idx)
#686 ns ± 25.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 84.7 µs ± 1.82 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit fancy_bool(x, bool_mask)
#845 ns ± 31 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 843 µs ± 14.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit taker(x, idx)
#814 ns ± 21.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 87 µs ± 1.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit ndtaker(x, idx)
#831 ns ± 24.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
# large arrays: 85.4 µs ± 2.69 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
摘要
雖然沒有numba的numpy很明顯,小數組是迄今爲止最好的用布爾掩碼索引(大約是因子2與ndarray.take(idx)
相比),對於較大的數組ndarray.take(idx)
將表現最好,在這種情況下,大約比布爾索引快6倍。盈虧平衡點的陣列大小約爲1000
單元,索引陣列大小約爲20
單元。
對於具有1e5
元素和5e3
索引數組大小的數組,ndarray.take(idx)
將大約是比布爾值掩碼索引更快。所以看起來布爾索引似乎會隨着數組大小而顯着減慢,但在達到某個數組大小閾值後會稍微增加。
對於numba jitted函數,除布爾掩碼索引外,所有索引函數都有一個小的加速。簡單的花式索引在這裏效果最好,但仍然比沒有jitting的布爾掩碼更慢。
對於較大的數組,布爾值掩碼索引比其他方法慢很多,甚至比非jitted版本要慢。其他三種方法都表現相當不錯,比非拼接版本快大約15%。
對於我的情況,有許多不同大小的數組,花式索引與numba是最好的方法。也許其他人也可以在這篇相當長的文章中找到一些有用的信息。
編輯:
對不起,我忘了問我的問題,我其實有。我剛剛在工作日結束時迅速輸入了這個數據,並完全忘記了它... 那麼,你知道比我測試的更好更快的方法嗎?使用Cython我的時間在Numba和Python之間。
由於索引數組只是預定義一次,並且在長時間迭代中沒有改變地使用,所以任何預先定義索引過程的方式都會很好。爲此我想到了使用步伐。但我無法預先定義一組自定義的步幅。是否有可能使用大步獲得預定義的視圖到內存中?
編輯2:
我想我會提出我的問題有關,將相同的值陣列上使用預定義的常量數組索引(其中僅值的變化,但它沒有形狀)幾百萬次的迭代一個新的更具體的問題。這個問題太籠統了,也許我也提出了一些有點誤導的問題。一旦我開啓新的問題,我會在這裏發佈鏈接!
Here is the link to the followup question.
這裏有什麼問題?問一個真正的問題並自我回答,會不會更好? – MSeifert
Scotty,將你的問題變成一個真正的問題,並將所有這些都貼到自我回答中。如果你想我會通過社區維基粘貼它,所以你可以接受之前,這關閉(和刪除)爲「不清楚你要問什麼」 –
@DanielF謝謝你的提示!最後我加了一個問題! –