(如您所见,我对Python GIL和PYTHON(或cython(中的多线程等概念不是很熟悉(
我在 Cython 中编写了一个函数,它由一段带有双 for 循环的代码片段组成,其中重复调用函数 f。
for i in range(I):
for j in range(J):
res=f(A[i],B[j])
我有一台有 4 个 CPU 内核的机器,我想并行化不是第一个而是第二个循环。 我发现了这个很棒的网站,但它没有处理内部循环的情况,也没有详细介绍。 所以在我看来,我可以写:
for i in range(I):
#In what case can I release the GIL safely ? Is that necessary at all ?
with nogil, parallel(num_threads=4):
for j in prange(J,shedule="dynamic"):
res=f(A[i],B[j])
那行得通吗?我是否必须将 with nogil 放在两个循环之外,这样它就不会反复运行到它释放和"捕获"这个 GIL 的东西?有人可以向我解释一下写这些陈述背后的逻辑和逻辑是什么,以便我能够概括到看不见的问题。
释放和重新捕获 GIL 需要时间成本,设置并行循环也需要时间成本。出于这个原因,通常最好将最外层的循环设置为并行循环。但是,如果您有充分的理由专门要并行化内部循环,那么它将起作用,并且希望与f
中包含的实际工作相比,成本应该很小。
释放 GIL 可防止您访问 Python 变量和调用 Python 函数。类型化的Cython变量,cdef
函数和Cython内存视图工作正常。您将通过尽可能远离with nogil:
来获得一个小的加速。因此,如果可能的话,把它放在外循环周围,但如果不可能,那么在你展示它的地方
就可以了。有必要释放到 GIL 以进行prange
循环。如有必要,您可以在循环中回收它(with gil
(,但尝试只对循环的一小部分执行此操作,并且仅在需要时执行此操作(需要 GIL 的代码不能与其他需要 GIL 的代码并行运行(。
对于并行代码,行res=f(A[i],B[j])
有点奇怪,因为只会保存最后一个循环中的res
。通常你会写入数组的元素(例如res[i,j]=f(A[i],B[j])
(。但是,像您展示的那样这样做可能有充分的理由......
Cython 会(通常(警告您,如果您尝试做需要 GIL 的事情,所以一个好主意是尝试一下看看。