一行代碼讓 Python 的運行速度提高100倍
(點擊上方公眾號,可快速關注一起學Python)
來源: python寶典 鏈接:
https://mp.weixin.qq.com/s/Vm0BKSljCzMMgRmIBbpxdQ
python一直被病垢運行速度太慢,但是實際上python的執行效率並不慢,慢的是python用的解釋器Cpython運行效率太差。
「一行代碼讓python的運行速度提高100倍」這絕不是嘩眾取寵的論調。
我們來看一下這個最簡單的例子,從1一直累加到1億。
最原始的代碼:
time time time 0 for in print "Time used: {} sec"import
.
format
(time
.time
()-tt))return
s1
,100000000
))結果:
Time used
: 6
.779874801635742
sec
4999999950000000我們來加一行代碼,再看看結果:
from import import @jit def foo (x,y)
:
tt = time.time() s =0
for
iin
range(x,y): s += i print("Time used: {} sec"
.format(time.time()-tt))return
sprint(foo(1
,100000000
))結果:
Time
used
: 0.04680037498474121
sec
4999999950000000是不是快了100多倍呢?
那麼下面就分享一下「為啥
numba
庫的jit
模塊那麼牛掰?」NumPy
的創始人Travis Oliphant在離開Enthought之後,創建了CONTINUUM,致力於將Python大數據處理方面的應用。最近推出的Numba項目能夠將處理NumPy數組的Python函數JIT編譯為機器碼執行,從而上百倍的提高程序的運算速度。Numba項目的主頁上有Linux下的詳細安裝步驟。編譯LLVM需要花一些時間。Windows用戶可以從Unofficial Windows Binaries for Python Extension Packages下載安裝LLVMPy、meta和numba等幾個擴展庫。
下面我們看一個例子:
import as from import "f8(f8[:])" 0.0 0 for in
range(n): s += array[i]
return
simport
numpyas
nparray = np.random.random(10000
)%timeit sum1d(array)%timeit np.sum(array)%timeit sum(array)10000
loops, bestof
3
:38.9
us perloop
10000
loops, best
of
3
:32.3
us perloop
100
loops, bestof
3
:12.4
ms perloop
numba中提供了一些修飾器,它們可以將其修飾的函數
JIT
編譯成機器碼函數,並返回一個可在Python中調用機器碼的包裝對象。為了能將Python函數編譯成能高速執行的機器碼,我們需要告訴JIT編譯器函數的各個參數和返回值的類型。我們可以通過多種方式指定類型信息,在上面的例子中,類型信息由一個字元串』f8(f8[:])』指定。其中』f8』表示8個位元組雙精度浮點數,括弧前面的』f8』表示返回值類型,括弧里的表示參數類型,』[:]』表示一維數組。因此整個類型字元串表示sum1d()是一個參數為雙精度浮點數的一維數組,返回值是一個雙精度浮點數。需要注意的是,JIT所產生的函數只能對指定的類型的參數進行運算:
print 10 int32 print 10 float32 print 10 float64 1.2095376009e-312 1.46201599944e+185 10.0
如果希望JIT能針對所有類型的參數進行運算,可以使用
autojit
:from import @autojit def sum1d2 (array)
0.0
n = array.shape[0
]for
iin
range(n): s += array[i]return
s%timeit sum1d2(array)10
, dtype=np.int32))10
, dtype=np.float32))10
, dtype=np.float64))10000
loops, best of3
:143
us per loop10.0
10.0
10.0
autoit
雖然可以根據參數類型動態地產生機器碼函數,但是由於它需要每次檢查參數類型,因此計算速度也有所降低。numba的用法很簡單,基本上就是用jit和autojit這兩個修飾器,和一些類型對象。下面的程序列出numba所支持的所有類型:for in if float void int long double unsigned const char double unsigned short float float int int double long double double long double char long unsigned char unsigned long unsigned int shortprint [obj
工作原理
numba
的通過meta
模塊解析Python函數的ast語法樹,對各個變數添加相應的類型信息。然後調用llvmpy生成機器碼,最後再生成機器碼的Python調用介面。meta模塊
通過研究numba的工作原理,我們可以找到許多有用的工具。例如meta模塊可在程序源碼、ast語法樹以及Python二進位碼之間進行相互轉換。下面看一個例子:
def add2 (a, b)
return
a + bdecompile_func能將函數的代碼對象反編譯成ast語法樹,而str_ast能直觀地顯示ast語法樹,使用這兩個工具學習Python的ast語法樹是很有幫助的。
from import from import print "a" "b" None None "a" "b" "add2"
而python_source可以將ast語法樹轉換為Python源代碼:
from import def add2 (a, b)
return
(a + b)decompile_pyc將上述二者結合起來,它能將Python編譯之後的pyc或者pyo文件反編譯成源代碼。下面我們先寫一個tmp.py文件,然後通過py_compile將其編譯成tmp.pyc。
with "tmp.py" "w" as """def square_sum(n): s = 0 for i in range(n): s += i**2 return s""" import "tmp.py"
下面調用decompile_pyc將tmp.pyc顯示為源代碼:
with "tmp.pyc" "rb" as def square_sum (n)
0
for
iin
range(n): s += (i **2
)return
sllvmpy模塊
LLVM是一個動態編譯器,llvmpy則可以通過Python調用LLVM動態地創建機器碼。直接通過llvmpy創建機器碼是比較繁瑣的,例如下面的程序創建一個計算兩個整數之和的函數,並調用它計算結果。
# Create a new module with a function implementing this: # # int add(int a, int b) { # return a + b; # } # "add" "a" "b" "entry" # IRBuilder for our basic block "tmp" # Create an execution engine object. This will create a JIT compiler # on platforms that support it, or an interpreter otherwise # Each argument needs to be passed as a GenericValue object, which is a kind # of variant # Now let"s compile and run! # The return value is also GenericValue. Let"s print it. "returned"from llvm.core import Module, Type, Builderfrom llvm.ee import ExecutionEngine, GenericValue
f_add就是一個動態生成的機器碼函數,我們可以把它想像成C語言編譯之後的函數。在上面的程序中,我們通過ee.run_function調用此函數,而實際上我們還可以獲得它的地址,然後通過Python的ctypes模塊調用它。首先通過ee.get_pointer_to_function獲得f_add函數的地址:
addr = ee.get_pointer_to_function(f_add)addr2975997968L
然後通過ctypes.PYFUNCTYPE創建一個函數類型:
import ctypesf_type = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)
最後通過f_type將函數的地址轉換為可調用的Python函數,並調用它:
f = f_type(addr)f(100, 42)142
numba所完成的工作就是:解析Python函數的ast語法樹並加以改造,添加類型信息;將帶類型信息的ast語法樹通過llvmpy動態地轉換為機器碼函數,然後再通過和ctypes類似的技術為機器碼函數創建包裝函數供Python調用。
(完)
看完本文有收穫?請轉
發分享給更多人
關注「P
ython那些事」,做全棧開發工程師
推薦閱讀
Python面試必須要看的15個問題
程序員你為什麼這麼累?
數字簽名是什麼?
編程能力的4種境界
用Python代碼來下載任意指定網易雲歌曲
推薦閱讀:
※答題速度的快慢很重要,它直接影響學生的成績高低!
※為什麼拚命提高交易速度,利潤反而下降了?
※lets親測有效的速度高效瘦腿攻略——只需幾步,甩掉小粗腿
※會說外語好處多 雙語者大腦反應速度更快
※衰老速度才是人類健康的根本