[翻译] 为什么 Python 这么慢?

本贴最后更新于 2148 天前,其中的信息可能已经时异事殊

原文链接:https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b

Python 正在蓬勃发展。它用于 DevOps,数据科学,Web 开发和安全。

然而,它并没有为速度赢得任何奖牌。

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 操作系统之间的线程结构和 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 解释器,因此_每个_请求只有一个锁。由于 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 将启动一长串读,lexing,解析,编译,解释和执行该代码。

如果您对该过程的工作方式感兴趣,我之前已经写过:

在 6 分钟内修改 Python 语言
_本周我向 CPython 核心项目提出了我的第一个拉取请求,该项目被拒绝:-(但不完全是..._hackernoon.com

该过程的一个重点是创建 .pyc 文件,在编译阶段,字节码序列被写入 __pycache__/ Python 3 内部的文件或 Python 2 中的同一目录。这不仅适用于您的脚本,而是您导入的所有代码,包括第三方模块。

所以大部分时间(除非你编写的代码只运行一次?),Python 正在解释字节码并在本地执行它。与 Java 和 C#.NET 相比:

Java 编译为“中间语言”,Java 虚拟机读取字节码,并及时将其编译为机器代码。.NET CIL 是相同的,.NET 公共语言 - 运行时,CLR,使用即时编译到机器代码。

那么,如果 Python 使用虚拟机和某种字节码,为什么 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 中运行的名为 Dtrace 的系统调用跟踪工具。CPython 发行版没有内置 DTrace,因此您必须重新编译 CPython。我正在使用 3.6.6 进行演示

wget [https://github.com/python/cpython/archive/v3.6.6.zip](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 启动文件来测量函数调用,执行时间,CPU 时间,系统调用,各种乐趣。例如

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

py_callflow 示踪显示在您的应用程序中的所有函数调用

那么,Python 的动态类型会让它变慢吗?

  • 比较和转换类型是昂贵的,每次读取变量,写入或引用类型时都要检查
  • 很难优化一种如此动态的语言。Python 的许多替代品之速度如此之快的原因在于它们在性能名称上对灵活性做出了妥协
  • 看着用 Cython,它结合了 C-静态类型和 Python 优化,其中类型是已知的代码可以提供一个 84X 性能改进。

结论

由于其动态性和多功能性,Python 主要是缓慢的。它可以用作各种问题的工具,可能有更多优化和更快的替代方案。

但是,有一些方法可以通过利用异步,理解分析工具以及考虑使用多个解释器来优化 Python 应用程序。

对于启动时间不重要且代码有益于 JIT 的应用程序,请考虑 PyPy。

对于性能至关重要并且有更多静态类型变量的代码部分,请考虑使用 Cython

进一步阅读

杰克 VDP 的优秀文章(虽然略显过时)https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/

Dave Beazley 关于 GIL 的讨论 http://www.dabeaz.com/python/GIL.pdf

关于 JIT 编译器的所有信息 https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/

  • 翻译
    58 引用 • 84 回帖 • 1 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    543 引用 • 672 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...