如果PyPy比CPython快6.3倍,为什么我不应该使用PyPy?

800

我一直听说PyPy项目。他们声称在他们的网站上,它比CPython解释器快6.3倍。

每当我们谈到像Python这样的动态语言时,速度是最重要的问题之一。为了解决这个问题,他们说PyPy比较快6.3倍。

第二个问题是并行性,臭名昭著的全局解释器锁定(GIL)。对此,PyPy表示它可以提供无GIL Python

如果PyPy能够解决这些巨大的挑战,那么是什么阻止了它的广泛采用呢?也就是说,是什么阻止了像我这样的典型Python开发人员立即切换到PyPy呢?


42
删除了大部分评论,因为其中大多数东西应该在回答中阐述(在某些情况下已经有了),或者根本不应该说出来。同时进行编辑以回应提出的一些关于这个问题主观性的担忧。请尽量使用事实回答,并在可能的情况下用来源支持论点! - Shog9
5
我经常使用Pypy,它很有效率。然而,虽然Pypy在许多CPU密集型工作负载上比较快,但在我测试的I/O密集型工作负载中却变慢了。例如,我写了一个去重备份程序叫做backshift。对于初始备份,需要大量文件分块,Pypy表现得非常出色。但对于后续备份,因为主要更新时间戳,CPython更快。 - dstromberg
12个回答

726
注意:PyPy现在比2013年这个问题被提出时更加成熟和得到更好的支持。避免从过时的信息中得出结论。
  1. 正如其他人迅速提到的那样,PyPy对于C扩展的支持有些脆弱。它有支持,但通常比Python速度慢,并且最好是不确定的。因此,许多模块仅需要CPython。请查看支持包列表,但请注意该列表更新的日期,因为它与实际支持不是同步的,因此仍然可能存在标记为不受支持的软件包实际上得到了支持。
  2. Python支持通常滞后几个版本,因此如果您绝对需要最新功能,则可能需要等待一段时间,直到PyPy支持它们。
  3. 对于许多人使用Python的“脚本”,PyPy有时并不真正更快。这些是执行简单小任务的短期运行程序。由于PyPy是JIT编译器,其主要优势来自长时间运行和简单类型(例如数字)。与CPython相比,PyPy的预JIT速度可能很慢。
  4. 惯性。转向PyPy通常需要重新调整,对于某些人和组织来说,这只是太多的工作。

这些是我认为对我产生影响的主要原因。


19
很高兴你提到了重新改装。例如,我的网络主机只能选择Python 2.4和2.5;而我附近的“一个主要的娱乐软件生产商”正在使用2.6版本,没有计划很快升级。有时甚至发现转换成本都需要付出巨大的代价。 - Mike Housky
23
PyPy说“跟C一样快”更多的是指通用的C而不是高度优化的多线程缓存感知的用于数值计算的C库。对于数值计算,Python只是用来传递大型数组指针的。因此,PyPy“跟C一样快”的意思是“您的指针+元数据会像C一样快速移动”。这并不是什么大不了的事情。那么为什么还要使用Python呢?去看看cblas和lapacke中的函数签名吧。 - cjordan1
13
@cjordan1:我不理解你在说什么。在Python中,高级NumPy结构非常具有表现力(np.sum(M[1:2*n**2:2, :2*n**2] * M[:2*n**2:2, :2*n**2].conjugate(), axis=1)?),这使得Python非常适合科学界。此外,在Python中处理不太密集的部分并将其转换为C语言以处理更小的密集循环是一种常见而可行的策略。 - Veedrac
27
这是需要翻译的内容:“@Veedrac,这就是我的意思。比如说,“去看一下cblas和lapacke中的函数签名”,因为它们太长而且难以使用,你会立即明白为什么我们使用Python来传递指针和元数据。” - cjordan1
5
@tommy.carstensen 这里不是深入探讨的好地方,但我会尽力。1. 当我写这篇文章时,它更为真实,而现在则不然了。2. "脚本"通常会涉及到I/O操作。PyPy的I/O速度仍然经常比CPython慢,曾经慢得多。3. PyPy曾经在处理字符串上比CPython慢,现在通常更好而很少更差。4. 很多"脚本"只是粘合代码 - 让解释器更快并不能改进总运行时间。5. PyPy的预热时间曾经更长 - 短执行时间的脚本很少能产生大量的热代码。 - Veedrac
显示剩余13条评论

114

该网站并未声称PyPy比CPython快6.3倍。引用原话:

所有基准测试的几何平均值为0.16,比CPython快6.3倍

这是一种非常不同的说法,当你理解了它们之间的区别时,你就会明白至少一个不能仅仅说“使用PyPy”的原因。这可能听起来像我在挑剔,但是理解这两个声明为什么完全不同是至关重要的。

