你是不是曾经有这样的苦恼,python 真的太好用了,但是它真的好慢啊(哭死) ; C++ 很快,但是真的好难写啊,此生能不碰它就不碰它。老天啊,有没有什么两全其美的办法呢?俗话说的好:办法总是比困难多,大家都有这个问题,自然也就有大佬来试着解决这个问题,这就请出我们今天的主角: numba
每次一个Python小技巧!
不过在介绍 numba 之前,我们还是得来看看 python 为什么这么慢:
为什么 python 这么慢
用过 python 的人都知道, 尤其是在有循环的情况下,python 会比 C++ 慢很多,所以很多人都避免在 python 代码里引入复杂的 for 循环。我们可以想想 python 和 C++ 写起来有哪些区别呢:
动态变量
如果你写过 C/C++ 就会发现,我们需要对变量类型有严格的定义,我们需要定义变量的类型是 int 或者 float 之类的。但是 python 就不一样了,写过的 python 的人都知道,它去掉了变量申明和数据类型。也就是说,无论啥数据,咱啥都不用管,想存就存!那么 python 是如何做到这样洒脱自由的呢?这就不得不提 python 中万物皆是对象了,真正的数据是存在对象里面的。对于一个简单的两个变量的加法,python 每次在做运算的时候都得先判断变量的类型,再取出来进行运算,而对于 C 来说,简单的内存读写和机器指令 ADD 即可。其实在 C/C++ 中也有可变数据类型,但是其声明是非常复杂的,是一种非常令人头疼的结构。
解释性语言
C/C++ 这类编译性语言最大的好处就是其编译过程是发生在运行之前的,源代码在调用前被编译器转换为可执行机器码,这样就节约了大量的时间。而 python 作为一种解释性语言,没法做到一次编译,后续可以直接运行,每次运行的时候都要重新将源代码通过解释器转化为机器码。这样一个好处就是非常容易 debug( 这里要再次感叹一下 python 真不愧是新手友好型语言~), 当然,这个问题自然也是有尝试解决的办法,一个很重要的技术就是 JIT (Just-in-time compilation):JIT 即时编译技术是在运行时(runtime)将调用的函数或程序段编译成机器码载入内存,以加快程序的执行。说白了,就是在第一遍执行一段代码前,先执行编译动作,然后执行编译后的代码。
上面只是简单列出了两点,当然还有更多的原因,限于篇幅就不再具体介绍,而我们开篇提到的 numba 就是通过 JIT 加速了 python 代码。那么怎么使用 numba 加速我们的代码呢?我们可以看一些简单的例子:
numba 加速 python 的小例子
用 numba 加速 python 代码多简单方便呢,我们先来看看如何使用 numba 加速 python 代码:
如果让你用单纯的 python 计算一个矩阵所有元素的和,很容易可以写出下面的代码:
def cal_sum(a):
result = 0
for i in range(a.shape[0]):
for j in range(a.shape[1]):
result += a[i, j]
return result
当需要计算的矩阵很小的时候,貌似速度也不慢,可以接受,但是如果输入的矩阵大小为 (500, 500),
a = np.random.random((500, 500))
%timeit cal_sum(a)
输出结果为:
47.8 ms ± 499 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
我们尝试加上 numba:
import numba
@numba.jit(nopython=True)
def cal_sum(a):
result = 0
for i in range(a.shape[0]):
for j in range(a.shape[1]):
result += a[i, j]
return result
输入同样大小的矩阵
a = np.random.random((500, 500))
%timeit cal_sum(a)
输出结果为:
236 µs ± 545 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
注意在这里我们使用了 %itemit 测试运行时间(原因我们留到后面说),通过对比两个时间,我们可以发现通过 numba 获得了非常明显的加速效果!