GIL性能分析

5
有没有办法对Python进程使用GIL进行分析?基本上,我想知道GIL被占用的时间百分比。该进程是单线程的。
我的动机是我编写了一些使用nogil的Cython代码。理想情况下,我希望在多线程进程中运行它,但为了知道这是否可能是一个好主意,我需要知道GIL是否有很多空闲时间。
我发现了一个相关问题(这个链接),它是8年前的。那里唯一的答案是“没有”。希望现在情况有所改变。

我在David Beazley的一次关于GIL的演讲中看到了他如何对GIL进行分析:http://www.dabeaz.com/GIL/ - denfromufa
@denfromufa 如果我的问题的答案在这个链接中,请您将其作为答案发布,我会非常感激。 - shx2
3个回答

8

我完全是偶然发现了一个可以胜任这一任务的工具:gil_load

实际上,这个工具是在我发问题之后发布的

干得好,@chrisjbillington。

>>> import sys, math
>>> import gil_load
>>> gil_load.init()
>>> gil_load.start(output = sys.stdout)
>>> for x in range(1, 1000000000):
...     y = math.log(x**math.pi)
[2017-03-15 08:52:26]  GIL load: 0.98 (0.98, 0.98, 0.98)
[2017-03-15 08:52:32]  GIL load: 0.99 (0.99, 0.99, 0.99)
[2017-03-15 08:52:37]  GIL load: 0.99 (0.99, 0.99, 0.99)
[2017-03-15 08:52:43]  GIL load: 0.99 (0.99, 0.99, 0.99)
[2017-03-15 08:52:48]  GIL load: 1.00 (1.00, 1.00, 1.00)
[2017-03-15 08:52:52]  GIL load: 1.00 (1.00, 1.00, 1.00)
<...>

>>> import sys, math
>>> import gil_load
>>> gil_load.init()
>>> gil_load.start(output = sys.stdout)
>>> for x in range(1, 1000000000):
...     with open('/dev/null', 'a') as f:
...         print(math.log(x**math.pi), file=f)

[2017-03-15 08:53:59]  GIL load: 0.76 (0.76, 0.76, 0.76)
[2017-03-15 08:54:03]  GIL load: 0.77 (0.77, 0.77, 0.77)
[2017-03-15 08:54:09]  GIL load: 0.78 (0.78, 0.78, 0.78)
[2017-03-15 08:54:13]  GIL load: 0.80 (0.80, 0.80, 0.80)
[2017-03-15 08:54:19]  GIL load: 0.81 (0.81, 0.81, 0.81)
[2017-03-15 08:54:23]  GIL load: 0.81 (0.81, 0.81, 0.81)
[2017-03-15 08:54:28]  GIL load: 0.81 (0.81, 0.81, 0.81)
[2017-03-15 08:54:33]  GIL load: 0.80 (0.80, 0.80, 0.80)
<...>

0

我不知道有这样的工具。

但是有一些启发式方法可以帮助你猜测是否使用多线程会有所帮助。正如你可能知道的那样,在IO操作期间,GIL将被释放,并且一些调用本地代码的操作,特别是第三方本地模块。如果你没有太多这样的代码,那么多线程可能不会对你有所帮助。

如果你确实有IO/本地代码,那么你可能只能尝试一下。根据代码库的不同,将整个代码库转换以利用多个线程可能需要大量的工作,因此你可能想尝试将多线程应用于你知道正在调用IO/本地代码的部分,并测量是否有任何改进。

根据你的使用情况,对于主要是CPU密集型的情况,多进程可能是可行的。多进程确实增加了开销,因此它通常适用于持续时间相对较长(几秒或更长)的CPU密集型任务。


1
除了第一句话外,这并没有回答问题。特别是因为这个答案忽略了一种情况,即多线程在没有任何IO发生的情况下仍然有用,尽管OP明确提到了它。特别是没有任何理由在多个进程中使用nogil运行cython代码,而不是多个线程。 - Voo
在没有涉及IO或本地(或者说非Python)代码的情况下,多线程会在什么情况下有帮助呢?顺便说一句,我没有看到这个问题提到任何关于IO的内容。 - Heikki Toivonen

0
如果你想知道 GIL 被获取了多少次,你可以使用 gdb 断点。例如:
> cat gil_count_example.py
import sys
import threading
from threading import Thread

def worker():
    k=0
    for j in range(10000000):
        k+=j
    return

num_threads = int(sys.argv[1])
threads = []
for i in range(num_threads):
    t = Thread(target = worker)
    t.start()
    threads.append(t)

for t in threads:
    t.join()

对于3.X,在take_gil处中断

> cgdb --args python3 gil_count_example.py 8
(gdb) b take_gil
(gdb) ignore 1 100000000
(gdb) r
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00007ffff7c85f10 in take_gil
                                                   at Python-3.4.3/Python/ceval_gil.h:208
        breakpoint already hit 1886 times

对于2.X,在PyThread_acquire_lock处中断

> cgdb --args python2 gil_count_example.py 8
(gdb) b PyThread_acquire_lock
(gdb) ignore 1 100000000
(gdb) r
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
  1       breakpoint     keep y   0x00000039bacfd410 
        breakpoint already hit 1584561 times

一种高效的简单分析器也可以用于对函数执行的墙上时间进行分析,我使用 https://github.com/knielsen/knielsen-pmp

 > ./get_stacktrace --max=100 --freq=10 `/sbin/pidof python2`
 ...
 292 71.92% sem_wait:PyThread_acquire_lock

.

> ./get_stacktrace --max=100 --freq=10 `/sbin/pidof python3`
...
557  77.68%  pthread_cond_timedwait:take_gil
这段内容与编程有关,意思是使用命令"./get_stacktrace --max=100 --freq=10 `/sbin/pidof python3`"获取Python3进程的堆栈跟踪信息,并显示在屏幕上。其中,557表示线程ID,77.68%表示该线程在等待GIL时调用pthread_cond_timedwait函数的占比。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接