Python-3.12 告别 GIL 锁 & 性能原地飞升!

2023-05-2617:18:06编程语言入门到精通Comments945 views字数 2243阅读模式
多年以来由于全局解释器锁(GIL)的存在,导致 Python 生态一直就没有真正的多线程,也就是说所有线程都运行在同一个核心上,不管你的 CPU 物理上有多少个核心它只用一个。那场面真的是一核有难 8 核围观。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

随着 Python 之父的回归,Python 也是越来越看重性能;GIL 这个老大难问题也提上了日程。从最近的讨论我们可以看到 GIL 在 Python-3.12 之后将会是一个可选项。详细的可以看官方的 PEP 703 提案。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

Python-3.12 告别 GIL 锁 & 性能原地飞升!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html


GIL 面对 CPU 密集型场景是真的坑文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

记得刚开始搞 Python 量化投资的时候,逻辑还比较简单整个模型都是自己手撸,也没有用什么第三方库;那时刚入门的我就发现一个问题;我的程序好像只能用到一个核的算力。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

后来才知道是 GIL 坑的我,大意了!凡是过往、皆为序章,就此打住。先来构造一个简单的 CPU 密集型场景,体验一下 GIL 有多坑。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

#!/usr/bin/env python3# -*- encoding: utf8 -*-
"""测试多线程下 CPU 密集型场景 GIL 的性能表现"""

from concurrent.futures import ThreadPoolExecutor
def fun_sum(max_number:int = 0):    """从 0 累加到 max_number -1 
    Parameter:    ----------    max_number: int
    Return:    -------        int    """    if max_number <= 0:        return 0        total = 0    for i in range(max_number):        total = total + i    print("total = {0}".format(total))    return total

def main():    threads = 8    max_number = 10000000000    with ThreadPoolExecutor(max_workers=threads) as executor:        for _ in range(threads):            executor.submit(fun_sum, max_number)
if __name__ == "__main__":    main()

1. 在双核的机器上它的表现如下,也就是说它只能用到一个核心的 100% 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

Python-3.12 告别 GIL 锁 & 性能原地飞升!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

2. 在 8 核心的机器上它是这样的,也就是说它也只能用到一个核心的 100% 。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

Python-3.12 告别 GIL 锁 & 性能原地飞升!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

这么个老实的程序(不算再多的核心它都只用一个),定然不是我们在 cpu 密集型场景下想要看到的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html


 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

之前的解决方案文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

经过多年的磨合,社区为了临时解决这个 GIL 锁的问题,宏观上大致上有 2 类不同的方案。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

1. 使用 C/C+ 编写处理逻辑,在这个里面就完全没有 GIL 的限制了,想怎么玩就怎么玩,非常的自由; 最后只能由 Python 去调用相应的处理逻辑就行。这个对动手能力的要求就比较高。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

2. 第二个方案就比较简单,就是直接多开几个进程,不同的进程处理不同的数据。可以说是简单粗暴,直接有效。我们这里用第二个方案演示一下。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

# 开两个进程python3 mult-threads.py &python3 mult-threads.py &

 文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

Python-3.12 告别 GIL 锁 & 性能原地飞升!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html


不优雅就是原罪文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

前面我们提到的两个绕过 GIL 的方案都不太优雅,优雅的解决方案就应该是把 GIL 锁拿掉。以前也不是没有大牛做过这个事,只是他们都失败了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

这次 703 没有之前那么激进,而是把 GIL 做成一个可选项,在编译时安装时指定要不要编译一个没有 GIL 的版本。另外这次的不同之处在于这个优化进了 PEP ,也就是说这次有官方背书文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

希望他们能成功!我用了一个内部的版本测试了下,性能可以说是原地飞升!!!Python 再也不是那个多线程不行小老弟了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html


Python 新版本测试文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

这个新版本的不方便之处就是它要重新编译安装解释器,并且有可能还有一些特殊场景下的兼容性问题要适配,不过我们上面的例子不存在不兼容性的事,可以直接测试。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

1. 编译时的关键参数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

./configure --prefix=/usr/local/python-nogil --enable-optimizations

2. 代码一行不改还是直接上用线程池文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

#!/usr/bin/env python3# -*- encoding: utf8 -*-
"""测试多线程下 CPU 密集型场景 GIL 的性能表现"""

from concurrent.futures import ThreadPoolExecutor
def fun_sum(max_number:int = 0):    """从 0 累加到 max_number -1 
    Parameter:    ----------    max_number: int
    Return:    -------        int    """    if max_number <= 0:        return 0        total = 0    for i in range(max_number):        total = total + i    print("total = {0}".format(total))    return total

def main():    threads = 8    max_number = 10000000000    with ThreadPoolExecutor(max_workers=threads) as executor:        for _ in range(threads):            executor.submit(fun_sum, max_number)
if __name__ == "__main__":    main()

3. 启动程序文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

python3 mult-threads.py &

4. 观察没有 GIL 的 CPU 使用情况文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

Python-3.12 告别 GIL 锁 & 性能原地飞升!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

可以看到这下一个进程占满了所有的 CPU 核心,牛逼++ !文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/42699.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/42699.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定