关于 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 计算的任务。

相关文章
|
7月前
|
算法 Java 程序员
Python 基础知识: 解释 Python 的垃圾回收机制是如何工作的?
Python 基础知识: 解释 Python 的垃圾回收机制是如何工作的?
56 1
|
7月前
|
存储 数据可视化 数据挖掘
python学习1补充
python学习1补充
|
5月前
|
SQL 存储 算法
一些使用python过程中的小贴士
摘要: 在Python中,`type`是一个特殊类型,既是类型也是对象。`isinstance()`函数表明`type`和`object`都是类型并且是彼此的基类。`all()`和`any()`函数分别检查可迭代对象的所有元素是否都为真或至少有一个元素为真。链式操作如`==`和`in`具有相同的优先级,从左到右结合,可能导致意外的结果,例如`False == False in [False]`实际上是`True`,因为`False in [False]`先计算并返回`True`,然后与`False == True`比较。
|
7月前
|
Python
在Python Web开发过程中:`is`和`==`在Python中的区别是什么?
【4月更文挑战第25天】Python的`is`与`==`用于比较。`is`检查对象是否相同(内存地址一致),而`==`检查值是否相等。例如,`a = [1, 2, 3]`,`b = a`,`c = [1, 2, 3]`,则`a is b`和`a == b`均为True,但`a is c`为False,`a == c`为True,因`a`和`b`引用同一对象,而`a`和`c`值虽等但对象不同。
33 1
|
Python
【Python零基础学习入门篇②】——第二节:Python的常用语句
1️⃣学习目标——明方向 ✅ ✅ ✅ 🔘🔘🔘持之以恒, 坚持不懈地去完成我们内心的目标,只有勇于学习,才能不断进步! 熟悉并掌握Python中判断语句的语法及使用 熟悉并掌握Python中循环语句的语法及使用 了解一些Python中其他语句的语法,还是要会使用的哈😁😁😁 2️⃣ 学习任务——冲鸭!☑️ ☑️ ☑️
144 0
|
算法 JavaScript Unix
怎么样才算是精通 Python?
程序员写过简历的都知道,先说精通->后说熟悉->最后说了解,要把精通放在最前面。 But 很少人会说自己精通Python,这也是因为Python的应用领域是真的多,最好还是说自己精通的领域,而不是语言,除非你真的是大佬。
259 0
怎么样才算是精通 Python?
|
Python
python with as有什么好处?
python with as有什么好处?
76 0
|
JavaScript Java PHP
Python 入门篇-用Notepad++编写出第一个python程序
Python 入门篇-用Notepad++编写出第一个python程序
170 0
Python 入门篇-用Notepad++编写出第一个python程序
|
Python
Python 一行代码的神奇之处!!
你们知道今天聊聊关于 Python 一行代码的神奇之处!!!
125 0