具体说明如下:

  • 他们所说的只适用于他们使用的基准测试。除非你的程序与他们的基准测试完全相同,否则它对你的程序没有任何意义。

  • 这个声明是关于一组基准测试的平均值。并没有声称运行PyPy即使对于他们测试的程序而言也会有6.3倍的提升。

  • 并没有声称PyPy可以运行CPython所有程序,更别说比CPython更快了。


21
当然,并不是所有Python代码都能在PyPy上运行得更快。但是如果你考虑所有的纯Python应用程序,我敢打赌其中相当大一部分在PyPy上的运行速度会比在CPython上快得多(超过3倍)。 - Robert Zaremba
30
你的前两个要点都没有意义。你怎么能说基准测试“绝对无法说明你的程序”?很明显,基准测试并不是所有真实应用的完美指标,但它们可以作为一个指示器,相当有用。此外,我不理解你对他们报告一组基准测试平均值所发现的误导性。他们非常清楚地说明这是一个平均值。如果一个程序员不知道什么是平均值,那么他们有比语言性能更严重的问题。 - Sean Geoffrey Pietz
9
@SeanGeoffreyPietz - 我并没有声称PyPy的网站有任何误导性,他们准确地呈现了他们的结果。但是原来的问题引用不准确,并且显示作者没有理解“平均”这个词的重要性。许多单独的基准测试并不是6.3倍快。如果你使用不同类型的平均值,你会得到不同的值,因此“6.3倍更快”不是“几何平均值是6.3倍更快”的充分概括。“A组比B组快Z倍”太含糊了,没有实际意义。 - spookylukey
13
@spookylukey,您似乎在暗示基准测试套件存在偏见,但没有提供支持此主张的证据。批评应始终有证据支持! - Evgeni Sergeev
9
@EvgeniSergeev - 不,我的意思是所有基准测试都存在偏差!当然,并不一定是故意的。有用的程序可能性无限且非常多样化,一组基准测试只能测量这些基准测试中的性能。问“PyPy比CPython快多少?”就像问“Fred比Joe快多少?”,这似乎是OP想知道的。 - spookylukey
显示剩余4条评论

90

由于pypy不完全兼容,编译需要8GB内存,是一个不断更新的实验性工具,而cpython则是稳定的默认目标,已成为模块构建者2十年来的首选(包括不适用于pypy的c扩展),并已广泛部署。

pypy很可能永远不会成为参考实现,但它是一个不错的工具。


2
根据http://pypy.org/download.html的说明,PyPy编译需要4GB的RAM(在64位系统上),而不是8GB。如果需要的话,该页面还提供了在3GB内完成编译的选项。 - knite
4
1: 从2015年开始,文档中的规定变成了16 GB,历史上一直是8 GB。 2: 实际情况是,在2015年你仍然需要至少8 GB,其中有6-7 GB可用空间。 - Tritium21
5
如果你使用构建或分发,编译所需的内存要求就不是那么重要了。至于“移动目标和高度实验性”,你能举几个会出问题的例子吗?同样,如果人们使用发布版本而不是每夜构建或源代码,他们难道不应该有一个合理的功能期望吗? - smci
1
@smci 这是一个基于古老数据的古老问题,带有古老的答案。请将此问题和每个答案视为4年前pypy状态的历史记录。 - Tritium21
2
@Tritium21:我只对当前答案感兴趣。它是什么?您可能需要编辑您的答案,并说“截至2013年,比较pypy与Python 2.x版本时...”。此外,如果问题中的“6.3x几何平均值”声明已过时(截至2017年4月,他们声称为7.5x,但仍取决于基准测试...),那么也需要进行编辑(版本号、最新数据等)。我认为基准测试套件并不太相关,现在很少有人会在CPU上使用脚本语言运行raytracing。我在https://pybenchmarks.org找到了一些信息。 - smci
@smic 为了得到更加实时的答案,请重新提问,并将此问题标记为重复。该问题已经过时。 - Tritium21

41
第二个问题比较容易回答: 如果你所有的代码都是纯Python,那么基本上你可以使用PyPy作为替代品。然而,许多广泛使用的库(包括一些标准库)是用C编写的,并编译为Python扩展。其中一些可以与PyPy一起使用,而有些则不能。PyPy提供了与Python相同的“前向”工具,也就是说,它是Python,但是其内部实现不同,因此无法与那些与内部实现进行接口的工具一起使用。
至于第一个问题,我想这可能是第一个困难的问题:PyPy正努力发展以提高速度并增强与其他代码的互操作性。这使它比官方版本更加实验性。
我认为如果PyPy进入稳定状态,它可能会开始被广泛使用。我也认为将Python从其C语言基础中移开将是很好的。但这需要一段时间。PyPy还没有达到临界质量,即几乎足够自行完成所有您想要的任务,这将激励人们填补空白。

