Python现在越来越流行,并迅速扩展到包括DevOps、数据科学、Web开发、信息安全等各个领域。但是,相对于Python扩展的速度,运行Python代码的速度是有点自卑。Java、C、C++、C#和Python在代码速度方面如何比较?没有一刀切的标准,因为具体结果很大程度上取决于程序运行的类型,语言基准ComputerLanguageBenchmarksGames可以作为衡量的一个方面。根据我多年来运行语言基准测试的经验,Python的运行速度比许多语言都要慢。无论是使用JIT编译器的C#、Java,还是使用AOT编译器的C、C++,亦或是JavaScript等解释型语言,Python的运行速度都比它们慢。注:文中的“Python”,泛指CPython的官方实现。当然我也会在本文中提到其他语言的Python实现。我想回答的是这个问题:对于类似的程序,Python在任何地方都比其他语言慢2到10倍。是什么原因?有什么办法可以改善吗?主流论点如下:“这是全局解释器锁(GIL)的原因”“是因为Python是一种解释型语言而不是编译型语言”“是因为Python是一种动态类型语言”哪个是对的?是影响Python运行效率的主要原因吗?是全局解释器锁的原因吗?今天的许多计算机都配备多核CPU,有时甚至配备多处理器。为了更充分地利用它们的处理能力,操作系统定义了一个称为线程的低级结构。一个进程(如Chrome浏览器)可以创建多个线程来执行系统中不同的操作。在这种情况下,CPU密集型进程可以跨内核分担负载,从而可以大大提高应用程序的效率。例如,在我撰写本文时,我的Chrome浏览器打开了44个线程。需要说明的是,基于POSIX的操作系统(如MacOS、Linux)和Windows操作系统的线程结构和API是不同的,因此操作系统也负责调度每个线程。如果你没有写过多线程执行的代码,你需要了解线程锁的概念。多线程进程比单线程进程更复杂,因为需要线程锁来保证同一内存地址的数据不被多个线程同时访问或更改。CPython解释器在创建变量时,首先分配内存,然后对变量的引用进行计数,这称为引用计数。如果变量的引用计数变为0,则变量从内存中释放。这就是为什么在for循环代码块中创建临时变量不会增加内存消耗的原因。当一个变量在多个线程之间共享时,CPython锁定引用计数的关键是使用GIL,它仔细控制线程的执行。不管有多少个线程同时存在,解释器一次只允许一个线程运行。.这对Python程序的性能有何影响?如果您的程序只是单线程和单进程,那么您的代码的速度和性能将不会受到全局解释器锁的影响。但是如果在单进程中使用多线程来实现并发,而且是IO密集型(比如网络IO或者磁盘IO)线程,那么GIL竞争的效果就很明显了。GILraceconditiondiagrambyDavidBeazleyhttp://dabeaz.blogspot.com/2010/01/python-gil-visualized.html对于同样使用WSGI的web应用程序(比如Django),那么对于这个web应用程序的每个请求运行一个单独的Python解释器,每个请求只有一个锁。同时,由于Python解释器的启动比较慢,一些WSGI的实现也有一种“守护模式”,可以让Python进程保持就绪状态。其他Python解释器的行为如何?PyPy也是一个带有GIL的解释器,但它通常比CPython快3倍以上。Jython是没有GIL的解释器,因为Jython中的Python线程是使用Java线程实现的,由JVM内存管理系统管理。JavaScript是如何做到这一点的?所有Javascript引擎都使用标记清除垃圾收集算法,而GIL使用CPython的内存管理算法。JavaScript没有GIL,而且是单线程的,不需要用到GIL。JavaScript的事件循环和Promise/Callback模式实现了异步编程而不是并发。Python中有一个类似的asyncio事件循环。是因为Python是一种解释型语言吗?我经常听到这种说法,但它是对Python实际功能的粗略简化。实际上,在终端执行pythonmyscript.py时,CPython会对代码进行读取、解析、解析、编译、解释、执行等一系列操作。如果你对这一系列过程感兴趣,也可以阅读我之前的文章:6分钟改造Python语言。.pyc文件的创建是此过程的重点。在代码编译阶段,Python3会将字节码序列写入__pycache__/下的文件,而Python2会将字节码序列写入当前目录下的.pyc文件。这适用于您编写的脚本、您导入的所有代码以及第三方模块。因此,在大多数情况下(除非您的代码是一次性的……),Python将解释字节码并在本机执行它。与Java相比,C#.NET:Java代码被编译成一种“中间语言”,字节码由Java虚拟机读取并动态编译成机器码。.NETCIL也是如此,.NETCLR(Common-Language-Runtime)将字节码动态编译为机器码。由于Python使用虚拟机或某种字节码,如Java和C#,为什么Python在基准测试中仍然比Java和C#慢很多?第一个原因是.NET和Java都是JIT编译的。即时(JIT)编译需要一种中间语言,以便将代码拆分为块(或帧)。另一方面,提前(AOT)编译器需要确保CPU在任何交互发生之前理解每一行代码。JIT本身并没有加快执行速度,因为它仍然执行相同的字节码序列。但是JIT将允许在运行时进行优化。一个好的JIT优化器会分析程序的哪些部分被执行了多次,哪些是程序中的“热点”,然后优化器会用更高效的版本替换这些代码来实现优化。这意味着如果你的程序多次重复相同的操作,它可能会被优化器优化得更快。而且Java和C#都是强类型语言,优化器可以更准确的判断代码。PyPy使用比CPython快得多的JIT。更详细的结果可以在这篇性能基准测试文章中看到:哪个Python版本最快?。那么为什么CPython不使用JIT?JIT也不完美,其显着缺点之一是启动时间。CPython的启动时间已经比较慢了,PyPy比CPython启动慢2到3倍。Java虚拟机的启动速度也是出了名的慢。.NETCLR从系统启动开始优化体验,CLR的开发者也在CLR之上开发操作系统。因此,如果您有一个长时间运行的Python进程,那么JIT是有意义的,因为代码中存在可以优化的“热点”。然而,CPython是一个通用的实现。想象一下用Python开发命令行程序,但每次调用CLI时都必须等待JIT缓慢启动,这是一种非常糟糕的体验。CPython试图对各种用例有用。可以实现插入CPython的JIT,但这种改进的进展在很大程度上停滞不前。如果您想充分利用JIT,请使用PyPy。是因为Python是动态类型语言吗?在C、C++、Java、C#、Go等静态类型语言中,声明变量时必须指定变量的类型。在动态类型语言中,虽然也有类型的概念,但是变量的类型是可以改变的。a=1a="foo"在上面的例子中,Python释放了变量a原来存放整型变量的内存空间,并新建了一块存放字符串类型的内存空间,并且与原变量同名。静态类型语言不是为了难为你而设计的,它们是为了让CPU更容易运行而设计的。因为所有的运算最终都需要对应简单的二元运算,所以需要将对象、类型等高层数据结构转化为低层数据结构。Python也实现了这样的转换,只是用户看不到,也不需要关心。不必声明类型并不意味着让Python变慢,Python的设计使用户可以使各种事物动态化:对象上的方法可以在运行时更改,低级系统调用可以动态地添加到值中在运行时语句上,几乎可以做任何事情。但正是这种设计让Python的优化变得异常困难。为了证明我的观点,我使用了DTrace,MacOS上的系统调用跟踪工具。DTrace没有内置到CPython发行版中,因此必须重新编译CPython。下面以Python3.6.6为例:wgethttps://github.com/python/cpython/archive/v3.6.6.zipunzipv3.6.6.zipcdv3.6.6./configure--with-dtracemake这样python.exe将使用DTrace跟踪所有代码。PaulRoss还就DTrace发表了精彩演讲。您可以下载适用于Python的DTrace启动文件,以查看函数调用、执行时间、CPU时间、系统调用和各种其他内容。sudodtrace-stoolkit/
