当前位置: 首页 > 科技观察

为什么Python运行这么慢?AnthonyShaw的

时间:2023-03-12 18:59:54 科技观察

是Python软件基金会的成员,也是Apache基金会的成员。  Python最近人气飙升。这种编程语言用于DevOps、数据科学、网站开发和安全。  然而,它并没有获得任何速度奖牌。  在速度方面,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的操作系统(例如MacOS和Linux)和Windows操作系统的线程结构和API不同。操作系统还处理线程的调度。  如果你之前没有接触过多线程编程,一个需要尽快熟悉的概念就是锁。与单线程进程不同,当您需要确保更改内存中的变量时,多个线程不会同时尝试访问/更改同一内存地址。  当CPython创建一个变量时,它会分配内存,然后计算该变量有多少引用。这个概念称为引用计数。如果引用计数为0,那么它会从系统中释放这部分内存。这就是为什么在某个代码段(如for循环的范围)内创建一个“临时”变量不会搞砸应用程序的内存消耗。  当多个线程共享变量时,就会出现这个难题:CPython如何锁定引用计数。有一个“全局解释器锁”可以仔细控制线程执行。解释器一次只能执行一个操作,不管它有多少个线程。  这对Python应用程序的性能意味着什么?  如果你有单线程、单解释器的应用程序,这对速度没有影响。删除GIL根本不会影响代码的性能。  如果你想在单个解释器(Python进程)内使用线程实现并发功能,而线程是IO密集型的(比如网络IO或磁盘IO),你就会看到GIL争用的后果。  上图来自DavidBeazley的文章《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引擎都使用标记清除垃圾收集。如上所述,GIL的主要需求是CPython的内存管理算法。  JavaScript没有GIL,但是也是单线程的,所以不需要内存管理算法。JavaScript的事件循环和Promise/Callback模式是实现异步编程而不是并发的方法。Python与asyncio事件循环有相似之处。  “这是因为它是一种解释型语言”    我经常听到这种说法,但我觉得这是对CPython实际工作方式的过度简化。如果您在终端中编写pythonmyscript.py,CPython将启动读取、解析、解析、编译、解释和执行代码的长链操作。  如果你对这个过程的机制感兴趣,我之前写过一篇文章:《6 分钟内修改 Python 语言》(https://hackernoon.com/modifying-the-python-language-in-7-minutes-b94b0a99ce14)。  这个过程中一个重要的节点就是.pyc文件的创建;在编译阶段,字节码序列在Python3中写入__pycache__/中的文件,在Python2中写入同一目录。这不仅适用于您的脚本,也适用于所有导入的代码,包括第三方模块。  所以大多数时候(除非你编写的代码只运行一次?),Python解释字节码并在本地执行它。比较Java和C#.NET:  Java编译为“中间语言”,其中Java虚拟机读取字节码并将其编译为运行中的机器代码。与.NETCIL一样,.NET公共语言运行时(CLR)使用即时编译,将编译后的代码编译为机器代码。  那么,为什么Python在基准测试中比Java和C#慢得多,因为它们都使用虚拟机和某种字节码?首先,.NET和Java是JIT编译的。  JIT或即时编译需要一种中间语言,以便将代码拆分为块(或帧)。提前(AOT)编译器旨在确保CPU在任何交互发生之前理解每一行代码。  JIT本身并不能使执行速度更快,因为它仍然执行相同的字节码序列。但是,JIT允许在运行时优化代码。一个好的JIT优化器会看到应用程序的哪些部分被频繁执行,这些代码被称为“热点”。然后,它通过用更高效的版本替换它们来优化这些代码。  这意味着当您的应用程序一遍又一遍地执行相同的操作时,它可以运行得更快。另一件事要记住:Java和C#是强类型语言,因此优化器可以对代码做出更多假设。  PyPy有一个JIT,如上所述,它比CPython快得多。这篇性能基准文章对其进行了更详细的介绍:《哪个 Python 版本的速度最快?》(https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b)。  那么为什么CPython不使用JIT?  JIT有几个缺点:其中一个缺点是启动时间。CPython的启动时间已经很慢,PyPy的启动时间比CPython慢2到3倍。众所周知,Java虚拟机的启动速度很慢。.NETCLR通过在系统启动时启动来解决这个问题,但是CLR的开发人员也开发了运行CLR的操作系统。  如果您有一个长时间运行的Python进程,其代码由于“热代码”而可以优化,那么JIT就很有意义。  然而,CPython是一个通用的实现。因此,如果您正在使用Python开发命令行应用程序,则每次调用CLI时都必须等待JIT启动,这非常慢。  CPython必须尝试满足尽可能多的用例。之前有人曾尝试将JIT插入CPython,但该项目基本上失败了。  如果你想要JIT的好处并且有适合它的工作负载,请使用PyPy。  “这是因为它是动态类型语言”  在“静态类型”语言中,必须在声明变量时指定变量的类型。此类语言包括C、C++、Java、C#和Go。  在动态类型语言中,类型的概念依然存在,只是变量的类型是动态的。  在这个例子中,Python创建了第二个str类型的同名变量,并释放了为a的第一个实例创建的内存。  静态类型语言不是为了挡你的路,它们是为了考虑到CPU的运行方式而设计的。如果一切最终都需要等同于简单的二进制操作,则必须将对象和类型转换为低级数据结构。  Python为您完成这项工作,您永远看不到它,也不必担心。  不必声明类型并不是Python变慢的原因,Python语言的设计使您几乎可以使所有内容动态化。您可以通过monkey-patch来包含对运行时声明的值进行低级系统调用的代码。几乎一切皆有可能。  正是这种设计,让优化Python异常困难。  为了说明我的观点,我将使用MacOS中可用的系统调用跟踪工具Dtrace。CPython发行版没有内置DTrace,因此您必须重新编译CPython。我使用3.6.6进行演示。  现在python.exe将在整个代码中使用DTrace跟踪器。PaulRoss写了一篇关于DTrace的闪电演讲(https://github.com/paulross/dtrace-py#the-lightning-talk)。您可以下载适用于Python的DTrace启动文件(https://github.com/paulross/dtrace-py/tree/master/toolkit)来测量函数调用、执行时间、CPU时间、系统调用和各种有趣的指标。例如,  py_callflow跟踪器显示了应用程序中的所有函数调用。  那么,Python的动态类型会变慢吗?比较和转换类型是昂贵的,每次读取、写入或引用变量时,都必须检查类型。很难优化最动态的语言。许多Python的替代品要快得多,因为它们牺牲了灵活性以换取性能。Cython结合C-Static类型和Python优化类型已知代码,可将性能提高84倍。  结论  Python速度慢主要是由于动态性和多功能性。它可以用作解决各种问题的工具,并且有几个更优化和更快的Python替代品。  但是,有一些方法可以优化您的Python应用程序,例如充分利用异步、深入了解分析工具以及考虑使用多个解释器。  对于启动时间不重要且代码将从JIT中受益的应用程序,请考虑PyPy。  对于具有更多静态类型变量的代码的性能关键部分,请考虑使用Cython。