摘要:最近coding時用到了Python裝飾器,它的作用太強大了,而且使用也簡單,解決了我代碼中大量重複計算的瓶頸,下面以計算Fibonacci數列爲例來說明問題:
C語言版:
#include <stdio.h>
//fib.c
int fib(int n)
{
if(n < 3)
{
return 1;
}
else
{
return fib(n-1) + fib(n-2);
}
}
int main(int argc, char* argv[])
{
if(argc == 2)
printf("%d\n", fib(atoi(argv[1])));
else
printf("Please enter a parameter\n");
}
編譯:gcc -O3 -o fib fib.c
運行:
用時3.28s。
來看下純Python版的:
#!/usr/bin/env python
#fib.py
import sys
def fib(n):
if n < 2:
return n
else:
return fib(n-2) + fib(n-1)
if len(sys.argv) == 2:
print fib(int(sys.argv[1]))
else:
print "Please enter a parameter"
運行:
用時40.19s,本來是想算45的,無奈遲遲不出結果。
和上面的C版用時相差幾百倍多,因爲此處計算的是40,當數據越大,則fib的調用呈指數級上升,所以此處預估爲幾百倍不算誇張,下面會分析此處fib重複調用次數。從這裏可以看出Python的確不適合做這種大量重複計算,所以我們就想到了用C/C++來擴展Python,提高計算速度,用Python使用bitey調用C模塊中的方法來重寫。
Python+ctypes版的:
#!/usr/bin/env python
#fib_ctypes.py
import sys
import ctypes
lib = ctypes.CDLL("./fib.so")
if len(sys.argv) == 2:
print lib.fib(int(sys.argv[1]))
else:
print "Please enter a parameter"
運行:
用時9.88s,明顯比純Python版快,但還是和純C版的相差三倍左右。
再來看下Python+bitey版的:
#!/usr/bin/env python
#fib_bitey.py
import sys
import bitey
import fib
if len(sys.argv) == 2:
print fib.fib(int(sys.argv[1]))
else:
print "Please enter a parameter"
運行:
用時6.58s,可以看出比ctypes版的要好點,但比純C版還是要差些,不過已經很不錯了,應該是clang+llvm的功勞。
現在回過頭來分析下純Python版的爲什麼會這麼慢,運行:
發現對fib調用了331160281次,不慢都不正常了,基於Fibonacci數列的特點,我們可以想辦法記錄上一次計算後的結果,比如計算f(10),可以把計算過的f(9)和f(8)的結果直接返回,而不用遞歸下去。剛好Python裝飾器可以用來記錄上次計算的結果,關於Python裝飾器的使用請看Python裝飾器與面向切面編程。
Python裝飾器版:
#!/usr/bin/env python
#fib_cache.py
import sys
def cache(fib):
temp = {}
def _cache(n):
if n not in temp:
temp[n] = fib(n)
return temp[n]
return _cache
@cache
def fib(n):
if n < 2:
return n
else:
return fib(n-2) + fib(n-1)
if len(sys.argv) == 2:
print fib(int(sys.argv[1]))
else:
print "Please enter a parameter"
運行:
用時0.02s,比純C版的都快150多倍!這就是經過裝飾器處理後的效果,其實C也能做到緩存結果,但比起Python這種簡單明瞭的方式來說它太複雜了。
總結:在提升python的處理速度時,我們可以通過boost/ctypes/bitey調用c/c++寫的模塊,也可以用cython來編譯,但最終利用語言自身的特性來優化算法纔是王道。