• Python-3.13.0 去掉 GIL 性能原地起飞
  • 发布于 1个月前
  • 83 热度
    0 评论
概要
以前的 Python 由于有 GIL 锁的存在,不管机器上的 CPU 有多少核心,它只用一个核。也就说 Python 的多线程发挥不出 CPU 的性能。于 2024-10-01 发布的 Python-3.13.0 版本,可以体验去掉 GIL 特性,我编译测试了下,确实强多了!

编译安装 Python-3.13.0 版本
用下面的代码完成下载、编译、安装; 
wget wget https://www.python.org/ftp/python/3.13.0/Python-3.13.0.tgz
tar -xvf Python-3.13.0.tgz
cd Python-3.13.0
 ./configure --disable-gil --enable-optimizations \
   --prefix=/usr/local/python-3.13.0 && \
 make && \
 make install
搞完上面这一套,你就有了一个没有 GIL 的解释器了;但是要说新解释器强在哪里,这个还要从老的解释器说起。

老版本 Python 多线程的假象
由于有 GIL 锁的存在,老版本的 Python 解释器进程中同一时刻至多只有一个线程在执行;也就是说,就算业务代码里面各个线程之间没有同步关系,实际上也并发不了,原因是解释器层面的这个 GIL 锁给你限死了。假设我们有这么一段线程间完全独立且 CPU 密集型的业务代码
#!/usr/bin/env python3


import sys
from datetime import datetime
import threading


ITER_TIMES = 100000000


def foo(times: int = ITER_TIMES):
    start_at = datetime.now()
    s = 0
    for i in range(times):
        s = s + 1

    end_at = datetime.now()

    print(
        "    python-{} 线程 {}   执行 foo() 执行耗时 = {} s = {} ".format(
            sys.version[:6], threading.current_thread().ident, end_at - start_at, s
        )
    )


def main():
    print("程序开始执行")
    # 开 10 个线程
    threads = [threading.Thread(target=foo) for _ in range(10)]

    start_at = datetime.now()
    for thread in threads:
        thread.start()

    for thread in threads:
        thread.join()

    end_at = datetime.now()

    print(
        "python-{} 版下 main 函数执行耗时 = {}s ".format(
            sys.version[:6], end_at - start_at
        )
    )
if __name__ == "__main__":
    main()
用系统自带的 Python-3.9.9 版本跑起来是这个一个效果,你说它没有多线程吧;从操作系统层面可以看到它是有多线程的。
ps -ef | grep main.py
root     3295935 3258202 93 21:50 pts/4    00:00:18 python main.py
root     3296071 3250835  0 21:50 pts/8    00:00:00 grep --color=auto main.py

ps -Tp 3295935
    PID    SPID TTY          TIME CMD
3295935 3295935 pts/4    00:00:00 python
3295935 3295936 pts/4    00:00:02 python
3295935 3295937 pts/4    00:00:02 python
3295935 3295944 pts/4    00:00:02 python
3295935 3295945 pts/4    00:00:02 python
3295935 3295946 pts/4    00:00:02 python
3295935 3295947 pts/4    00:00:02 python
3295935 3295948 pts/4    00:00:02 python
3295935 3295949 pts/4    00:00:02 python
3295935 3295950 pts/4    00:00:02 python
3295935 3295951 pts/4    00:00:02 python
如果我们这个时候用 top 来观察,会发现它只用了 1 个核心(由于有上下文切换我们看到的就是 2 个核心,各自用 50%; 如果你的机器是 4 核心,那么你应该能看到每个核心都是 25% 的使用率)。
top

top - 21:51:08 up 76 days, 10:22,  3 users,  load average: 2.13, 1.92, 1.75
Tasks: 139 total,   1 running, 138 sleeping,   0 stopped,   0 zombie
%Cpu0  : 50.3 us,  1.7 sy,  0.0 ni, 47.7 id,  0.3 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 51.3 us,  1.3 sy,  0.0 ni, 47.0 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
MiB Mem :   7432.3 total,    224.5 free,   6160.0 used,   1047.8 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.    947.4 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
3295935 root      20   0  748620  11088   4756 S 100.3   0.1   1:02.89 python
Python-3.13.0 版本的涅槃
同一个程序我们用新版本跑起来是这样的
ps -ef | grep main.py
root     3296632 3258202 99 21:52 pts/4    00:00:16 /usr/local/python-3.13.0/bin/python3 main.py
root     3296717 3250835  0 21:52 pts/8    00:00:00 grep --color=auto main.py


ps -Tp 3296632
    PID    SPID TTY          TIME CMD
