python的控制流语句的性能

时间:2018-06-08 01:53:56

标签: python loops for-loop while-loop

我正在阅读关于loops的Python维基,它说

  

列表推导也在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_2while_loop慢?为什么递增与递减计数器的差异会产生速度上的差异?我的天真使我相信更少的代码行=更快 - 显然情况并非如此

  3. 修改 这是在Python 2.7中完成的。在3.6 while_loop_2实际上比while_loop更快。所以新问题:

    1. Python 2.7和3.x之间while循环的区别是什么?

2 个答案:

答案 0 :(得分:6)

作为序言,你应该知道你的“比较”应该被孤立地分析(而不是相互比较),因为

  1. for循环是一个固定的迭代器,它的内部不执行任何操作
  2. while循环在其体内执行递减/递增,
  3. 列表理解不仅仅是一个for循环,并且据说,我会回答问题#1。
  4. #1,因为dis循环迭代。列表推导迭代,在内存中创建一个列表。当然,这有助于节省总时间。仅此一点就足以说服你,但如果不是,请查看反汇编的字节代码,看看每个人在做什么。您可以使用dis模块执行此操作。我实际上使用%%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) 来回答你的第三个问题。

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

    %%timeit
    n = 100000
    while  n > 0: n -= 1
    
    10.8 ms ± 380 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    n > 0

    通常,基于递减的循环应该更快一些,因为与0(i < n)的比较通常比对非零值(import dis )的比较更快。但三角洲通常“认真,不要担心它”有点小。

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

    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
    

    python3.6

    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>
    

    python2.7

    {{1}}

    请注意,生成的字节码存在巨大差异。不同之处在于。

答案 1 :(得分:1)

您忘记在链接中测试above loop

newlist = []
for word in oldlist:
    newlist.append(word.upper())

使用list append迭代:

In [104]: %%timeit
     ...: alist = []
     ...: for i in range(10000):
     ...:    alist.append(i)
     ...: 
1.07 ms ± 10.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

等效列表理解

In [105]: timeit [i for i in range(10000)]
491 µs ± 20.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

这确实比列表追加循环更快。

从范围对象创建相同的列表:

In [106]: timeit list(range(10000))
265 µs ± 679 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

不做任何迭代:

In [107]: %%timeit
     ...: for i in range(10000):
     ...:    pass
     ...: 
273 µs ± 9.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

其他一些列表创作:

In [109]: timeit list(map(lambda i:i, range(10000)))
1.41 ms ± 3.12 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [110]: timeit list(i for i in range(10000))
784 µs ± 19.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)