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

为什么Python这么慢?

时间:2023-03-14 21:17:40 科技观察

大数据文摘编译:jojo、Hope、张秋月、CoolBoyPython语言近几年人气爆棚。它广泛用于WebDevOps、数据科学、Web开发和Web安全问题。但是,Python在速度上完全没有优势。就速度而言,Java与C、C++、C#或Python相比如何?答案几乎完全取决于正在运行的应用程序。没有完美的方法来判断这个问题,但是计算机语言基准游戏是一个很好的方法。链接:http://benchmarksgame.alioth.debian.org根据我十多年来对计算机语言基准游戏的观察,Python是相对于Java、C#、Go、JavaScript、C++、等。其中包括JIT(C#、Java)和AOT(C、C++)编译器,以及JavaScript等解释型语言。动态编译:https://en.wikipedia.org/wiki/Just-in-time_compilation静态编译:https://en.wikipedia.org/wiki/Ahead-of-time_compilation注意:当我提到“Python”时,我我指的是官方解释器CPython。我还将在本文中提及其他解释器。我要回答的问题是:为什么在运行同样的程序时,Python比其他语言慢2到10倍?为什么我们不能让它更快?主要有以下几个原因:“是GIL(GlobalInterpreterLockGlobalInterpreterLock)”“是解释型语言,不是编译型语言”“是动态类型语言”那么上面哪个原因影响最大表现?“它是一个全局解释器锁”现代计算机的CPU通常是多核的,有些有多个处理器。为了利用多余的处理能力,操作系统定义了一个称为线程的低级结构:一个进程(例如Chrome浏览器)可以产生多个线程并指挥内部系统。如果一个进程是CPU密集型进程,它的负载可以同时由多个内核处理,从而有效地提高大多数应用程序的速度。在我撰写本文时,我的Chrome浏览器有44个并发线程。请注意,线程结构和API在基于POSIX的操作系统(例如MacOS和Linux)和Windows操作系统之间是不同的。操作系统还处理线程的调度。如果你以前没有做过多线程编程,你需要快速熟悉一下锁的概念。与单线程进程不同的是,需要保证当修改内存中的一个变量时,多个线程不会同时尝试访问或更改同一个存储地址。当CPython创建一个变量时,它会预先分配存储空间,然后计算对当前变量的引用数。这个概念称为引用计数。如果引用计数为零,则它会从系统中释放相应的存储区域。这就是为什么在CPython中创建“临时”变量不会在应用程序中占用大量存储空间的原因-特别是如果应用程序使用可能会创建大量“临时”变量的for循环等结构。当有多个线程调用变量时,CPython如何锁定引用计数成为一个挑战。“全局解释锁”应运而生,可以细致地控制线程的执行。不管有多少个线程,解释器一次只能执行一个操作。这对Python性能意味着什么?如果你的应用是基于单线程、单解释器的,那么谈速度是没有意义的,因为去掉GIL并不会影响代码的性能。如果你想在单个解释器(Python进程)中使用线程实现并发,而你的线程是IO密集型的(比如网络IO或磁盘IO),你会看到GIL竞争的结果。此图来自DavidBeazley的GILVisualization如果您有一个Web应用程序(例如Django)并使用WSGI,那么对您的Web应用程序的每个请求都将是一个单独的Python解释器,因此每个请求只有一个锁。由于Python解释器启动缓慢,一些WSGI集成了一个“守护进程”来保持Python进程运行。那么其他Python解释器的速度如何呢?PyPy有一个GIL,通常至少比CPython快三倍。Jython没有GIL,因为Python线程在Jython中由Java线程表示,这要归功于JVM内存管理系统。JavaScript是如何做到这一点的?首先,所有的Javascript引擎都采用了mark-and-sweep垃圾回收系统,而前面提到的GIL的基本诉求就是CPython的存储管理算法。JavaScript没有GIL,但因为它是单线程的,所以它也不需要GIL。JavaScript通过事件循环和promise/callback模式在异步编程中实现并发。Python与异步事件循环有类似的过程。“因为它是一种解释性语言”我经常听到这样的话。我认为这只是对CPython实际工作原理的简单解释。如果你在终端输入pythonmyscript.py,CPython将开始一系列读取、词法分析、解析、编译、解释和运行这段代码。这个过程中重要的一步是在编译阶段创建一个.pyc文件,这个字节码序列会被写入到Python3下__pycache__/路径下的一个文件中(对于Python2,文件路径是一样的)。此步骤不仅适用于脚本文件,还适用于所有导入的代码,包括第三方模块。所以大多数时候(除非你编写的代码只运行一次),Python正在解释字节码并在本机执行它。下面我们将Java与C#.NET进行比较:Java被编译成一种“中间语言”,然后Java虚拟机读取字节码并即时编译成机器码。.NET的通用中间语言(CIL)也是一样,它的通用语言运行时(CLR)也是使用即时编译翻译成机器码的。那么,如果Python使用与Java和C#相同的虚拟机和某种字节码,为什么它在基准测试中要慢得多?首先,.NET和Java是JIT编译的。JIT,也称为即时编译,需要一种中间语言将代码分成块(或数据帧)。预编译器(AOT,AheadofTime)的设计保证了CPU在交互之前能够理解每一行代码。JIT本身不会使执行速度更快,因为它仍然执行相同的字节码序列。但是,JIT允许在运行时进行优化。一个好的JIT优化器可以检测出哪些部分执行得比较频繁,这些部分被称为“热点”。然后它将用更高效的代码替换它们,完成优化。这意味着当计算机应用程序需要重复做一件事时,它会更快。另外,我们要知道Java和C#都是强类型语言(需要预定义变量),所以优化器可以对代码做更多的假设。PyPy使用即时编译器,如前所述,它比CPython更快。这篇关于基准测试的文章更详细——什么版本的Python最快?链接:https://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b那么,为什么CPython不使用即时编译器呢?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都是这样的语言。在动态类型语言中,类型的概念依然存在,只是这个变量的类型是动态改变的。a=1a="foo"在上面的例子中,Python在创建第二个变量时使用了相同的名称,但是变量类型是str(字符类型),这样之前在内存中分配给a的空间被处理释放并重新分配。静态类型语言并不是以这种方式设计来打扰每个人的——它们被设计成以CPU的方式运行。如果最后一切都需要变成简单的二进制操作,对象和类型必须转换为低级数据结构。Python自动完成了这个过程,我们看不到,也不需要看到。不必声明类型并不是使Python变慢的原因。Python语言的设计允许我们创建几乎任何动态变量。我们可以在运行时替换对象中的方法,或者我们可以将低级系统调用随机分配给一个值。几乎任何东西都可以修改。正是这种设计使得优化Python异常困难。为了说明我的观点,我将使用MacOS中的一个应用程序。这是一个名为Dtrace的系统调用跟踪工具。CPython发行版没有内置DTrace,因此您必须重新编译CPython。下面的演示中使用了3.6.6版本。wgethttps://github.com/python/cpython/archive/v3.6.6.zipunzipv3.6.6.zipcdv3.6.6./configure--with-dtracemakepython.exe现在将在其代码中使用DTrace跟踪器。PaulRoss就DTrace做了一个很棒的简短演讲。您可以下载Python的DTrace启动文件来测试函数调用、执行时间、CPU时间、系统调用和各种有趣的事情。示例:sudodtrace-stoolkit/.d-c'../cpython/python.exescript.py'DTrace启动文件:https://github.com/paulross/dtrace-py/tree/master/toolkit谈话链接:httpshttps://github.com/paulross/dtrace-py#the-lightning-talkpy_callflow跟踪器显示了应用程序中的所有函数调用:那么,Python的动态类型是否使其变慢了?比较和转换类型很耗时是的,因为每次读取、写入或引用变量类型时都进行检查,很难针对如此动态的语言进行优化。其他语言之所以这么快,是因为它们为了性能牺牲了一些灵活性。看看Cython,它结合了C-Static类型和Python来优化已知类型的代码,可以提供84倍速度的性能提升。结论Python的缓慢主要是由于它的动态和通用性。它可以用来解决几乎任何问题,但可能存在更优化和更快的替代方案。但是,有一些方法可以通过利用异步计算、了解分析工具以及考虑使用多个解释器来优化Python应用程序。对于一些启动时间相对不重要且即时编译器(JIT)可以提高效率的应用程序,可以考虑使用PyPy。对于性能优先且静态变量较多的部分代码,可以考虑使用Cython。相关报道:https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b【本文为专栏组织大数据文摘原创翻译,微信公众号》大数据文摘(id:BigDataDigest)》】戳这里,阅读更多本作者的好文