Python很慢。 怎么了

最近,人们可以观察到Python编程语言的日益流行。 它用于DevOps,数据分析,Web开发,安全领域和其他领域。 但这就是速度。这里没有什么可夸耀的。 该材料的作者(我们今天将其翻译发表)决定找出Python缓慢的原因并找到加速它的方法。



一般规定


就性能而言,Java与C或C ++有何关系? 如何比较C#和Python? 这些问题的答案在很大程度上取决于研究人员分析的应用程序类型。 没有完美的基准,但是研究使用不同语言编写的程序的性能时,“计算机语言基准测试”可能是一个不错的起点

我已经提到计算机语言基准游戏已有十多年了。 与其他语言(例如Java,C#,Go,JavaScript,C ++)相比,Python是最慢的语言之一。 这包括使用JIT编译(C#,Java)和AOT编译(C#,C ++)的语言,以及诸如JavaScript之类的解释语言。

在这里,我想指出的是,当我说“ Python”时,是指Python解释器的参考实现-CPython。 在本文中,我们将介绍其其他实现。 实际上,在这里我想找到以下问题的答案:为什么Python需要比其他语言多2到10倍的时间来解决类似的问题,以及它是否可以更快地完成。

以下是一些基本理论试图解释Python为何运行缓慢:

  • 其原因是GIL(全局解释器锁定,全局解释器锁定)。
  • 原因是Python是一种解释性而非编译语言。
  • 原因是动态键入。

我们将分析这些想法,并尝试找到对Python应用程序的性能影响最大的问题的答案。

吉尔


现代计算机具有多核处理器,有时会发现多处理器系统。 为了使用所有这些计算能力,操作系统使用称为线程的低级结构,而进程(例如Chrome浏览器进程)可以启动许多线程并相应地使用它们。 结果,例如,如果一个进程特别急需处理器资源,则可以在几个内核之间分配其执行,这使大多数应用程序可以更快地解决其面临的任务。

例如,当我撰写本文时,我的Chrome浏览器具有44个开放线程。 应该记住,用于流的系统的结构和API在基于Posix的操作系统(Mac OS,Linux)和Windows操作系统家族中有所不同。 操作系统还计划线程。

如果您以前从未遇到过多线程编程,那么现在您需要熟悉所谓的锁(locks)。 锁的含义是,当在多线程环境中,例如,当更改内存中的某个变量时,多个线程无法访问同一内存区域(用于读取或更改)时,它们使您可以确保系统的这种行为。

当CPython解释器创建变量时,它将分配内存,然后计算对这些变量的现有引用数。 这个概念称为参考计数。 如果链接数等于零,则释放相应的内存。 这就是为什么例如在循环范围内创建“临时”变量不会导致应用程序占用的内存量过度增加的原因。

当几个线程共享相同的变量时,最有趣的部分开始了,这里的主要问题是CPython如何精确地执行引用计数。 这是“全局解释器锁”的动作出现的地方,它仔细地控制线程的执行。

解释器一次只能执行一个操作,而不管程序中有多少线程。

ILGIL如何影响Python应用程序的性能?


如果我们有一个在同一Python解释器进程中运行的单线程应用程序,则GIL不会以任何方式影响性能。 例如,如果摆脱了GIL,我们将不会注意到性能上的任何差异。

如果在一个Python解释器进程的框架内,有必要使用多线程机制来实现并行数据处理,并且所使用的流将大量使用I / O子系统(例如,如果它们与网络或磁盘一起工作),那么就有可能观察到GIL如何管理线程。 这是使用两个线程,大量加载进程的情况。


GIL可视化( 从此处获取

如果您有一个Web应用程序(例如,基于Django框架)并且使用WSGI,则对该Web应用程序的每个请求将由一个单独的Python解释器进程处理,即,我们只有1个请求锁。 由于Python解释器启动缓慢,因此在某些WSGI实现中,存在一种所谓的“守护程序模式”,在使用该模式时, 解释器进程将保持工作状态,从而使系统能够更快地处理请求。

other其他Python解释器的行为如何?


PyPy有一个GIL,通常比CPython快3倍以上。

Jython中没有GIL,因为Jython中的Python线程表示为Java线程。 此类线程使用JVM的内存管理功能。

the如何用JavaScript组织流控制?


如果我们谈论JavaScript,那么首先应注意,所有JS引擎都使用标记清除垃圾收集算法。 如前所述,使用GIL的主要原因是CPython中使用的内存管理算法。

JavaScript没有GIL,但是JS是一种单线程语言,因此不需要这种机制。 除了使用并行代码执行之外,JavaScript使用基于事件循环,承诺和回调的异步编程技术。 Python具有asyncio模块提供的类似asyncio

Python-解释语言


我经常听到,Python性能差是由于它是一种解释语言。 这样的陈述是基于对CPython实际工作方式的总体简化。 如果在终端中输入python myscript.py类的命令,那么CPython将开始执行一系列长动作,包括读取,词法分析,解析,编译,解释和执行脚本代码。 如果您对这些细节感兴趣,请查看材料。

对我们来说,考虑此过程时,在编译阶段创建一个.pyc文件,并将字节码序列写入__pycache__/目录中的文件特别重要,该文件在Python 3和Python中都使用2。

这不仅适用于我们编写的脚本,还适用于导入的代码,包括第三方模块。

结果,在大多数情况下(除非编写只运行一次的代码),Python将执行完成的字节码。 如果将其与Java和C#中发生的情况进行比较,结果证明Java代码被编译为“中间语言”,并且Java虚拟机读取字节码并将其JIT编译为机器代码。 “中间语言” .NET CIL(与.NET Common-Language-Runtime,CLR相同)使用JIT编译导航到机器代码。

结果,在Java和C#中,都使用了一些“中间语言”,并且存在类似的机制。 如果所有这些语言都使用虚拟机和某种字节码,那么为什么Python的基准测试比Java和C#差得多? 首先,由于在.NET和Java中使用了JIT编译这一事实。

JIT编译(即时编译,即时编译或即时编译)需要一种中间语言,以允许将代码拆分为片段(帧)。 AOT编译系统(提前编译,在执行之前编译)的设计方式应确保在此代码与系统开始交互之前确保代码的完整功能。

就其本身而言,使用JIT并不能加快代码的执行速度,因为某些字节代码片段已开始执行,就像Python中那样。 但是,JIT允许您在执行过程中执行代码优化。 一个好的JIT优化器能够识别应用程序中负载最大的部分(该应用程序的这一部分称为“热点”)并优化相应的代码片段,用比以前使用的优化的和更有生产力的选项代替它们。

这意味着当某个应用程序一遍又一遍执行某些动作时,这种优化可以显着加快此类动作的执行速度。 另外,请记住Java和C#是强类型语言,因此优化器可以对有助于提高程序性能的代码做出更多假设。

PyPy中有一个JIT编译器,并且如上所述,该Python解释器实现比CPython快得多。 在本文中可以找到有关比较不同的Python解释器的信息。

▍为什么CPython不使用JIT编译器?


JIT编译器也有缺点。 其中之一是启动时间。 CPython已经相对较慢地启动,而PyPy比CPython慢​​2-3倍。 JVM的长时间运行也是众所周知的事实。 CLR .NET通过在系统引导过程中启动来解决此问题,但应注意,CLR和运行CLR的操作系统均由同一公司开发。

如果您有一个运行了很长时间的Python进程,而在这样的进程中,由于包含了使用率很高的部分,因此可以对其进行优化的代码,那么您应该认真看一下具有JIT编译器的解释器。

但是,CPython是通用Python解释器的实现。 因此,如果您正在使用Python开发命令行应用程序,则每次启动该应用程序时都需要长时间等待JIT编译器启动,这将大大减慢工作速度。

CPython试图为尽可能多的Python用例提供支持。 例如,有可能将JIT编译器连接到Python,但是,实现此想法的项目并不是很活跃。

结果,我们可以说,如果您正在使用Python编写一个使用JIT编译器可以提高性能的程序,请使用PyPy解释器。

Python是一种动态类型的语言


在静态类型语言中,声明变量时,必须指定其类型。 在这些语言中,可以注意到C,C ++,Java,C#,Go。

在动态类型的语言中,数据类型的概念具有相同的含义,但是变量的类型是动态的。

 a = 1 a = "foo" 

在这个最简单的示例中,Python首先创建第一个变量a ,然后创建第二个变量,其名称相同,类型为str ,并释放分配给第一个变量a的内存。

用动态类型的语言编写文字似乎比使用静态类型的语言编写文字更为方便和简单,但是,这种语言并不是一时兴起的。 在开发过程中,已经考虑了计算机系统的功能。 最后,程序文本中编写的所有内容都归结为处理器指令。 这意味着程序使用的数据(例如以对象或其他类型的数据形式)也将转换为低级结构。

Python自动执行此类转换,程序员看不到这些过程,并且他不需要照顾此类转换。

在声明变量的类型时不必指定变量的类型,这不是使Python变慢的语言的功能。 语言体系结构使几乎任何事物都可以动态化。 例如,在运行时,您可以替换对象方法。 同样,在程序执行期间,可以使用应用于低级系统调用的“猴子补丁”技术。 在Python中,几乎一切皆有可能。

Python架构使优化变得极为困难。

为了说明这个想法,我将使用一个名为DTrace的工具来跟踪MacOS上的系统调用。

在完成的CPython发行版中没有DTrace支持机制,因此将需要使用适当的设置重新编译CPython。 这里使用版本3.6.6。 因此,我们使用以下操作序列:

 wget https://github.com/python/cpython/archive/v3.6.6.zip unzip v3.6.6.zip cd v3.6.6 ./configure --with-dtrace make 

现在,使用python.exe ,您可以使用DTRace来跟踪代码。 在此处阅读有关将DTrace与Python结合使用的信息在这里,您可以找到用于使用DTrace测量Python程序的各种性能指标的脚本。 其中包括用于调用函数的参数,程序运行时,处理器使用时间,有关系统调用的信息等等。 这是使用dtrace命令的方法:

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

这是py_callflow跟踪py_callflow如何显示应用程序中的函数调用。


使用DTrace进行跟踪

现在让我们回答动态类型是否影响Python性能的问题。 这是一些想法:

  • 类型检查和转换是繁重的操作。 每次访问,读取或写入变量时,都会执行类型检查。
  • 具有这种灵活性的语言难以优化。 其他语言比Python快得多的原因是,它们通过在灵活性和性能之间进行选择而妥协。
  • Cython项目将Python和静态类型结合在一起,例如,如本文中所示,与常规Python相比,其性能提高了84倍。 如果需要速度,请签出此项目。

总结


Python性能不佳的原因是其动态特性和多功能性。 它可以用作解决各种任务的工具。 为了实现相同的目标,您可以尝试寻找更高效,更优化的工具。 也许他们将能够找到,也许找不到。

可以使用异步代码执行,性能分析工具以及选择正确的解释器的功能来优化用Python编写的应用程序。 因此,要优化那些启动时间不重要并且其性能可能会受益于使用JIT编译器的应用程序的速度,请考虑使用PyPy。 如果您需要最大的性能并且准备好应对静态类型的限制,请查看Cython。

亲爱的读者们! 您如何解决糟糕的Python性能问题?

Source: https://habr.com/ru/post/zh-CN418823/


All Articles