为什么'.join()在Python中比+ =更快?

时间:2016-09-03 23:11:20

标签: python optimization

我能够在线找到一大堆信息(在Stack Overflow和其他方面),关于如何在Python中使用++=进行连接是一种非常低效和糟糕的做法。 p>

我似乎无法找到为什么+=如此低效。除了提及here之外,“它在某些情况下已针对20%的改进进行了优化”(仍然不清楚这些情况是什么),我找不到任何其他信息。

在更技术层面上发生了什么使''.join()优于其他Python串联方法?

2 个答案:

答案 0 :(得分:74)

让我们说你有这个代码来建立一个来自三个字符串的字符串:

x = 'foo'
x += 'bar'  # 'foobar'
x += 'baz'  # 'foobarbaz'

在这种情况下,Python首先需要分配并创建'foobar'才能分配和创建'foobarbaz'

因此,对于每个被调用的+=,字符串的全部内容以及添加到其中的任何内容都需要复制到一个全新的内存缓冲区中。换句话说,如果要连接N个字符串,则需要分配大约N个临时字符串,并将第一个子字符串复制~N次。最后一个子字符串只被复制一次,但平均而言,每个子字符串都被复制~N/2次。

使用.join,Python可以发挥一些技巧,因为不需要创建中间字符串。 CPython预先知道它需要多少内存,然后分配一个正确大小的缓冲区。最后,它然后将每个部分复制到新的缓冲区中,这意味着每个部分只复制一次。

在某些情况下,还有其他可行的方法可以提高+=的性能。例如。如果内部字符串表示实际上是rope,或者运行时实际上足够聪明,以某种方式弄清楚临时字符串对程序没用,并优化它们。

然而,CPython确实可靠地执行这些优化(尽管它可能用于few corner cases)并且因为它是最常用的实现,所以许多最佳实践基于什么适用于CPython。拥有一套标准化的规范也使其他实现更容易集中他们的优化工作。

答案 1 :(得分:7)

我认为这种行为最好在Lua's string buffer chapter中解释。

要在Python的上下文中重写该解释,让我们从一个无辜的代码片段开始(Lua的文档的衍生物):

s = ""
for l in some_list:
  s += l

假设每个l为20个字节,s已经解析为50 KB的大小。当Python连接s + l时,它会创建一个包含50,020字节的新字符串,并将s中的50 KB复制到此新字符串中。也就是说,对于每个新行,程序移动50 KB的内存,并且不断增长。在阅读了100个新行(仅2 KB)之后,该代码段已经移动了超过5 MB的内存。更糟糕的是,在作业之后

s += l

旧字符串现在是垃圾。在两个循环周期之后,有两个旧字符串总共产生超过100 KB的垃圾。因此,语言编译器决定运行其垃圾收集器并释放那些100 KB。问题是这将每两个周期发生一次,程序将在读取整个列表之前运行其垃圾收集器两千次。即使完成所有这些工作,它的内存使用量也会是列表大小的倍数。

最后:

  

这个问题并不是Lua所特有的:其他具有真正垃圾的语言   集合,以及字符串是不可变对象,呈现类似的   行为,Java是最着名的例子。 (Java提供   结构StringBuffer以改善问题。)

Python字符串也是immutable objects