Python 比很多语言运行起来都要慢。无论是使用 JIT 编译器的 C#、Java,还是使用 AOT 编译器的 C、C++,又或者是 JavaScript 这些解释型语言,Python 都比它们运行得慢。
注意:对于文中的 “Python” ,一般指 CPython 这个官方的实现。当然我也会在本文中提到其它语言的 Python 实现。

成都加米谷大数据培训机构,大数据开发、数据分析与挖掘,年前报名(年后学习),预报名特别优惠进行中,仅限前50名!
我要回答的是这个问题:对于一个类似的程序,Python 要比其它语言慢 2 到 10 倍不等,这其中的原因是什么?又有没有改善的方法呢?
主流的说法有这些:
“是全局解释器锁Global Interpreter Lock(GIL)的原因”
“是因为 Python 是解释型语言而不是编译型语言”
“是因为 Python 是一种动态类型的语言”
哪一个才是是影响 Python 运行效率的主要原因呢?
是全局解释器锁的原因吗?
现在很多计算机都配备了具有多个核的 CPU ,有时甚至还会有多个处理器。为了更充分利用它们的处理能力,操作系统定义了一个称为线程的低级结构。某一个进程(例如 Chrome 浏览器)可以建立多个线程,在系统内执行不同的操作。在这种情况下,CPU 密集型进程就可以跨核心分担负载了,这样的做法可以大大提高应用程序的运行效率。
例如在我写这篇文章时,我的 Chrome 浏览器打开了 44 个线程。需要提及的是,基于 POSIX 的操作系统(例如 Mac OS、Linux)和 Windows 操作系统的线程结构、API 都是不同的,因此操作系统还负责对各个线程的调度。
如果你还没有写过多线程执行的代码,你就需要了解一下线程锁的概念了。多线程进程比单线程进程更为复杂,是因为需要使用线程锁来确保同一个内存地址中的数据不会被多个线程同时访问或更改。
CPython 解释器在创建变量时,首先会分配内存,然后对该变量的引用进行计数,这称为引用计数
reference counting
。如果变量的引用数变为 0,这个变量就会从内存中释放掉。这就是在 for 循环代码块内创建临时变量不会增加内存消耗的原因。
而当多个线程内共享一个变量时,CPython 锁定引用计数的关键就在于使用了 GIL,它会谨慎地控制线程的执行情况,无论同时存在多少个线程,解释器每次只允许一个线程进行操作。
这会对 Python 程序的性能有什么影响?
如果你的程序只有单线程、单进程,代码的速度和性能不会受到全局解释器锁的影响。
但如果你通过在单进程中使用多线程实现并发,并且是 IO 密集型(例如网络 IO 或磁盘 IO)的线程,GIL 竞争的效果就很明显了。
由 David Beazley 提供的 GIL 竞争情况图http://dabeaz.blogspot.com/2010/01/python-gil-visualized.html
对于一个 web 应用(例如 Django),同时还使用了 WSGI,那么对这个 web 应用的每一个请求都运行一个单独的 Python 解释器,而且每个请求只有一个锁。同时因为 Python 解释器的启动比较慢,某些 WSGI 实现还具有“守护进程模式”,可以使 Python 进程一直就绪。
其它的 Python 解释器表现如何?
PyPy 也是一种带有 GIL 的解释器,但通常比 CPython 要快 3 倍以上。
Jython 则是一种没有 GIL 的解释器,这是因为 Jython 中的 Python 线程使用 Java 线程来实现,并且由 JVM 内存管理系统来进行管理。
JavaScript 在这方面又是怎样做的呢?
所有的 Javascript 引擎使用的都是 mark-and-sweep 垃圾收集算法,而 GIL 使用的则是 CPython 的内存管理算法。
JavaScript 没有 GIL,而且它是单线程的,也不需要用到 GIL, JavaScript 的事件循环和 Promise/Callback 模式实现了以异步编程的方式代替并发。在 Python 当中也有一个类似的 asyncio 事件循环。
是因为 Python 是解释型语言吗?
我经常会听到这个说法,但是这过于粗陋地简化了 Python 所实际做的工作了。其实当终端上执行 python myscript.py 之后,CPython 会对代码进行一系列的读取、语法分析、解析、编译、解释和执行的操作。
如果你对这一系列过程感兴趣,也可以阅读一下我之前的文章:在 6 分钟内修改 Python 语言 。
.pyc 文件的创建是这个过程的重点。在代码编译阶段,Python 3 会将字节码序列写入 __pycache__/下的文件中,而 Python 2 则会将字节码序列写入当前目录的 .pyc 文件中。对于你编写的脚本、导入的所有代码以及第三方模块都是如此。
因此,绝大多数情况下(除非你的代码是一次性的……),Python 都会解释字节码并本地执行。与 Java、C#.NET 相比:
Java 代码会被编译为“中间语言”,由 Java 虚拟机读取字节码,并将其即时编译为机器码。.NET CIL 也是如此,.NET CLR(Common-Language-Runtime)将字节码即时编译为机器码。
既然 Python 像 Java 和 C# 那样都使用虚拟机或某种字节码,为什么 Python 在基准测试中仍然比 Java 和 C# 慢得多呢?首要原因是,.NET 和 Java 都是 JIT 编译的。查看全文:https://www.toutiao.com/i6630644941179585032/
网友评论