-
介绍
-
为什么选择 Numba?
-
Numba 是如何工作的?
-
使用 Numba 的基本功能(只需要加上
@jit
!)
-
@vectorize 装饰器
-
在 GPU 上运行函数
-
扩展阅读
-
参考
注意: 这篇文章的 Jupyter Notebook 代码在我的 Github 上:
SpeedUpYourAlgorithms-Numba
1. 介绍
Numba 是 python 的即时(Just-in-time)编译器,即当您调用 python 函数时,您的全部或部分代码就会被转换为“即时”执行的机器码,它将以您的本地机器码速度运行!它由 Anaconda 公司赞助,并得到了许多其他组织的支持。
在 Numba 的帮助下,您可以加速所有计算负载比较大的 python 函数(例如循环)。它还支持 numpy 库!所以,您也可以在您的计算中使用 numpy,并加快整体计算,因为 python 中的循环非常慢。 您还可以使用 python 标准库中的 math 库的许多函数,如
sqrt
等。有关所有兼容函数的完整列表,请查看
此处
。
2. 为什么选择 Numba?
那么,当有像
cython
和
Pypy
之类的许多其他编译器时,为什么要选择 numba?
原因很简单,这样您就不必离开写 python 代码的舒适区。是的,就是这样,您根本不需要为了获得一些的加速来改变您的代码,这与您从类似的具有类型定义的 cython 代码获得的加速相当。那不是很好吗?
您只需要添加一个熟悉的 python 功能,即添加一个包装器(一个装饰器)到您的函数上。
类的装饰器
也在开发中了。
所以,您只需要添加一个装饰器就可以了。例如:
from numba import jit
def function(x):
# your loop or numerically intensive computations
return x
这仍然看起来像一个原生 python 代码,不是吗?
3. 如何使用 Numba?
“question mark neon signage” by Emily Morter on Unsplash
Numba 使用 LLVM 编译器基础结构 将原生 python 代码转换成优化的机器码。使用 numba 运行代码的速度可与 C/C++ 或 Fortran 中的类似代码相媲美。
以下是代码的编译方式:
首先,Python 函数被传入,优化并转换为 numba 的中间表达,然后在类型推断(type inference)之后,就像 numpy 的类型推断(所以 python float 是一个 float64),它被转换为 LLVM 可解释代码。 然后将此代码提供给 LLVM 的即时编译器以生成机器码。
您可以根据需要在运行时或导入时 生成 机器码,导入需要在 CPU(默认)或 GPU 上进行。
4. 使用 numba 的基本功能(只需要加上 @jit !)
Photo by Charles Etoroma on Unsplash
小菜一碟!
为了获得最佳性能,numba 实际上建议在您的 jit 装饰器中加上
nopython=True
参数,加上后就不会使用 Python 解释器了。或者您也可以使用
@njit
。如果您加上
nopython=True
的装饰器失败并报错,您可以用简单的
@jit
装饰器来编译您的部分代码,对于它能够编译的代码,将它们转换为函数,并编译成机器码。然后将其余部分代码提供给 python 解释器。
所以,您只需要这样做:
from numba import njit, jit
@njit # or @jit(nopython=True)
def function(a, b):
# your loop or numerically intensive computations
return result
当使用
@jit
时,请确保您的代码有 numba 可以编译的内容,比如包含库(numpy)和它支持的函数的计算密集型循环。否则它将不会编译任何东西,并且您的代码将比没有使用 numba 时更慢,因为存在 numba 内部代码检查的额外开销。
还有更好的一点是,numba 会对首次作为机器码使用后的函数进行缓存。 因此,在第一次使用之后它将更快,因为它不需要再次编译这些代码,如果您使用的是和之前相同的参数类型。
如果您的代码是 可并行化 的,您也可以传递
parallel=True
作为参数,但它必须与
nopython=True
一起使用,目前这只适用于CPU。
您还可以指定希望函数具有的函数签名,但是这样就不会对您提供的任何其他类型的参数进行编译。 例如:
from numba import jit, int32
@jit(int32(int32, int32))
def function(a, b):
# your loop or numerically intensive computations
return result
# or if you haven't imported type names
# you can pass them as string
@jit('int32(int32, int32)')
def function(a, b):
# your loop or numerically intensive computations
return result
现在您的函数只能接收两个 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 装饰器
“gray solar panel lot” by
American Public Power Association
on
Unsplash
通过使用 @vectorize 装饰器,您可以对仅能对标量操作的函数进行转换,例如,如果您使用的是仅适用于标量的 python 的
math
库,则转换后就可以用于数组。 这提供了类似于 numpy 数组运算(ufuncs)的速度。 例如:
@vectorize
def func(a, b):
# Some operation on scalars
return result
您还可以将
target
参数传递给此装饰器,该装饰器使 target 参数为
parallel
时用于并行化代码,为
cuda
时用于在 cuda\GPU 上运行代码。
@vectorize(target="parallel")
def func(a, b):
# Some operation on scalars
return result
使
target=“parallel”
或
“cuda”
进行矢量化通常比 numpy 实现的代码运行得更快,只要您的代码具有足够的计算密度或者数组足够大。如果不是,那么由于创建线程以及将元素分配到不同线程需要额外的开销,因此可能耗时更长。所以运算量应该足够大,才能获得明显的加速。
这个视频讲述了一个用 Numba 加速用于计算流体动力学的Navier Stokes方程的例子:
6. 在GPU上运行函数
“time-lapsed of street lights” by Marc Sendra martorell on Unsplash
您也可以像装饰器一样传递 @jit 来运行 cuda/GPU 上的函数。 为此您必须从
numba
库中导入
cuda
。 但是要在 GPU 上运行代码并不像之前那么容易。为了在 GPU 上的数百甚至数千个线程上运行函数,需要先做一些初始计算。 实际上,您必须声明并管理网格,块和线程的层次结构。这并不那么难。
要在GPU上执行函数,您必须定义一个叫做
核函数
或
设备函数
的函数。首先让我们来看
核函数
。
关于核函数要记住一些要点:
a)核函数在被调用时要显式声明其线程层次结构,即块的数量和每块的线程数量。您可以编译一次核函数,然后用不同的块和网格大小多次调用它。
b)核函数没有返回值。因此,要么必须对原始数组进行更改,要么传递另一个数组来存储结果。为了计算标量,您必须传递单元素数组。
# Defining a kernel function
from numba import cuda
@cuda.jit
def func(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
def func(a, result):
pos = cuda.grid(1) # For 1D array
# x, y = cuda.grid(2) # For 2D array
if pos < 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 的时间(除非必要)。
另一方面,
设备函数
只能从设备内部(通过核函数或其他设备函数)调用。 比较好的一点是,您可以从
设备函数
中返
from numba import cuda
@cuda.jit(device=True)
def device_function(a, b):
return a + b
您还应该在这里查看 Numba 的 cuda 库支持的功能。
Numba 在其 cuda 库中也有自己的
原子操作
,
随机数生成器
,
共享内存实现
(以加快数据的访问)等功能。
ctypes/cffi/cython 的互用性:
7. 扩展阅读
-
https://nbviewer.jupyter.org/github/ContinuumIO/gtc2017-numba/tree/master/
-
https://devblogs.nvidia.com/seven-things-numba/
-
https://devblogs.nvidia.com/numba-python-cuda-acceleration/
-
https://jakevdp.github.io/blog/2015/02/24/optimizing-python-with-numpy-and-numba/
-
https://www.youtube.com/watch?v=1AwG0T4gaO0
8. 参考
-
http://numba.pydata.org/numba-doc/latest/user/index.html
-
https://github.com/ContinuumIO/gtc2018-numba
-
http://stephanhoyer.com/2015/04/09/numba-vs-cython-how-to-choose/
谢谢阅读!
(点击标题可跳转阅读)
8 个流行的 Python 可视化工具包,你喜欢哪个?
GitHub 热门:Python 算法大全,Star 超过 2 万
觉得本文对你有帮助?请分享给更多人
关注「Python开发者」加星标,提升Python技能
好文章,我
在看
❤️
之前我们讨论的并行,都是线程级别的,即CUDA开启多个线程,并行执行核函数内的
代码
。GPU最多就上千个核心,同一时间只能并行执行上千个任务。当我们处理千万级别的数据,整个大任务无法被GPU一次执行,所有的计算任务需要放在一个队列中,排队顺序执行。CUDA将放入队列顺序执行的一系列操作称为流(Stream)。
由于异构计算的硬件特性,CUDA中以下操作是相互独立的,通过编程,是可以操作他们并发地执行的:
主机端上的计算
设备端的计算(核函数)
数据从主机和设备间相互拷贝
数据从设备内拷贝或转
sudo apt-get install vllm
sudo pip3 install llvmlite==0.32.0
sudo pip3 install
numba
==0.48.0 -i http://pypi.douban.com/simple
前面两条指令安装
numba
依赖,安装不成功大都为llvm和llvmlite版本不对应,应该还可以是其它版本,试了很多个,只有这个成功了,如果还有其它版本的安装欢迎在评论区交流。
【树莓派】树莓派4B安装librosa库
最近需要使用树莓派制作一个分布式系统,用于采集和处理音频,librosa是一个常用的处理音频的
python
库,在树莓派4B上直接使用pip3 install librosa会出现很多错误,网上教程参差不齐,自己捣鼓了两天终于安装成功,虽然不能安装最新版的librosa,但是
达到
了我的预期目标,所以特意记录下来。
在安装之前,建议重新安装树莓派系统,否则在安装时可能会出现无法预知的错误。这里使用到的有:树莓派4B一个、16G SD卡一个、电脑屏幕一个、MiniH
计算机底层基础知识,CPU、机器码、编译等《编译型语言与解释型语言如何在计算机底层运行》《计算机底层运转机制:多核、缓存、CPU、CU、ALU、Cache》
Python
代码
与GPU
加速
的关系《
Python
程序如何用GPU
加速
:Tesla、CUDA、
Numba
》
在CPU入门
numba
《
Python
代码
在CPU下
加速
:
Numba
入门》
在GPU入门
numba
《...
目录简单的
numba
+ CUDA 实测起因
numba
+ CUDA
numba
天生支持NumPy,但是CUDA部分仅提供非常有限的支持CUDA部分
代码
简单的
numba
+ CUDA 实测
一时兴起,是我太闲了吧。
最近需要对一个4k图像进行单个像素级别的处理,由于用
python
用得人有点懒,直接上
python
在所有像素上循环一遍。每个像素做的工作其实很简单,就是判断一下这个像素是否符合某一...
我尝试使用
numba
加
快
代码
速度
,但似乎不起作用。该程序用@jit,@njit或纯
python
花费相同的时间(约10秒)。但是我用了numpy而不是list或dict。
这是我的
代码
:
import numpy as np
from
numba
import njit
import random
import line_profiler
import atexit
profile =...
numba
是一个用于编译
Python
数组和数值计算函数的编译器,这个编译器能够大幅提高直接使用
Python
编写的函数的运算
速度
。
numba
使用LLVM编译器架构将纯
Python
代码
生成优化过的机器码,通过一些添加简单的注解,将面向数组和使用大量数学的
python
代码
优化到与c,
c++
和Fortran类似的性能,而无需改变
Python
的解释器。
Numba
的主要特性:
动态
代码
生成 (...
1. 使用更
快
的算法:通常来说,使用更高效的算法可以大大提升
代码
的执行
速度
。
2. 使用预编译
代码
:可以使用工具,如 Cython 或
Numba
,将
Python
代码
编译成 C 或其他底层语言的
代码
,从而提升
代码
的执行
速度
。
3. 使用多线程或多进程:可以使用
Python
的多线程或多进程功能,将任务分解到多个线程或进程中,从而提升
代码
的执行
速度
。
4. 使用
加速
器:可以使用
加速
器,如 PyPy 或
Numba
,来提升
Python
代码
的执行
速度
。
5. 优化
代码
:可以通过优化
代码
的结构和实现来提升
代码
的执行
速度
,例如避免无效循环、使用合适的数据结构和算法等。