移除 GIL,可显著提升多线程性能么?
来源:开源中国
近日,一位名叫 Sam Gross 的开发者提出了一个对全局解释器锁(GIL)进行重大修改的设想。其目标在于移除 CPython 中的 GIL,以使得多线程能够并行执行 Python 代码。目前,该项目已经引起了 Python 核心开发团队的关注。
我一直在对 CPython 进行修改,使其能够在没有全局解释器锁的情况下运行。我想与大家分享一个可以在没有 GIL 的情况下运行的概念验证。这个概念验证涉及到对 CPython 内部的大量修改,但对 C-API 的修改相对较少。它可以与许多 C 语言扩展兼容:扩展必须被重建,但通常只需要对源代码进行较小的修改或不需要修改。我已经从科学的 Python 生态系统中构建了兼容的软件包版本,它们可以通过捆绑的"pip"来安装。
Gross 在设计文档中详细列举了移除 GIL 需要承担的一些风险以及他认为要移除 GIL 的理由:
风险
移除 GIL 将是一项大型、多年的任务,引入错误和回归的风险会增加。 移除 GIL 意味着在单线程和多线程性能之间进行权衡。“我相信 - 我们可以创建一个没有 GIL 的 CPython,它在单线程和多线程工作负载方面都比当前的 CPython 更快;但是如果我们保留 GIL 并只专注于单线程工作负载,我们将实现更好的单线程性能”。(有关更多详细信息,可参阅“Performance”部分。) 大多数 Python 程序都没有针对多线程性能进行优化;他们可能需要更改以利用没有 GIL 的运行。 多线程程序容易出现并发错误。尽管 Python 已经支持线程(with the GIL),但成功移除 GIL 可能会增加线程的使用,从而导致 Python 程序员暴露于并发错误。
为什么要移除 GIL?
GIL 是并发的主要障碍。对于科学计算任务,这种缺乏并发性通常比执行 Python 代码的速度更重要,因为大部分处理器周期都花费在优化的 CPU 或 GPU 内核中。GIL 引入了一个全局瓶颈,如果其他线程调用任何 Python 代码,它都会阻止其他线程取得进展。 在“Why Swift for Tensorflow”文档中,Chris Lattner 写道“GIL 在大规模设置中增加了整个堆栈的复杂性:它使模型作者无法为某些事情实现合理的性能(无需自己编写 C++/CUDA),并阻止模型中的并发算法的自然表达。” 围绕 GIL 进行了大量工程工作。这样做的后果之一是,简单地删除 GIL 不太可能神奇地提高现有代码的速度。但是 Gross 认为,移除 GIL 将可以更容易的利用并行性、简化新库的开发、并允许改进现有库。
Gross 称,自己进行概念验证的目的是想要证明,取消 GIL 是可行的和值得的;且这个项目的技术想法可以作为这种努力的基础。同时,他也想就这些想法和大家展开讨论,并衡量社区对这种移除 GIL 的方法的兴趣。
如果 Gross 的提议被接受,就会重写 Python 在其运行时从多个线程访问对象的序列化方式,并将显着提高多线程的性能。这个项目对 Python 字节码解释器(ceval.c)进行了大幅修改;目的在于提高单线程性能,减少引用计数变化对性能的影响,以及提高多线程运行时的可扩展性。
测试结果表明,无 GIL 概念验证的解释器在单线程的 pyperformance 基准上比 CPython 3.9(和 3.10)快 10% 左右。截至 2021 年 9 月初,它的平均性能与 CPython 3.11 的"main"分支大致相同。在一些基准测试中,多线程性能实现了良好的提升。例如,在有 20 个线程的情况下,有一个基准测试中的任务速度提高了 18.1 倍,另一个基准测试中的任务速度则提高了 19.8 倍。
InfoWorld 认为,Gross 的提议包含了很大的更改,以至于相当数量的直接与 Python 内部运作的现有 Python 库(例如 Cython))都需要进行重写。但是,Python 的发布时间表的节奏则意味着,这种破坏性变更需要在一个主要版本而不是次要版本中进行发布。
还不过瘾?试试它们