Numba

Numba介绍
1
2
3
4
5
6
7
   Cython毕竟不是原生的 Python 代码,使用起来还是有诸多不便的。为此,numba 就成了一个功能强大又容易上手的替代选择,是一个用于编译python数组和数值计算函数的编译器,在使用NumPy数组和循环的代码上效果最佳
使用方法:添加装饰器
Jit(just-in-time compiler)即时编译器,在运行时将某些函数编译成二进制代码



☆☆☆☆☆:numba内不能有第三方包
适用情景
1
2
使用numpy数组做大量科学计算时
使用for循环时
Numba的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
@jit 对于函数中能够编译的部分转换成机器码,剩余的代码使用Python解释器 Numba 提供的最灵活的装饰器
@njit 与 @jit(nopython=True) 二者等价,将全部代码转换成机器码,无法实现时会报错
@generated_jit 有时需要根据输入变量的类型来决定函数的实现功能
@vectorize 矢量化
@guvectorize 矢量化


装饰器可选参数
parallel = True @jit的自动并行化仅在64位平台上可用。注意需要和nopython=true一起使用,numba的多线程的数量通过全局变量来设置
import numba
numba.config.NUMBA_NUM_THREADS=8 全局变量设置多线程数目
cache=True 为了避免每次调用 Python 程序时的编译时间,可以指示 Numba 将函数编译的结果写入基于文件的缓存中,将函数编译完成的结果保存在一个file文件中。
nogil = True 一旦编译完成,就会释放GIL,这样的情况下就可以充分利用多核系统,但是需要注意多线程编程中需要注意的同步、一致性、竞争等情况

#####jit

1
2
3
4
5
6
7
8
9
10
11
12
13
#斐波那契数列
import time
from numba import jit
@jit
def fib(n):
if n<=2 :
return 1;
else:
return fib(n-1)+fib(n-2);
start = time.time()
fib(40)
end = time.time()
print("python3+numba cost_seconds:", end-start)

1595401428838

#####generated_jit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
from numba import generated_jit,types
@generated_jit(nopython=True)
def is_missing(x):
print(type(x))
print(numba.typeof(x))
if isinstance(x, types.Float):
return lambda x:x+2
if isinstance(x, types.Integer):
return lambda x:x+2
if isinstance(x, types.List):
return lambda x:x
if isinstance(x, types.Array):
return lambda x:list(x)
else:
return lambda x:x
is_missing(np.array([1,2,3]))
numba.typeof(x) types,只能在generated_jit中用,
generated_jit会将数据转换成Numba中的数据类型
上面的代码完成的是根据输入去判断缺省值的事情。
注意下面问题:
1、在调用的时候,传入的参数,使用变量的numba类型,而不是值;
2、这个修饰函数返回的结果不是一个计算结果;

1595584369339

1595585033204

#####vectorize和guvectorize(矢量化计算)

1
2
3
4
5
6
7
8
9
10
11
12
13
vectorize和guvectorize,Numba可以将纯Python函数编译为一个ufunc 
(ufunc,通用函数是numpy的特点之一,会将函数作用于array对象的每一个元素上,常见的如add,subtract,multiply,divide...)
vectorize 是一个一个元素处理的,而guvectorize是一组一组数据处理的
eg:[[1,2,3]
[4,5,6]]
vectorize每次传入一个数据,去计算.而guvectorize是将[1,2,3]这样一组(行)传进去
guvectorize() 函数不返回其结果值:它们将其结果作为数组参数传进去。
guvectorize格式:
@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)')
'(n),()->(n)'是输入和输出布局的声明,告诉 NumPy 该函数采用 n 元素的一维数组,一个标量(用符号表示为空元组())并返回 n 元素的一维数组;
[:] 表示一维数组,[::]表示二维数组.int64表示数组内的数据类型

vectorize() 和 guvectorize() 都支持传递nopython=True ,如同@jit 装饰器。使用它来确保生成的代码不会回退到对象模式。

######直接传矩阵会报错

1
2
3
4
5
6
7
import numpy
import math
def trig(a, b):
return math.sin(a**2) * math.exp(b)
a = numpy.ones((5,5))
b = numpy.ones((5,5))
trig(a, b)

1596165994666

jit也无法使用
1
2
3
4
5
6
7
8
import numpy
import math
@jit(nopython=True)
def trig(a, b):
return math.sin(a**2) * math.exp(b)
a = numpy.ones((5,5))
b = numpy.ones((5,5))
trig(a, b)

######使用vectorize后会使函数转换成numpy中的unfunc,作用于每一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy
import math
@vectorize(nopython=True)
def trig(a, b):
return math.sin(a**2) * math.exp(b)
a = numpy.ones((5,5))
b = numpy.ones((5,5))
trig(a, b)
#下面案列同样可使用
import numpy
import math
from numba import vectorize,int32
@vectorize([int32(int32, int32)]) #@vectorize(["int32(int32, int32)"])这样不用从numba导入,且列表里面允许多种类型,传的参数符合一个即可,否则报错
def trig(a, b):
return math.sin(a**2) * math.exp(b)
a = numpy.ones((5,5),dtype=numpy.int32)
b = numpy.ones((5,5),dtype=numpy.int32)
trig(a, b)

1596166115954

######guvectorize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import timeit
import numpy as np
from numba import jit, guvectorize

@guvectorize(["float64[:], float64[:]"], "(n) -> ()", target="parallel", nopython=True)
def row_sum_gu(input, output) :
output[0] = np.sum(input)

@jit(nopython=True)
def row_sum_jit(input_array, output_array) :
m, n = input_array.shape
for i in range(m) :
output_array[i] = np.sum(input_array[i,:])

