這篇文章簡(jiǎn)介一下python、cython、以及numba庫(kù)在計(jì)算上的效率對(duì)比
Basel problem(巴塞爾問(wèn)題)
這里用巴塞爾問(wèn)題作為引例,用以上代碼方式計(jì)算pi值,通過(guò)cProfile庫(kù)比較計(jì)算效率。
三種編譯方式
1、python
# calc_pi.py
def recip_square(i):
return 1. / i ** 2
def approx_pi(n=10000000):
val = 0
for k in range(1, n + 1):
val += recip_square(k)
print((6 * val) ** .5)
# profile_cal.py
import cProfile
import pstats
from tutorial.calc_pi import approx_pi
cProfile.runctx("approx_pi()", globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()
2、cython
# calc_pi_cy.pyx
# cython: profile=True
# cython: language_level=2
cimport cython
@cython.profile(False)
cdef inline double recip_square(int i):
return 1. / (i ** 2)
def approx_pi(int n=10000000):
cdef double val = 0
cdef int k
for k in range(1, n + 1):
val += recip_square(k)
print((6 * val) ** .5)
# profile_cal_cy.py
import cProfile
import pstats
import pyximport
pyximport.install()
from tutorial.calc_pi_cy import approx_pi
cProfile.runctx("approx_pi()", globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()
3、jit【jit的編譯需要安裝numba?官網(wǎng)】
# calc_pi_jit.py
import numba
@numba.jit(nopython=True)
def recip_square(i):
return 1. / i ** 2
@numba.jit(nopython=True)
def approx_pi(n=10000000):
val = 0
for k in range(1, n + 1):
val += recip_square(k)
print((6 * val) ** .5)
# profile_cal_jit.py
import cProfile
import pstats
from tutorial.calc_pi_jit import approx_pi
cProfile.runctx("approx_pi()", globals(), locals(), "Profile.prof")
s = pstats.Stats("Profile.prof")
s.strip_dirs().sort_stats("time").print_stats()
接下來(lái)分別使得n為不同的數(shù)值來(lái)看執(zhí)行時(shí)間的差距。
1、n=10000000
# python
3.1415925580959025
Sun May 10 13:45:40 2020 Profile.prof
10000005 function calls in 4.701 seconds
# cython
3.1415925580959025
Sun May 10 13:48:25 2020 Profile.prof
5 function calls in 0.011 seconds
# jit
3.1415925580959025
Sun May 10 13:49:02 2020 Profile.prof
519799 function calls (490055 primitive calls) in 0.428 seconds
分析:可以看到在n為一千萬(wàn)(1000 0000)時(shí),python方式比cython慢了470多倍,比jit方式慢了10倍左右;
而jit比cython又慢了40倍左右,不過(guò)jit和cython的對(duì)比在小數(shù)據(jù)方面表現(xiàn)的差異并不是很大,只是略大,numba的jit編譯方式在計(jì)算次數(shù)越多表現(xiàn)出的優(yōu)勢(shì)越明顯,看下面。
2、n=100000000
# python
3.14159264498239
Sun May 10 13:57:28 2020 Profile.prof
100000005 function calls in 49.787 seconds
# cython
3.14159264498239
Sun May 10 13:56:53 2020 Profile.prof
5 function calls in 0.106 seconds
# jit
3.14159264498239
Sun May 10 13:57:34 2020 Profile.prof
519983 function calls (490217 primitive calls) in 0.501 seconds
分析:可以看到在n為一億(1 0000 0000)時(shí),python耗時(shí)近50s,是cython的500倍左右,jit的100倍左右;
而jit比cython慢了5倍左右,這與上面的那種方式對(duì)比看來(lái),與cython之間縮減了將近8倍的時(shí)間。
jit相較于之前1000 0000次計(jì)算,雖然這次增加了10倍計(jì)算量,python與cython都明顯也是成10倍的耗時(shí)增加,但jit卻只是增加了0.08s而已,相較于之前只是增加了17%左右的耗時(shí)。
看起來(lái)好像jit的這種方式優(yōu)勢(shì)凸顯了。
3、n=1000000000
# python
3.14159264498239
Sun May 10 14:08:18 2020 Profile.prof
1000000005 function calls in 485.110 seconds
# cython
3.14159264498239
Sun May 10 14:11:16 2020 Profile.prof
5 function calls in 1.059 seconds
# jit
3.14159264498239
Sun May 10 14:10:38 2020 Profile.prof
519965 function calls (490199 primitive calls) in 1.402 seconds
分析:可以看到本次n為十億(10 0000 0000)時(shí),python耗時(shí)485s,是上一次一億次元算的10倍關(guān)系,是cython(cython本身也在成10倍增加計(jì)算時(shí)間)的485倍左右,是jit的345倍左右;
而jit愈來(lái)愈趨近于cython的執(zhí)行效率,是cython的1.32倍左右。
4、n=2000000000
# cython
3.14159264498239
Sun May 10 14:43:43 2020 Profile.prof
5 function calls in 2.080 seconds
# jit
3.14159264498239
Sun May 10 14:44:32 2020 Profile.prof
519904 function calls (490148 primitive calls) in 2.502 seconds
分析:鑒于python是單線程,且隨著數(shù)據(jù)成倍增長(zhǎng),這次n設(shè)置為二十億(20 0000 0000)次,只比較cython和jit的運(yùn)行時(shí)間差,但是二者差別在毫秒級(jí)別,并不是很大。
cython相較于前一次增加1s的時(shí)間,jit也基本同樣增加了1s的時(shí)間。
總結(jié)
可以看出在進(jìn)行cpu密集型運(yùn)算時(shí),原始python(即默認(rèn)cpython解釋器)的計(jì)算效率很低,原因在于GIL的限制,使得只能使用單線程。
cython會(huì)把相應(yīng)的cython書(shū)寫(xiě)的代碼編譯為c語(yǔ)言,這就大大提高了執(zhí)行效率。
而numba是使用了jit(Just-in-time)即時(shí)編譯器,他會(huì)在程序運(yùn)行期間實(shí)時(shí)編譯,有別于python的默認(rèn)解釋器,所以速度要快很多很多。