performance pypy使用 - 如果PyPy的速度提高了6.3倍,为什么我不应该在Python上使用PyPy?





pypy知乎 编译pypy (9)


第二个问题更容易回答:如果您的所有代码都是纯Python,您基本上可以使用PyPy作为嵌入式替代品。 但是,许多广泛使用的库(包括一些标准库)都是用C编写的,并且被编译为Python扩展。 其中一些可以与PyPy一起使用,有些则不能。 PyPy提供了与Python相同的“前向”工具 - 也就是Python--但它的内部结构不同,所以与这些内部接口的工具将无法工作。

至于第一个问题,我认为它是第一个Catch-22:PyPy一直在迅速发展,以提高速度并增强与其他代码的互操作性。 这使得它比官方更具实验性。

我认为如果PyPy进入稳定状态,它可能会开始得到更广泛的使用。 我也认为,对于Python来说,摆脱C的基础是件好事。 但这一段时间不会发生。 PyPy还没有达到临界质量,它几乎足以帮助你做任何你想做的事情,这将激励人们填补空白。

我一直听到很多关于PyPy项目的消息。 他们声称它比他们网站上的CPython解释器快6.3倍。

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

第二个问题是并行,即臭名昭着的全球口译员锁 (GIL)。 为此,PyPy说它可以给GIL少的Python

如果PyPy能够解决这些重大挑战,那么它有哪些弱点会阻止更广泛的采用? 也就是说,什么阻止像我这样的典型Python开发人员现在转向PyPy?




我在这个主题上做了一个小基准。 虽然许多其他海报已经说明了兼容性的好处,但我的经验是,PyPy在移动位数方面并没有那么快。 对于Python的许多用途,它实际上只存在于两个或多个服务之间转换位。 例如,没有太多的Web应用程序正在对数据集进行CPU密集型分析。 相反,他们从客户端获取一些字节,将它们存储在某种数据库中,然后将它们返回给其他客户端。 有时数据的格式会改变。

BDFL和CPython开发人员是一群非常聪明的人,并且设法帮助CPython在这种情况下表现出色。 这是一个无耻的博客插件: http://www.hydrogen18.com/blog/unpickling-buffers.htmlhttp://www.hydrogen18.com/blog/unpickling-buffers.html 。 我使用了从CPython派生的Stackless,并保留了完整的C模块接口。 在这种情况下,我没有发现使用PyPy的好处。




为了简单起见,PyPy提供了CPython缺少的速度,但牺牲了它的兼容性。 然而,大多数人选择Python的灵活性和“电池供电”功能(高兼容性),而不是它的速度(尽管如此,仍然是首选)。




问:如果PyPy能够解决这些与CPython相比的巨大挑战(速度,内存消耗,并行性),那么它的弱点在哪些方面会阻止更广泛的应用?

答:首先,几乎没有证据表明PyPy团队可以解决速度问题。 长期证据表明,PyPy比CPython运行某些Python代码更慢,这种缺陷似乎深深植根于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. 正如其他人所说的,PyPy 对C扩展的支持薄弱 。 它支持,但通常以低于Python的速度运行,并且最好也是如此。 因此很多模块只需要 CPython。 Cython和Numpy对于数字来说非常棒 ,而大多数真正需要Python速度的人都会大量使用这些(+ Pandas,SciPy等)。 由于它们要么不存在,要么得到很少的支持,并且需要快速Python的人们放慢速度和易用性往往会更好地利用CPython
  2. Python 3支持目前是实验性的。 刚刚达到稳定! 截至2014年6月20日, PyPy3 2.3.1 - 支点出局
  3. 对于“脚本”,PyPy实际上并不是更快,很多人使用Python。 这些是短时间运行的程序,可以做一些简单而小巧的事情。 由于PyPy是一个JIT编译器,它的主要优点来自长时间运行和简单类型(如数字)。 坦率地说,与CPython相比, PyPy的pre-JIT速度相当糟糕
  4. 惯性 。 转移到PyPy通常需要重新安装,对于某些人员和组织而言,这只是太多的工作。

这些都是影响我的主要原因,我会说。

注意:这个问题是古老的! 避免从过时的信息中得出结论。




该网站并没有声称PyPy比CPython快6.3倍。 去引用:

