用 Numba 加速 Python 代码,变得像 C++ 一样快

1个月前 (11-17 23:51)阅读2回复0
王富贵
王富贵
  • 管理员
  • 注册排名5
  • 经验值87890
  • 级别管理员
  • 主题17578
  • 回复0
楼主

↓保举存眷↓

来源:收集

来源:收集

1. 介绍

Numba 是 python 的立即(Just-in-time)编译器,即当你挪用 python 函数时,你的全数或部门代码就会被转换为“立即”施行的机器码,它将以你的当地机器码速度运行!它由 Anaconda 公司赞助,并得到了许多其他组织的撑持。

在 Numba 的搀扶帮助下,你能够加速所有计算负载比力大的 python 函数(例如轮回)。它还撑持 numpy 库!所以,你也能够在你的计算中利用 numpy,并加快整体计算,因为 python 中的轮回十分慢。你还能够利用 python 原则库中的 math 库的许多函数,如 sqrt 等。有关所有兼容函数的完好列表,请查看 此处。

2. 为什么选择 Numba?

那么,当有像 cython 和 Pypy 之类的许多其他编译器时,为什么要选择 numba?

原因很简单,如许你就没必要分开写 python 代码的温馨区。是的,就是如许,你底子不需要为了获得一些的加速来改动你的代码,那与你从类似的具有类型定义的 cython 代码获得的加速相当。那不是很好吗?

你只需要添加一个熟悉的 python 功用,即添加一个包拆器(一个粉饰器)到你的函数上。类的粉饰器也在开发中了。

所以,你只需要添加一个粉饰器就能够了。例如:

cd ~/pythia/data

fromnumba importjit

@jit

deffunction(x):

# your loop or numerically intensive computations

returnx

那仍然看起来像一个原生 python 代码,不是吗?

3. 若何利用 Numba?

Numba 利用 LLVM 编译器根底构造 将原生 python 代码转换成优化的机器码。利用 numba 运行代码的速度可与 C/C++ 或 Fortran 中的类似代码相媲美。

展开全文

以下是代码的编译体例:

起首,Python 函数被传入,优化并转换为 numba 的中间表达,然后在类型揣度(type inference)之后,就像 numpy 的类型揣度(所以 python float 是一个 float64),它被转换为 LLVM 可阐明代码。然后将此代码提赐与 LLVM 的立即编译器以生成机器码。

你能够根据需要在运行时或导入时 生成 机器码,导入需要在 CPU(默认)或 GPU 长进行。

4. 利用 numba 的根本功用

(只需要加上 @jit !)

为了获得更佳性能,numba 现实上定见在你的 jit 粉饰器中加上 nopython=True 参数,加上后就不会利用 Python 阐明器了。或者你也能够利用 @njit 。若是你加上 nopython=True 的粉饰器失败并报错,你能够用简单的 @jit 粉饰器来编译你的部门代码,关于它可以编译的代码,将它们转换为函数,并编译成机器码。然后将其余部门代码提赐与 python 阐明器。

所以,你只需要如许做:

fromnumba importnjit, jit

@njit # or @jit(nopython=True)

deffunction(a, b):

# your loop or numerically intensive computations

returnresult

当利用 @jit 时,请确保你的代码有 numba 能够编译的内容,好比包罗库(numpy)和它撑持的函数的计算密集型轮回。不然它将不会编译任何工具,而且你的代码将比没有利用 numba 时更慢,因为存在 numba 内部代码查抄的额外开销。

还有更好的一点是,numba 会对初次做为机器码利用后的函数停止缓存。因而,在第一次利用之后它将更快,因为它不需要再次编译那些代码,若是你利用的是和之前不异的参数类型。

若是你的代码是 可并行化 的,你也能够传递 parallel=True 做为参数,但它必需与 nopython=True 一路利用,目前那只适用于CPU。

你还能够指定希望函数具有的函数签名,但是如许就不会对你供给的任何其他类型的参数停止编译。例如:

fromnumba importjit, int32

@jit(int32(int32, int32))

deffunction(a, b):

# your loop or numerically intensive computations

returnresult

# or if you haven t imported type names

# you can pass them as string

@jit( int32(int32, int32) )

deffunction(a, b):

# your loop or numerically intensive computations

returnresult

如今你的函数只能领受两个 int32 类型的参数并返回一个 int32 类型的值。通过那种体例,你能够更好地掌握你的函数。若是需要,你以至能够传递多个函数签名。

你还能够利用 numba 供给的其他粉饰器:

@vectorize:允许将标量参数做为 numpy 的 ufuncs 利用,

@guvectorize:生成 NumPy 广义上的 ufunc s,

@stencil:定义一个函数使其成为 stencil 类型操做的核函数

@jitclass:用于 jit 类,

@cfunc:声明一个函数用于当地回调(被C/C++等挪用),

@overload:注册你本身的函数实现,以便在 nopython 形式下利用,例如: @overload(scipy.special.j0) 。

Numba 还有 Ahead of time(AOT)编译,它生成不依赖于 Numba 的已编译扩展模块。但:

