关于 Python 3.13 你所需要知道的几点

简介: 关于 Python 3.13 你所需要知道的几点

什么是全局解释器锁 (GIL)?

自20世纪80年代末,Guido Van Rossum在荷兰阿姆斯特丹东部的一个科技园区开始开发Python编程语言,它最初被设计为一种单线程的解释型语言。这到底是什么意思呢?

你可能会听说,编程语言分为解释型和编译型两种。那么Python属于哪一类?答案是:它属于两者。

实际上,你很少遇到完全由解释器直接从源代码进行解释的编程语言。对于解释型语言而言,可读的源代码通常会被转换成一种中间形式,也就是字节码。然后,解释器会逐条读取这些字节码并执行指令。

这里,“解释器”通常被称作“虚拟机”,尤其是在Java等其他语言中,它们和Python一样,都是通过虚拟机来处理字节码。在Java等语言中,更常见的做法是直接分发编译后的字节码,而Python应用则通常以源代码的形式分发(尽管现在,包也经常以wheel和sdist的形式部署)。

这种意义上的虚拟机在许多意想不到的地方都会出现,比如在PostScript格式中(PDF文件本质上就是编译后的PostScript),以及在字体渲染过程中。

如果你在自己的Python项目中看到过很多*.pyc文件,这些就是你的应用程序编译后的字节码。你可以像探索Java类文件一样,对这些pyc文件进行反编译和研究。

当们执行Python程序时,Python的执行程序会生成一系列指令流,即字节码,然后解释器会逐一读取并执行这些指令。

如果你启动了多个线程,它们将共享相同的内存空间(局部变量除外),因此它们都能访问和修改相同的数据对象。每个线程都有自己独立的栈和指令指针来执行字节码。

如果多个线程同时尝试访问或修改同一个对象,比如一个线程在向字典中添加内容,而另一个线程在读取字典,这时你有两个选择:

  1. 让字典和其他所有对象都实现线程安全,这需要付出很多努力,并且对于单线程应用来说会降低效率;
  2. 创建一个全局互斥锁(通常称为mutex),这样任何时候都只有一个线程能够执行字节码。

后者就是们所说的全局解释器锁(GIL)。而前者是Python开发者提到的“自由线程”模式。

另外值得一提的是,GIL简化了垃圾回收的过程,使其变得更快速。由于垃圾回收本身是一个复杂的话题,们在这里不深入讨论,但简单来说,Python会跟踪一个对象被引用的次数,当引用计数降到零时,Python就知道可以安全地删除这个对象了。如果多个线程同时创建和删除对不同对象的引用,可能会导致竞态条件和内存损坏,因此任何自由线程的版本都需要为所有对象使用原子计数的引用。

GIL还简化了Python的C扩展开发(例如使用Cython),因为你可以在开发时做一些关于线程安全的假设,这会让你的工作更加轻松。

为什么会有全局解释器锁?

尽管Python近年来变得非常流行,但它并不是一门新语言——它在20世纪80年代末诞生,首次发布于1991年2月20日(比稍微早一点)。在那个时代,计算机的形态与现在大相径庭。大多数程序都是单线程的,单个CPU核心的性能正以摩尔定律所描述的那样呈指数级增长。在那种环境下,对于大多数不使用多核的程序来说,牺牲单线程性能以换取线程安全是没有必要的。

而且,实现线程安全显然需要付出很多努力。

这并不是说你不能在Python中利用多核CPU。只是说,你不能通过线程来实现,而必须通过多个进程(例如使用multiprocessing模块)。

多进程与多线程的主要区别在于,每个进程都有自己的Python解释器和独立的内存空间。这意味着多个进程无法访问内存中的相同对象,而必须使用特殊的机制和通信方式来共享数据(可以参考“在进程间共享状态”和multiprocessing.Queue)。

值得一提的是,与多线程相比,使用多进程会有更大的开销,而且在数据共享上也更加困难。