所有基准的几何平均值比CPython快0.16或6.3倍

这是对你所做的全面陈述的一个非常不同的陈述,当你明白其中的差异时,你至少会理解一组为什么你不能说“使用PyPy”的理由。 这可能听起来像我挑选,但理解为什么这两个陈述完全不同是至关重要的。

打破这一点:

  • 他们所作的陈述仅适用于他们使用的基准。 它对你的程序一无所知(除非你的程序和他们的基准程序完全一样)。

  • 该声明是关于一组基准的平均值 。 没有人声称,即使对于他们测试过的程序,运行PyPy也会提高6.3倍。

  • 没有人声称PyPy甚至可以运行CPython运行的所有程序 ,更不用说更快了。




因为pypy不是100%兼容的,所以需要8个ram来编译,是一个移动的目标,并且是高度实验性的,其中cpython是稳定的,这是二十年来模块构建者的默认目标(包括不支持pypy的c扩展) ),并已广泛部署。

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




我找到了一些例子,PyPy比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(!)倍。 但不是在窗户上。 它只有3倍的速度。




除了本地/全局变量存储时间之外, 操作码预测还可以使函数更快。

正如其他答案所解释的那样,该函数在循环中使用STORE_FAST操作码。 这里是函数循环的字节码:

    >>   13 FOR_ITER                 6 (to 22)   # get next value from iterator
         16 STORE_FAST               0 (x)       # set local variable
         19 JUMP_ABSOLUTE           13           # back to FOR_ITER

通常,当程序运行时,Python逐个执行每个操作码,跟踪堆栈并在每个操作码执行后在堆栈帧上执行其他检查。 操作码预测意味着在某些情况下,Python可以直接跳到下一个操作码,从而避免了一些开销。

在这种情况下,每次Python看到FOR_ITER (循环的顶部)时,它都会“预测” STORE_FAST是它必须执行的下一个操作码。 Python然后偷看下一个操作码,如果预测正确,则直接跳转到STORE_FAST 。 这具有将两个操作码压缩成单个操作码的效果。

另一方面, STORE_NAME操作码在全局级循环中使用。 当它看到这个操作码时,Python不*做类似的预测。 相反,它必须返回到评估循环的顶部,这对循环执行的速度有明显的影响。

为了给出关于这个优化的更多技术细节,下面是ceval.c文件(Python虚拟机的“引擎”)的引用:

一些操作码往往成对出现,从而可以在第一次运行时预测第二个代码。 例如, GET_ITER后面往往跟着FOR_ITER 。 而FOR_ITER后面往往是STORE_FASTUNPACK_SEQUENCE

验证预测花费了一个寄存器变量对一个常量的单个高速测试。 如果配对是好的,那么处理器自己的内部分支预测成功的可能性很高,导致到下一个操作码的转换开销几乎为零。 一个成功的预测可以节省通过包括其两个不可预知的分支, HAS_ARG测试和开关情况的评估循环。 结合处理器的内部分支预测,一个成功的PREDICT具有使两个操作码运行的效果,就好像它们是一个单一的新操作码一起结合了主体。

我们可以在FOR_ITER操作码的源代码中看到FOR_ITER预测的确切位置:

case FOR_ITER:                         // the FOR_ITER opcode case
    v = TOP();
    x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
    if (x != NULL) {                     
        PUSH(x);                       // put x on top of the stack
        PREDICT(STORE_FAST);           // predict STORE_FAST will follow - success!
        PREDICT(UNPACK_SEQUENCE);      // this and everything below is skipped
        continue;
    }
    // error-checking and more code for when the iterator ends normally                                     

PREDICT函数扩展为if (*next_instr == op) goto PRED_##op即我们跳转到预测操作码的开始位置。 在这种情况下,我们跳转到这里:

PREDICTED_WITH_ARG(STORE_FAST);
case STORE_FAST:
    v = POP();                     // pop x back off the stack
    SETLOCAL(oparg, v);            // set it as the new local variable
    goto fast_next_opcode;

现在设置了局部变量,并且下一个操作码开始执行。 Python继续遍历迭代器,直到它结束,每次都成功进行预测。

Python wiki页面提供了关于CPython虚拟机如何工作的更多信息。







python performance jit pypy cpython