为什么在Python中使用range()循环比使用while循环快?

98 投票
6 回答
83614 浏览
提问于 2025-04-15 11:37

前几天我在做一些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语言代码的时间比解释器要短得多,可以忽略不计。

撰写回答