本篇文章是学习《Python金融大数据分析》第10章“高性能的Python”的笔记~
下面以计算平均数为例。原始代码为:
import random
def average_py(n):
s=0
for i in range(n):
s+=random.random()
return s/n
若n=10000000,大致运行2s。
numpy的优势在于其向量化能力。从形式上看,Python级别的循环消失了,循环在更深的一级上。平均数可变为:
import random
import numpy as np
def average_np(n):
s=np.random.random(n)
return s.mean()
若n=10000000,大致运行224ms。不过需要注意的是,虽然速度提升了,内存占用也更多。
Numba可以动态编译纯Python代码,在简单情况下,它的应用非常直观。
import numba
average_nb=numba.jit(average_py)
n=10000000
average_nb(n) #第一次运行大概278ms
average_nb(n) #第二次运行81.7ms,第二次运行会明显变快。
以上运行结果显示,第一次运行较慢,第二次及之后运行速度明显加快。所以对于会重复运行的函数,可以用numba进行编译。(自己总结,非书中观点)
numba对性能的提升效果很明显,但是值得注意的是,许多情况下 Numba 并不适用,性能增进几乎难以察觉,甚至无法实现。
Cython=Python+C, 可以静态编译python代码,但是它的应用不想Numba那么简单,通常需要更改代码才能明显加速,即向python代码里面加入一些C语言元素。由于比较复杂,笔者就没有举例子了,需要更加深入复杂的学习~
但是一般用法是,在代码开头加上
%load_ext Cython
%%cpython
注意:使用之前要先安装Cython第三方包
Python的常规递归函数实现也相对较慢。递归函数需要多次调用自身,Numba加速效果不明显。下面以斐波那契数列为例,介绍几种加速方法。
'''
原始函数
'''
def fib_rec_py1(n):
if n<2:
return n
else:
return fib_rec_py1(n-1)+fib_rec_py1(n-2)
当n=35时,执行时间约为3.74s。若用numba,运行时间大约为3.54s。
'''
cython加速
'''
%%cython
def fib_rec_cy(int n):
if n<2:
return n
else:
return fib_rec_cy(n-1)+fib_rec_cy(n-2)
当n=35时,执行时间约为936ms。
递归算法的主要问题是中间结果不会缓存,而是重新计算。为了避免出现这种特有的问题,可以使用一个装饰器(decorator)来负责缓存中间结果。它可以将执行速度提高好几个数量级。
'''
修饰器
'''
from functools import lru_cache as cache
@cache(maxsize=None)
def fib_rec_py2(n):
if n<2:
return n
else:
return fib_rec_py2(n-1)+fib_rec_py2(n-2)
当n=35时,执行时间约为98us,几乎瞬间出结果。
递归的效率很低,如果可以,尽量不使用递归。比如斐波那契数列可以用以下方式计算:
def fib_it_py(n):
x,y=0,1
for i in range(1,n+1):
x,y=y,x+y
return x
当n=80时,运行时间大约为26us,如果用上Numba,cython,会更加迅速