然而,使用多线程并不总是像人们通常认为的那样糟糕。如果Python正在进行I/O操作,比如读取文件或发起网络请求,它会释放GIL,让其他线程得以运行。这意味着,如果你的程序主要是I/O密集型的,那么多线程通常会和多进程一样高效。只有在你的程序是CPU密集型的,GIL才会成为一个显著的问题。

为什么想要去掉全局解释器锁?

虽然移除GIL的呼声已经存在多年,但主要阻碍并非因为工程量大,而是因为这可能会让单线程程序的性能受损。

如今,电脑的单个CPU核心的性能提升速度已经放缓(尽管像苹果硅芯片这样的定制处理器架构取得了巨大进步),而核心数却在不断增加。这就意味着程序越来越需要利用多核的优势,而Python在多线程利用上的不足也日益凸显。

到了2021年,Sam Gross开发了一个无GIL的原型,这激发了Python决策委员会提出并投票通过了PEP 703——将CPython中的GIL变为可选。投票结果是肯定的,决策委员会决定接受这个提案,并计划分三个阶段逐步实施:

第一阶段:自由线程模式作为一个实验性功能,不是默认启用的。 第二阶段:自由线程模式得到官方支持,但不是默认选项。 第三阶段:自由线程模式成为默认设置。 从讨论中们可以看出,大家普遍不希望Python分裂成两个版本——一个有GIL,一个没有——而是希望在自由线程模式成为默认后,GIL最终被完全移除,只留下自由线程模式。

在关于GIL的讨论进行的同时,还有一个名为“更快的CPython”的项目在并行推进。这个项目得到了微软的资助,由Mark Shannon和Python之父Guido van Rossum领导。

这个团队的努力已经取得了显著的成果,尤其是在Python 3.11版本中,性能相比3.10有了大幅提升。

有了社区和决策委员会的支持,多核处理器的普及,以及“更快的CPython”项目的成功,现在正是开始实施GIL可选化计划的第一阶段的好时机。

即时编译器(JIT,Just-in-Time)

在这个Python新版本中,除了全局解释器锁(GIL)的重大变革外,还引入了一个实验性的即时编译器(JIT)。

即时编译器(JIT)是一种编译技术,它在执行前即时生成机器代码,与传统的提前编译(AOT)方式,如C语言的gcc或clang编译器不同。

之前们已经讨论过字节码和解释器。关键点是,在Python 3.13之前,解释器会逐条处理字节码,将其转换成机器代码后再执行。而现在,有了JIT编译器,字节码可以一次性转换成机器代码,并在需要时更新,不必每次都重新解释。

需要强调的是,Python 3.13中引入的这种即时编译器是一种“复制和修补”类型的JIT。这是一种2021年新提出的概念,其核心思想是使用一系列预设的模板——JIT编译器会寻找匹配这些模板的字节码,并用预设的机器代码进行替换。

传统的JIT编译器在功能上要强大得多,同时对内存的需求也更大,尤其是与Java或C#这类重度使用JIT编译的语言相比。(这也是Java程序占用较多内存的原因之一。)

JIT编译器的美妙之处在于它们可以在代码运行过程中进行适应。例如,JIT编译器会追踪代码的“热度”,并根据代码的执行频率进行增量优化,甚至可以根据程序的实际运行情况来指导优化过程(类似于AOT编译器中的性能分析优化)。这意味着JIT编译器不会在只运行一次的代码上浪费时间优化,而是对频繁执行的代码部分进行重点优化。

目前,Python 3.13中的JIT编译器还比较简单,不会进行过于复杂的操作,但它为Python性能的未来发展带来了极大的期待。

JIT编译器将带来哪些改变?

在短期内,JIT编译器的加入可能不会影响你编写或执行Python代码的方式。但它是Python解释器内部工作机制的一个激动人心的变化,这可能会在未来显著提升Python的性能。

