为什么以下代码不需要 python 解释器?

Why doesn't the following code need a python interpreter?

我正在观看 video 来学习 Numba。在 17:00 时,演示者在屏幕上显示以下代码:

@njit
def simulate_spring_mass_funky_damper(x0, T=10, dt=0.0001, vt=1.0):
    times = np.arange(0, T, dt)
    positions = np.zeros_like(times)
    
    v = 0
    a = 0
    x = x0
    positions[0] = x0/x0
    
    for ii in range(len(times)):
        if ii == 0:
            continue
        t = times[ii]
        a = friction_fn(v, vt) - 100*x
        v = v + a*dt
        x = x + v*dt
        positions[ii] = x/x0
    return times, positions

演示者然后继续指示 numba 使用 jnit(nogil=True) 释放 GIL。他的论点是

This function does not need to access the python interpreter while it is running. In fact, we made sure of that. So we can additionally tell it to release the GIL

演示者然后将此代码与多线程一起使用:

%%time
from concurrent.futures import ThreadPoolExecutor

with ThreadPoolExecutor(8) as ex:
    ex.map(simulate_spring_mass_funky_damper, np.arange(0, 1000, 0.1))

我了解希望与 python 解释器交互的线程需要 GIL。如果代码不需要与 python 解释器交互,那么释放或不释放 GIL 是不是没有实际意义?

  1. 我不明白这个函数怎么不需要python解释器?
  2. 如果代码不需要与GIL交互,那么释放GIL还有什么意义呢?它不会与 python 解释器交互,GIL 也不会发挥作用

I do not understand how this function does not need python interpreter?

Numba 使用 JIT 编译器 (LLVM-Lite) 将 Python 代码翻译成可以在解释器上下文中 运行 的快速二进制文件。当函数使用 @njit 装饰器(或等效的 @jit(nopython=True))时,Numba 会在内部生成两个函数:一个是 wrapper,它将 convert输入 pure-Python objects 到内部原生类型,另一个将执行实际计算(然后输出值由包装函数转换回来)。问题是 像 CPython 列表或字典这样的对象需要使用 CPython 由 GIL 保护,但内部 Numba 原生值不需要 。 Numba 将 CPython 整数对象转换为低级固定大小整数,lists/dictionaries 和 Numpy 数组被类型化并转换为独立于 CPython 的内部数据结构。但是,此方法不适用于所有 CPython 对象:Numba 在释放 GIL 时无法处理 CPython 对象。

Numpy 数组的情况有点特殊,因为 Numpy 被设计为当您使用 原生数组 时可以释放 GIL(仅与它们一起使用)。包含 CPython 对象的 Numpy 数组需要 GIL。另请注意,Numba 不直接使用 Numpy CPython API。它在内部重新实现了 Numpy。

对于列表和字典,Numba 将整个数据结构复制到另一个不需要 GIL 的结构中。这只有在 fully-typed list/dictionaries 下才有可能。包装函数将在将键入的 lists/dictionaries 转换回 CPython 时。此操作开销很大。

简而言之: 在您的情况下,Numba 生成的包装函数 需要 GIL 因为它适用于 CPython objects 但实际的计算功能确实需要GIL。因此,指定 nogil=True 会导致包装函数在调用计算函数之前释放 GIL(然后在执行计算函数后获取 GIL)。

If the code does not need to interact with the GIL then what is even the point of releasing the GIL? It won't interact with python interpreter anyway and GIL won't come into play

好吧,如果您使用 @njit 装饰器(或等效的 @jit(nopython=True)),那么如果顺序调用代码,则指定 nogil=True 通常是无用的,因为无法使用 GIL无论如何(因为根据文档,在这种情况下,Numba“生成不访问 Python C API 的代码”)。

但是,在您的情况下,有许多线程调用 Numba 函数。如果 GIL 未被生成的包装函数释放,则 Numba 计算函数将串行执行(由于线程之间的上下文切换代价高昂,效率低于顺序代码)。释放 GIL 解决了这个问题。请注意,由于 GIL(由于 CPython 对象转换,这是必需的),包装代码仍然串行执行。