背景
加速实践原本只分了三篇的(算法篇、工程代码篇,硬件篇),但是写的过程中觉得有必要要介绍一下神库numba,说实话当年第一次使用的时候,真有一种想见恨晚的感觉,后来工作中用它为公司写了回测系统,速度能 达到原本C++系统的100倍以上(是的你没看错我也没写错,就是C++;除了jit外这主要还得益于矩阵运算和并行计算的使用,另外C++系统是稳定跑了3年多的系统,所以baseline不低;当然C++花时间认真写的话可以达到甚至超过这个效率,但是python当时只花了两周左右的时间,这性价比绝对是我目前做过的项目里最高的之一)
JIT是什么
jit 的全称是 Just-in-time,在 numba 里面则特指 Just-in-time compilation(即时编译),它是一种编译技术,下面的对比即可对jit进行清晰的定位编译方式动态编译(dynamic compilation):指的是“在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)
JIT编译(just-in-time compilation)狭义来说是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。JIT编译是动态编译的一种特例。JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别
自适应动态编译(adaptive dynamic compilation)也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序“以某种式”先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化。
注意事项但是jit技术并不总是能够如预期的加速代码,甚至有可能降低代码效率,这于代码的结构有关,不过绝大多数情况还是能够有明显的效果的
JIT之于PythonPython啥都好,就是太“动态”了,导致去运行效率不高,jit之于python那简直是如虎添翼
Numbahttps://numba.pydata.org/
首页第一句话便是:Numba是一个开源的JIT编译器,它可以将Python和NumPy代码的子集转换为高效的机器码。numba使用LLVM编译器架构将纯Python代码生成优化过的机器码,将面向数组和使用大量数学的python代码优化到与c,c++和Fortran类似的性能,而无需改变Python的解释器。入门:@numba.jit
import jit
@numba.jit
def add(x,y):
return x + y
上面这段代码是numba.jit的简单应用,在函数第一次执行的时候,numba推断出参数类型,然后基于这个信息生产优化后的代码进阶1:指定签名
import numba
@numba.jit(int32(int32, int32))
def add_signatured(x,y):
return x+y编译器将控制类型选择,并不允许其他特性(即其他类型的参数输入,如float),这回带来速度上的优势类型检查速度优势
签名规范
显式的@jit签名可以使用许多类型。以下是一些常见的例子:void是什么都不返回的函数的返回类型(即:Python调用时实际上返回None)
intp和uintp是指针大小的整数(分别是有符号和无符号的)
intc和uintc等价于C整型和无符号整型
int8, uint8, int16, uint16, int32, int32, int64, uint64是对应位宽的固定宽度整数(有符号和无符号)
float32和float64分别是单精度浮点数和双精度浮点数
complex64和complex128分别是单精度复数和双精度复数
数组类型可以通过索引任意数值类型来指定,例如:用于一维单精度数组的float32[:],用于8位整数的二维数组的int8[:,:]
编译选项:nopython vs object
nopython和object是numba的两种编译模式,前者编译的代码更快,但是可能会因为某些限制但是退化为object, 通过nopython=True可以阻止退化并抛出异常进阶2:编译模式
# 官方例子
import numba
import random
@numba.jit(nopython=True)
def monte_carlo_pi(nsamples):
acc = 0
for i in range(nsamples):
x = random.random()
y = random.random()
if (x ** 2 + y ** 2) < 1.0:
acc += 1
return 4.0 * acc / nsamples
其它选项:nogil: 如果设置为True的话将在进入函数之后释放gil锁,这样可以利用多核算力
cache:将编译结果保存到一个基于文件的缓存中
parallel: 将函数中的操作自动并行化(慎用,搞不好的话可能更慢,例如代码里只是简单的for循环的话开启的效果就会比较明显)
generated_jit:允许用户在编译期控制不同的特性的选择,下面是参考资料2给的例子
import numpy as np
from numba import generated_jit, types
@generated_jit(nopython=True)
def is_missing(x):
"""Return True if the value is missing, False otherwise."""
if isinstance(x, types.Float):
return lambda x: np.isnan(x)
elif isinstance(x, (types.NPDatetime, types.NPTimedelta)):
# The corresponding Not-a-Time value
missing = x('NaT')
return lambda x: x == missing
else:
return lambda x: Falsegenerated_jit有以下几点需要注意:调用装饰器函数是使用Numba的类型作为参数,而不是他们的值。
装饰器函数并不真的计算结果,而是返回一个对于给定类型,可调用的实际定义的函数执行。
可以在编译期预先计算一些数据,使其在编译后执行过程中重用。
函数定义使用和装饰器函数中相同名字的参数,这将确保通过名字传递参数能够如期望的工作。
Vectorize
Numba的vectorize允许Python函数将标量输入参数作为Numpy的ufunc使用,将纯Python函数编译成ufunc,使之速度与使用c编写的传统的ufunc函数一样。
关于Vectorize和其它高级特性,参考资料2已经做了详细的介绍,这里就不再赘述了,后续用到的话,再回来这里补充更多的实例促进理解# 加速python运行-numbawww.jianshu.com
参考资料