20
我不认为C语言会在短时间内消失(我愿意说,在我们有生之年内它不会消失)。除非出现另一种能够运行在任何地方的语言,否则我们将一直使用C语言。(请注意,JVM是用C语言编写的。即使是“到处运行”的Java语言也需要C语言来实现其“无处不在”)除此之外,我在大多数观点上都同意这篇文章。 - Tritium21
9
@Tritium21:是的,我只是在发表一下自己的编辑意见。我认为C语言的存在很好,但是我认为Python对C语言的依赖是非常不利的,PyPy就是一个很好的例子:现在我们有机会得到更快的Python,但却因多年依赖C而受阻。如果Python能够独立发展会更好。即使Python本身是用C编写的,也没有问题,但问题在于扩展机制的存在,这鼓励人们以依赖C语言的方式扩展Python。 - BrenBarn
4
这是一把双刃剑——Python 之所以如此受欢迎的部分原因是它能够扩展其他应用程序,也能够被其他应用程序扩展。如果你剥夺了这一点,我认为我们不会谈论 Python。 - Tritium21
12
声称 Python 对 C 的依赖是有害的完全是愚蠢的。如果没有 Python 的 C-API,在其青少年时期(90年代末)Python 获得的大多数真正强大的库和出色的互操作性,包括整个数字/科学生态系统和 GUI 界面,都将不可能存在。在做出这样的概括性陈述之前,请看看 Python 的整个用途领域,以获取更全面的视角。 - Peter Wang
4
@PeterWang 所有这些库都可以用 Python 编写,但是它们的速度不会像现在这样快。BrenBarn 的意思是说我们现在有机会让 Python 变得足够快,以便可以用 Python 写这些库,但我们拒绝采取这个机会,因为采取这个机会就意味着失去使用 C 库的能力。我相信他所说的有害是指这一点,而不是 C 库的存在本身是不好的,而是制作快速库的唯一方法是使用 C。 - vikki
显示剩余8条评论

15

我对这个话题进行了小小的基准测试。虽然其他许多帖子中提出了很多有关兼容性的好观点,但我的经验是,PyPy在仅移动位时并不比其他解释器快很多。对于 Python 的许多用途,它实际上只存在于两个或多个服务之间传输位。例如,很少有 Web 应用程序执行 CPU 密集型数据集分析。相反,它们从客户端获取一些字节,将其存储在某种数据库中,稍后将其返回给其他客户端。有时数据的格式会发生变化。

BDFL 和 CPython 开发人员是一群非常聪明的人,他们已经成功地帮助 CPython 在这种情况下表现出色。这里插一下博客链接: http://www.hydrogen18.com/blog/unpickling-buffers.html。我正在使用源自 CPython 并保留完整的 C 模块接口的 Stackless。在那种情况下,我没有发现使用 PyPy 有任何优势。


1
PyPy有许多精心运行的基准测试(不像CPython不幸的是,目前并没有真正面向用户的基准测试套件)。当然,对于网络流量,PyPy不能神奇地使任何东西更快。 - Julian
2
Julian,值得注意的是,PyPy团队多年来一直致力于改进特定基准测试的运行时间。在某种程度上,他们似乎正在将优化过度拟合到这组基准测试中,并且根据我的经验,除了纯数值计算(最好使用Fortran或C99),我从未让PyPy比CPython快超过~2倍。 - Alex Rubinsteyn
9
在PyPy的开发人员看来,如果你发现了一个PyPy比CPython慢的情况,并且你可以将其转化为一个合理的基准测试,那么它有很大的可能被添加到测试套件中。 - gsnedders
1
我查看了你的博客。在你的结果中,纯Python的(pickle, StringIO)组合表明pypy比cpython快约6.8倍。我认为这是一个有用的结果。在你的结论中,你指出(正确地)pypy代码(它是纯Python!)比C代码(cPickle,cStringIO)慢,而不是cpython代码。 - Caleb Hattingh
2
@gsnedders 我已经基于rinohtype多个场合上提供了基准测试。他们还没有将其添加到套件中。 - Brecht Machiels

15

问:如果与CPython相比,PyPy可以解决这些巨大的挑战(速度、内存消耗、并行性),那么它的弱点是什么,阻止了更广泛的采用?

答:首先,很少有证据表明PyPy团队可以总体上解决速度问题。长期的证据显示,PyPy运行某些Python代码比CPython慢,而且这个缺点似乎根深蒂固地存在于PyPy中。

