为什么在Python中使用range()循环比使用while循环快?
前几天我在做一些Python性能测试时,发现了一些有趣的事情。下面有两个循环,它们基本上做的是同样的事情。第一个循环的执行时间大约是第二个循环的两倍。
循环1:
i = 0
while i < 100000000:
i += 1
循环2:
for n in range(0,100000000):
pass
为什么第一个循环这么慢呢?我知道这是个简单的例子,但这引起了我的兴趣。range()函数有什么特别之处,让它比用同样的方法增加一个变量更高效吗?
6 个回答
21
得说,在这个 while 循环里,创建和销毁对象的过程是非常频繁的。
i += 1
这和下面的代码是一样的:
i = i + 1
但是因为 Python 的整数是不可变的,它并不会修改原来的对象,而是会创建一个全新的对象,值也会变。这基本上就是:
i = new int(i + 1) # Using C++ or Java-ish syntax
垃圾回收器也会有很多工作要做,毕竟“创建对象是很耗费资源的”。
更新:有趣的是,现在使用 3.11 版本时,对于低迭代次数,for
循环实际上比 while
循环要慢:
迭代次数 | while 循环 | for 循环 |
---|---|---|
1 | 55 | 109 |
5 | 23 | 26 |
10 | 18 | 16 |
1_000 | 18 | 9.2 |
1_000_000 | 18 | 11 |
(每次迭代的纳秒数,Win 10,CPython 3.11.4,ipython %timeit)
在超过 100 次迭代时,两者的表现都相当稳定。
41
range()
是用C语言实现的,而 i += 1
是通过解释器来执行的。
如果使用 xrange()
,在处理大数字时可能会更快。从Python 3.0开始, range()
的功能和之前的 xrange()
是一样的。
184
查看Python字节码的反汇编,你可能会对它有更清晰的理解。
使用while循环:
1 0 LOAD_CONST 0 (0)
3 STORE_NAME 0 (i)
2 6 SETUP_LOOP 28 (to 37)
>> 9 LOAD_NAME 0 (i) # <-
12 LOAD_CONST 1 (100000000) # <-
15 COMPARE_OP 0 (<) # <-
18 JUMP_IF_FALSE 14 (to 35) # <-
21 POP_TOP # <-
3 22 LOAD_NAME 0 (i) # <-
25 LOAD_CONST 2 (1) # <-
28 INPLACE_ADD # <-
29 STORE_NAME 0 (i) # <-
32 JUMP_ABSOLUTE 9 # <-
>> 35 POP_TOP
36 POP_BLOCK
这个循环的主体有10个操作。
使用range函数:
1 0 SETUP_LOOP 23 (to 26)
3 LOAD_NAME 0 (range)
6 LOAD_CONST 0 (0)
9 LOAD_CONST 1 (100000000)
12 CALL_FUNCTION 2
15 GET_ITER
>> 16 FOR_ITER 6 (to 25) # <-
19 STORE_NAME 1 (n) # <-
2 22 JUMP_ABSOLUTE 16 # <-
>> 25 POP_BLOCK
>> 26 LOAD_CONST 2 (None)
29 RETURN_VALUE
这个循环的主体只有3个操作。
运行C语言代码的时间比解释器要短得多,可以忽略不计。