为什么Python这么慢?
Java在速度方面与C或C ++或C#或Python相比如何?答案很大程度上取决于您正在运行的应用程序的类型。没有基准是完美的,但是“计算机语言基准测试”是一个很好的起点。
十多年来,我一直在谈论计算机语言基准游戏。与其他语言(例如Java,C#,Go,JavaScript,C ++)相比,Python是最慢的语言之一。这包括JIT(C#,Java)和AOT(C,C ++)编译器,以及诸如JavaScript之类的解释语言。
注意:当我说“ Python”时,我是在说CPython语言的参考实现。我将在本文中引用其他运行时。
我想回答这个问题:当Python完成比其他语言慢2到10倍的可比较应用程序时,为什么它变慢并且我们不能使其变快?
以下是最重要的理论:
“这是GIL(全球口译员锁) ”
“这是因为它被解释而不是被编译”
“这是因为它是一种动态类型的语言”
以下哪个原因对性能有最大影响?
“这是GIL”
现代计算机配备了具有多个内核,有时还有多个处理器的CPU。为了利用所有这些额外的处理能力,操作系统定义了一个称为线程的低级结构,在该结构中,进程(例如Chrome浏览器)可以产生多个线程,并在其中包含有关系统的指令。这样,如果一个进程特别占用大量CPU,则可以在内核之间共享该负载,这实际上使大多数应用程序更快地完成任务。
在撰写本文时,我的Chrome浏览器有44个线程处于打开状态。请记住,基于POSIX的线程(例如Mac OS和Linux)与Windows OS的线程结构和API不同。操作系统还处理线程的调度。
如果您以前没有做过多线程编程,则需要快速熟悉锁的概念。与单线程进程不同,您需要确保在更改内存中的变量时,多个线程不会尝试同时访问/更改同一内存地址。
当CPython创建变量时,它会分配内存,然后计算对该变量存在多少引用,这就是称为引用计数的概念。如果引用数为0,则它??将那部分内存从系统中释放出来。这就是为什么在for循环范围内创建“临时”变量不会消耗应用程序的内存的原因。
当多个线程共享变量时,CPython如何锁定引用计数就成为挑战。有一个“全局解释器锁”,可以仔细控制线程的执行。解释器一次只能执行一个操作,而不管它有多少线程。
这对Python应用程序的性能意味着什么?
如果您有一个单线程,单个解释器应用程序。这不会影响速度。删除GIL不会影响代码的性能。
如果您想使用线程在单个解释器(Python进程)中实现并发,并且您的线程是IO密集型的(例如,网络IO或磁盘IO),那么您会看到GIL争用的后果。
摘自David Beazley的GIL可视化文章
http://dabeaz.blogspot.com/2010/01/python-gil-visualized.html
如果您有一个Web应用程序(例如Django)并且使用的是WSGI,则对您的Web应用程序的每个请求都是一个单独的Python解释器,因此每个请求只有1个锁。由于Python解释器启动缓慢,因此某些WSGI实现具有“守护程序模式” ,可让您随时随地使用Python进程。
那么其他Python运行时呢?
PyPy有一个GIL,通常比CPython快3倍以上。
Jython没有GIL,因为Jython中的Python线程由Java线程表示,并受益于JVM内存管理系统。
JavaScript如何做到这一点?
好吧,首先所有的Javascript引擎都使用标记清除垃圾收集。如前所述,对GIL的主要需求是CPython的内存管理算法。
JavaScript没有一个GIL,但它也是一个-threaded所以它不需要一个。JavaScript的事件循环和Promise / Callback模式是实现异步编程并发的方式。Python在asyncio事件循环中也有类似的事情。
“因为它是一种解释性语言”
我听到了很多,我发现它大大简化了CPython的实际工作方式。如果您在终端上编写了代码,python myscript.py那么CPython将开始读取,整理,解析,编译和解释该代码的漫长过程。
如果您对该流程的工作方式感兴趣,那么我之前已经写过:
在6分钟内修改Python语言
本周,我向CPython核心项目提出了我的第一个拉取请求,该请求被拒绝了:-(但不完全是…… hackernoon.com
该过程中的重要一点是创建 .pyc文件,在编译器阶段,字节码序列被写入__pycache__/Python 3内部或Python 2同一目录中的文件。这不仅适用于您的脚本,而且您导入的所有代码,包括第三方模块。
因此,大多数时候(除非您编写只运行一次的代码?),Python会解释字节码并在本地执行。将其与Java和C#.NET进行比较:
Java编译为“中间语言”,而Java虚拟机读取字节码,并及时将其编译为机器码。.NET CIL是相同的,.NET公共语言运行库CLR使用即时编译来处理机器代码。
那么,如果Python和C#都使用虚拟机和某种字节码,为什么在基准测试中Python却比Java和C#慢得多?首先,.NET和Java是JIT编译的。
JIT或即时编译需要一种中间语言,以允许将代码拆分为块(或帧)。提前(AOT)编译器旨在确保CPU在进行任何交互之前可以理解代码中的每一行。
JIT本身不会使执行速度更快,因为它仍在执行相同的字节码序列。但是,JIT可以在运行时进行优化。一个好的JIT优化器将看到应用程序的哪些部分正在执行很多,称之为“热点”。然后,通过将其替换为更有效的版本,对这些代码位进行优化。
这意味着,当您的应用程序一次又一次地执行相同的操作时,它可能会更快。另外,请记住Java和C#是强类型语言,因此优化程序可以对代码进行更多假设。
PyPy具有JIT,并且如上一节所述,它比CPython快得多。此性能基准测试文章会更详细地介绍-
哪个是最快的Python版本?
当然,“取决于”,但是它取决于什么,以及如何评估哪个版本的Python最快…… hackernoon.com
那么CPython为什么不使用JIT?
JIT有缺点:其中之一是启动时间。CPython的启动时间已经相对较慢,PyPy的启动时间比CPython慢??2-3倍。众所周知,Java虚拟机的启动速度很慢。.NET CLR通过从系统启动开始来解决此问题,但是CLR的开发人员还开发了运行CLR的操作系统。
如果您有一个长时间运行的Python进程,并且由于包含“热点”而可以对代码进行优化,那么JIT就很有意义。
但是,CPython是通用实现。因此,如果您正在使用Python开发命令行应用程序,那么每次调用CLI时都必须等待JIT启动会非常慢。
CPython必须尝试并提供尽可能多的用例。有可能将JIT插入CPython,但该项目已大步停滞。
如果您想要JIT的好处并且您有适合它的工作量,请使用PyPy。
“这是因为它是一种动态类型的语言”
在“静态类型”语言中,必须在声明变量时指定其类型。这些将包括C,C ++,Java,C#,Go。
在动态类型语言中,仍然存在类型的概念,但是变量的类型是动态的。
a = 1
a =“ foo”
在这个玩具示例中,Python创建了一个具有相同名称和类型的第二个变量,str并取消分配了为第一个实例创建的内存。a
静态类型的语言并不是为了让您的生活变得艰难而设计的,而是因为CPU的运行方式而设计的。如果最终所有事情都需要等同于简单的二进制操作,则必须将对象和类型转换为低级数据结构。
Python为您做到了,您只是从未见过,也不需要关心。
不必声明类型并不是让Python变慢的原因,Python语言的设计使您几乎可以使任何东西动态化。您可以在运行时替换对象上的方法,还可以将低级系统调用猴子补丁到运行时声明的值。几乎所有可能。
正是这种设计使优化Python变得异常困难。
为了说明我的观点,我将使用在Mac OS中工作的syscall跟踪工具Dtrace。CPython发行版没有内置DTrace,因此您必须重新编译CPython。我正在使用3.6.6进行演示
wget
https://github.com/python/cpython/archive/v3.6.6.zip
解压缩v3.6.6.zip
cd v3.6.6
./configure --with-dtrace
make
现在,python.exe在整个代码中将具有Dtrace跟踪器。保罗·罗斯(Paul Ross)在Dtrace上写了一篇很棒的闪电演讲。您可以下载适用于Python的DTrace入门文件,以测量函数调用,执行时间,CPU时间,系统调用以及各种乐趣。例如
sudo dtrace -s toolkit/<tracer>.d -c ‘../cpython/python.exe script.py’
该py_callflow示踪显示了所有应用程序中的函数调用
那么,Python的动态类型会使其变慢吗?
比较和转换类型的成本很高,每次读取,写入或引用变量时都要检查类型
很难优化如此动态的语言。Python的许多替代方案之所以如此之快,是因为它们以性能为名折衷了灵活性。
看着用Cython,它结合了C-静态类型和Python来优化代码,其中所述类型是已知的可以提供一个84X性能改进。
结论
Python主要由于其动态特性和多功能性而运行缓慢。它可以用作解决各种问题的工具,其中可能有更优化和更快的替代方案。

关注 CDA人工智能学院 ,回复“录播”获取更多人工智能精选直播视频!