为何Python如此慢

Python当前人气暴涨。它在DevOps,数据科学,Web开发和安全领域均有使用。javascript

可是在速度方面没有赢得美誉。html

这里有关于Python比较其余语言如,Java, C#, Go, JavaScript, C++进行性能对比,其中Python是最慢的。包含了JIT(C#, Java)和AOT(C,C++)编译器,也有像解释型语言如JavaScript。java

注意:文章中我所提到的"Python"均指使用C语言实现的CPython。python

为何要比其余语言慢到2-10x的速度?git

这是相关缘由:github

  • 它的GIL(Global Interpreter Lock)
  • 由于是解释器型非编译型
  • 由于是动态语言

那这些缘由中哪一个占最大成分呢?web

它的GIL

现代计算机大多数都具有多核,有时还有多处理器。为了充分利用这些处理能力,操做系统底层提供了一个叫作线程的东西,它(例如Chrome浏览器)能够系统内容建立多个线程进行指令处理。也就是说当一个进程是CPU密集型,那就能够经过多个核协同工做来提升应用的运行速度。算法

我本地Chrome浏览器当前会开启44个线程,在不一样的操做系统例如POSIX(Mac OS和Linux)和Windows提供的线程API结构不同。操做系统来负责线程的调度。编程

若是你以前并无进行过多线程编程,你须要熟悉一个叫作锁的概念。不像单线程进行,你须要确保在内存改变一个变量时,多个线程不会同时进行操做。浏览器

CPython建立变量时,它会开辟内存,而后计算有多少引用该变量,这个概念叫作引用计数。若是引用技术为0时,它会将内存释放回给系统。这也就是建立临时变量,进行循环操做时并不会耗光内存

在多线程共享变量时,CPython时如何对引用计数上锁呢。这里就是“全局解释锁(global interpreter lock)”负责作的事情,无论你有多少的线程,解释器在同一个时间只能有一个线程进行操做。

那么它对Python应用有什么性能影响

若是应用是单解释器,单线程。在速度上没有任何影响。

若是你想在单解释器使用线程了实现并发操做,而且它们是IO密集型(例如网络IO或者硬盘IO),那你将会看到GIL相似以下竞争执行:

alt

如你有一个web应用(例如Django)而且使用WSGI,每一个请求将会分配到单独Python解释器,此时一个请求只有一把锁。由于Python解释器启动比较慢,一些WSGI会实现为"Daemon Mode"执行,

那其余Python运行时呢

PyPy实现的GIL一般要比CPython快3x倍。

Jython没有GIL,由于Jython的线程受益与JVM的内存管理机制。

JavaScript 是怎么实现的

首先,JavaScript使用的是标记清除算法实现的垃圾回收机制。而CPython须要GIL主要缘由就是内存管理算法。

JavaScript没有GIL,可是由于它设计的就是单线程,因此它并不须要。JavaScript的event loop和Promise/Callback机制来进行异步编码实现并发。Python也有相似的asyncio的event-loop机制。

由于它是解释型语言

若是你在终端使用python myscript.py运行,CPython将会进行一系列的读取,词法分析,语法分析,编译,解释和执行代码。

一个很是重要点事,在编译过程当中生成的.pyc文件,Python3是放置在__pycache__目录下,Python2是在文件相同目录下。该文件就是Python里面的字节码,在执行文件不会生成,只会在倒入的模块或者第三方模块生成。

因此,Python解释成字节码而且进行运行。与Java和C#.NET相比:

Java 编译成一个中间语言,而后JVM加载字节码,进行just in time编译成机器码。.NET CLI也是一样的方式,.NET common language runtime使用just in time编译成机器码

那么,Python为何要比Java和C#测试性能差那么多,都是使用字节码,区别就在于JIT编译方式。

Just in time须要一个中间语言容许代码被拆为多个chunks(或者frames),AOT编译器设计用来确保CPU可以理解里面的内容。

JIT自己没有提升代码执行,可是由于它执行仍然是字节码。而后,JIT容许在运行中优化执行。一个好的JIT加应用程序执行很高的代码,将字节码直接优化为机器码,从而提升执行效率,这种技术成为 Hot Spot。

也就是说在程序一次次执行过程当中,会变得愈来愈快。还有就是,Java和C#是强类型语言,因此优化器可以进一步优化。

PyPy也有JIT,设计比CPython执行更快。

那么CPython为何不用JIT

JIT也有缺点,就是减慢了启动时间。CPython启动时间已经很慢了,PyPy更是比CPython慢2-3x倍。JVM启动速度是臭的不行。.NET CLR在系统启动的时候就先启动了。

若是你有一个Python进程要长时间运行,那么可使用JIT的hot spots带来的益处。

然而,CPython设计为通用语言。因此,当你在实现命令行应用时,每次都要长时间等待JIT启动那是很是讨厌的事情。

CPython也作不少尝试,也尝试性使用Plugging方式加入JIT,可是项目如今是停滞的。

由于它是动态类型的

在静态类型语言中,你必须在申明时就定义它的类型,这些语言有C,C++,Java,C#和Go。

在一个动态类型元中,虽然也有类型的概念,可是一个变量的类型是动态的

a = 1
a = "foo"

在上面示例中,Python使用了相同的变量赋于了不一样的str类型,它会释放第一次建立的内存。

静态语言并非设计来让你编码头疼,而是为了适应CPU的操做方式。由于全部的操做都是二进制操做,你须要将全部的对象和类型转换为低级别的数据结构。

Python已经为了作了这件事情,你看不见也不须要关系。

不用申明类型并非致使Python变慢的直接缘由,这样设计让你几乎全部操做都是动态的。你能够在运行时替换对象的方法,你能够在运行时对底层系统调用进行monkey-patch操做,一切皆有可能。

这样的设计致使Python很难进行优化。

为了验证个人观点,我将在个人Ubuntu系统中使用syscall追踪工具Dtrace。CPython并不内建DTrace,因此你须要从新编译CPython,我将使用Python 3.7.0:

./configure --with-dtrace
make

如今代码中就可使用Dtrace进行追踪了,你能够下载工具来对Python的函数调用,执行时间,CPU时间,syscalls等进行分析:

sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’

py_callflow追踪器打印了相似以下信息:

alt

因此,Python动态类型是否让它变慢呢

  • 对比和转换类型代价很高,每次变量读取,类型检查时都很耗时。
  • 动态类型很难对语言进行优化。Python其余替代方案要快,是由于它们为达性能对灵活性作出了妥协。
  • 看看Cython,将C静态类和和Python优化类型能够在84x性能提升。

结论

Python主要慢的缘由是由于它的动态和灵活性。它能够做为解决大多数问题的工具,也存在更优更快的可选方案。

能够在应用程序中利用async,理解性能工具,考虑使用多解释器等进行优化。

若是对于启动时间不是那么关心的话能够考虑使用JIT,例如PyPy。

对于性能要求比较苛刻的,你可使用更多的静态类型变量,考虑使用Cython

相关文章
相关标签/搜索