Python:为啥弹出队列比 for-in 块更快?

Posted

技术标签:

【中文标题】Python:为啥弹出队列比 for-in 块更快?【英文标题】:Python: Why is popping off a queue faster than for-in block?Python:为什么弹出队列比 for-in 块更快? 【发布时间】:2015-10-03 12:37:44 【问题描述】:

我一直在编写一个 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 个条目,然后进行比较。 【参考方案1】:

while_list 正在改变全局 datatimeit.timeit 不会重置 data 的值。默认情况下,timeit.timeit 调用 for_listwhile_list 各一百万次。第一次调用while_list 后,后续调用while_list 执行0 次循环后返回,因为data 已经为空。

您需要在每次调用 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),而O(n) 的复杂度为for_list。所以正如预期的那样,for_list 更快,并且优势随着n 的增长而增长,data 的长度变大。

【讨论】:

上面的例子首先运行for_list - 所以你的论点并不真正适用恕我直言。 @sebastian: timeit.timeit 默认重复调用while_list 一百万次。第一次调用while_list 后,data 为空。因此,对于 999,999 次运行,while_loop 完成得太快了。 @JoranBeasley:你如何重置data?它必须在语句中完成,而不是设置,因为设置只运行一次。

以上是关于Python:为啥弹出队列比 for-in 块更快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 python itertools “消耗”配方比调用下 n 次更快?

为啥我的线性搜索比我在 Python3 中的二分搜索运行得更快?

为啥这个循环比创建字典的字典理解更快?

为啥 Pandas 应用可以比矢量化内置函数更快 [重复]

为啥创建 HashMap 比创建 Object[] 更快?

为啥滚动 UITableView 比滚动 UIScrollView 响应更快?