列表中所有字符串的长度:最快的方式

时间:2014-02-01 17:26:35

标签: python performance

我正在尝试:

python3 -m timeit -c 'len("".join([str(x) for x in range(0, 999999)]))'
10 loops, best of 3: 330 msec per loop

python3 -m timeit -c 'sum((len(y) for y in [str(x) for x in range(0, 999999)]))
10 loops, best of 3: 439 msec per loop

为什么会这样?有更快的方法吗?

P.S。假设字符串列表将提前。

3 个答案:

答案 0 :(得分:6)

暂时忽略相当小的时差,你的两种方式在内存中实际上存在巨大差异。

sum((len(y) for y in [str(x) for x in range(0, 999999)]))

这将为每个数字创建一个字符串并将其存储在列表中。然后使用生成器表达式循环遍历该列表并将长度相加。所以你基本上每个数字都有一个字符串,一个存储所有字符串的列表,以及一个为长度添加的数字。

len(''.join([str(x) for x in range(0, 999999)]))

这将再次为每个数字创建一个字符串并将其存储在列表中。然后你创建一个包含所有数字的巨大字符串。然后你调用长度(然后是O(1)调用)。所以你没有你添加的数字(总结长度),但你有另一个长字符串,它再次组合了所有其他字符串。

所以,即使速度更快,你也会丢掉大量内存,这可能会对以后的性能产生影响。

为了改善这一切,您应该考虑尽可能永久地创建小东西。不要使用列表推导,因为它实际上会创建列表;不要使用str.join,因为它需要一个列表并迭代它两次。

sum(len(str(x)) for x in range(0, 999999)))

现在,这仍然比len(''.join(…))方法慢,但不会有那么多的内存开销。实际上,它一次只创建一个字符串对象,获取其长度并将其添加到总和中。然后可以立即收集字符串。

这仍然很慢的原因是它需要在生成器内的每次迭代中查找lenstr。要加快速度,请使用map仅查找两次。 wim在评论中提出了一个非常好的建议:

sum(map(len, map(str, range(999999))))

对我来说,这实际上比len(''.join(…))方式更快。我的时间顺序在我的回答中提到:

62.36836282166257
50.54277449168785
58.24419845897603
40.3403849521618

答案 1 :(得分:3)

IPython更好的基准测试表明情况比你想象的还要糟糕:

>>> lst = [str(x) for x in range(0, 999999)]
>>> %timeit len("".join(lst))
100 loops, best of 3: 9.94 ms per loop
>>> %timeit sum(len(x) for x in lst)
10 loops, best of 3: 62.2 ms per loop

你在这里看到两个效果,Python中函数调用的开销以及迭代的开销。 "".join没有,因为它是一个在C中执行循环的单个方法调用。具有较少内存使用的中间性能可以从map获得:

>>> %timeit sum(map(len, lst))
10 loops, best of 3: 29.4 ms per loop

答案 2 :(得分:2)

第一个(更快)版本有1个调用len函数,1个调用join和100个调用str。查看第二行,您可以看到lenstr都被称为100k次,这使得第二种情况下的总函数调用次数增加了一倍。