【发布时间】:2019-07-30 11:27:35
【问题描述】:
假设我们有一个字符串列表,我们想通过连接该列表中的所有元素来创建一个字符串。像这样:
def foo(str_lst):
result = ''
for element in str_lst:
result += element
return result
由于字符串是不可变对象,我希望 python 创建一个新的 str 对象并在每次迭代时复制结果和元素的内容。它使O(M * N^2)的时间复杂度,M是每个元素的长度,N是列表的大小。
但是,我的实验表明它以线性时间运行。
N = 1000000 # 1 million
str_lst = ['a' for _ in range(N)]
foo(str_lst) # It takes around 0.5 seconds
N = 2000000 # 2 million
str_lst = ['a' for _ in range(N)]
foo(str_lst) # It takes around 1.0 seconds
N = 10000000 # 10 million
str_lst = ['a' for _ in range(N)]
foo(str_lst) # It takes around 5.3 seconds
我怀疑 python 在后台使用了类似 stringbuffer 的东西。因此,它不会在每次迭代时创建新对象。
现在考虑一个稍微不同的实现。唯一的区别是一项额外的任务。
def foo2(str_lst):
result = ''
for element in str_lst:
result += element
temp = result # new added line
return result
我知道temp = result 行不会创建新对象。 temp 只是指向同一个对象。所以,这个小改动应该不会对性能产生太大影响。
N = 1000000 # 1 million
str_lst = ['a' for _ in range(N)]
foo(str_lst) # It takes around 0.5 seconds
foo2(str_lst) # It takes around 30 seconds
N = 2000000 # 2 million
str_lst = ['a' for _ in range(N)]
foo(str_lst) # It takes around 1 seconds
foo2(str_lst) # It takes around 129 seconds
但是,有很大的不同。看起来 foo2 函数是 O(N^2) 而 foo 是 O(N)。
我的问题是 python 如何在不破坏其他语言组件(如不可变对象分配)的情况下实现字符串连接的线性时间?以及那条额外的线如何对性能产生如此大的影响?我在 cpython 实现中搜索了一下,但找不到确切的位置。
更新
这是线路分析结果。
foo 函数的结果
Total time: 0.545577 s
File: <ipython-input-38-b9bb169e8fe0>
Function: foo at line 1
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def foo(str_lst):
2 1 2.0 2.0 0.0 result = ''
3 1000001 238820.0 0.2 43.8 for element in str_lst:
4 1000000 306755.0 0.3 56.2 result += element
5 1 0.0 0.0 0.0 return result
foo2 函数的结果
Total time: 30.6663 s
File: <ipython-input-40-34dd53670dd9>
Function: foo2 at line 1
Line # Hits Time Per Hit % Time Line Contents
==============================================================
1 def foo2(str_lst):
2 1 2.0 2.0 0.0 result = ''
3 1000001 299122.0 0.3 1.0 for element in str_lst:
4 1000000 30033127.0 30.0 97.7 result += element
5 1000000 413283.0 0.4 1.3 temp = result
6 1 0.0 0.0 0.0 return result
不知何故,temp = result 行会影响result += element 行的性能。
【问题讨论】:
-
你没有嵌套 for 循环,所以它不是 O(N^2)
-
@naivepredictor 请参阅 Joel Spolsky 的 Back to Basics,了解字符串连接如何成为隐藏二次行为的一个很好的例子。
-
与
+=连接时显然有一些优化,见this example at wtfpython。 -
我似乎记得字符串连接优化是一个 CPython 实现细节,但我找不到任何文档来确认。
标签: python python-3.x string cpython python-internals