numba是一个用于编译Python数组和数值计算函数的编译器,这个编译器能够大幅提高直接使用Python编写的函数的运算速度。

numba使用LLVM编译器架构将纯Python代码生成优化过的机器码,通过一些添加简单的注解,将面向数组和使用大量数学的python代码优化到与c,c++和Fortran类似的性能,而无需改变Python的解释器。

Numba的主要特性:

  • 动态代码生成 (在用户偏爱的导入期和运行期)
  • 为CPU(默认)和GPU硬件生成原生的代码
  • 集成Python的科学软件栈(Numpy)

下面是使用Numba优化的函数方法,将Numpy数组作为参数:

import numba
@numba.jit
def sum2d(arr):M, N = arr.shaperesult = 0.0for i in range(M):for j in range(N):result += arr[i,j]return result

如果你对此不是太感兴趣,或者对于其他的加速方案已经很熟悉,可以到此为止,只需要了解加上jit装饰器就可以实现了。

使用jit

使用jit的好处就在于让numba来决定什么时候以及怎么做优化。

from numba import jit@jit
def f(x, y):# A somewhat trivial examplereturn x + y

比如这段代码,计算将延期到第一次函数执行,numba将在调用期间推断参数类型,然后基于这个信息生成优化后的代码。numba也能够基于输入的类型编译生成特定的代码。例如,对于上面的代码,传入整数和复数作为参数将会生成不同的代码:

>>>f(1,2)
3
>>>f(1j,2)
(2+1j)

我们也可以加上所期望的函数签名:

from numba import jit, int32@jit(int32(int32, int32))
def f(x, y):# A somewhat trivial examplereturn x + y

int32(int32, int32) 是函数签名,这样,相应的特性将会被@jit装饰器编译,然后,编译器将控制类型选择,并不允许其他特性(即其他类型的参数输入,如float)

Numba编译的函数可以调用其他编译函数。 函数调用甚至可以在本机代码中内联,具体取决于优化器的启发式。 例如:

@jit
def square(x):return x ** 2@jit
def hypot(x, y):return math.sqrt(square(x) + square(y))

@jit装饰器必须添加到任何库函数,否则numba可能生成速度更慢的代码。

签名规范

Explicit @jit signatures can use a number of types. Here are some common ones:

void is the return type of functions returning nothing (which actually return None when called from Python)
intp and uintp are pointer-sized integers (signed and unsigned, respectively)
intc and uintc are equivalent to C int and unsigned int integer types
int8, uint8, int16, uint16, int32, uint32, int64, uint64 are fixed-width integers of the corresponding bit width (signed and unsigned)
float32 and float64 are single- and double-precision floating-point numbers, respectively
complex64 and complex128 are single- and double-precision complex numbers, respectively
array types can be specified by indexing any numeric type, e.g. float32[:] for a one-dimensional single-precision array or int8[:,:] for a two-dimensional array of 8-bit integers.

编译选项

numba有两种编译模式:nopython模式和object模式。前者能够生成更快的代码,但是有一些限制可能迫使numba退为后者。想要避免退为后者,而且抛出异常,可以传递nopython=True.

@jit(nopython=True)
def f(x, y):return x + y

当Numba不需要保持全局线程锁时,如果用户设定nogil=True,当进入这类编译好的函数时,Numba将会释放全局线程锁。

@jit(nogil=True)
def f(x, y):return x + y

这样可以利用多核系统,但不能使用的函数是在object模式下编译。

想要避免你调用python程序的编译时间,可以这顶numba保存函数编译结果到一个基于文件的缓存中。可以通过传递cache=True实现。

@jit(cache=True)
def f(x, y):return x + y

开启一个实验性质的特性将函数中的这些操作自动并行化。这一特性可以通过传递parallel=True打开,然后必须也要和nopython=True配合起来一起使用。编译器将编译一个版本,并行运行多个原生的线程(没有GIL)

@jit(nopython=True, parallel=True)
def f(x, y):return x + y

generated_jit

