python的字符串相加效率

    今天文章被人纠了错,就跑去人家主页上逛。结果看到有篇文章说字符串相加速度的,看看结论很奇怪。就做了一下实验。原文可以看这里。我们只讨论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倍。

6 thoughts on “python的字符串相加效率

  1. @猪之哀伤, 我查阅过2.7的源码,发现+=优化并不彻底,机制上还是一定会引发多次拷贝问题。问题不是+=被加速了,而是append的速度太慢,导致join对+=的优势并不明显。
    也许可以用一个更加有效(也更加浪费内存)的list实现来加速这个过程,但是由于list和dict是python内部实现中常用的结构,任何对他们的修改可能导致复杂的系统性能变化,所以我不倾向于直接在list中修改,而是新增一个clone实现。

  2. append慢是没办法的吧,谁叫list设计为这么简单通用的东西呢……

    shell 回复:

    @ayanamist, 其实是有办法的,不过。。。
    list的append慢的最主要理由,是list对象每增长12.5%就复制一次,如果使用更大的复制步长和增长规模,性能就很快上去。但是由于大量的空间浪费,内存消耗也很快上去。

  3. 使用StringIO或cStringIO是不是快得多?

    shell 回复:

    实话说我还不知道。
    如果有空的话,你可以测试一下,发一篇blog?

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>