具体来说,这为逐步的性能改进打开了大门,这些改进可能会逐渐提升Python的性能,使其与其它编程语言更具有竞争力。尽管如此,由于目前仍处于初期阶段,复制和修补的JIT技术还很新且轻量级,因此在们开始从JIT编译器中获得显著的性能提升之前,还需要进行更多的重大改进。

如何尝试 JIT?

JIT 编译器在 3.13 中是“实验性”的,并且没有提供开箱即用的支持(至少当使用 pyenv 下载 3.13.0rc2 时)。您可以通过执行以下操作来启用实验性 JIT 支持:

$ PYTHON_CONFIGURE_OPTS="--enable-experimental-jit" pyenv install 3.13-dev
python-build: use openssl@3 from homebrew
python-build: use readline from homebrew
Cloning https://github.com/python/cpython...
Installing Python-3.13-dev...
python-build: use tcl-tk from homebrew
python-build: use readline from homebrew
python-build: use ncurses from homebrew
python-build: use zlib from xcode sdk
Installed Python-3.13-dev to /Users/drew.silcock/.pyenv/versions/3.13-dev
$ python -c 'import sysconfig;print("JIT enabled 🚀" if "-D_Py_JIT" in sysconfig.get_config_var("PY_CORE_CFLAGS") else "JIT disabled 😒")'
JIT enabled 🚀

你可以在PEP 744的讨论页面上找到一些额外的配置选项(比如,要启用JIT编译器,需要在程序运行时加上 -X jit=1 参数等)。

这个测试只能确认JIT是否在编译时被启用,而不能判断它当前是否正在工作状态(例如,是否在运行时被关闭)。虽然可以在程序运行时检查JIT是否启用,但这稍微复杂一些。这里有一个脚本可以帮助你弄清楚这一点(来源于PEP 744的讨论页面):

import _opcode
import types


def is_jitted(f: types.FunctionType) -> bool:
    for i in range(0, len(f.__code__.co_code), 2):
        try:
            _opcode.get_executor(f.__code__, i)
        except RuntimeError:
            # This isn't a JIT build:
            return False
        except ValueError:
            # No executor found:
            continue
        return True
    return False


def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a


def main():
    fibonacci(100)
    if is_jitted(fibonacci):
        print("JIT enabled 🚀")
    else:
        print("Doesn't look like the JIT is enabled 🥱")



if __name__ == "__main__":
    main()

在PEP 744的讨论中,有人提到了PYTHON_JIT=0/1和-X jit=0/1这两种设置方式——测试后发现-X命令行选项似乎并没有什么效果,但是设置环境变量似乎能够达到预期的效果。

$ python is-jit.py
JIT enabled 🚀
$ PYTHON_JIT=0 python is-jit.py
Doesn't look like the JIT is enabled 🥱

总结

Python 3.13 版本带来了一些激动人心的全新概念和功能,这对 Python 的运行时环境来说是一个巨大的进步。虽然这些更新可能不会立即改变你编写和执行 Python 代码的方式,但可以预见,随着自由线程和即时编译器(JIT)技术的不断成熟和普及,它们将在未来几个月甚至几年内,逐渐提升 Python 代码的性能,尤其是对那些需要大量 CPU 计算的任务。

相关文章
|
19天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
15天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2555 20
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
11天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
15天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1545 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
13天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
17天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
752 14
|
12天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
564 6
|
6天前
|
Docker 容器
Docker操作 (五)
Docker操作 (五)
156 68
|
6天前
|
Docker 容器
Docker操作 (三)
Docker操作 (三)
144 69
|
17天前
|
人工智能 自动驾驶 机器人
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界
过去22个月,AI发展速度超过任何历史时期,但我们依然还处于AGI变革的早期。生成式AI最大的想象力,绝不是在手机屏幕上做一两个新的超级app,而是接管数字世界,改变物理世界。
589 49
吴泳铭:AI最大的想象力不在手机屏幕,而是改变物理世界