python sum函数numpy_如何用numba加速python?
我把寫好的markdown導(dǎo)入進來,但是沒想到知乎的排版如此感人。如果對知乎排版不滿想要看高清清爽版,請移步微信公眾號原文 如何用numba加速python?同時歡迎關(guān)注
前言
說道現(xiàn)在最流行的語言,就不得不提python。可是python雖然容易上手,但速度卻有點感人。如何用簡單的方法讓python加速到近乎可以媲美C的速度呢?今天來就來談?wù)刵umba這個寶貝。對你沒看錯,不是numpy,就是numba。
目錄
用函數(shù)編程
Numba的優(yōu)勢
如何使用numba
? 只用1行代碼即可加速,對loop有奇效
? 兼容常用的科學(xué)計算包,可以創(chuàng)建ufunc
? 會自動調(diào)整精度,保證準確性
拓展
? 更多numba的加速選項
? Numba的精度問題
附錄
用函數(shù)編程
在面對一個計算project的時候,我們最容易想到的就是直接碼代碼,最后寫出一個超長的程序。這樣一來,一旦出錯往往就需要花很多時間定位問題。
有一個簡單的辦法解決這個問題,就是定義各種各樣的函數(shù),把任務(wù)分解成很多小部分。因為每個函數(shù)都不是特別復(fù)雜,并且在寫好的時候就可以隨時檢查,因此簡潔的主程序一旦出問題就很容易定位并解決。面向?qū)ο缶幊痰乃枷刖褪腔诤瘮?shù)。
寫好函數(shù)之后,還可以使用裝飾器(decorator)讓它變得強大。裝飾器本身是一個函數(shù),不過是函數(shù)的函數(shù),目的是增加函數(shù)的功能。比如首先定義一個輸出當前時間的函數(shù),再定義一個規(guī)定時間格式的函數(shù),把后一個函數(shù)作用在前一個函數(shù)上,就是一個裝飾器,作用是用特定格式輸出當前時間。
Numba的優(yōu)勢
1.簡單,往往只要1行代碼就有驚喜;
2.對循環(huán)(loop)有奇效,而往往在科學(xué)計算中限制python速度的就是loop;
3.兼容常用的科學(xué)計算包,如numpy、cmath等;
4.可以創(chuàng)建ufunc;
5.會自動調(diào)整精度,保證準確性。
如何使用numba
針對上面提到的numba的優(yōu)勢,我來進行逐一介紹。首先導(dǎo)入numba
import numba as nb只用1行代碼即可加速,對loop有奇效
因為numba內(nèi)置的函數(shù)本身是個裝飾器,所以只要在自己定義好的函數(shù)前面加個@nb.jit()就行,簡單上手。下面以一個求和函數(shù)為例
# 用numba加速的求和函數(shù) @nb.jit() def nb_sum(a):Sum = 0for i in range(len(a)):Sum += a[i]return Sum# 沒用numba加速的求和函數(shù) def py_sum(a):Sum = 0for i in range(len(a)):Sum += a[i]return Sum來測試一下速度
import numpy as np a = np.linspace(0,100,100) # 創(chuàng)建一個長度為100的數(shù)組%timeit np.sum(a) # numpy自帶的求和函數(shù) %timeit sum(a) # python自帶的求和函數(shù) %timeit nb_sum(a) # numba加速的求和函數(shù) %timeit py_sum(a) # 沒加速的求和函數(shù)結(jié)果如下
# np.sum(a) 7.1 μs ± 537 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) # sum(a) 27.7 μs ± 2.64 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each) # nb_sum(a) 1.05 μs ± 27.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each) # py_sum(a) 43.7 μs ± 1.71 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)可以看出,numba甚至比號稱最接近C語言速度運行的numpy還要快6倍以上。但大家都知道,numpy往往對大的數(shù)組更加友好,那我們來測試一個更長的數(shù)組
a = np.linspace(0,100,10**6) # 創(chuàng)建一個長度為100萬的數(shù)組測試結(jié)果如下
# np.sum(a) 2.51 ms ± 246 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) # sum(a) 249 ms ± 19.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) # nb_sum(a) 3.01 ms ± 59.7 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) # py_sum(a) 592 ms ± 42 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)可見即便是用很長的loop來計算,numba的表現(xiàn)也絲毫不亞于numpy。在這里,我們可以看到numba相對于numpy一個非常明顯的優(yōu)勢:numba可以把各種具有很大loop的函數(shù)加到很快的速度,但numpy的加速只適用于numpy自帶的函數(shù)。
但要注意的是,numba對沒有循環(huán)或者只有非常小循環(huán)的函數(shù)加速效果并不明顯,用不用都一樣。(偷偷告訴你,numba的loop甚至常常比numpy的矩陣運算還要快)
兼容常用的科學(xué)計算包,可以創(chuàng)建ufunc
上一部分我們比較了numba和numpy的表現(xiàn),可以說numba非常亮眼了。但numpy還有一個非常強大的功能——ufunc (universal functions),它可以讓一個函數(shù)同時處理很多數(shù)據(jù)。比如要求一個數(shù)組每一個元素的三角函數(shù),只需要
np.sin(a) # 這里的a仍然是上面有100萬個元素的數(shù)組而不需要寫個循環(huán)一個一個求。可如果不用numpy但又想要很快的速度,那應(yīng)該怎么求呢?我們可以用從math庫里導(dǎo)入sin,然后寫個loop再用numba加速。除了這個方法,在這里我還想說numba另一個強大的功能,矢量化(vectorize)。像上面的jit一樣,只要添加一行vectorize就可以讓普通函數(shù)變成ufunc
from math import sin@nb.vectorize() def nb_vec_sin(a):return sin(a)來比較一下用各種方式寫出的三角函數(shù)
# 用numba加速的loop 13.5 ms ± 405 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) # nb_vec_sin(a) 14.2 ms ± 55.2 μs per loop (mean ± std. dev. of 7 runs, 100 loops each) # np.sin(a) 5.75 ms ± 85 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)可以看出,用vectorize改寫的三角函數(shù)具有了和np.sin()一樣的同時處理數(shù)組每個元素的功能,而且表現(xiàn)也不必numpy差。當遇到numpy沒有的數(shù)學(xué)函數(shù)時(比如sech),用numba矢量化不失為一個好的選擇。除了math中的sin,它支持的其他函數(shù)列表可以在documentation中找到(鏈接見附錄)。
其實numpy也有矢量化的功能,只不過比numba差遠了。
會自動調(diào)整精度,保證準確性
上面我們用的測試數(shù)組數(shù)字范圍只是從0到100的。可如果數(shù)字很大,那么就很容易出現(xiàn)overflow的問題,比如
a = np.arange(10**6) # a的最小值為0,最大值為10**6-1你猜猜用python自帶的sum,我們自己寫的py_sum,np.sum和nb_sum給出的結(jié)果一不一樣呢?你會發(fā)現(xiàn)
# np.sum(a) 1783293664 # sum(a) 1783293664 # nb_sum(a) 499999500000 # py_sum(a) 1783293664numba的結(jié)果和其他三個都不一樣,肯定錯了呀,還用問么?
且慢!其實在運行的時候,我并沒有告訴你sum和py_sum都報錯了“RuntimeWarning: overflow encountered in long_scalars”。但奇怪的是np.sum并沒有報錯。
在上面的四個函數(shù)里,其實numba表現(xiàn)的最好,因為它自動調(diào)整了整數(shù)類型。如果你用nb.typeof()查看,你會發(fā)現(xiàn)numba給出的結(jié)果是int64,而其他三個都是int32。不得不說,numba不僅快還在精度方面表現(xiàn)很好!
拓展
在本文的最后一部分,我想談兩個問題。
更多numba的加速選項
除了上面提到的jit和vectorize,其實numba還支持很多加速類型。常見的比如
? @nb.jit(nopython=True,fastmath=True) 犧牲一丟丟數(shù)學(xué)精度來提高速度
? @nb.jit(nopython=True,parallel=True) 自動進行并行計算
切記一定要用nopython。默認都是True的,但有時候如果定義的函數(shù)中遇到numba支持不良好的部分,它就會自動關(guān)閉nopython模式。沒有nopython的numba就好像沒有武器的士兵,雖然好過沒兵,但確實沒什么戰(zhàn)斗力。因此,在使用jit時候要明確寫出nopython=True。如果遇到問題,就找到這些支持不良好的部分,然后改寫。畢竟numba對loop非常友好,改寫這些部分應(yīng)當是非常容易的。
其實如何選擇這些模式會對函數(shù)有最佳的加速效果,是一個玄學(xué)。我前段時間向一位精通numba的prof請教,他給我的建議是,多試試就知道有沒有用了。。。另外,numba還支持多個用GPU加速的包,比如CUDA。
Numba的精度問題
精度方面,在上面我也談到numba會自動轉(zhuǎn)換數(shù)據(jù)類型以適應(yīng)計算。但是在個別時候,這種自動轉(zhuǎn)變類型可能會引起一些計算誤差。通常這個誤差是非常小的,幾乎不會造成任何影響。但如果你所處理的問題會積累誤差,比如求解非線性方程,那么在非常多的計算之后誤差可能就是肉眼可見了。如果你發(fā)現(xiàn)有這樣的問題,記得在jit中指定輸入輸出的數(shù)據(jù)類型。numba具有C所有的數(shù)據(jù)類型,比如對上面的求和函數(shù),只需要把@nb.jit()改為@nb.jit(nb.int64(nb.int32[:]))即可。nb.int64是說輸出的數(shù)字為int64類型,nb.int32是說輸入的數(shù)據(jù)類型為int32,而[:]是說輸入的是數(shù)組。
附錄
Numba documentation鏈接
https://numba.pydata.org/numba-doc/dev/index.html
速度比較:C, Julia, Python, Numba, Cython和LU Factorization
https://www.ibm.com/developerworks/community/blogs/jfp/entry/A_Comparison_Of_C_Julia_Python_Numba_Cython_Scipy_and_BLAS_on_LU_Factorization?lang=en
總結(jié)
以上是生活随笔為你收集整理的python sum函数numpy_如何用numba加速python?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二分排序法(折半排序)
- 下一篇: python numeric_Pytho