隻需要這一行代碼就能讓python計算速度提高十倍
一、前言
Python語言近年來人氣爆棚。它廣泛應用於數據科學,人工智能,以及網絡安全問題中,由於代碼可讀性較強,學習效率較高,吸引瞭許多非科班的同學進行學習。然而,使用Python一段時間以後,發現它在速度上完全沒有優勢可言,特別是計算密集型任務裡,性能問題一直是Python的軟肋。本文主要介紹瞭Python的JIT編譯器Numba,能夠在對代碼侵入最少的情況下,極大加速計算核心函數的運行速度,適合數據分析業務相關的同學使用。
首先要回答這樣一個問題:當運行同一個程序時,為什麼Python會 比其他語言慢2到10倍?為什麼我們無法將它變得更快?
以下是最主要的原因:
- “它是GIL(Global Interpreter Lock全局解釋器鎖)”
- “它是解釋型語言而非編譯語言”
- “它是動態類型語言
由於本文的著重點並不是解釋Python速度慢的原因以及背後的邏輯,這部分就不深入探討瞭,歡迎有興趣的同學自行搜索🔍
二、Python的JIT編譯器
為瞭兼具移植性和性能,聰明的工程師們發明瞭 JIT 這個東西,所謂的 JIT 就是說在解釋型語言中,對於經常用到的或者說有較大性能提升的代碼在解釋的時候編譯成機器碼,其他一次性或者說沒有太大性能提升的代碼還是以字節碼的方式執行。這樣的話,就能在保證移植性的同時,又能讓性能提升一大截,
JIT編譯在代碼運行時動態將Python代碼編譯為機器代碼執行,由於避免瞭Python內置的解釋器,運行速度會有很大提升。比較流行的JIT方案是Numba和Pypy,但由於Python的歷史包袱和語法變化等原因,沒有一個能夠完美實現的方案。方案各自存在不同的優缺點,需要在根據使用領域選擇合適的方案。
- Pypy支持全局的加速,但對C庫支持不好,較為適合用於Web服務等事務型任務。
- Numba能夠對某些函數和庫進行加速,高性能的同時保持瞭Python的兼容性,但使用的范圍會受到一定限制。
三、Numba快速學習
我們主要介紹Numba的基本用法,能夠在對代碼侵入最少的情況下,極大加速計算核心函數的運行速度,適合數據分析業務相關的同學使用。
Numba通過使用LLVM技術,將Python代碼編譯生成優化後的機器碼,可以大幅提高代碼執行效率。
對於Numba的學習,紐約大學提供瞭一套入門級別的視頻,代碼簡單,紐約大學Numba快速學習,如果想要瀏覽中文文章歡迎繼續往下看!
關於安裝
首先是安裝numba,根據python環境,運行不同的安裝命令:
conda install numba pip install numba
四、關於使用
一句話總結:使用Numba最簡單的方式就是在函數定義前加@jit 或 @njit的裝飾即可。
Numba通過在函數定義前加decorator(修飾符)來申明是否進行加速。如上文所說,最簡單的使用方法是@jit。對於Numba的@jit有兩種編譯模式:nopython和object模式。
nopython模式會完全編譯這個被修飾的函數,函數的運行與Python解釋器完全無關,不會調用Python的C語言API。如果想獲得最佳性能,推薦使用此種模式。同時由於@jit(nopython=True)太常用瞭,Numba提供瞭@njit修飾符,和這句話等價,方便使用。但這種模式要求函數中所有變量的類型都可以被編譯器推導(一些基本類型,如不能是一些庫或自己定義的數據類型等),否則就會報錯。
object模式中編譯器會自動識別函數中循環語句等可以編譯加速的代碼部分,並編譯成機器碼,對於剩下不能識別的部分交給Python解釋器運行。如果想獲取最佳性能,避免使用這種方法(For best performance avoid using this mode!)。
如果沒設置參數nopython=True,Numba首先會嘗試使用nopython模式,如果因為某些原因無法使用,則會使用object模式。加瞭nopython後則會強制編譯器使用nopython模式,但如果代碼出現瞭不能自動推導的類型,有報錯的風險。
五、實驗提升
from numba import jit import random, time def monte_carlo_pi(sam): account = 0 for i in range(sam): x = random.random() y = random.random() if (x ** 2 + y ** 2) < 1.0: account += 1 return 4.0 * account / sam @jit def jit_monte_carlo_pi(sam): account = 0 for i in range(sam): x = random.random() y = random.random() if (x ** 2 + y ** 2) < 1.0: account += 1 return 4.0 * account / sam loops = [100000, 1000000, 10000000, 100000000, 1000000000] for loop in loops: startTime = time.time() monte_carlo_pi(loop) t = time.time() - startTime print('python {} loop: {}'.format(loop, t)) startTime = time.time() jit_monte_carlo_pi(loop) t = time.time() - startTime print('numba {} loop: {}'.format(loop, t))
對於以上代碼,運行的結果是:
python 100000 loop: 0.0469999313354
numba 100000 loop: 0.213999986649
python 1000000 loop: 0.478999853134
numba 1000000 loop: 0.0110001564026
python 10000000 loop: 4.82499980927
numba 10000000 loop: 0.107000112534
python 100000000 loop: 48.728000164
numba 100000000 loop: 1.05900001526
python 1000000000 loop: 489.142100134
numba 1000000000 loop: 11.01402001452
可以看到,jit編譯後有約47倍的提升。循環次數越多,numba的加速效果就越明顯。對於更復雜的計算函數,numba可能會有更好的效果。
到此這篇關於隻需要這一行代碼就能讓python計算速度提高十倍的文章就介紹到這瞭,更多相關提高python計算速度內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- python中的Numpy二維數組遍歷與二維數組切片後遍歷效率比較
- 利用Numba與Cython結合提升python運行效率詳解
- numba提升python運行速度的實例方法
- 源碼解析python中randint函數的效率缺陷
- python實現蒙特卡羅模擬法的實踐