前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python-3.12 告别 GIL 锁 & 性能原地飞升!

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

作者头像
初代庄主
发布2023-02-20 10:52:04
8.5K0
发布2023-02-20 10:52:04
举报
文章被收录于专栏:初代庄主

概要

多年以来由于全局解释器锁(GIL)的存在,导致 Python 生态一直就没有真正的多线程,也就是说所有线程都运行在同一个核心上,不管你的 CPU 物理上有多少个核心它只用一个。那场面真的是一核有难 8 核围观。

随着 Python 之父的回归,Python 也是越来越看重性能;GIL 这个老大难问题也提上了日程。从最近的讨论我们可以看到 GIL 在 Python-3.12 之后将会是一个可选项。详细的可以看官方的 PEP 703 提案。


GIL 面对 CPU 密集型场景是真的坑

记得刚开始搞 Python 量化投资的时候,逻辑还比较简单整个模型都是自己手撸,也没有用什么第三方库;那时刚入门的我就发现一个问题;我的程序好像只能用到一个核的算力。

后来才知道是 GIL 坑的我,大意了!凡是过往、皆为序章,就此打住。先来构造一个简单的 CPU 密集型场景,体验一下 GIL 有多坑。

代码语言:javascript
复制
#!/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%

2. 在 8 核心的机器上它是这样的,也就是说它也只能用到一个核心的 100%

这么个老实的程序(不算再多的核心它都只用一个),定然不是我们在 cpu 密集型场景下想要看到的。


之前的解决方案

经过多年的磨合,社区为了临时解决这个 GIL 锁的问题,宏观上大致上有 2 类不同的方案。

1. 使用 C/C+ 编写处理逻辑,在这个里面就完全没有 GIL 的限制了,想怎么玩就怎么玩,非常的自由; 最后只能由 Python 去调用相应的处理逻辑就行。这个对动手能力的要求就比较高。

2. 第二个方案就比较简单,就是直接多开几个进程,不同的进程处理不同的数据。可以说是简单粗暴,直接有效。我们这里用第二个方案演示一下。

代码语言:javascript
复制
# 开两个进程
python3 mult-threads.py &
python3 mult-threads.py &

不优雅就是原罪

前面我们提到的两个绕过 GIL 的方案都不太优雅,优雅的解决方案就应该是把 GIL 锁拿掉。以前也不是没有大牛做过这个事,只是他们都失败了。

这次 703 没有之前那么激进,而是把 GIL 做成一个可选项,在编译时安装时指定要不要编译一个没有 GIL 的版本。另外这次的不同之处在于这个优化进了 PEP ,也就是说这次有官方背书

希望他们能成功!我用了一个内部的版本测试了下,性能可以说是原地飞升!!!Python 再也不是那个多线程不行小老弟了。


Python 新版本测试

这个新版本的不方便之处就是它要重新编译安装解释器,并且有可能还有一些特殊场景下的兼容性问题要适配,不过我们上面的例子不存在不兼容性的事,可以直接测试。

1. 编译时的关键参数

代码语言:javascript
复制
./configure --prefix=/usr/local/python-nogil --enable-optimizations

2. 代码一行不改还是直接上用线程池

代码语言:javascript
复制
#!/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. 启动程序

代码语言:javascript
复制
python3 mult-threads.py &

4. 观察没有 GIL 的 CPU 使用情况

可以看到这下一个进程占满了所有的 CPU 核心,牛逼++ !


最后

私信回复 “disable-gil” 获取源代码 !!!

都到这里了,是时候图穷匕见了!我这人比较 real 就直说了,我想涨粉帮忙点下关注!我的技术文章质量还可以,关注应该不亏。

“在看” + “分享” + “点赞” + “收藏” 也是我继续写下去的动力;再次感谢!!!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 初代庄主 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档