我有一个python程序,它从文件中读取行并将它们放入dict中,简单来说,它看起来像:
data = {'file_name':''}
with open('file_name') as in_fd:
for line in in_fd:
data['file_name'] += line
我发现需要几个小时才能完成。
然后,我对程序做了一些改动:
data = {'file_name':[]}
with open('file_name') as in_fd:
for line in in_fd:
data['file_name'].append(line)
data['file_name'] = ''.join(data['file_name'])
它在几秒钟内完成。
我认为+=
使程序变慢,但似乎没有。请查看以下测试的结果。
我知道我们可以使用list append和join来提高concat字符串的性能。但我从未想过追加和加入和添加和分配之间的性能差距。
所以我决定做更多的测试,最后发现它的dict更新操作会使程序变得非常慢。这是一个脚本:
import time
LOOPS = 10000
WORD = 'ABC'*100
s1=time.time()
buf1 = []
for i in xrange(LOOPS):
buf1.append(WORD)
ss = ''.join(buf1)
s2=time.time()
buf2 = ''
for i in xrange(LOOPS):
buf2 += WORD
s3=time.time()
buf3 = {'1':''}
for i in xrange(LOOPS):
buf3['1'] += WORD
s4=time.time()
buf4 = {'1':[]}
for i in xrange(LOOPS):
buf4['1'].append(WORD)
buf4['1'] = ''.join(buf4['1'])
s5=time.time()
print s2-s1, s3-s2, s4-s3, s5-s4
在我的笔记本电脑(mac pro 2013 mid,OS X 10.9.5,cpython 2.7.10)中,它的输出是:
0.00299620628357 0.00415587425232 3.49465799332 0.00231599807739
受到juanpa.arrivillaga评论的启发,我对第二个循环做了一些改动:
trivial_reference = []
buf2 = ''
for i in xrange(LOOPS):
buf2 += WORD
trivial_reference.append(buf2) # add a trivial reference to avoid optimization
更改后,现在第二个循环需要19秒才能完成。所以它似乎只是一个优化问题,正如juanpa.arrivillaga所说。
答案 0 :(得分:14)
+=
在构建大型字符串时表现非常糟糕,但在CPython中可以有效。
使用str.join()
确保快速删除字符串连接。
来自String Concatenation下的Python Performance Tips部分:
避免这种情况:
s = ""
for substring in list:
s += substring
请改用s = "".join(list)
。在构建大字符串时,前者是一个非常常见和灾难性的错误。
s += x
比s['1'] += x
或s[0] += x
更快?CPython实现细节:如果s和t都是字符串,有些 像CPython这样的Python实现通常可以就地执行 表单
s = s + t
或s += t
的分配优化。什么时候 适用时,此优化使二次运行时间更少 有可能。此优化既是版本又是实现 依赖。对于性能敏感的代码,最好使用str.join()
方法,确保一致的线性连接 跨版本和实现的性能。
CPython的优化是,如果一个字符串只有一个引用,那么我们可以resize it in-place。
/ *请注意,我们不必为非共享Unicode修改* unicode 对象,因为我们可以就地修改它们。 * /
现在后两者不是简单的就地添加。事实上,这些都不是就地添加。
s[0] += x
相当于:
temp = s[0] # Extra reference. `S[0]` and `temp` both point to same string now.
temp += x
s[0] = temp
示例:
>>> lst = [1, 2, 3]
>>> def func():
... lst[0] = 90
... return 100
...
>>> lst[0] += func()
>>> print lst
[101, 2, 3] # Not [190, 2, 3]
但一般情况下从不使用s += x
来连接字符串,请始终在字符串集合上使用str.join
。
<强>计时强>
LOOPS = 1000
WORD = 'ABC'*100
def list_append():
buf1 = [WORD for _ in xrange(LOOPS)]
return ''.join(buf1)
def str_concat():
buf2 = ''
for i in xrange(LOOPS):
buf2 += WORD
def dict_val_concat():
buf3 = {'1': ''}
for i in xrange(LOOPS):
buf3['1'] += WORD
return buf3['1']
def list_val_concat():
buf4 = ['']
for i in xrange(LOOPS):
buf4[0] += WORD
return buf4[0]
def val_pop_concat():
buf5 = ['']
for i in xrange(LOOPS):
val = buf5.pop()
val += WORD
buf5.append(val)
return buf5[0]
def val_assign_concat():
buf6 = ['']
for i in xrange(LOOPS):
val = buf6[0]
val += WORD
buf6[0] = val
return buf6[0]
>>> %timeit list_append()
1000 loops, best of 3: 1.31 ms per loop
>>> %timeit str_concat()
100 loops, best of 3: 3.09 ms per loop
>>> %run so.py
>>> %timeit list_append()
10000 loops, best of 3: 71.2 us per loop
>>> %timeit str_concat()
1000 loops, best of 3: 276 us per loop
>>> %timeit dict_val_concat()
100 loops, best of 3: 9.66 ms per loop
>>> %timeit list_val_concat()
100 loops, best of 3: 9.64 ms per loop
>>> %timeit val_pop_concat()
1000 loops, best of 3: 556 us per loop
>>> %timeit val_assign_concat()
100 loops, best of 3: 9.31 ms per loop
val_pop_concat
在这里很快,因为通过使用pop()
,我们将列表中的引用放到该字符串中,现在CPython可以就地调整它(由@niemmi in comments正确猜出)。< / p>