为什么列表上的总和(有时)比itertools.chain更快?

时间:2017-01-20 20:43:03

标签: python list

我在这里回答了几个问题,并将其用于"展平"列表清单:

>>> l = [[1,2,3],[4,5,6],[7,8,9]]
>>> sum(l,[])

它工作正常并且产量:

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

虽然我被告知sum运营商a = a + b执行itertools.chain但效果不如sum

我计划的问题是"为什么它可以在字符串上阻止字符串",但我在我的机器上进行了快速基准测试,比较itertools.chain.from_iterableimport itertools,timeit print(timeit.timeit("sum(l,[])",setup='l = [[1,2,3],[4,5,6],[7,8,9]]')) print(timeit.timeit("list(itertools.chain.from_iterable(l))",setup='l = [[1,2,3],[4,5,6],[7,8,9]]')) 相同的数据:

0.7155522836070246
0.9883352857722025

我做了几次,我总是得到与下面相同的数字:

chain

令我惊讶的是,sum - 在我的答案的几条评论中,每个人都推荐for以上的列表 - 速度要慢得多。

sum循环中进行迭代时仍然很有趣,因为它实际上并没有创建列表,但在创建列表时,itertools.chain获胜。

当预期结果是sum时,我们应该放弃list并使用s = 'l = [[4,5,6] for _ in range(20)]' print(timeit.timeit("sum(l,[])",setup=s)) print(timeit.timeit("list(itertools.chain.from_iterable(l))",setup=s)) 吗?

编辑:感谢一些评论,我通过增加列表数量进行了另一项测试

6.479897810702537
3.793455760814343

现在我反其道而行:

/dev/sda1       9.8G  9.2G   16M 100% /

2 个答案:

答案 0 :(得分:8)

您的测试输入很小。在那些尺度上,sum版本的可怕O(n ^ 2)渐近运行时是不可见的。时间由常数因素支配,sum具有更好的常数因子,因为它不必通过迭代器。

对于更大的列表,很明显sum根本不是为此类设计的:

>>> timeit.timeit('list(itertools.chain.from_iterable(l))',
...               'l = [[i] for i in xrange(5000)]; import itertools',
...               number=1000)
0.20425895931668947
>>> timeit.timeit('sum(l, [])', 'l = [[i] for i in xrange(5000)]', number=1000)
49.55303902059097

答案 1 :(得分:7)

对于第一个问题,“令我惊讶的是,链条 - 在我的答案的几条评论中,每个人都推荐使用列表的总和 - 要慢得多”,您观察到的时间有两个原因:

  • 对于小输入,时序由函数调用开销控制。同时调用listchain.from_iterable比仅调用sum更昂贵。连接小输入的实际工作比进行函数和方法调用的工作要快。

  • 对于较大的输入,a = a + b逻辑的预期二次方行为将占主导地位。

对于您的其他问题,“为什么可以在字符串上阻止它的列表”,答案是我们无法检测并报告所有二次情况,所以我们只是报告用户最容易偶然发现的那个。

另外,''.join(list_of_strings)的解决方法如果你还不知道的话就很难弄清楚。相比之下,列表的高性能解决方法更容易找到,t=[]; for s in list_of_lists: t+=s

使用非itertools替代,您应该能够通过简单的就地列表扩展获得合理的性能:

result = []
for seq in list_of_lists:
    result += seq

循环以“python-speed”而不是“C-speed”运行,但是没有函数调用开销,没有额外的迭代层,更重要的是,列表连接可以利用已知的长度输入,以便它可以预先分配结果所需的空间(这称为 __ length_hint __ )。

另一个想法,您永远不应该信任涉及逐步增长列表的时间。内部逻辑使用 realloc()在列表增长时调整列表大小。在时序套件中,环境是有利的,并且realloc通常可以就地扩展,因为没有其他数据在路上。但是,实际代码中使用的相同逻辑可能会执行得更糟,因为更多碎片的内存会导致realloc必须将所有数据复制到更大的空白区域。换句话说,时间可能根本不表示您关心的实际代码中的实际性能。

无论如何 sum()的主要原因是因为Guido van Rossum和Alex Martelli认为这对语言来说是最好的: