关于python:使用-Numba-让-Python-计算得更快两行代码提速-13-倍

53次阅读

共计 2928 个字符,预计需要花费 8 分钟才能阅读完成。

Python 自身是一门运行较慢的语言,因而对于计算场景,最好的优化形式就是优化代码写法。你能够应用现有的科学计算库:比方 Numpy 和 Scipy。但如果想要在不应用低级语言(如 CPython、Rust 等)实现扩大的前提下实现一个新的算法时,该如何做呢?

对于某些特定的、尤其是针对数组的计算场景,Numba 能够显著放慢代码的运行速度。在应用时,咱们有时候须要调整一下原始代码,而有时候却又不须要做任何改变。当它真正起到作用时,成果将会非常明显。

在本篇文章中,咱们谈判及以下几方面:

  • 为什么 有时候独自应用 Numpy 是不够的
  • Numba 的根底应用形式
  • Numba 是如何在很高的档次上来对你的代码运行造成影响的

Numpy”心有余而力不足“的时刻

假如你想要将一个十分大的数组转变为按递增程序排序:很好了解,就是将元素按值的大小升序排列,如:

[1, 2, 1, 3, 3, 5, 4, 6] → [1, 2, 2, 3, 3, 5, 5, 6]

以下是一个简略的就地转换形式:

def monotonically_increasing(a):
    max_value = 0
    for i in range(len(a)):
        if a[i] > max_value:
            max_value = a[i]
        a[i] = max_value

Numpy 运行很快,是因为它能够在不调用 python 本身解释器的前提下实现所有计算。但对于下面这个场景(python 中的循环),就会暴露出一个问题:咱们会失去 Numpy 得天独厚的性能劣势。

对一个含有一千万个元素的 Numpy 数组应用下面的函数进行转换,在我的电脑上须要运行 2.5 秒。那么,还能够优化得更快吗?

应用 Numba 提速

Numba 是一款为 python 打造的、专门针对 Numpy 数组循环计算场景的即时编译器。显然,这正是咱们所须要的。让咱们在原有函数的根底上增加两行代码试试:

from numba import njit

@njit
def monotonically_increasing(a):
    max_value = 0
    for i in range(len(a)):
        if a[i] > max_value:
            max_value = a[i]
        a[i] = max_value

再次运行,发现仅须要 0.19 秒,在齐全重用旧代码逻辑的前提下,感觉成果还不错。

实际上 Numpy 也有一个非凡的函数能够解决这种场景(然而会批改原有函数的代码逻辑):numpy.maximum.accumulate[1]。通过应用它,函数的运行时长会缩短至 0.03 秒。

Runtime
Python for loop 2560ms
Numba for loop 190ms
np.maximum.accumulate 30ms

Numba 简介

在 Numpy 或 Scipy 中找到指标函数,能够很快解决常见的计算问题。然而如果函数不存在呢?(比方刚刚的 numpy.maximum.accumulate)。这种状况下如果想减速代码运行。可能会抉择其余低级的编程语言来实现扩大[2],但这也意味着切换编程语言,会让模块构建和零碎总体变得更简单。

应用 Numba 你能够做到:

  • 应用 python 和领有更快编译速度的解释器运行同一份代码
  • 简略疾速地迭代算法

Numba 首先会解析代码,而后依据数据的输出类型以即时的形式编译它们。例如,当输出是 u64 数组和浮点型数组时,别离失去的编译后果是不一样的。

Numba 还能够对非 CPU 的计算场景失效:比方你能够 在 GPU 上运行代码 [3]。诚然,上文中的示例只是 Numba 的一个最小利用,官网文档[4] 中还有很多个性可供选择。

Numba 的一些短板

须要一次代码编译耗时

当第一次调用 Numba 润饰的函数时,它须要破费肯定的工夫来生成对应的机器代码。比方,咱们能够应用 IPython 的 %time 命令来计算运行一个 Numba 润饰的函数须要破费多长时间:

In [1]: from numba import njit

In [2]: @njit
   ...: def add(a, b): a + b

In [3]: %time add(1, 2)
CPU times: user 320 ms, sys: 117 ms, total: 437 ms
Wall time: 207 ms

In [4]: %time add(1, 2)
CPU times: user 17 µs, sys: 0 ns, total: 17 µs
Wall time: 24.3 µs

In [5]: %time add(1, 2)
CPU times: user 8 µs, sys: 2 µs, total: 10 µs
Wall time: 13.6 µs

能够看到,函数第一次调用后运行十分慢(留神单位时毫秒而不是微秒),这就是因为它须要工夫来编译生成机器代码。不过函数前面的运行速度会显著晋升。这种工夫老本在输出数据的类型发生变化时会再次耗费,比方,咱们将输出类型换为浮点数:

In [8]: %time add(1.5, 2.5)
CPU times: user 40.3 ms, sys: 1.14 ms, total: 41.5 ms
Wall time: 41 ms

In [9]: %time add(1.5, 2.5)
CPU times: user 16 µs, sys: 3 µs, total: 19 µs
Wall time: 26 µs

计算两数之和当然不须要启用 Numba,这里用这个案例是因为可能比拟容易地看出编译所需的工夫老本。

与 python 和 Numpy 的不同实现形式

Numba 在性能方面能够说是实现了 python 的一个子集,也能够说是实现了 Numpy API 的一个子集,这将会导致一些潜在的问题:

  • 会呈现 python 和 Numpy 局部个性都不反对的状况
  • 因为 Numba 从新实现了 Numpy 的 API,在应用时可能会呈现以下状况
    • 因为应用的不必的算法,两者的性能体现会有区别
    • 可能会因为 bug 导致后果不统一
  • 另外,当 Numba 编译失败时,其裸露的错误信息可能会很难了解

Numba 与其余选项的比照

  • 仅应用 Numpy 和 Scipy:能够让 python 代码运行时达到其余语言编译器的速度,然而对于某些循环计算的场景不失效
  • 间接应用低级语言编写代码:这意味着你能够优化所有的代码语句,然而须要摈弃 python 应用另一门语言
  • 应用 Numba:能够优化 python 循环计算的场景,然而对于某些 python 语言自身和 Numpy API 的个性应用会受到限制

结语

Numba 最棒的中央在于尝试起来非常简单。因而每当你有一个做一些数学运算且运行迟缓的 for 循环时,能够尝试应用 Numba:运气好的话,它只须要两行代码就能够显著放慢代码运行速度。

以上就是本次分享的所有内容,如果你感觉文章还不错,欢送关注公众号:Python 编程学习圈,每日干货分享,发送“J”还可支付大量学习材料。或是返回编程学习网,理解更多编程技术常识。

正文完
 0