3296632 3296632 pts/4    00:00:00 python3
3296632 3296633 pts/4    00:00:03 python3
3296632 3296634 pts/4    00:00:03 python3
3296632 3296635 pts/4    00:00:03 python3
3296632 3296636 pts/4    00:00:03 python3
3296632 3296637 pts/4    00:00:03 python3
3296632 3296638 pts/4    00:00:03 python3
3296632 3296639 pts/4    00:00:03 python3
3296632 3296640 pts/4    00:00:03 python3
3296632 3296641 pts/4    00:00:03 python3
3296632 3296642 pts/4    00:00:03 python3
还是老样子看 top 
top - 21:53:35 up 76 days, 10:24,  3 users,  load average: 3.40, 2.47, 1.98
Tasks: 140 total,   1 running, 139 sleeping,   0 stopped,   0 zombie
%Cpu0  : 99.3 us,  0.7 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 98.7 us,  1.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7432.3 total,    370.7 free,   6007.6 used,   1054.0 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   1099.8 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND 
3297109 root      20   0 1795452  42880   5288 S 196.7   0.6   0:20.27 python3
可以看到这次每一个核心都用到了接近 100% ,说明这个 GIL 没有了之后,提升还是非常明显的。

Python-3.9.9 vs Python-3.13.0
Python-3.9.9 是系统自带的版本, Python-3.13.0 是我刚编译好的一个版本,我的开发云主机是双核心的,两个不同解释对同一个程序的耗时表现如下。

Python-3.9.9
python main.py 
python-3.9.9  线程 139869657892416   执行 foo() 执行耗时 = 0:01:01.385917 s = 100000000 
python-3.9.9  线程 139869275801152   执行 foo() 执行耗时 = 0:01:02.075561 s = 100000000 
python-3.9.9  线程 139869300979264   执行 foo() 执行耗时 = 0:01:03.367839 s = 100000000 
python-3.9.9  线程 139869674677824   执行 foo() 执行耗时 = 0:01:08.089851 s = 100000000 
python-3.9.9  线程 139869666285120   执行 foo() 执行耗时 = 0:01:09.167412 s = 100000000 
python-3.9.9  线程 139869292586560   执行 foo() 执行耗时 = 0:01:09.030335 s = 100000000 
python-3.9.9  线程 139869284193856   执行 foo() 执行耗时 = 0:01:09.061387 s = 100000000 
python-3.9.9  线程 139869691463232   执行 foo() 执行耗时 = 0:01:09.642818 s = 100000000 
python-3.9.9  线程 139869649499712   执行 foo() 执行耗时 = 0:01:09.646820 s = 100000000 
python-3.9.9  线程 139869683070528   执行 foo() 执行耗时 = 0:01:09.967401 s = 100000000 
python-3.9.9  版下 main 函数执行耗时 = 0:01:09.978749s 

Python-3.13.0
/usr/local/python-3.13.0/bin/python3 main.py 
python-3.13.0 线程 140078739351104   执行 foo() 执行耗时 = 0:00:34.497822 s = 100000000 
python-3.13.0 线程 140078730958400   执行 foo() 执行耗时 = 0:00:34.534890 s = 100000000 
python-3.13.0 线程 140078839526976   执行 foo() 执行耗时 = 0:00:34.689895 s = 100000000 
python-3.13.0 线程 140078831134272   执行 foo() 执行耗时 = 0:00:34.766135 s = 100000000 
python-3.13.0 线程 140078143764032   执行 foo() 执行耗时 = 0:00:34.783514 s = 100000000 
python-3.13.0 线程 140078705780288   执行 foo() 执行耗时 = 0:00:34.808335 s = 100000000 
python-3.13.0 线程 140078747743808   执行 foo() 执行耗时 = 0:00:34.878912 s = 100000000 
python-3.13.0 线程 140078722565696   执行 foo() 执行耗时 = 0:00:34.864017 s = 100000000 
python-3.13.0 线程 140078697387584   执行 foo() 执行耗时 = 0:00:34.831461 s = 100000000 
python-3.13.0 线程 140078714172992   执行 foo() 执行耗时 = 0:00:34.879561 s = 100000000 
python-3.13.0 版下 main 函数执行耗时 = 0:00:34.923439s

同样的程序耗时由之前的 69s 下降到 34s ,耗时少一半;跟之前只能用一个核心现在能用两个核心,数值上能对上。

结论
从测试来看没有 GIL 之后性能上确实是有明显提升的,至于特定场景下能提升多说少,这个估计业务场景才是大头。
用户评论