有时候想要编写一个函数,基于输入的类型实现不同的实现,generated_jit()装饰器允许用户在编译期控制不同的特性的选择。假定想要编写一个函数,基于某些需求,返回所给定的值是否缺失的类型,具体定义如下:

  • 对于浮点数,缺失的值为NaN。
  • 对于Numpy的datetime64和timedelta64参数,缺失值为NaT
  • 其他类型没有定义的缺失值
import numpy as npfrom 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 valuemissing = x('NaT')return lambda x: x == missingelse:return lambda x: False

有以下几点需要注意:

  1. 调用装饰器函数是使用Numba的类型作为参数,而不是他们的值。
  2. 装饰器函数并不真的计算结果,而是返回一个对于给定类型,可调用的实际定义的函数执行。
  3. 可以在编译期预先计算一些数据,使其在编译后执行过程中重用。
  4. 函数定义使用和装饰器函数中相同名字的参数,这将确保通过名字传递参数能够如期望的工作。

使用@vectorize 装饰器创建Numpy的 universal 函数

Numba的vectorize允许Python函数将标量输入参数作为Numpy的ufunc使用,将纯Python函数编译成ufunc,使之速度与使用c编写的传统的ufunc函数一样。

vectorize()有两种操作模型:

  1. 主动,或者装饰期间编译:如果传递一个或者多个类型签名给装饰器,就将构建Numpy的universal function。后面将介绍使用装饰期间编译ufunc。
  2. 被动(惰性),或者调用期间编译:当没有提供任何签名,装饰器将提供一个Numba动态universal function(DUFunc),当一个未支持的新类型调用时,就动态编译一个新的内核,后面的“动态 universal functions”将详细介绍

如上所描述,如果传递一个签名给vectorizer()装饰器,函数将编译成一个numpy 的ufunc:

from numba import vectorize, float64@vectorize([float64(float64, float64)])
def f(x, y):return x + y

如果想传递多个签名,注意顺序,精度低的在前,高的在后,否则就会出奇怪的问题。例如int32就只能在int64之前。

@vectorize([int32(int32, int32),int64(int64, int64),float32(float32, float32),float64(float64, float64)])
def f(x, y):return x + y

如果给定的类型正确:

>>> a = np.arange(6)
>>> f(a, a)
array([ 0,  2,  4,  6,  8, 10])
>>> a = np.linspace(0, 1, 6)
>>> f(a, a)
array([ 0. ,  0.4,  0.8,  1.2,  1.6,  2. ])

如果提供了不支持的类型:

>>> a = np.linspace(0, 1+1j, 6)
>>> f(a, a)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: ufunc 'ufunc' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''

vectorizer与jit装饰器的差别:numpy的ufunc自动加载其他特性,例如:reduction, accumulation or broadcasting:

>>> 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]])

vectorize() 装饰器支持多个ufunc 目标:

Target Description
cpu Single-threaded CPU
parallel Multi-core CPU
cuda CUDA GPU

guvectorize装饰器只用了进一步的概念,允许用户编写ufuncs操作输入数组中的任意数量的元素,返回不同纬度的数组。典型的应用是运行求均值或者卷积滤波。

Numba支持通过jitclass装饰器实现对于类的代码生成。可以使用这个装饰器来标注优化,类中的所有方法都被编译成nopython function。

import numpy as np
from numba import jitclass          # import the decorator
from numba import int32, float32    # import the typesspec = [('value', int32),               # a simple scalar field('array', float32[:]),          # an array field
]@jitclass(spec)
class Bag(object):def __init__(self, value):self.value = valueself.array = np.zeros(value, dtype=np.float32)@propertydef size(self):return self.array.sizedef increment(self, val):for i in range(self.size):self.array[i] = valreturn self.array

性能建议

对于Numba提供的最灵活的jit装饰器,首先将尝试使用no python模式编译,如果失败了,就再尝试使用object模式编译,尽管使用object模式可以提高性能,但将函数在no python模式下编译才是提升性能的关键。想要直接使用nopython模式,可以直接使用装饰器@njit,这个装饰器与@jit(nopython=True)等价。