其次,当前版本的PyPy在相当大的一部分情况下消耗的内存比CPython多得多。因此,PyPy尚未解决内存消耗问题。

无论PyPy是否解决了上述巨大挑战,并且是否总体上比CPython更快、更节省内存和更友好地支持并行性,这都是一个无法在短期内解决的开放性问题。有些人打赌PyPy永远无法提供一个总体上的解决方案,在所有情况下都能主导CPython 2.7和3.3。

如果PyPy在一般情况下比CPython更好,这是值得怀疑的,那么影响其广泛采用的主要弱点将是其与CPython的兼容性。还存在一些问题,例如CPython可以运行在更广泛的CPU和操作系统上,但与PyPy的性能和CPython兼容性目标相比,这些问题要不重要得多。

问:为什么我现在不能用PyPy来替换CPython?

答:PyPy与CPython不是100%兼容,因为它并没有在底层模拟CPython。一些程序可能仍然依赖于CPython的独特功能,这些功能在PyPy中不存在,比如C绑定、Python对象和方法的C实现,或者CPython垃圾回收器的增量特性。


1
此答案未引用任何基准或提供参考资料。 - qwr

11

CPython有引用计数和垃圾回收机制,而PyPy只有垃圾回收机制。

因此,在CPython中对象往往更早被删除,并以更可预测的方式调用__del__方法。一些软件依赖于这种行为,因此它们还没有准备好迁移到PyPy。

另一些软件可以同时使用两者,但使用CPython可以节省更多内存,因为未使用的对象会更早地被释放。(我没有任何测量数据来表明这有多重要,以及其他实现细节如何影响内存使用。)


20
强调即使在CPython中,仅仅依赖__del__被早期或根本不会被调用是错误的。就像你所说,“通常”情况下可以工作,有些人认为这意味着它是保证的。如果任何引用该对象的内容都被包含在引用循环中(这相当容易 - 你知道以某种非假设方式检查当前异常会创建一个引用循环吗?),最终化将无限期地延迟,直到下一个循环GC(可能会永远延迟)。如果该对象本身是引用循环的一部分,则在Python 3.4之前根本不会调用__del__ - user395760
4
在CPython中,每个对象的开销更高,在创建大量对象时这非常重要。我相信PyPy默认会执行类似于__slots__的功能。 - user1024732

7

对于许多项目而言,在工程时间占主导且所有Python版本都拥有相同数量的库支持的情况下,实际上不同的Python版本在速度方面没有任何区别。


1
如果你的项目非常简单,那么显然这并不重要,但是同样可以说对于任何语言的任何实现:如果你只是通过相对高效的 ABI 聚合其他库的函数,那么这一切都是无关紧要的。 - user1024732
2
这与简单无关。在工程时间中,反馈环路很重要。有时比运行时间更重要。 - Stephan Eggermont
1
你说话非常含糊(工程时间没有提到正在进行的工程、约束条件等;反馈循环没有提到反馈给谁等),所以我选择退出这个对话,而不是交换神秘的参考。 - user1024732
1
请看OODA循环或PDCA,这里没有任何模糊的地方。 - Stephan Eggermont
4
任何一次性运行的项目,编写需要一个月,运行只需要一分钟,即使PyPy快了一千倍,使用PyPy后总体加速比为0.0%(1个月+1分钟 vs 1个月)。 Stephan并不是说所有项目都会有0%的加速比。 - gmatht
显示剩余3条评论

6
简单来说,PyPy提供了CPython所缺乏的速度,但是牺牲了其兼容性。然而,大多数人选择Python是因为它具有灵活性和“电池包含”特性(高兼容性),而不是因为它的速度(尽管速度仍然受欢迎)。

20
“battery-included” 意思是“大型标准库”,据我所知。 - tshepang

5

我发现有些例子中,PyPy在Windows上比Python慢。

但是:只有在Windows上。
C:\Users\User>python -m timeit -n10 -s"from sympy import isprime" "isprime(2**521-1);isprime(2**1279-1)"
10 loops, best of 3: 294 msec per loop

C:\Users\User>pypy -m timeit -n10 -s"from sympy import isprime" "isprime(2**521-1);isprime(2**1279-1)"
10 loops, best of 3: 1.33 sec per loop

所以,如果你想到PyPy,就忘记Windows吧。 在Linux上,你可以实现惊人的加速。 例如(列出1到1,000,000之间的所有质数):

from sympy import sieve
primes = list(sieve.primerange(1, 10**6))

在 PyPy 上运行比在 Python 上快了 10 倍!但是在 Windows 上并不是这样。在那里,速度只能提高 3 倍。


1
有趣!更多的比较和数字会更好。 - ben26941

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接