首页 > 解决方案 > python的控制流语句的性能

问题描述

我正在阅读关于循环的 Python wiki ,它说

在 2.0 版本中,列表推导也被添加到 Python 中。它们提供了一种语法上更紧凑、更有效的方式来编写上述 for 循环:

但是,我发现当我对此进行测试时,我得到了一些意想不到的结果。

In [22]: def while_loop(n):
    ...:     i = 0
    ...:     while i < n:
    ...:         i+=1
    ...:

In [23]: def while_loop_2(n):
    ...:     while n > 0:
    ...:         n-=1
    ...:

In [24]: def for_loop(n):
    ...:     for _ in range(n):
    ...:         pass
    ...:

In [30]: %timeit(for_loop(1000000))
10 loops, best of 3: 23.9 ms per loop

In [31]: %timeit(while_loop(1000000))
10 loops, best of 3: 37.1 ms per loop

In [32]: %timeit(while_loop_2(1000000))
10 loops, best of 3: 38 ms per loop

In [33]: %timeit([1 for _ in range(1000000)])
10 loops, best of 3: 43.2 ms per loop

这让我想到了一些问题:

  1. 为什么for循环比列表理解要快得多?(它似乎快了近一倍)

  2. 为什么while_loop_2比 慢while_loop?为什么递增和递减计数器的差异会产生速度差异?我的天真让我相信更少的代码行 = 更快 - 显然情况并非如此

编辑: 这是在 Python 2.7 中完成的。在 3.6while_loop_2中实际上比while_loop. 所以新的问题:

  1. whilePython 2.7 和 3.x 之间的循环有什么区别?

标签: pythonloopsfor-loopwhile-loop

解决方案


作为序言,您应该意识到您的“比较”应该单独分析(而不是相互比较),因为

  1. for 循环是一个固定的迭代器,在它的主体内什么都不做
  2. while 循环在其主体中执行递减/递增,并且
  3. 列表理解不仅仅是一个for循环,话虽如此,我继续回答问题 #1。

#1,因为for循环迭代。列表推导迭代,在内存中创建一个列表。当然,这会影响所花费的总时间。仅此一项就足以说服您,但如果不是,请查看反汇编的字节码以了解每个字节在做什么。您可以使用该dis模块来执行此操作。我实际上dis用来回答你的第三个问题。


#2,至于这个,我无法在python3.6上重现。

%%timeit
i = 0; n = 100000
while i < n: i += 1

11.5 ms ± 65.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%%timeit
n = 100000
while  n > 0: n -= 1

10.8 ms ± 380 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

一般来说,基于递减的循环应该快一点,因为与 0 ( n > 0) 的比较通常比与非零值 ( i < n) 的比较快。但三角洲通常是“认真的,别担心”有点小。


要回答#3,我们需要挖掘一下。让我们看一下字节码。

import dis

python3.6

dis.dis(
'''n = 100000
while  n > 0: n -= 1'''
)

  1           0 LOAD_CONST               0 (100000)
              2 STORE_NAME               0 (n)

  2           4 SETUP_LOOP              20 (to 26)
        >>    6 LOAD_NAME                0 (n)
              8 LOAD_CONST               1 (0)
             10 COMPARE_OP               4 (>)
             12 POP_JUMP_IF_FALSE       24
             14 LOAD_NAME                0 (n)
             16 LOAD_CONST               2 (1)
             18 INPLACE_SUBTRACT
             20 STORE_NAME               0 (n)
             22 JUMP_ABSOLUTE            6
        >>   24 POP_BLOCK
        >>   26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

python2.7

dis.dis(
'''n = 100000
while  n > 0: n -= 1'''
)
          0 JUMP_FORWARD    15648 (to 15651)
          3 SLICE+2        
          4 <49>           
          5 <48>           
          6 <48>           
          7 <48>           
          8 <48>           
          9 <48>           
         10 UNARY_POSITIVE 
         11 CONTINUE_LOOP   26984
         14 IMPORT_NAME      8293 (8293)
         17 SLICE+2        
         18 JUMP_FORWARD    15904 (to 15925)
         21 SLICE+2        
         22 <48>           
         23 INPLACE_DIVIDE 
         24 SLICE+2        
         25 JUMP_FORWARD    11552 (to 11580)
         28 DELETE_SUBSCR  
         29 SLICE+2        
         30 <49>

请注意,生成的字节码存在巨大差异。区别就在这里。


推荐阅读