我正在尝试:
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。假设字符串列表将提前。
答案 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(…))
方法慢,但不会有那么多的内存开销。实际上,它一次只创建一个字符串对象,获取其长度并将其添加到总和中。然后可以立即收集字符串。
这仍然很慢的原因是它需要在生成器内的每次迭代中查找len
和str
。要加快速度,请使用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
。查看第二行,您可以看到len
和str
都被称为100k次,这使得第二种情况下的总函数调用次数增加了一倍。