我们先看一个原始代码:
import time
def foo(x, y):
tt = time.time()
s = 0
for i in range(x, y):
s *= i
print('time:{}'.format(time.time() - tt))
return s
print(foo(1,100000000))
看一下运算时间结果:
time:8.983859300613403
然后我们加一行代码,再看看结果:
import numba as nu
import time
@nu.jit()
def foo(x, y):
tt = time.time()
s = 0
for i in range(x, y):
s *= i
print('time:{}'.format(time.time() - tt))
return s
print(foo(1,100000000))
看一下运算时间结果:
time:0.16590428352355957
咦~好像快了不止100倍啊
我丢,厉害了我的一行代码
下面来讲解一下神奇的 numba库
如何使用numba
import numba as nb
只用1行代码即可加速,对loop有奇效
这回肯定又有人问了神马是loop,这里我讲解一下: loop就是循环的意思
然后numba 的优势就是 loop,对循环(loop)有奇效,而往往在科学计算中限制python速度的就是loop。
但要注意的是,numba对没有循环或者只有非常小循环的函数加速效果并不明显,用不用都一样。(偷偷告诉你,numba的loop甚至常常比numpy的矩阵运算还要快)
重点:
numba可以把各种具有很大loop的函数加到很快的速度,但numpy的加速只适用于numpy自带的函数。
示例:
import numba as nb
import numpy as np
import time
# 用numba加速的求和函数
@nb.jit()
def nb_foo(x, y):
tt = time.time()
s = 0
for i in range(x, y):
s += i
print('time:{}'.format(time.time() - tt))
return s
print(nb_foo(1,100000000))
# 没用numba加速的求和函数
def py_foo(x, y):
tt = time.time()
s = 0
for i in range(x, y):
s += i
print('time:{}'.format(time.time() - tt))
return s
print(py_foo(1,100000000))
a = list(range(100000000)) # 创建一个长度为100000000的数组
start = time.time()
np.sum(a) # numpy自带的求和函数
end = time.time()
print(end-start)
start = time.time()
sum(a) # python自带的求和函数
end = time.time()
print(end-start)
看一下输出结果:
time:0.06898283958435059 # numba加速的求和函数
4999999950000000
time:10.004251480102539 # 没加速的求和函数
4999999950000000
9.993287086486816 #numpy自带的求和函数
4.011701583862305 # python自带的求和函数
可以看出,numba甚至比号称最接近C语言速度运行的numpy还要快6倍以上。
在这里我还想说numba另一个强大的功能,矢量(vectorize)。像上面的jit一样,只要添加一行vectorize就可以让普通函数变成ufunc
from math import sin
@nb.vectorize()
def nb_vec_sin(a):
return sin(a)
用vectorize改写的三角函数具有了和np.sin()一样的同时处理数组每个元素的功能,而且表现也不必numpy差。当遇到numpy没有的数学函数时(比如sech),用numba矢量化不失为一个好的选择。除了math中的sin,它支持的其他函数列表可以在documentation中找到(链接见附录)。
其实numpy也有矢量化的功能,只不过比numba差远了。
更多numba的加速选项
除了上面提到的jit和vectorize,其实numba还支持很多加速类型。常见的比如
@nb.jit(nopython=True,fastmath=True) 牺牲一丢丢数学精度来提高速度
@nb.jit(nopython=True,parallel=True) 自动进行并行计算
还有一个:
如果希望JIT能针对所有类型的参数进行运算,可以使用autojit:
import numba as nb
import time
@nb.autojit()
def nb_sum(a):
start = time.time()
Sum = 0
for i in range(len(a)):
Sum += a[i]
end = time.time()
return end-start
autoit虽然可以根据参数类型动态地产生机器码函数,但是由于它需要每次检查参数类型,因此计算速度也有所降低。numba的用法很简单,基本上就是用jit和autojit这两个修饰器,和一些类型对象。下面的程序列出numba所支持的所有类型:
print [obj for obj in nb.__dict__.values() if isinstance(obj, nb.minivect.minitypes.Type)]
[size_t, Py_uintptr_t, uint16, complex128, float, complex256, void, int , long double,
unsigned PY_LONG_LONG, uint32, complex256, complex64, object_, npy_intp, const char *,
double, unsigned short, float, object_, float, uint64, uint32, uint8, complex128, uint16,
int, int , uint8, complex64, int8, uint64, double, long double, int32, double, long double,
char, long, unsigned char, PY_LONG_LONG, int64, int16, unsigned long, int8, int16, int32,
unsigned int, short, int64, Py_ssize_t]
Numba的精度问题
精度方面,在上面我也谈到numba会自动转换数据类型以适应计算。但是在个别时候,这种自动转变类型可能会引起一些计算误差。通常这个误差是非常小的,几乎不会造成任何影响。但如果你所处理的问题会积累误差,比如求解非线性方程,那么在非常多的计算之后误差可能就是肉眼可见了。如果你发现有这样的问题,记得在jit中指定输入输出的数据类型。
numba具有C所有的数据类型,比如对上面的求和函数,只需要把@nb.jit()改为@nb.jit(nb.int64(nb.int32[:]))即可。nb.int64是说输出的数字为int64类型,nb.int32是说输入的数据类型为int32,而[:]是说输入的是数组。
参考文章:
https://zhuanlan.zhihu.com/p/60994299
https://blog.csdn.net/qq_39241986/article/details/102486661