【问题标题】:Why Numba's "Eager compilation" slows down the execution为什么 Numba 的“急切编译”会减慢执行速度
【发布时间】:2021-03-30 14:47:28
【问题描述】:

附上一个小例子:

from numba import jit
import numba as nb
import numpy as np

@jit(nb.float64[:, :](nb.int32[:, :])) 
def go_fast(a): 
    trace = 0.0
    for i in range(a.shape[0]):  
        trace += np.tanh(a[i, i]) 
    return a + trace          

@jit 
def go_fast2(a): 
    trace = 0.0
    for i in range(a.shape[0]):  
        trace += np.tanh(a[i, i]) 
    return a + trace 

在 Jupyter 中运行:

x = np.arange(10000).reshape(100, 100)
%timeit go_fast(x)
%timeit go_fast2(x)

导致

每个循环 5.65 µs ± 27.1 ns(7 次运行的平均值 ± 标准偏差,每次 100000 次循环)

每个循环 3.8 µs ± 46.6 ns(平均值 ± 标准偏差,7 次运行,每次 100000 次循环)

为什么急切的编译会导致执行速度变慢?

【问题讨论】:

    标签: python-3.x numpy jupyter numba


    【解决方案1】:

    知道内存访问是连续的,可以简化优化器的生命周期(这里是 an example 用于 Cython,但类似的适用于 numba,即使 clang 通常比 gcc 更聪明)。

    你的例子好像是这样的:

    1. 没有“急切编译”,numba 将检测到数据是 C 连续的并利用它,例如用于矢量化。
    2. 使用 Eager 编译时,您无需提供此信息,因此优化器必须考虑到内存访问可能是不连续的,并且会创建一个性能低于第一个版本的 jit 代码。李>

    因此,您应该提供更精确的签名:

    @jit(nb.float64[:, ::1](nb.int32[:, ::1])) 
    def go_fast3(a): 
        trace = 0.0
        for i in range(a.shape[0]):  
            trace += np.tanh(a[i, i]) 
        return a + trace
    

    [:,::1]tells numba,数据将是 C 连续的,并且一旦使用此信息:

    x = np.arange(10000).astype(np.int32).reshape(100, 100)
    %timeit go_fast(x)     # 15.6 µs ± 241 ns per loop
    %timeit go_fast2(x)    # 8.2 µs ± 90.7 ns per loop
    %timeit go_fast3(x)    # 8.2 µs ± 49.6 ns per loop
    

    热切编译的版本没有区别。

    【讨论】:

    • 在哪里可以找到您使用的语法的指南?
    • 还有一些可以补充的。如果 Numba 在 jit 模式下检测到非连续数组,则如果在此之后使用连续数组调用函数,则不会重新编译该函数。这也是一个经常导致混淆的特性。
    • 你说“即使clang比gcc更聪明”,但你不应该说“numba”或“LLVM”而不是“clang”吗?不是在这里使用clang吗?
    • @JensRenders llvm 是最正确的。 Numba/clang 是使用 llvm 的前端。 cython 使用 clang 前端或 gcc,这就是我使用这对的原因。
    猜你喜欢
    • 2021-01-02
    • 2018-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多