Python:为什么从队列中弹出元素比使用for-in循环块更快?

10

我一直在编写一个Python脚本来分析CSV文件。其中一些文件相当大(1-2百万条记录),而且脚本需要几个小时才能完成。

我将记录处理方式从for-in循环改为while循环,速度提升显著。以下为演示:

>>> def for_list():
...     for d in data:
...             bunk = d**d
... 
>>> def while_list():
...     while data:
...             d = data.pop(0)
...             bunk = d**d
... 
>>> data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> import timeit
>>> timeit.timeit(for_list)
1.0698931217193604
>>> timeit.timeit(while_list)
0.14515399932861328

速度提升近一个数量级。我从未查看过Python的字节码,但我认为这可能会有所帮助,但事实证明while_list拥有更多的指令。

那么这里发生了什么?我能够将其应用于其他程序吗?在哪些情况下forwhile快十倍?

编辑:正如@HappyLeapSecond指出的那样,我没有完全理解timeit内部正在发生的情况,使用以下代码后差异消失了:

>>> def for_list():
...     data = [x for x in range(1000)]
...     for d in data:
...             bunk = d**d
... 
>>> def while_list():
...     data = [x for x in range(1000)]
...     while data:
...             d = data.pop(0)
...             bunk = d**d
>>> timeit.timeit(while_list, number=1000)
12.006330966949463
>>> timeit.timeit(for_list, number=1000)
11.847280025482178
很奇怪的是,我的“真正”脚本在做出如此简单的更改后加速了这么多。我最好的猜测是迭代方法需要更多的交换?我有一个40G的交换分区,该脚本占用其中约15-20G。弹出会减少交换吗?

我可以想象为for循环创建一个迭代器可能相对昂贵。尝试让你的列表变得更长,例如100k条目,并进行比较。 - 9000
1个回答

13
while_list改变了全局变量data的值,timeit.timeit不会重置data的值。默认情况下,timeit.timeit会调用for_listwhile_list各一百万次。在第一次调用while_list后,由于data已经为空,随后的调用将立即返回并执行0次循环。

为了进行公正的基准测试,在每次调用for_listwhile_list之前,需要重置data的值。


import timeit

def for_list(data):
    for d in data:
        bunk = d ** d


def while_list(data):
    while data:
        d = data.pop(0)
        bunk = d ** d

data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print(timeit.timeit('data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; for_list(data)', 'from __main__ import for_list'))
# 0.959696054459

print(timeit.timeit('data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; while_list(data)', 'from __main__ import while_list'))
# 2.40107011795

pop(0) 是一个 O(n) 的操作。在长度为 n 的循环中执行此操作会使 while_list 的时间复杂度总体上为 O(n**2),而 for_list 的复杂度为 O(n)。因此,正如预期的那样,for_list 更快,并且随着 data 的长度 n 增加,其优势也越来越大。


1
以上示例首先运行for_list - 所以我认为您的论点并不适用。 - sebastian
3
默认情况下,timeit.timeit 会将对 while_list 的调用重复一百万次。在第一次调用 while_list 后,data 就为空了。因此,在剩下的 999,999 次运行中,while_loop 完成得太快了。 - unutbu
1
@JoranBeasley:你是如何重置“data”的?它必须在语句中完成,而不是在设置中,因为设置只运行一次。 - unutbu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接