1. NumExpr是什么?
NumExpr是一個(gè)用于numpy類型快速數(shù)值表達(dá)式計(jì)算的第三方Python加速庫。有了它, 在數(shù)組上操作的表達(dá)式(如3xa+4xb)相比在python中執(zhí)行速度更快,所需內(nèi)存空間占用更少。 值得一提的是,NumExpr集成了intel的vml(向量數(shù)學(xué)計(jì)算庫 vectore math library)技術(shù), 這使得數(shù)值型表達(dá)式的計(jì)算速度得到了進(jìn)一步提升。
2. Numexpr如何實(shí)現(xiàn)高性能?
NumExpr獲得比numpy更好的性能的主要原因是避免為中間結(jié)果分配內(nèi)存,這樣可以提高緩存利用率,減少內(nèi)存訪問。因此,NumExpr在處理大型數(shù)組時(shí)相比numpy效果最好。
3. 如何安裝NumExpr?
我們可以通過conda或者pip來進(jìn)行安裝,命令如下:
$ conda install -c anaconda numexpr
或者
pip install numexpr
4. 性能評估
我們計(jì)算由兩個(gè)向量a,b 組成的表達(dá)式 f 為例,來評估輸入不同大小的兩個(gè)向量下使用NumExpr和Numpy二者的性能差異。其中f表達(dá)式如下:
1)導(dǎo)入相關(guān)庫
import numexpr as ne
from time import time
2)使用Numpy來實(shí)現(xiàn)
def timeIt_numpy(a, b, runs, loops):
output = np.zeros(runs)
for r in range(runs):
to = time()
for _ in range(loops):
current = 2*a**3 + 3*b**2
ti = time()
output[r] = ti - to
return output
上述代碼實(shí)現(xiàn)了利用Numpy來計(jì)算表達(dá)式f的功能,其中: a,b 代表輸入的兩個(gè)向量 runs 代表我們做幾次對比實(shí)驗(yàn) loops 代表每次實(shí)驗(yàn)表達(dá)式重復(fù)執(zhí)行多少次,以方便我們統(tǒng)計(jì)時(shí)間 output 代表每次實(shí)驗(yàn)(即上述表達(dá)式執(zhí)行l(wèi)oops次)所耗費(fèi)的時(shí)間
3)使用NumExpr來實(shí)現(xiàn)
def timeIt_numexpr(a, b, runs, loops):
output = np.zeros(runs)
for r in range(runs):
to = time()
for _ in range(loops):
current = ne.evaluate('2*a**3 + 3*b**2')
ti = time()
output[r] = ti - to
return output
上述代碼實(shí)現(xiàn)了利用NumExpr來計(jì)算表達(dá)式f的功能,相關(guān)參數(shù)含義同上。這里需要注意的是,使用NumExpr來計(jì)算表達(dá)式的值,只需要將表達(dá)式寫成:
current = ne.evaluate('2*a**3 + 3*b**2')
形式即可,對程序的修改及其簡單。
4)主函數(shù)
這里為了直觀對比輸入不同size下的vector,統(tǒng)計(jì)兩者的時(shí)間差異,使用matplotlib來畫圖顯示,代碼如下:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1, figsize=(16, 8))
N = 16
runs = 10
loops = 1000
time_numpy = np.zeros((runs, N))
time_numexpr = np.zeros((runs, N))
# Time
for i in range(N):
a = np.random.rand(2**(i + 1))
b = np.random.rand(2**(i + 1))
time_numpy[:,i] = timeIt_numpy(a, b, runs, loops)
time_numexpr[:,i] = timeIt_numexpr(a, b, runs, loops)
# Plot
x = np.arange(N)
mu = np.mean(time_numpy, axis=0)
ax.plot(mu, label="NumPy")
x = np.arange(N)
mu = np.mean(time_numexpr, axis=0)
ax.plot(mu, label="NumExpr")
ax.set_xlabel('N')
ax.set_xticks(x)
ax.set_xticklabels([f'$2^{{{e + 1}}}$'for e in range(N)])
ax.set_ylabel('time in seconds for each run')
ax.set_title(f'runs = {runs} | loops = {loops}')
ax.legend()
plt.show()
代碼含義如下: N 代表一共輸入16組不同大小size的向量,每次實(shí)驗(yàn)輸入向量的size為[2^1, 2^2 … 2^16] runs 代表每組向量,進(jìn)行10次實(shí)驗(yàn) loops 代表表達(dá)式執(zhí)行1000次,方便我們統(tǒng)計(jì)表達(dá)式執(zhí)行耗時(shí)
耗時(shí)對比
上述圖像中,橫坐標(biāo)表示輸入向量的size,縱坐標(biāo)表示向量size為N時(shí),執(zhí)行10次實(shí)驗(yàn)的平均耗時(shí),每次實(shí)驗(yàn)中表達(dá)式執(zhí)行1000次。 通過上述曲線,我們可以看出:
- 當(dāng)輸入向量size小于2^12時(shí),使用Numpy和NumExpr,所耗費(fèi)的時(shí)間相差不大
- 當(dāng)size大于 2^12時(shí),隨著size的增大,NumExpr的加速效果越來越明顯。
- 當(dāng)size等于2^16時(shí),NumExpr加速效果?超過了10倍。
5. 總結(jié)
在代碼中使用NumExpr的好處:
- 對程序的修改簡單,當(dāng)輸入尺度較大時(shí),對支持的表達(dá)式的提速效果明顯
- 自動化的多線程;
6. 參考
|