我有一些想要尽快运行的 Cython 代码。我是否需要释放 GIL 才能执行此操作?
假设我的代码与此类似:
import numpy as np
# trivial definition just for illustration!
cdef double some_complicated_function(double x) nogil:
return x
cdef void func(double[:] input) nogil:
cdef double[:] array = np.zeros_like(input)
for i in range(array.shape[0]):
array[i] = some_complicated_function(input[i])
Run Code Online (Sandbox Code Playgroud)
我从np.zeros_like类似于以下内容的行中收到大量错误消息:
nogilcode.pyx:7:40: Calling gil-requiring function not allowed without gil
nogilcode.pyx:7:29: Accessing Python attribute not allowed without gil
nogilcode.pyx:7:27: Accessing Python global or builtin not allowed without gil
nogilcode.pyx:7:40: Constructing Python tuple not allowed without gil
nogilcode.pyx:7:41: Converting to Python object not allowed without gil
Run Code Online (Sandbox Code Playgroud)
我是否需要找到一种np.zeros_like没有 GIL的调用方式?或者找到一些其他方法来分配不需要 GIL 的数组?
注意:这是一个自我回答的问题,旨在消除对 Cython 和 GIL 的一些常见误解(当然,当然也欢迎您回答它!)
不- 您可能不需要发布 GIL。
GIL(全局解释器锁)的基本功能是通过确保一次只能运行一个 Python 线程来确保 Python 的内部机制不受竞争条件的影响。然而,仅仅持有 GIL 并不会减慢您的代码速度。
您应该发布 GIL 的两个(相关)场合是:
使用Cython 的并行机制。prange例如,循环的内容必须是nogil.
如果您希望其他(外部)Python 线程能够同时运行。
一种。如果您有一个不需要 GIL 的大型计算/IO 密集型块,那么发布它可能是“礼貌的”,只是为了使想要进行多线程的代码用户受益。然而,这主要是有用的而不是必要的。
湾 (非常非常偶尔)用一个短with nogil: pass块来短暂释放 GIL 有时很有用。这是因为 Cython 不会自发释放它(与 Python 不同),因此如果您正在等待另一个 Python 线程完成任务,这可以避免死锁。除非您使用 Cython 编译 GUI 代码,否则这个子点可能不适用于您。
可以在没有 GIL 的情况下运行的 Cython 代码(不调用 Python,纯 C 级数字运算)通常是高效运行的代码。这有时会给人们一种印象,反之亦然,而诀窍是释放 GIL,而不是他们正在运行的实际代码。不要被这个误导——你的(单线程)代码在有或没有 GIL 的情况下都会以相同的速度运行。
因此,如果您有一个很好的快速 Numpy 函数,它可以在大量数据上快速执行您想要的操作,但只能使用 GIL 调用,那么只需调用它 - 不会造成任何伤害!
最后一点:即使在一个nogil块内(例如一个prange循环),如果需要,您也可以随时取回 GIL:
with gil:
... # small block of GIL requiring code goes here
Run Code Online (Sandbox Code Playgroud)
尽量不要经常这样做(获取/释放它需要时间,当然一次只能有一个线程运行这个块),但同样这是在需要时执行小型 Python 操作的好方法。