它只允许常规函数(ufuncs 就不可),

你必需指定函数签名。而且你只能指定一种签名,若是需要指定多个签名,需要利用差别的名字。

它还根据你的CPU架构系列生成通用代码。

5. @vectorize 粉饰器

通过利用 @vectorize 粉饰器,你能够对仅能对标量操做的函数停止转换,例如,若是你利用的是仅适用于标量的 python 的 math 库,则转换后就能够用于数组。那供给了类似于 numpy 数组运算(ufuncs)的速度。例如:

fromnumba importjit, int32

@vectorize

deffunc(a, b):

# Some operation on scalars

returnresult

你还能够将 target 参数传递给此粉饰器,该粉饰器使 target 参数为 parallel 时用于并行化代码,为 cuda 时用于在 cudaGPU 上运行代码。

@vectorize(target="parallel")

deffunc(a, b):

# Some operation on scalars

returnresult

使 target=“parallel” 或 “cuda” 停止矢量化凡是比 numpy 实现的代码运行得更快,只要你的代码具有足够的计算密度或者数组足够大。若是不是,那么因为创建线程以及将元素分配到差别线程需要额外的开销,因而可能耗时更长。所以运算量应该足够大,才气获得明显的加速。

那个视频讲述了一个用 Numba 加速用于计算流体动力学的Navier Stokes方程的例子:

6. 在GPU上运行函数

你也能够像粉饰器一样传递 @jit 来运行 cuda/GPU 上的函数。为此你必需从 numba 库中导入 cuda 。但是要在 GPU 上运行代码其实不像之前那么容易。为了在 GPU 上的数百以至数千个线程上运行函数,需要先做一些初始计算。现实上,你必需声明并办理网格,块和线程的条理构造。那其实不那么难。

要在GPU上施行函数,你必需定义一个叫做 核函数或 设备函数的函数。起首让我们来看 核函数。

关于核函数要记住一些要点:

核函数在被挪用时要显式声明其线程条理构造,即块的数量和每块的线程数量。你能够编译一次核函数,然后用差别的块和网格大小屡次挪用它。

核函数没有返回值。因而,要么必需对原始数组停止更改,要么传递另一个数组来存储成果。为了计算标量,你必需传递单位素数组。

fromnumba importcuda

@cuda.jit

deffunc(a, result):

# Some cuda related computation, then

# your computationally intensive code.

# (Your answer is stored in result )

因而,要启动核函数,你必需传入两个参数:

每块的线程数,

块的数量。

例如:

threadsperblock = 32

blockspergrid = (array.size + (threadsperblock - 1)) // threadsperblock

func[blockspergrid, threadsperblock](array)

每个线程中的核函数必需晓得它在哪个线程中,以便领会它负责数组的哪些元素。Numba 只需挪用一次即可轻松获得那些元素的位置。

@cuda.jit

deffunc(a, result):

pos = cuda.grid( 1) # For 1D array

# x, y = cuda.grid(2) # For 2D array

ifpos a.shape[ 0]:

result[pos] = a[pos] * (some computation)

为了节省将 numpy 数组复造到指定设备,然后又将成果存储到 numpy 数组中所浪费的时间,Numba 供给了一些 函数 来声明并将数组送到指定设备,如: numba.cuda.device_array , numba.cuda。device_array_like , numba.cuda.to_device 等函数来节省没必要要的复造到 cpu 的时间(除非需要)。

另一方面, 设备函数只能从设备内部(通过核函数或其他设备函数)挪用。比力好的一点是,你能够从 设备函数中返

fromnumba importcuda

@cuda.jit(device=True)

defdevice_function(a, b):

returna + b

你还应该在那里查看 Numba 的 cuda 库撑持的功用。

Numba 在其 cuda 库中也有本身的原子操做,随机数生成器,共享内存实现(以加快数据的拜候)等功用。

ctypes/cffi/cython 的互用性:

cffi – 在 nopython 形式下撑持挪用 CFFI 函数。

ctypes – 在 nopython 形式下撑持挪用 ctypes 包拆函数。

Cython 导出的函数是 可挪用 的。

cffi – 在 nopython 形式下撑持挪用 CFFI 函数。

ctypes – 在 nopython 形式下撑持挪用 ctypes 包拆函数。

- EOF -

加主页君微信,不只Python技能+1

主页君日常还会在小我微信分享 Python相关东西、资本和 精选手艺文章,不按期分享一些 有意思的活动、 岗位内推以及 若何用手艺做业余项目

加个微信,翻开一扇窗

点击题目可跳转

1、 用 Python 算法预测客户行为案例!

2、 超详细的 Python 文件操做常识!

3、 利用 Numba 让 Python 计算得更快:两行代码,提速 13 倍

觉得本文对你有搀扶帮助?请分享给更多人

保举存眷「Python开发者」,提拔Python技能

点赞和在看就是更大的撑持❤️

0
回帖

用 Numba 加速 Python 代码,变得像 C++ 一样快 期待您的回复!

取消
载入表情清单……
载入颜色清单……
插入网络图片

取消确定

图片上传中
编辑器信息
提示信息