python - 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
这让我想到了一些问题:
为什么
for
循环比列表理解要快得多?(它似乎快了近一倍)为什么
while_loop_2
比 慢while_loop
?为什么递增和递减计数器的差异会产生速度差异?我的天真让我相信更少的代码行 = 更快 - 显然情况并非如此
编辑:
这是在 Python 2.7 中完成的。在 3.6while_loop_2
中实际上比while_loop
. 所以新的问题:
while
Python 2.7 和 3.x 之间的循环有什么区别?
解决方案
作为序言,您应该意识到您的“比较”应该单独分析(而不是相互比较),因为
- for 循环是一个固定的迭代器,在它的主体内什么都不做
- while 循环在其主体中执行递减/递增,并且
- 列表理解不仅仅是一个
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>
请注意,生成的字节码存在巨大差异。区别就在这里。
推荐阅读
- hibernate - 为单个瞬态对象创建重复条目
- javascript - 如何在新文档 onCreate 中添加字段?
- azure-devops - 有没有办法在 Azure DevOps CI/CD 构建管道中设置日期和时间(时区)
- angular - 如何修复“无法解构未定义或空的属性扩展”
- rest - 如何在使用 JWT 的 asp.net 核心 Web 应用和 Web api 中使用 google authenticaion
- reactjs - 如何在 Reactjs 中使用 mobx 渲染之前更新状态
- json - 在 ionic 中以 JSON 格式保存数组
- ios - 可绑定属性未更新 iOS 上的视图
- rust - 错误[E0599]:在当前范围内没有为类型“&mut G”找到名为“gen”的方法
- python - .loc 在多级索引数据帧上的意外行为