numba最新官方文档(待续)
- 2021 年 2 月 20 日
- AI
//numba.readthedocs.io/en/stable/user/troubleshoot.html
1、numba的安装问题,默认用pip安装就行,但是numba的并行功能如果要使用intel高性能的tbb后端则需要对tbb进行配置,
win10可见上,比较麻烦,numba的官网里提到tbb和openmp在不同的条件下表现各有优劣,嫌麻烦直接pip install 就行;
各种组件安装可见:
Installation – Numba 0.52.0-py3.7-linux-x86_64.egg documentation
使用默认的pip安装基本够用了
2、numba的noPython模式:
Numba的用法很简单,目标函数加一个装饰器就可以了,@jit
装饰器从根本上以两种编译模式( nopython
模式和object
模式)运行,基本上要选择nopython的模式,否则完全没法加速,nopython
编译模式实质上是编译装饰后的函数,以便其完全运行而无需Python解释器的参与,这是使用Numbajit
装饰器的推荐和最佳实践方法,因为它可以带来最佳性能。
如果在nopython
模式下编译失败,则Numba会提示你用object模式0,在这种模式下,Numba将识别可以编译的循环并将其编译为在机器代码中运行的函数,并在解释器中运行其余代码。为了获得最佳性能,请避免使用此模式!
3、原理性部分不解释了,之前的文章写得够清楚了:
马东什么:为什么python这么慢?numba解决了什么问题?
然后统一介绍一下,numba中的各种装饰器
1、jit
from numba import jit
@jit
def f(x, y):
# A somewhat trivial example
return x + y
from numba import jit, int32
@jit(int32(int32, int32))
def f(x, y):
# A somewhat trivial example
return x + y
numba支持像cython一样定义函数输入的变量的数据类型,但是这并不会起到加速的作用,而在cython中,指定输入的变量的数据类型可以大大加速程序运行的速度。
@jit
def square(x):
return x ** 2
@jit
def hypot(x, y):
return math.sqrt(square(x) + square(y))
不同的numba函数可以互相调用。
@jit
装饰器可以使用多种数据类型:
void
是什么都不返回的返回类型(实际上是None
从Python调用时返回的)intp
和uintp
是指针大小的整数(分别为有符号和无符号)intc和uintc
等效于Cint
和 整数类型unsigned
int
int8
,uint8
,int16
,uint16
,int32
,uint32
,int64
,uint64
相应的位宽度的被固定宽度的整数(符号和无符号)float32
和float64
分别是单精度和双精度浮点数complex64
和complex128
分别是单精度和双精度复数- 可以通过索引任何数字类型来指定数组类型,例如
float32[:]
用于一维单精度数组或int8[:,:]
用于8位整数的二维数组,依次类推
然后介绍jit的一些重要配置:
@jit(nopython=True)
def f(x, y):
return x + y
@jit(nogil=True)
def f(x, y):
return x + y
@jit(cache=True)
def f(x, y):
return x + y
@jit(nopython=True, parallel=True)
def f(x, y):
return x + y
Numba有两种编译模式:nopython模式和object模式。前者产生的代码的运行速度要快得多,但是有一些局限性,可能迫使Numba退回到后者。为防止Numba退回,而是引发错误,请使用nopython=True
。
每当Numba将Python代码优化为仅适用于本机类型和变量(而不是Python对象)的本机代码时,就不再需要保持Python的全局解释器锁(GIL)。通过nogil=True,Numba会释放GIL。
在发布了GIL的情况下运行的代码与其他执行Python或Numba代码的线程(相同的编译函数或另一个)并行运行,从而使您能够利用多核系统。如果函数是在object模式下编译的,则这是不可能的。
使用时nogil=True
,您必须警惕多线程编程的常见陷阱(一致性,同步,竞争条件等)。
为了避免每次调用Python程序时都要进行编译,可以指示Numba将函数编译的结果写入基于文件的缓存中。这可以通过传递cache=True
来实现;
为已知具有并行语义的函数中的那些操作启用自动并行化(和相关的优化)。有关支持的操作的列表,请参见使用@jit自动并行化。此功能通过传递启用,parallel=True
并且必须与nopython=True
一起使用:
更加灵活的generated_git
虽然jit()
装饰是很多情况下是有用的,但是有时你想实现取决于输入类型有不同的返回的功能。那么可以使用generated_jit()
,它装饰允许用户控制在编译时的特化的选择,同时完全保留了JIT功能的运行时执行速度。
假设您想编写一个函数,该函数根据某些约定返回给定值是否为“缺失”值。为了便于说明,我们采用以下定义:
- 对于浮点参数,缺少的值是
NaN
- 对于Numpy datetime64和timedelta64参数,缺少的值是
NaT
- 其他类型没有缺失值的概念。
使用generated_jit()
装饰器可以轻松实现该编译时逻辑 :
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: False
这里有几件事要注意:
- 装饰函数将使用 参数的Numba类型而不是其值进行调用。
- 装饰的函数实际上并不计算结果,它返回一个可调用的函数,用于实现给定类型的函数的实际定义。
- 可以在编译时预先计算一些数据(
missing
上面的变量),以使它们在已编译的实现中重用。 - 函数定义使用与修饰后的函数中的参数相同的名称,这是确保通过名称传递参数按预期工作所必需的。
generated_jit的参数和jit完全一样;
在介绍numba的ufunc函数之前,先介绍numpy的ufunc函数,实际上我们平常使用的向量计算大都是一种内置的numpy的ufunc函数,ufunc是numpy速度如此迅速的一个重要原因,即向量化计算:
Gemfield:为什么向量化计算(vectorization)会这么快?
简单来说,向量化计算就是将多次循环的计算转化为一次矩阵的运算,例如矩阵的加法,简单实现循环两个矩阵每一行的数据进行相加,但是从矩阵的角度,进行一次计算即可,具体的例子看看这几篇文章的demo就可以了:
numpy的ufunc_pyStar_公众号的博客-CSDN博客NumPy-ufunc 函数_烟雨-CSDN博客
NumPy ufunc通用函数
当然,这里我们要提的是自定义的ufunc,即通过编写python代码,并且通过一些方式将代码逻辑转化为可以实现向量化计算的函数。
例如这里的例子:
Numpy:自定义ufunc函数_bqw的博客-CSDN博客_numpy 自定义函数
# triangle_wave()是一个将x值转换成三角波上对应的y值
def triangle_wave(x,c,c0,hc):
x = x - int(x)
if x >= c: r =0.0
elif x< c0: r = x/c0*hc
else: r = (c-x)/(c-c0)*hc
return r
在numpy中我们可以使用:
triangle_ufunc1 = np.frompyfunc(triangle_wave,4,1)
y2 = triangle_ufunc1(x,0.6,0.4,1.0)
4和1分别表示函数的输入参数的个数和函数的输出结果的个数。
除此之外还可以使用vectorize:
triangle_ufunc2 = np.vectorize(triangle_wave,otypes=[np.float])
y3 = triangle_ufunc2(x,0.6,0.4,1.0)
print(y3.dtype)
二者的区别在于后者可以显式地定义输入和输出的数据的类型
numpy.vectorize – NumPy v1.21.dev0 Manual
简单来说,numpy的向量化函数和pandas中的map是非常相似的。
numba也存在一样的功能:
在标量上运行的函数使用@vectorize
;
在高维数组和标量上运行的函数使用@guvectorize
@vectorize
Numba的向量化功能可将采用标量输入参数的Python函数用作NumPy 的ufuncs。创建传统的NumPy ufunc不是最简单的过程,它涉及编写一些C代码。Numba可以轻松做到这一点。使用vectorize()
装饰器,Numba可以将纯Python函数编译为ufunc,该ufunc以与用C编写的传统ufunc一样快的方式在NumPy数组上运行。
使用vectorize()
,您可以将函数编写为对输入标量(而不是数组)进行操作。Numba将生成周围的循环(或内核),从而允许对实际输入进行有效的迭代。
该vectorize()
装饰有两种操作模式:
- 急切地编译:如果将一个或多个类型签名传递给装饰器,则将构建Numpy通用函数(ufunc)。
- 延迟或调用时编译:如果未提供任何签名,则装饰器将为您提供Numba动态通用函数(
DUFunc
),当使用以前不受支持的输入类型进行调用时,该函数可以动态编译新内核。
例如
from numba import vectorize, float64
@vectorize([float64(float64, float64)])
def f(x, y):
return x + y
@vectorize([int32(int32, int32),
int64(int64, int64),
float32(float32, float32),
float64(float64, float64)])
def f(x, y):
return x + y
可以针对不同地数据类型实现不同地返回,括号里表示参数的数据类型,括号外表示返回值的数据类型
除此,我们自定义的ufun具有一些高级的内置方法:
>>> a = np.arange(12).reshape(3, 4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> f.reduce(a, axis=0)
array([12, 15, 18, 21])
>>> f.reduce(a, axis=1)
array([ 6, 22, 38])
>>> f.accumulate(a)
array([[ 0, 1, 2, 3],
[ 4, 6, 8, 10],
[12, 15, 18, 21]])
>>> f.accumulate(a, axis=1)
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
ufunc和jit一样支持不同的硬件调用:

一般准则是针对不同的数据大小和算法选择不同的目标。“ cpu”目标适用于较小的数据大小(大约小于1KB)和低计算强度算法。它的通信开销最少。“并行”目标适用于中等数据大小(约小于1MB),因为线程调用增加了一定的时间开销。“ cuda”目标适用于大数据量(大约大于1MB)和高计算强度算法,因为往返于GPU的内存转移会增加大量通信开销。
@guvectoriz
虽然vectorize()
允许您编写一次在一个元素上工作的ufunc,但是guvectorize()
装饰器使概念更进一步,并允许您编写将在任意数量的输入数组的元素上工作的ufunc,并采用和返回不同维数的数组。典型示例是运行中值或卷积滤波器。
与vectorize()
函数相反,guvectorize()
函数不需要使用return返回其结果值:它们将其作为数组参数,必须由函数填充。这是因为该数组实际上是由NumPy的调度机制分配的,该机制调用Numba生成的代码。
@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)')
def g(x, y, res):
for i in range(x.shape[0]):
res[i] = x[i] + y
底层的Python函数只是将给定的标量(y
)添加到一维数组的所有元素中。更有趣的是声明。有两件事:
- 输入和输出形式的声明,以符号形式:
(n),()->(n)
告诉NumPy,该函数采用n元一维数组,标量(用空元组符号表示()
)并返回n元一维数组; - 支持的具体类型按
@vectorize
; 在此,如上面的示例所示,我们演示了int64
数组。
现在,我们可以通过一个简单的示例检查已编译的ufunc的功能:
>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> g(a, 2)
array([2, 3, 4, 5, 6])
令人高兴的是,NumPy将根据其形状自动分派更复杂的输入:
>>> a = np.arange(6).reshape(2, 3)
>>> a
array([[0, 1, 2],
[3, 4, 5]])
>>> g(a, 10)
array([[10, 11, 12],
[13, 14, 15]])
>>> g(a, np.array([10, 20]))
array([[10, 11, 12],
[23, 24, 25]])
牛逼这功能。
vectorize()
和guvectorize()支持通过nopython=True作为@jit装饰。使用它来确保生成的代码不会退回到object模式。
性能技巧:
jit(nopython=True)=njit性能>>jit(nopython=False)