本文翻译自 Unlocking Python’s Cores: Hardware Usage and Energy Implications of Removing the GIL,原载于 Hacker News。
背景:GIL 的困境
Python 的全局解释器锁(GIL,Global Interpreter Lock)一直是多线程并行执行的瓶颈。即使使用多个线程,GIL 也会阻止代码同时在多个 CPU 核心上执行。这个设计决策最初是为了简化内存管理,但随着多核处理器的普及,GIL 逐渐成为 Python 性能优化的主要障碍。
从 Python 3.13 开始,官方提供了实验性的 free-threaded(无 GIL)构建版本,让开发者可以选择禁用 GIL。虽然之前的研究主要关注速度提升,但对于能耗和硬件利用率的影响却鲜有深入分析。
这项研究正好填补了这个空白,通过四类工作负载对比测试了 GIL 版本和 free-threaded 版本的 Python 3.14.2。
测试方法与工作负载
研究团队设计了四类代表性工作负载:
- NumPy 密集型计算 - 依赖 NumPy 的数值计算任务
- 顺序内核(Sequential Kernels) - 串行执行的代码
- 线程化数值计算(Threaded Numerical Workloads) - 多线程处理独立数值数据
- 线程化对象操作(Threaded Object Workloads) - 多线程频繁访问和修改共享对象
测试指标包括:执行时间、CPU 利用率、内存使用量和能耗。
核心发现:权衡与取舍
1. 可并行化工作负载:显著提升
对于可以并行处理独立数据的任务,free-threaded 版本带来了高达 4 倍的执行时间缩短。
更重要的是,能耗的降低与执行时间的缩短成正比——这意味着更快的完成速度直接转化为更低的能源消耗。同时,CPU 多核利用率显著提升,真正发挥了现代多核处理器的潜力。
代价是内存使用量的增加,这主要是由于每个对象需要独立的锁机制以及运行时额外的线程安全措施。
2. 顺序工作负载:不升反降
如果代码本身是串行执行的,移除 GIL 不仅没有好处,反而会导致 13-43% 的能耗增加。
这是一个关键发现:no-GIL 并非”白嫖”的性能提升,对于不适合并行化的任务,它可能带来负面影响。
3. 共享对象工作负载:锁竞争拖后腿
当多个线程需要频繁访问和修改同一个对象时,由于锁竞争(lock contention),性能提升会大打折扣,甚至可能出现性能下降。
这提醒我们:并行化设计需要仔细考虑数据访问模式,盲目引入多线程可能适得其反。
4. 能耗与执行时间成正比
在所有测试中,能耗与执行时间始终成正比。这说明禁用 GIL 本身并不会显著改变 CPU 的功耗特性,即使 CPU 利用率增加也是如此。
简单来说:跑得快 = 省电。但前提是代码真的能并行执行。
5. 内存占用:普遍增加
no-GIL 构建版本在内存使用上呈现普遍增加的趋势,这在虚拟内存(virtual memory)层面比物理内存(physical memory)更加明显。
内存增加的三个主要原因:
- Per-object Locking - 每个对象都需要独立的锁
- 线程安全机制 - 运行时需要额外的线程安全检查
- 新内存分配器 - free-threaded 版本采用了新的内存管理策略
给开发者的建议
这项研究最重要的结论是:Python 的 no-GIL 构建版本并非通用改进方案。
在考虑是否采用 free-threaded Python 时,建议先评估以下问题:
-
任务是否可并行化? 如果代码本质上是串行的,no-GIL 可能带来额外开销。
-
数据访问模式如何? 线程间是否需要频繁共享和修改对象?如果是,锁竞争可能抵消并行化的好处。
-
内存是否充裕? no-GIL 会增加内存消耗,在内存受限的环境下需要谨慎。
-
能耗是关键指标吗? 如果追求低能耗,确保任务能真正从并行执行中受益。
技术细节
Python 的 no-GIL 实现涉及深层次的架构变更:
传统 GIL 模式:
┌─────────────────────────────────────┐
│ Thread 1 ████░░░░░░░░░░░░░░░░░░░░ │
│ Thread 2 ░░░░░░░░░░░░████░░░░░░░░ │
│ Thread 3 ░░░░░░░░░░░░░░░░░░░░████ │
│ (同一时刻只有一个线程执行) │
└─────────────────────────────────────┘
Free-threaded 模式:
┌─────────────────────────────────────┐
│ Thread 1 ████████████████████████ │
│ Thread 2 ████████████████████████ │
│ Thread 3 ████████████████████████ │
│ (多核并行执行,但需要更多内存开销) │
└─────────────────────────────────────┘
需要注意的是,即使禁用了 GIL,Python 代码仍然需要正确处理线程同步问题。移除 GIL 只是让多线程可以真正并行执行,但不会自动解决数据竞争(data race)等并发问题。
个人思考
这项研究揭示了一个重要的工程现实:没有银弹。
no-GIL Python 的出现无疑是 Python 生态的重要里程碑,对于计算密集型、可并行化的任务(如科学计算、数据处理),它可能带来显著的性能提升。但对于 IO 密集型任务或需要大量共享状态的场景,传统方案(如 asyncio、multiprocessing)可能仍然是更好的选择。
作为开发者,我们应该:
- 理解工具的适用场景 - 不要盲目追求新技术
- 进行基准测试 - 在自己的工作负载上实际测试性能
- 考虑整体架构 - 并行化只是优化手段之一,有时候算法优化或架构调整效果更好
随着 Python 3.13 和未来版本的普及,no-GIL 构建版本将逐渐成熟。期待看到更多实际项目的性能数据和最佳实践分享。
关键要点总结
- 可并行化任务:执行时间最多缩短 4 倍,能耗成正比降低,但内存增加
- 顺序任务:能耗增加 13-43%,无性能收益
- 共享对象任务:锁竞争可能抵消并行化收益
- 能耗规律:与执行时间成正比,不受 GIL 状态显著影响
- 内存开销:普遍增加,虚拟内存层面更明显
- 核心建议:先评估工作负载特性,再决定是否采用 no-GIL 版本