@njit
def ident_np(x):return np.cos(x) ** 2 + np.sin(x) ** 2@njit
def ident_loops(x):r = np.empty_like(x)n = len(x)for i in range(n):r[i] = np.cos(x[i]) ** 2 + np.sin(x[i]) ** 2return r
Function Name @njit Execution time
ident_np No 0.581s
ident_np Yes 0.659s
ident_loops No 25.2s
ident_loops Yes 0.670s

有时候不那么严格的规定数据将会带来性能的提升,此时,恶意使用fastmath关键字参数:

@njit(fastmath=False)
def do_sum(A):acc = 0.# without fastmath, this loop must accumulate in strict orderfor x in A:acc += np.sqrt(x)return acc@njit(fastmath=True)
def do_sum_fast(A):acc = 0.# with fastmath, the reduction can be vectorized as floating point# reassociation is permitted.for x in A:acc += np.sqrt(x)return acc
Function Name Execution time
do_sum 35.2 ms
do_sum_fast 17.8 ms

Trubleshooting and tips

想要编译什么?

通常建议是编译代码中耗时最长的关键路径,如果有一部分代码耗时很长,但在一些高阶的代码之中,可能就需要重构这些对于性能有更高要求的代码到一个单独的函数中,让numba专注于这些对于性能敏感的代码有以下好处:

  1. 避免遇见不支持的特性
  2. 减少编译时间
  3. 在需要编译的函数外,高阶的代码会更简单

不想要编译什么?

numba编译失败的原因很多,最常见的一个原因就是你写的代码依赖于不支持的Python特性,尤其是nopython模式,可以查看支持的python特性

在numba编译代码之前,先要确定所有使用的变量的类型,这样就能生成你的代码的特定类型的机器码。一个常见的编译失败原因(尤其是nopython模式)就是类型推导失败,numba不能确定代码中所有变量的类型。

例如:参考这个函数:

@jit(nopython=True)
def f(x, y):return x + y

如果使用两个数字作为参数:

>>> f(1,2)3

如果传入一个元组和一个数字,numba不能得到数字和元组求和的结果,就会触发编译报错:

>>> f(1, (2,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<path>/numba/numba/dispatcher.py", line 339, in _compile_for_argsreraise(type(e), e, None)
File "<path>/numba/numba/six.py", line 658, in reraiseraise value.with_traceback(tb)
numba.errors.TypingError: Failed at nopython (nopython frontend)
Invalid usage of + with parameters (int64, tuple(int64 x 1))
Known signatures:
* (int64, int64) -> int64
* (int64, uint64) -> int64
* (uint64, int64) -> int64
* (uint64, uint64) -> uint64
* (float32, float32) -> float32
* (float64, float64) -> float64
* (complex64, complex64) -> complex64
* (complex128, complex128) -> complex128
* (uint16,) -> uint64
* (uint8,) -> uint64
* (uint64,) -> uint64
* (uint32,) -> uint64
* (int16,) -> int64
* (int64,) -> int64
* (int8,) -> int64
* (int32,) -> int64
* (float32,) -> float32
* (float64,) -> float64
* (complex64,) -> complex64
* (complex128,) -> complex128
* parameterized
[1] During: typing of intrinsic-call at <stdin> (3)File "<stdin>", line 3:

错误信息“Invalid usage of + with parameters (int64, tuple(int64 x 1))”可以解释为numba解释器遇到了一个整数和元组中的整数求和,

类型统一问题

另一个编译失败的常见原因是:不能静态的决定返回的类型;返回值的类型仅仅依赖于运行期。这样的事情也是仅仅发生在nopython 模式下。类型统一的概念仅仅只是尝试找到一个类型,两个变量能够使用该类型安全的显示;例如一个64位的浮点数和一个64位的复数可以同时使用128位的复数表示。

以下是一个类型统一错误,这个函数的返回类型是基于x的值在运行期决定的:

In [1]: from numba import jitIn [2]: @jit(nopython=True)
...: def f(x):
...:     if x > 10:
...:         return (1,)
...:     else:
...:         return 1
...:

尝试执行这个函数,就会得到以下的错误:

In [3]: f(10)
TypingError: Failed at nopython (nopython frontend)
Can't unify return type from the following types: tuple(int64 x 1), int64
Return of: IR name '$8.2', type '(int64 x 1)', location:
File "<ipython-input-2-51ef1cc64bea>", line 4:
def f(x):<source elided>if x > 10:return (1,)^
Return of: IR name '$12.2', type 'int64', location:
File "<ipython-input-2-51ef1cc64bea>", line 6:
def f(x):<source elided>else:return 1

错误信息: “Can’t unify return type from the following types: tuple(int64 x 1), int64” 可以理解为: “Numba cannot find a type that can safely represent a 1-tuple of integer and an integer”.

编译的太慢

最常见的编译速度很慢的原因是:nopython模式编译失败,然后尝试使用object模式编译。object模式当前几乎没有提供加速特性,只是提供了一种叫做loop-lifting的优化,这个优化将允许使用nopython模式在内联迭代下编译。

可以在编译好的函数上使用inspect_types()方法来查看函数的类型推导是否成功。例如,对于以下函数:

@jit
def f(a, b):s = a + float(b)return s

当使用numbers调用时,该函数将和numba一样快速的将数字转换为浮点数:


>>> f(1, 2)
3.0
>>> f.inspect_types()
f (int64, int64)
--------------------------------------------------------------------------------
# --- LINE 7 ---@jit# --- LINE 8 ---def f(a, b):# --- LINE 9 ---# label 0#   a.1 = a  :: int64#   del a#   b.1 = b  :: int64#   del b#   $0.2 = global(float: <class 'float'>)  :: Function(<class 'float'>)#   $0.4 = call $0.2(b.1, )  :: (int64,) -> float64#   del b.1#   del $0.2#   $0.5 = a.1 + $0.4  :: float64#   del a.1#   del $0.4#   s = $0.5  :: float64#   del $0.5s = a + float(b)# --- LINE 10 ---#   $0.7 = cast(value=s)  :: float64#   del s#   return $0.7return s

关闭jit编译

设定NUMBA_DISABLE_JIT 环境变量为 1.

FAQ

Q:能否传递一个函数作为参数?
A:不能,但可以使用闭包来模拟实现,例如:

@jit(nopython=True)
def f(g, x):return g(x) + g(-x)result = f(my_g_function, 1)

可以使用一个工厂函数重构:

def make_f(g):# Note: a new f() is compiled each time make_f() is called!@jit(nopython=True)def f(x):return g(x) + g(-x)return ff = make_f(my_g_function)
result = f(1)

Q:对于全局变量修改的问题
A:非常不建议使用全局变量,否则只能使用recompile()函数重新编译,这样还不如重构代码,不使用全局变量。

Q:如何调试jit的函数?
A:可以调用pdb,也可以临时关闭编译环境变量:NUMBA_DISABLE_JIT。

Q:如何增加整数的位宽
A:默认情况下,numba为整形变量生成机器整形位宽。我们可以使用np.int64为相关变量初始化(例如:np.int64(0)而不是0)。

Q:如何知道parallel=True已经工作了?
A:如果parallel=True,设定环境变量NUMBA_WARNING为非0,所装饰的函数转换失败,就显示报警;同样,环境变量:NUMBA_DEBUG_ARRAY_OPT_STAT将展示一些统计结果。

numba 高级用法相关推荐

  1. vim的高级用法配置以及在系统中如何获取帮助

    vim的高级用法配置以及在系统中如何获取帮助 1 vim的三种模式 1.1 使用方法 1.2 vim模式 2 vim工作的基本配置 2.1 临时设定(set设定) 2.2 永久设定方式 3 搜索 4 ...

  2. Cacti Weathermap 高级用法 (二)

    成都长宽Weathermap实际运用的效果图示例: 这是一个CNC出口的质量监控图. 途中cnc节点(红色)是一个展示图例,TEL节点根据存活状态显示为绿色 TEL节点上面P:47.2ms 是一个,这 ...

  3. JAVA正则表达式高级用法(分组与捕获)

    2019独角兽企业重金招聘Python工程师标准>>> 正则表达式在字符串处理中经常使用,关于正则简单的用法相信有一点程序基础的人都懂得一些,这里就不介绍简单基础了.这里主要讲解一下 ...

  4. GUN sed高级用法,sed脚本编写

    这里举一些sed常用的高级用法例子经供参考: 一下操作都针对file.txt文件作修改 [root@QX-××× ~]# cat file.txt libgcc-4.4.7-4.el6.x86_64 ...

  5. java return用法_Java枚举的高级用法之多键值的映射使用

    枚举Enum单映射使用 做Java的各位仁兄姐妹都知道,Java通过HashMap,以及枚举提供了方便的K-V映射功能,例如 枚举单映射使用 但是如果遇到多个键值映射,例如K-K-V的形式怎么办呢?可 ...

  6. (转)python requests 高级用法 -- 包括SSL 证书错误的解决方案

    (转)python requests 高级用法 -- 包括SSL 证书错误的解决方案 参考文章: (1)(转)python requests 高级用法 -- 包括SSL 证书错误的解决方案 (2)ht ...

  7. Python 内置函数sorted()在高级用法

    对于Python内置函数sorted(),先拿来跟list(列表)中的成员函数list.sort()进行下对比.在本质上,list的排序和内建函数sorted的排序是差不多的,连参数都基本上是一样的. ...

  8. class() 高级用法 -- lua

    class() 高级用法 class() 除了定义纯 Lua 类之外,还可以从 C++ 对象继承类.比如需要创建一个工具栏,并在添加按钮时自动排列已有的按钮,那么我们可以使用如下的代码:-- 从 CC ...

  9. Newtonsoft.Json高级用法

    手机端应用讲究速度快,体验好.刚好手头上的一个项目服务端接口有性能问题,需要进行优化.在接口多次修改中,实体添加了很多字段用于中间计算或者存储,然后最终用Newtonsoft.Json进行序列化返回数 ...

最新文章

  1. 绘制测试集、训练集的每一个病人或者样本的raidomics signiture图(绘制raidomics signature图),以及ROC曲线图
  2. Vue 使用 prerender-spa-plugin 添加loading
  3. 阿里重组AI实验室的背后主因,马云也无奈
  4. python实现数据恢复_使用sklearn进行对数据标准化、归一化以及将数据还原的方法...
  5. oracle查询100到200数据,100分数据库查询语句(ORACLE 11g)
  6. TensorFlow 常见API
  7. Java 面向对象:封装详解
  8. 页面切换主题风格,利用本地缓存
  9. 在linux中,boot与uboot有什么区别?
  10. 笔记 Activator.CreateInstance(Type)
  11. 新的开始,从CSDN
  12. 小程序容器化:基于uni-app的Android小程序开发
  13. 【系统安全学习3】拒绝服务攻击
  14. 三线表的制作(硕士毕业论文WORD中)
  15. 【python面向对象】技能系统
  16. selenium+python实现登QQ邮箱并发送邮件自动化
  17. 数据分析之利用EXCEL做数据分析
  18. 问题解决丨对不起,小米路由器出现网络连接问题无法打开网页
  19. 智慧高铁、智慧机场对护照阅读器的应用 SDK说明
  20. 计算机图形学:机器人的画法与填充

热门文章

  1. React + MobX - 完全上手指南
  2. 2022年G3锅炉水处理考题模拟考试平台操作
  3. 前端电商 SKU 的全排列算法很难吗?学会这个套路,彻底掌握排列组合。
  4. 刚融资的作业帮押宝浣熊英语 少儿英语赛道还有机会吗?
  5. Verilog之i2c协议
  6. Java中的抽象类可以有构造函数吗?/抽象类中的构造方法作用是什么?
  7. 注意力汇聚:Nadaraya-Watson 核回归
  8. 实现“挨拉托色尼的筛子”
  9. 怎样给wordpress网站的分类目录,添加SEO标题和关键词?
  10. html下拉框选择触发方法,关于下拉列表的选择触发