用Numba加速你的Python


用Numba加速你的Python

Python 不是最快的语言,但速度的缺乏并没有阻止它成为分析、机器学习和其他需要大量数字运算的学科的主要力量。

Numba 是由 Anaconda Python 发行版背后的人们创建的,它采取了与大多数 Python 数学和统计库不同的方法。通常,这类库--如用于科学计算的NumPy--将用C、C++或Fortran编写的高速数学模块包裹在一个方便的Python包装中。Numba通过及时编译器或JIT的方式将你的Python代码转化为高速机器语言。

[ Also on InfoWorld: 11 tips for speeding up Python programs ]

这种方法有很大的优势。首先,你不会被库的隐喻和限制所束缚。你可以准确地写出你想要的代码,并让它以机器原生的速度运行,通常还可以进行库中不可能有的优化。更重要的是,如果你想将NumPy与Numba结合使用,你也可以这样做,并获得两个世界的最佳效果。

安装Numba

Numba与Python 3.6和Python支持的大多数主要硬件平台一起工作。Linux x86 或 PowerPC 用户、Windows 系统和 Mac OS X 10.9 都被支持。

要在一个特定的 Python 实例中安装 Numba,只需像使用其他软件包一样使用 pip:pip install numba。

由于 Numba 是 Anaconda 的一个产品,它也可以通过 conda 工具安装在 Anaconda 中:conda install numba。

Numba JIT装饰器

开始使用Numba的最简单方法是采取一些需要加速的数字代码,并用@jit装饰器来包装它。下面是一个关于pi值的蒙特卡洛搜索方法的实现--这不是一个有效的方法,但却是对Numba的一个很好的压力测试。

import randomdef monte_carlo_pi(nsamples): acc = 0 for i in range(nsamples): x = random. random() y = random.random() if (x ** 2 + y ** 2) < 1.0: acc += 1 return 4.0 * acc / nsamplesprint(mont_carlo_pi(10_000_000))

在现代机器上,这个Python代码在大约4或5秒内返回结果。

import numbaimport random@numba.jit()def monte_carlo_pi(nsamples): acc = 0 for i in range(nsamples): x = random.random() y = random.random() if (x ** 2 + y ** 2) < 1.0: acc += 1 return 4. 0 * acc / nsamplesprint(mont_carlo_pi(10_000_000))

这个版本将mont_carlo_pi()函数包裹在Numba的jit装饰器中,从而将该函数转换为机器码(或者考虑到我们代码的限制,Numba能够得到的最接近机器码的代码)。

使用@jit装饰器的最好部分是其简单性。我们可以在不改变代码的情况下实现巨大的改进。也许我们可以对代码进行其他优化,我们将在下文中讨论其中的一些,但是 Python 中大量的 "纯 "数字代码都是高度可优化的。

请注意,在函数第一次运行时,当 JIT 启动并编译函数时可能会有明显的延迟。然而,对该函数的每一次后续调用都应该执行得更快。

Numba JIT 选项

使用 jit() 装饰器的最简单方法是将其应用于您的函数,并让 Numba 进行优化,就像我们上面做的那样。

nopython

如果你在装饰器中设置nopython=True,Numba将尝试在不依赖Python运行时的情况下编译代码。这并不总是可能的,但你的代码越是由纯数字操作组成,nopython选项就越有可能发挥作用。这样做的好处是速度快,因为不依赖 Python 的 JIT 函数不必放慢速度与 Python 运行时对话。

parallel

在装饰器中设置 parallel=True,Numba 将编译你的 Python 代码,尽可能通过多处理来利用并行性。

[ Also on InfoWorld: 6 projects that push Python performance ]nogil

如果nogil=true,Numba将在运行JIT编译的函数时释放全局解释器锁(GIL)。这意味着解释器将同时运行你的Python应用程序的其他部分,如Python线程。注意,除非你的代码在nopython模式下编译,否则你不能使用nogil。

cache

设置 cache=True 来保存编译后的二进制代码到你的脚本的缓存目录(通常是 __pycache__)。在随后的运行中,Numba将跳过编译阶段,只是重新加载与之前相同的代码,假设没有任何变化。缓存可以稍微加快脚本的启动时间。

fastmath

当启用fastmath=True时,fastmath选项允许使用一些更快但不太安全的浮点转换。如果你的浮点代码确定不会产生NaN(非数字)或inf(无穷大)值,你可以安全地启用fastmath,以便在使用浮点时获得额外的速度--例如在浮点比较操作中。

boundscheck

当启用boundscheck=True时,boundscheck选项将确保数组访问不会越界,从而可能使你的应用程序崩溃。

Numba中的类型和对象

默认情况下,Numba对JIT装饰的函数将接收和返回的变量类型进行最佳猜测或推断。然而,有时你会想明确地指定函数的类型。JIT 装饰器让你做到这一点:

from numba import jit, int32@jit(int32(int32))def plusone(x): return x+1

Numba 的文档中有一个可用类型的完整列表。

注意,如果你想将一个列表或一个集合传递到一个JITted函数中,你可能需要使用Numba自己的List()类型来正确处理。

一起使用Numba和NumPy

Numba和NumPy旨在成为合作者,而不是竞争对手。NumPy本身工作得很好,但你也可以用Numba包装NumPy代码以加速其中的Python部分。Numba的文档详细介绍了Numba支持哪些NumPy功能,但绝大多数现有的代码应该可以正常工作。

Numba中的并行处理

如果你一次只能使用其中一个内核,那么16个内核有什么用呢?

Numba使得在多个内核上有效地并行化工作成为可能,并且可以极大地减少交付结果所需的时间。

为了在你的JIT化代码上启用并行化,请在jit()装饰器中添加parallel=True参数。Numba将尽最大努力来确定函数中的哪些任务可以被并行化。如果不成功,你会得到一个错误信息,它将提示为什么代码不能被加速。

你也可以通过使用Numba的prange函数使循环明确地平行化。下面是我们先前的蒙特卡洛pi程序的修改版:

import numbaimport random@numba.jit(parallel=True)def monte_carlo_pi(nsamples): acc = 0 for i in numba.prange(nsamples): x = random.random() y = random.random() if (x ** 2 + y ** 2) < 1.0: acc += 1 return 4. 0 * acc / nsamplesprint(mont_carlo_pi(10_000_000))[ 收听Serdar Yegulalp的Smart Python视频教程,在5分钟内学会聪明的Python技巧 ]

注意,我们只做了两个改动:添加parallel=True参数,以及将for循环中的range函数换成Numba的prange("平行范围")函数。最后一个变化是向Numba发出的信号,即我们希望将该循环中发生的任何事情都并行化。

Numba还提供了一些实用的函数来生成诊断,以了解并行化对你的函数的有效性。如果你没有从使用parallel=True中获得明显的速度提升,你可以倾倒出Numba并行化工作的细节,看看可能出了什么问题。


本文标签

热门标签

会员评论