本文介绍了对cpython解释器的并行优化,使其支持真正的多解释器并行执行的解决方案。
作者:字节跳动终端技术——谢俊逸
背景
在业务场景中,我们通过cpython执行算法包,由于cpython的实现,在一个进程内,无法利用CPU的多个核心去同时执行算法包。对此,我们决定优化cpython,目标是让cpython高完成度的支持并行,大幅度的提高单个进程内Python算法包的执行效率。
在2020年,我们完成了对cpython的并行执行改造,是目前业界首个cpython3的高完成度同时兼容Python C API的并行实现。
-
性能
- 单线程性能劣化7.7%
- 多线程基本无锁抢占,多开一个线程减少44%的执行时间。
- 并行执行对总执行时间有大幅度的优化
-
通过了cpython的单元测试
-
在线上已经全量使用
cpython痛, GIL
cpython是python官方的解释器实现。在cpython中,GIL,用于保护对Python对象的访问,从而防止多个线程同时执行Python字节码。GIL防止出现竞争情况并确保线程安全。 因为GIL的存在,cpython 是无法真正的并行执行python字节码的. GIL虽然限制了python的并行,但是因为cpython的代码没有考虑到并行执行的场景,充满着各种各样的共享变量,改动复杂度太高,官方一直没有移除GIL。
挑战
在Python开源的20年里,Python 因为GIL(全局锁)不能并行。目前主流实现Python并行的两种技术路线,但是一直没有高完成度的解决方案(高性能,兼容所有开源feature, API稳定)。主要是因为:
- 直接去除GIL 解释器需要加许多细粒度的锁,影响单线程的执行性能,慢两倍。
Back in the days of Python 1.5, Greg Stein actually implemented a comprehensive patch set (the “free threading” patches) that removed the GIL and replaced it with fine-grained locking. Unfortunately, even on Windows (where locks are very efficient) this ran ordinary Python code about twice as slow as the interpreter using the GIL. On Linux the performance loss was even worse because pthread locks aren’t as efficient.
- 解释器状态隔离 解释器内部的实现充满了各种全局状态,改造繁琐,工作量大。
It has been suggested that the GIL should be a per-interpreter-state lock rather than truly global; interpreters then wouldn’t be able to share objects. Unfortunately, this isn’t likely to happen either. It would be a tremendous amount of work, because many object implementations currently have global state. For example, small integers and short strings are cached; these caches would have to be moved to the interpreter state. Other object types have their own free list; these free lists would have to be moved to the interpreter state. And so on.
这个思路开源有一个项目在做 multi-core-python,但是目前已经搁置了。目前只能运行非常简单的算术运算的demo。对Type和许多模块的并行执行问题并没有处理,无法在实际场景中使用。
新架构-多解释器架构
为了实现最佳的执行性能,我们参考multi-core-python,在cpython3.10实现了一个高完成度的并行实现。
- 从全局解释器状态 转换为 每个解释器结构持有自己的运行状态(独立的GIL,各种执行状态)。
- 支持并行,解释器状态隔离,并行执行性能不受解释器个数的影响(解释器间基本没有锁相互抢占)
- 通过线程的Thread Specific Data获取Python解释器状态。
在这套新架构下,Python的解释器相互隔离,不共享GIL,可以并行执行。充分利用现代CPU的多核性能。大大减少了业务算法代码的执行时间。
共享变量的隔离
解释器执行中使用了很多共享的变量,他们普遍以全局变量的形式存在.多个解释器运行时,会同时对这些共享变量进行读写操作&#x