rows = int(64)
columns = int(1e6)
input_array = np.ones((rows, columns))
output_array = np.zeros((rows))
output_array2 = np.zeros((rows))

#the first run includes the compile time
row_sum_jit(input_array, output_array)
row_sum_gu(input_array, output_array2)

#run each function 100 times and record the time
print("jit time:", timeit.timeit("row_sum_jit(input_array, output_array)", "from __main__ import row_sum_jit, input_array, output_array", number=100))
print("guvectorize time:", timeit.timeit("row_sum_gu(input_array, output_array2)", "from __main__ import row_sum_gu, input_array, output_array2", number=100))

1596177562619

######

#####Numba中的数据类型

1
2
3
4
5
6
7
8
9
整形 Integer
浮点型 Float
负数 Complex
列表 List
字符串 UnicodeType
集合 Set
元组 UniTuple
数组 Array
其他..

Numba的两种模式Nopython和Object模式

1
2
3
4
Numba @jit装饰器从根本上以两种编译模式运行,nopython模式和object模式。
nopython编译模式,可以完全运行而无需Python解释器的参与。这是使用Numba jit装饰器的推荐和最佳实践方式,因为它可以带来最佳性能。

如果编译nopython模式失败(例如出现了字符串处理等numba无法编译的数据),Numba可以使用object模式 。在这种模式下,Numba将识别它可以编译的循环,并将它们编译成在机器代码中运行的函数,并且它将运行解释器中的其余代码。为获得最佳性能,请避免使用此模式。

1595412696382

案例1:斐波那契数列

Numba无法理解Pandas,因此Numba只需通过解释器运行此代码,但会增加Numba内部开销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#下面代码运行良好
from numba import jit
import numpy as np
x = np.arange(100).reshape(10, 10)
@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def go_fast(a): # Function is compiled to machine code when called the first time
trace = 0.0
for i in range(a.shape[0]): # Numba likes loops
trace += np.tanh(a[i, i]) # Numba likes NumPy functions
return a + trace # Numba likes NumPy broadcasting
print(go_fast(x))


#下面代码运行较差
from numba import jit
import pandas as pd
x = {'a': [1, 2, 3], 'b': [20, 30, 40]}
@jit
def use_pandas(a): # Function will not benefit from Numba jit
df = pd.DataFrame.from_dict(a) # Numba doesn't know about pd.DataFrame
df += 1 # Numba doesn't understand what this is
return df.cov() # or this!
print(use_pandas(x))

1595900106564

故障排除和技巧

http://numba.pydata.org/numba-doc/latest/user/troubleshoot.html#numba-troubleshooting

弃用警告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Numba弃用了列表和集合
from numba import njit
@njit
def foo(x):
x.append(10)
return x
a = [1, 2, 3]
foo(a)
NumbaDeprecationWarning或 NumbaPendingDeprecationWarning代表Numba弃用
弃用原因(官方解释)
首先回想一下,为了使Numba能够在nopython 模式下编译函数,所有变量必须具有通过类型推断确定的具体类型。在简单的情况下,很明显如何反映内部容器的更改nopython模式返回到原始的Python容器。但是,无法快速有效地将具有嵌套容器类型(例如,整数列表的列表)的复杂数据结构反映出来。经过多年的处理此问题的经验后,很明显,提供此行为既困难又常常导致代码性能不佳(所有反映的数据都必须通过特殊的API才能将数据转换为本地数据格式,然后在返回时返回CPython格式)
禁止弃用警告:
from numba.errors import NumbaDeprecationWarning, NumbaPendingDeprecationWarning
import warnings
warnings.simplefilter('ignore', category=NumbaDeprecationWarning)
warnings.simplefilter('ignore', category=NumbaPendingDeprecationWarning)

1595901667054

1595902092742

1595902995756

Numba无法编译原因
1
2
3
1.普遍的原因是依赖了不受支持的Python功能
2.无法确定函数的返回类型,类型统一失败,if--else中返回的类型不同
3.列表中类型无法推断(列表中的类型要一致)
Numba编译太慢的原因
1
2
编译JIT函数缓慢的最常见原因是,在nopython模式下编译失败,并且Numba编译器已退回到对象模式。 与常规的Python解释相比,对象模式目前几乎没有提供任何加速,其主要点是允许进行称为循环提升的内部优化 :无论哪种代码包围这些内部循环,此优化都将允许以nopython模式编译内部循环
要确定函数是否成功进行类型推断,可以使用inspect_types() f.inspect_types()
Numba中的cuda(大规模并行运算)———>了解有这个用法
1
2
3
4
CUDA:    一种通用并行计算架构,架构使GPU能够解决复杂的计算问题.(CPU与GPU"协同处理")
CUDA发展历程: GPU越来越强大,GPU为显示图像做了优化之外,在计算上已经超越了通用的CPU。如此强大的芯片如果只是作为显卡就太浪费了,因此NVidia推出CUDA,让显卡可以用于图像计算以外的目的,也就是超于游戏,使得GPU能够发挥其强大的运算能力。
CUDA作用: CUDA具有不同于用于CPU编程的传统顺序模型的执行模型。在CUDA中,您编写的代码将同时由多个线程(通常成百上千个)执行
CUDA环境配置: 失败========
注意
1
2
注意:numba装饰的函数在第一次调用的时候会进行编译,会消耗一些时间,再次调用就不会了
如果函数无法在nopython模式下编译,则会发出警告\报错,并说明编译失败的原因

swifter =========>优化apply,df.swifter.apply