摘要:最近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
来看下纯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"运行:
和上面的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来编译,但最终利用语言自身的特性来优化算法才是王道。