【问题标题】:Why is Python 3 is considerably slower than Python 2? [duplicate]为什么 Python 3 比 Python 2 慢得多? [复制]
【发布时间】:2015-10-11 11:24:36
【问题描述】:

我一直试图理解为什么在某些情况下 Python 3 与 Python 2 相比实际上要花费更多时间,以下是我从 python 3.4 到 python 2.7 验证的几个案例。

注意:我已经解决了一些问题,例如Why is there no xrange function in Python3? 和loop in python3 much slower than python2 和Same code slower in Python3 as compared to Python2,但我觉得我没有得到这个问题背后的真正原因。

我已经尝试了这段代码来展示它是如何产生影响的:

MAX_NUM = 3*10**7

# This is to make compatible with py3.4.
try:
    xrange
except:
    xrange = range


def foo():
    i = MAX_NUM
    while i> 0:
        i -= 1

def foo_for():
    for i in xrange(MAX_NUM):
        pass

当我尝试使用 py3.4 和 py2.7 运行这个程序时,我得到了以下信息 结果。

注意:这些统计数据来自带有2.6Ghz 处理器的64 bit 机器,并在单循环中使用time.time() 计算时间。

Output : Python 3.4
-----------------
2.6392083168029785
0.9724123477935791

Output: Python 2.7
------------------
1.5131521225
0.475143909454

我真的不认为 while 或 xrange 从 2.7 到 3.4 发生了变化,我知道 range 已经开始在 py3.4 中充当 xrange 但正如文档所述

range() 现在的行为类似于 xrange() 过去的行为,除了它适用于任意大小的值。后者不再存在。

这意味着从xrange 更改为range 与更改名称非常相似,但可以使用任意值。

我也验证了反汇编的字节码。

下面是函数foo()的反汇编字节码:

Python 3.4:
--------------- 

 13           0 LOAD_GLOBAL              0 (MAX_NUM)
              3 STORE_FAST               0 (i)

 14           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (i)
             12 LOAD_CONST               1 (0)
             15 COMPARE_OP               4 (>)
             18 POP_JUMP_IF_FALSE       34

 15          21 LOAD_FAST                0 (i)
             24 LOAD_CONST               2 (1)
             27 INPLACE_SUBTRACT
             28 STORE_FAST               0 (i)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE

python 2.7
-------------

 13           0 LOAD_GLOBAL              0 (MAX_NUM)
              3 STORE_FAST               0 (i)

 14           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (i)
             12 LOAD_CONST               1 (0)
             15 COMPARE_OP               4 (>)
             18 POP_JUMP_IF_FALSE       34

 15          21 LOAD_FAST                0 (i)
             24 LOAD_CONST               2 (1)
             27 INPLACE_SUBTRACT    
             28 STORE_FAST               0 (i)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK           
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE        

下面是函数foo_for()的反汇编字节码:

Python: 3.4

 19           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_GLOBAL              1 (MAX_NUM)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

 20          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE


Python: 2.7
-------------

 19           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_GLOBAL              1 (MAX_NUM)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

 20          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

如果我们比较两个字节码,它们会产生相同的反汇编字节码。

现在我想知道从 2.7 到 3.4 的哪些变化真正导致了给定代码中执行时间的巨大变化。

【问题讨论】:

  • 贴出完整代码和测量方法
  • 用整个解释器设置运行一次不会告诉你任何事情。改用timeit.timeit() 运行时间试验。

标签: python performance python-2.7 python-3.x cpython


【解决方案1】:

区别在于int类型的实现。 Python 3.x 专门使用任意大小的整数类型(long 在 2.x 中),而在 Python 2.x 中,对于高达 sys.maxint 的值,使用更简单的 int 类型,它使用简单的 C @987654325 @在引擎盖下。

一旦将循环限制为 long 整数,Python 3.x 会更快:

>>> from timeit import timeit
>>> MAX_NUM = 3*10**3
>>> def bar():
...     i = MAX_NUM + sys.maxsize
...     while i > sys.maxsize:
...         i -= 1
... 

Python 2:

>>> timeit(bar, number=10000)
5.704327821731567

Python 3:

>>> timeit(bar, number=10000)
3.7299320790334605

我使用sys.maxsize,因为sys.maxint是从Python 3中删除的,但整数值基本相同。

因此,Python 2 中的速度差异仅限于第一个 (2 ** 63) - 64 位系统上的 1 个整数,(2 ** 31) - 32 位系统上的 1 个整数。

由于您不能在 Python 2 上将 long 类型与 xrange() 一起使用,因此我没有包含该函数的比较。

【讨论】:

  • 您是否有任何理由不想针对 2**63 以下的整数进行优化?它们似乎是最常用的......
  • @thebjorn:使用一种int 类型的简化更为重要。此外,如果您正在执行for 在那么大的范围内循环,那么您可能做错了什么无论如何。
  • 但是这个选择不是让所有整数计算都在例如数组索引等也更慢?似乎其他语言(Smalltalk、Lisp、Haskell、Java)为了优化整数的装箱/拆箱做了一些努力,这些优化在 Python 这样的语言中是多余的吗?
  • @MartijnPieters 它仍然没有提供与 OP 测试显示的相同的性能。
  • @quant_dev:当然,这些测试不会提供相同的性能。我从来没有说过他们可以做出同样的表现。当您使用 大整数 超过 sys.maxint 时,您将获得相同的性能在 Python 2 中,因为那时涉及相同的代码路径。
猜你喜欢
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-05
相关资源
最近更新 更多