今天文章被人纠了错,就跑去人家主页上逛。结果看到有篇文章说字符串相加速度的,看看结论很奇怪。就做了一下实验。原文可以看这里。我们只讨论python部分的行为。首先是论证我观点的测试,无关部分就跳过了,大家应当可以自行补上。
def f():
s = ''
for i in xrange(3):
s += '123'
print id(s)
return s
f()
f()
输出:
138190216
138276992
138276992
138190216
138276992
138276992
至少在几十的规模,这个结论还是成立的。说明对象确实被缓存了,这导致了字符串相加的多次测试中,后续次数都没有实际的执行字符串分配动作。召dis来问之。
11 0 LOAD_CONST 1 ('')
3 STORE_FAST 0 (s)
12 6 SETUP_LOOP 41 (to 50)
9 LOAD_GLOBAL 0 (xrange)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1
18 GET_ITER
>> 19 FOR_ITER 27 (to 49)
22 STORE_FAST 1 (i)
13 25 LOAD_FAST 0 (s)
28 LOAD_CONST 3 ('123')
31 INPLACE_ADD
32 STORE_FAST 0 (s)
14 35 LOAD_GLOBAL 1 (id)
38 LOAD_FAST 0 (s)
41 CALL_FUNCTION 1
44 PRINT_ITEM
45 PRINT_NEWLINE
46 JUMP_ABSOLUTE 19
>> 49 POP_BLOCK
15 >> 50 LOAD_FAST 0 (s)
53 RETURN_VALUE
我们看到s是local变量,这个符合我们的预期。但是后续确实发生了add,而string的+算法,我们可以参考Objects/stringobject.c:1015这里,string_concat函数的内容。这里没有加速过程,即使有,也只有发生在len(a) == 0 or len(b) == 0的情况下。对于123的求和无法说明原因。
这里我只能做一个假定,我们发现的id相等,其实可能是由于内存重分配的结果。一个对象被回收后,是存放在对象池中的,再分配的时候,可能按照规则被重新分配。当然这只能是一个推测,实际证明必须在python源码中修改并且重编译,我就不找这个麻烦了。
另一个问题,实验的时候,只是把固定字符串更换为随机字符串而已,长度也没有发生变化。预期的结果应当是+=的速度变慢,结果+=速度不变。问题是join的速度加快算是怎么回事?关于join,我们可以参考Objects/stringobject.c:1574,这里说明了join的工作流程,也没有任何加速!
唯一的解释,就是append的工作速度慢到超乎正常人类的想像,实验证明了这点。
def g():
a = []
for i in xrange(1000):
a.append('1234567890')
# s = ''.join(a)
print Timer('g()', 'from __main__ import g').timeit(10000)
结果是0.91,append动作比join慢10倍。
以前大概是看这篇文章
http://www.skymind.com/~ocrow/python_string/
导致一直都使用append来拼接字符串,不过后来一次偶然发现+=其实也不慢,后来查阅资料发现+=在CPython2.5版本被优化过,看来以后也不是非得用append了
http://wiki.python.org/moin/PythonSpeed/PerformanceTips#String_Concatenation
@猪之哀伤, 我查阅过2.7的源码,发现+=优化并不彻底,机制上还是一定会引发多次拷贝问题。问题不是+=被加速了,而是append的速度太慢,导致join对+=的优势并不明显。
也许可以用一个更加有效(也更加浪费内存)的list实现来加速这个过程,但是由于list和dict是python内部实现中常用的结构,任何对他们的修改可能导致复杂的系统性能变化,所以我不倾向于直接在list中修改,而是新增一个clone实现。
append慢是没办法的吧,谁叫list设计为这么简单通用的东西呢……
shell 回复:
一月 18th, 2012 at 19:54
@ayanamist, 其实是有办法的,不过。。。
list的append慢的最主要理由,是list对象每增长12.5%就复制一次,如果使用更大的复制步长和增长规模,性能就很快上去。但是由于大量的空间浪费,内存消耗也很快上去。
使用StringIO或cStringIO是不是快得多?
shell 回复:
一月 30th, 2012 at 19:59
实话说我还不知道。
如果有空的话,你可以测试一下,发一篇blog?