Python字符串’join’比’+’更快(?),但这里有什么问题?

Python string 'join' is faster (?) than '+', but what's wrong here?

我在之前的一篇文章中询问了质量动态字符串连接的最有效方法,并建议我使用连接方法,这是最好、最简单和最快的方法(正如大家所说)。但当我玩字符串连接时,我发现了一些奇怪的(?)结果。我肯定发生了什么事,但我不太明白。以下是我所做的:

我定义了这些功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import timeit
def x():
    s=[]
    for i in range(100):
        # Other codes here...
        s.append("abcdefg"[i%7])
    return ''.join(s)

def y():
    s=''
    for i in range(100):
        # Other codes here...
        s+="abcdefg"[i%7]
    return s

def z():
    s=''
    for i in range(100):
        # Other codes here...
        s=s+"abcdefg"[i%7]
    return s

def p():
    s=[]
    for i in range(100):
        # Other codes here...
        s+="abcdefg"[i%7]
    return ''.join(s)

def q():
    s=[]
    for i in range(100):
        # Other codes here...
        s = s + ["abcdefg"[i%7]]
    return ''.join(s)

我尝试在整个函数中保持其他内容(串联除外)几乎相同。然后,我用下面的注释结果进行了测试(在Windows32位计算机上使用python 3.1.1 idle):

1
2
3
4
5
timeit.timeit(x) # 31.54912480500002
timeit.timeit(y) # 23.533029429999942
timeit.timeit(z) # 22.116181330000018
timeit.timeit(p) # 37.718607439999914
timeit.timeit(q) # 108.60377576499991

这意味着strng=strng+dyn-strng是最快的。虽然时间上的差异没有那么大(除了最后一个),但我想知道为什么会发生这种情况。这是因为我使用的是python 3.1.1,它提供了"+"作为最有效的方法吗?我应该使用"+"作为加入的替代方法吗?或者,我做了一些非常愚蠢的事情吗?或者什么?请解释清楚。


我们中的一些python提交者,我认为主要是rigo和hettinger,他们(在去2.5的路上,我相信)特意优化了一些非常常见的adocx1(0)枯萎病的特殊情况,他们认为已经证明,初学者永远不会被掩盖,''.join是正确的方式,+=的可怕缓慢。]可能给python起了个坏名字。我们中的其他人并没有那么热,因为他们不可能把每一次事件(甚至是其中的大多数)都优化到合适的性能;但是我们在这个问题上没有足够的热情去尝试和积极地阻止它们。

我相信这条线索证明我们应该更严厉地反对他们。现在,他们优化了+=,在某些难以预测的情况下,对于特定的愚蠢情况,它可能比正确的方法(仍然是''.join)快20%——这是一个完美的方法,让初学者通过使用错误的习语来追求那些不相关的20%的收益……代价是,每隔一段时间,从他们的pov中,突然失去了200%的性能(或者更多,因为非线性行为仍然潜伏在Hettinger和Rigo准备和放花的角落之外;-)——一个重要的,一个会让他们痛苦的。这与Python的"理想情况下只有一种明显的方法"格格不入,我觉得我们大家都为初学者设置了一个陷阱——也是最好的一种……那些不只是接受"更好的人"告诉他们的,而是好奇地去提问和探索的人。

啊,好吧——我放弃了。操作,@mshsayem,继续,到处使用+=功能,在琐碎的、微小的、不相关的情况下享受你不相关的20%加速,你最好尽情享受它们——因为有一天,当你看不到它的到来时,在一个重要的、大型的操作中,你会被迎面而来的200%减速的拖车撞到(除非你不走运D是2000%的1;-)。记住:如果你觉得"Python的速度太慢了",记住,它很可能是你最喜欢的+=环之一,它会转过身来,咬住喂养它的手。

对于我们中的其他人——那些明白这意味着什么的人来说,我们应该忘记小效率,比如说97%的时间,我会一直衷心地推荐''.join,这样我们都可以安睡在所有的宁静中,并且知道我们不会受到超线性减速的冲击,当我们最不期待和最不负担得起你的时候。但是对于你,阿敏·里戈和雷蒙德·赫廷格(最后两位,我亲爱的私人朋友,顺便说一句,不仅仅是联合委员会成员),愿你的+=顺利,你的大O永远不会比N差!-)

所以,对于我们其他人来说,这里有一套更有意义和有趣的测量方法:

1
2
$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 's="".join(r)'
1000 loops, best of 3: 319 usec per loop

900串297个字符的每个,直接加入名单当然是最快的,但运营商害怕在此之前必须做附加。但是:

1
2
3
4
$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 's=""' 'for x in r: s+=x'
1000 loops, best of 3: 779 usec per loop
$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 'z=[]' 'for x in r: z.append(x)' '"".join(z)'
1000 loops, best of 3: 538 usec per loop

…有了一个非常重要的数据量(非常少的100千字节——以每一种方式都需要一毫秒的可测量分数),即使是普通的好的老.append也是非常好的。此外,它显然非常容易优化:

1
2
$ python -mtimeit -s'r=[str(x)*99 for x in xrange(100,1000)]' 'z=[]; zap=z.append' 'for x in r: zap(x)' '"".join(z)'
1000 loops, best of 3: 438 usec per loop

在平均循环时间内再刮十分之一毫秒。每个人(至少每个完全沉迷于大量性能的人)显然都知道提升(从内部循环中去掉一个重复的计算,否则会反复执行)是优化中的一个关键技术——python不代表您提升,因此在那些罕见的情况下,当每一微秒都很重要。


至于为什么q慢得多:当你说

1
l +="a"

你在把字符串"a"附加到l的末尾,但是当你说

1
l = l + ["a"]

您正在创建一个包含l["a"]内容的新列表,然后将结果重新分配给l。因此,不断生成新的列表。


我假设x()较慢,因为您首先构建数组,然后加入它。因此,您不仅要测量连接所需的时间,还要测量构建数组所需的时间。

在已经有一个数组并且希望从其元素中创建一个字符串的场景中,join应该比遍历数组并逐步构建字符串更快。


这个问题实际上是关于东西的价格。我们将在这里进行一些快速和宽松的游戏,在类似的情况下减去结果。您可以自己决定这是否是一个有效的方法。以下是一些基本测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import timeit
def append_to_list_with_join():
    s=[]
    for i in xrange(100):
        s.append("abcdefg"[i%7])
    return ''.join(s)

def append_to_list_with_join_opt():
    s=[]
    x = s.append
    for i in xrange(100):
        x("abcdefg"[i%7])
    return ''.join(s)

def plus_equals_string():
    s=''
    for i in xrange(100):
        s+="abcdefg"[i%7]
    return s

def plus_assign_string():
    s=''
    for i in xrange(100):
        s=s+"abcdefg"[i%7]
    return s

def list_comp_join():
    return ''.join(["abcdefg"[i%7] for i in xrange(100)])

def list_comp():
    return ["abcdefg"[i%7] for i in xrange(100)]

def empty_loop():
    for i in xrange(100):
        pass

def loop_mod():
    for i in xrange(100):
        a ="abcdefg"[i%7]

def fast_list_join():
    return"".join(["0"] * 100)

for f in [append_to_list_with_join, append_to_list_with_join_opt, plus_equals_string,plus_assign_string,list_comp_join, list_comp, empty_loop,loop_mod, fast_list_join]:
    print f.func_name, timeit.timeit(f)

这就是它们的成本:

1
2
3
4
5
6
7
8
9
append_to_list_with_join 25.4540209021
append_to_list_with_join_opt 19.9999782794
plus_equals_string 16.7842428996
plus_assign_string 14.8312124167
list_comp_join 16.329590353
list_comp 14.6934344309
empty_loop 2.3819276612
loop_mod 10.1424356308
fast_list_join 2.58149394686

首先,在Python中,很多东西都有意想不到的成本。用join将u附加到u列表而用join opt将u附加到u列表表明即使在对象上查找方法也有不可忽略的成本。在这种情况下,查找s.append是四分之一的时间。

接下来,list-comp-join和list-comp显示join()非常快:它大约需要list-comp-join时间的1.7%或10%。

loop_mod显示,此测试的最大部分实际上是设置数据,而不管使用哪种字符串构造方法。通过推断,"string=string+","string+=",以及列表理解所用的时间为:

1
2
3
plus_equals_string = 16.78 - 10.14 = 6.64
plus_assign_string = 14.83 - 10.14 = 4.69
list_comp = 14.69 - 10.14 = 4.55

因此,对于op的问题,join()很快,但是无论是使用list原语还是使用list理解,创建底层列表的时间都与使用string原语创建字符串的时间相当。如果您已经有了一个列表,那么使用join()将其转换为字符串——这将很快。

OP给出的计时表明使用concatenate运算符构造列表的速度很慢。相反,使用列表理解很快。如果必须构建一个列表,请使用列表理解。

最后,让我们来看三个op最接近的函数:x、p和q之间的区别是什么?让我们简化一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import timeit
def x():
    s=[]
    for i in range(100):
        s.append("c")

def p():
    s=[]
    for i in range(100):
        s +="c"

def q():
    s=[]
    for i in range(100):
        s = s + ["c"]

for f in [x,p,q]:
    print f.func_name, timeit.timeit(f)

结果如下:

1
2
3
x 16.0757342064
p 87.1533697719
q 85.0999698984

拆卸步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
>>> import dis
>>> dis.dis(x)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              33 (to 42)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                19 (to 41)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_ATTR                1 (append)
             31 LOAD_CONST               2 ('c')
             34 CALL_FUNCTION            1
             37 POP_TOP
             38 JUMP_ABSOLUTE           19
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE
>>> dis.dis(p)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              30 (to 39)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                16 (to 38)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_CONST               2 ('c')
             31 INPLACE_ADD
             32 STORE_FAST               0 (s)
             35 JUMP_ABSOLUTE           19
        >>   38 POP_BLOCK
        >>   39 LOAD_CONST               0 (None)
             42 RETURN_VALUE
>>> dis.dis(q)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (s)

  3           6 SETUP_LOOP              33 (to 42)
              9 LOAD_GLOBAL              0 (range)
             12 LOAD_CONST               1 (100)
             15 CALL_FUNCTION            1
             18 GET_ITER
        >>   19 FOR_ITER                19 (to 41)
             22 STORE_FAST               1 (i)

  4          25 LOAD_FAST                0 (s)
             28 LOAD_CONST               2 ('c')
             31 BUILD_LIST               1
             34 BINARY_ADD
             35 STORE_FAST               0 (s)
             38 JUMP_ABSOLUTE           19
        >>   41 POP_BLOCK
        >>   42 LOAD_CONST               0 (None)
             45 RETURN_VALUE

这些循环几乎是相同的。比较相当于调用_function+pop_top与inplace_add+store_fast与build_list+binary_add+store_fast。然而,我不能给出比这更低级的解释——我只是无法在网络上找到Python字节码的成本。然而,您可能会从DougHellmann在dis上发布的本周python模块中获得一些灵感。


这里已经有很多好的总结,但只是为了提供更多的证据。

源代码:我盯着Python源代码看了一个小时,计算了复杂度!

我的发现。

对于2个字符串。(假设n是两个字符串的长度)

1
2
3
Concat (+) - O(n)
Join       - O(n+k)  effectively O(n)
Format     - O(2n+k) effectively O(n)

超过2个字符串。(假设n是所有字符串的长度)

1
2
3
Concat (+) - O(n^2)
Join       - O(n+k)  effectively O(n)
Format     - O(2n+k) effectively O(n)

结果:

如果您有两个字符串,技术上来说连接(+)更好,尽管它与join和format完全相同。

如果有两个以上的字符串,concat会变得很糟糕,join和format实际上是相同的,尽管技术上join更好一些。

总结:

如果你不关心效率,就使用上面的任何一个。(不过既然你问了这个问题,我想你会关心的)

因此-

如果有两个字符串,请使用concat(不在循环中时!)

如果有两个以上的字符串(所有字符串)(或在循环中),请使用join

如果没有任何字符串,请使用格式,因为duh。

希望这有帮助!


我从专家们发布的答案中找到了答案。python字符串连接(和计时测量)取决于这些(据我所见):

  • 连接数
  • 字符串的平均长度
  • 函数调用数

我已经构建了一个与这些相关的新代码。感谢Peter S Magnusson、SEPP2K、Hughdbrown、David Wolver和其他人指出了我之前错过的重要要点。另外,在这段代码中,我可能遗漏了一些东西。所以,我非常感谢任何指出我们的错误、建议、批评等的回复。毕竟,我是来学习的。这是我的新代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
from timeit import timeit

noc = 100
tocat ="a"
def f_call():
    pass

def loop_only():
    for i in range(noc):
        pass

def concat_method():
    s = ''
    for i in range(noc):
        s = s + tocat

def list_append():
    s=[]
    for i in range(noc):
        s.append(tocat)
    ''.join(s)

def list_append_opt():
    s = []
    zap = s.append
    for i in range(noc):
        zap(tocat)
    ''.join(s)

def list_comp():
    ''.join(tocat for i in range(noc))

def concat_method_buildup():
    s=''

def list_append_buildup():
    s=[]

def list_append_opt_buildup():
    s=[]
    zap = s.append

def function_time(f):
    return timeit(f,number=1000)*1000

f_callt = function_time(f_call)

def measure(ftuple,n,tc):
    global noc,tocat
    noc = n
    tocat = tc
    loopt = function_time(loop_only) - f_callt
    buildup_time = function_time(ftuple[1]) -f_callt if ftuple[1] else 0
    total_time = function_time(ftuple[0])
    return total_time, total_time - f_callt - buildup_time - loopt*ftuple[2]

functions ={'Concat Method\t\t':(concat_method,concat_method_buildup,True),
            'List append\t\t\t':(list_append,list_append_buildup,True),
            'Optimized list append':(list_append_opt,list_append_opt_buildup,True),
            'List comp\t\t\t':(list_comp,0,False)}

for i in range(5):
    print("

%d concatenation\t\t\t\t10'a'\t\t\t\t 100'a'\t\t\t1000'a'"
%10**i)
    print('-'*80)
    for (f,ft) in functions.items():
        print(f,"\t|",end="\t")
        for j in range(3):
            t = measure(ft,10**i,'a'*10**j)
            print("%.3f %.3f |" % t,end="\t")
        print()

这就是我得到的。[时间列中显示两次(按比例缩放):第一次是总函数执行时间,第二次是实际时间(?)连接时间。我已经扣除了函数调用时间、函数构建时间(初始化时间)和迭代时间。在这里,我正在考虑一个没有循环就无法完成的情况(在里面说更多的语句)。]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1 concatenation                 1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   2.310 2.168       |  2.298 2.156       |  2.304 2.162
Optimized list append   |   1.069 0.439       |  1.098 0.456       |  1.071 0.413
Concat Method           |   0.552 0.034       |  0.541 0.025       |  0.565 0.048
List append             |   1.099 0.557       |  1.099 0.552       |  1.094 0.552


10 concatenations                1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   3.366 3.224       |  3.473 3.331       |  4.058 3.916
Optimized list append   |   2.778 2.003       |  2.956 2.186       |  3.417 2.639
Concat Method           |   1.602 0.943       |  1.910 1.259       |  3.381 2.724
List append             |   3.290 2.612       |  3.378 2.699       |  3.959 3.282


100 concatenations               1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   15.900 15.758     |  17.086 16.944     |  20.260 20.118
Optimized list append   |   15.178 12.585     |  16.203 13.527     |  19.336 16.703
Concat Method           |   10.937 8.482      |  25.731 23.263     |  29.390 26.934
List append             |   20.515 18.031     |  21.599 19.115     |  24.487 22.003


1000 concatenations               1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   134.507 134.365   |  143.913 143.771   |  201.062 200.920
Optimized list append   |   112.018 77.525    |  121.487 87.419    |  151.063 117.059
Concat Method           |   214.329 180.093   |  290.380 256.515   |  324.572 290.720
List append             |   167.625 133.619   |  176.241 142.267   |  205.259 171.313


10000 concatenations              1'a'                  10'a'               100'a'
-------------------     ----------------------  -------------------  ----------------
List comp               |   1309.702 1309.560 |  1404.191 1404.049 |  2912.483 2912.341
Optimized list append   |   1042.271 668.696  |  1134.404 761.036  |  2628.882 2255.804
Concat Method           |   2310.204 1941.096 |  2923.805 2550.803 |  STUCK    STUCK
List append             |   1624.795 1251.589 |  1717.501 1345.137 |  3182.347 2809.233

综上所述,我为自己做了以下决定:

  • 如果有可用的字符串列表,字符串"join"方法最好,并且最快的。
  • 如果你能用列表理解,这也是最简单和最快的。
  • 如果需要1到10个连接(平均)长度1至100,列表附加"+"两个都需要相同的时间(几乎,注意时间是按比例缩放的)。
  • 优化的列表附加看起来非常大多数情况下都很好。
  • 当连接或字符串长度增加时,"+"开始显著增加还有更多的时间。请注意,对于10000个连接到100'A'的连接,我的电脑被卡住了!
  • 如果使用list append和join一直以来,你都很安全(亚历克斯指出马泰利)。
  • 但在某些情况下,比如说需要接受用户输入和打印"你好,用户的世界!",使用"+"最简单。我想列个清单对于这种情况,可以像x=input("输入用户名:")和x.join(["hello","'s world!")])比"你好%s的世界"更丑!%X或"你好"+X+"世界"
  • python 3.1改进了串联性能。但是,在一些实施像Jython一样,"+"效率较低。
  • 过早优化是根本一切邪恶(专家的说法)。大多数当时的您不需要优化。所以,不要在渴望中浪费时间用于优化(除非你正在写一个大的或计算性的项目,微秒/毫秒计数。
  • 使用这些信息并写入不管你喜欢用什么方式情况考虑。
  • 如果你真的需要优化,使用分析器,找到瓶颈并尝试优化这些。
  • 最后,我想更深入地学习Python。所以,在我的观察中会有错误是不寻常的。所以,请对此发表评论,并建议我是否走错了路线。感谢大家的参与。


    您要测量两个不同的操作:字符串数组的创建和字符串的串联。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        import timeit
        def x():
            s = []
            for i in range(100):
                s.append("abcdefg"[i%7])
            return ''.join(s)
        def y():
            s = ''
            for i in range(100):
                s +="abcdefgh"[i%7]

        # timeit.timeit(x) returns about 32s
        # timeit.timeit(y) returns about 23s

    从上面看来,'+'确实是比join更快的操作。但是考虑一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        src = []
        def c():
            global src
            s = []
            for i in range(100):
                s.append("abcdefg"[i%7])
            src = s
        def x2():
            return ''.join(src)
        def y2():
            s = ''
            for i in range(len(src)):
                s += src[i]
            return s

        # timeit.timeit(c) returns about 30s
        # timeit.timeit(x2) returns about 1.5s
        # timeit.timeit(y2) returns about 14s

    换句话说,通过计时x()与y(),源数组的构造会污染结果。如果你打破这一点,你会发现加入更快。

    此外,您使用的是小数组,而您的计时数字恰好是一致的。如果显著增加数组的大小和每个字符串的长度,则差异会更明显:

    1
    2
    3
    4
    5
    6
    7
    8
    9
       def c2():
           global src
           s = []
           for i in range(10000):
               s.append("abcdefghijklmnopqrstuvwxyz0123456789"
           src = s

       # timeit.timeit(x2, number=10000) returns about 1s
       # timeit.timeit(y2, number=10000) returns about 80s

    带字符串的+=和+之间有一个区别——如果没有其他对"x"的引用,x+=y可以直接附加到x,而不必复制要附加到的字符串——这与使用".join()获得的好处相同。

    ".join()优于+或+=的主要好处是join()应始终提供线性性能,而在许多情况下,+/+=将提供二次性能(即,当文本量翻倍时,所用时间将翻倍)。但这只对大量的文本有效,而不仅仅是100字节,而且我认为如果只有一个对要附加到的字符串的引用,它就不会被触发。

    详细说明:

    对于字符串连接,最好的情况是只查看最后一个字符串中的每个字符一次。".join()很自然地做到了这一点——它从一开始就拥有了所需的所有信息。

    然而,a+=b有两种工作方式,它可以将"b"添加到现有的字符串中,在这种情况下,它只需要查看"b"中的字符,或者也可以查看"a"中的字符。

    在C语言中,strcat()总是查看两个字符串中的所有字符,所以它总是工作得很糟糕。然而,在python中,字符串长度是存储的,因此只要不在其他地方引用字符串,就可以对其进行扩展——并且您只需复制"b"中的字符就可以获得良好的性能。如果它在其他地方被引用,python将首先复制"a",然后在末尾添加"b",这会导致性能下降。如果您以这种方式附加五个字符串,则所用的时间将是:

    1
    2
    3
    4
    ab = a+b       # Time is a + b
    abc = ab+c     # Time is (a+b) + c
    abcd = abc+d   # Time is (a+b+c) + d
    abcde = abcd+e # Time is (a+b+c+d) + e

    如果a,b,c,d,e的大小大致相同,比如,n,是n*(n-1)/2-1操作,或者基本上是n平方。

    要获得x+=y的不良行为,请尝试:

    1
    2
    3
    4
    5
    6
    def a(n=100):
        res =""
        for k in xrange(n):
            v=res
            res +="foobar"
        return res

    尽管实际上没有使用v,但它足以触发+=的较慢路径,并导致人们担心的不良行为。

    我相信在Python2.0之前,+=才被引入,所以在Python1.6和更早版本中不使用".join()"之类的东西就无法有效地附加。


    除了其他人说的以外,100个1字符的字符串真的很小。(我有点惊讶于结果的分离。)这是一种适合您的处理器缓存的数据集。你不会看到微基准的渐进性能。


    有趣的是:我做了一些测试,其中字符串的大小发生了变化,这就是我发现的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def x():
        x ="a" * 100
        s=[]
        for i in range(100):
            # Other codes here...
            s.append(x)
        return ''.join(s)

    def z():
        x ="a" * 100
        s=''
        for i in xrange(100):
            # Other codes here...
            s=s+x
        return s

    from timeit import timeit
    print"x:", timeit(x, number=1000000)
    print"z:", timeit(z, number=1000000)

    对于长度为1的字符串(x ="a" * 1

    1
    2
    x: 27.2318270206
    z: 14.4046051502

    对于长度为100的字符串:

    1
    2
    x: 30.0796670914
    z: 21.5891489983

    对于长度为1000的字符串,运行timeit 100000次,而不是1000000次

    1
    2
    x: 14.1769361496
    z: 31.4864079952

    如果我对Objects/stringobject.c的理解是正确的,那就有道理了。

    在第一次读取时,string.join算法(不考虑边缘情况)似乎是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def join(sep, sequence):
        size = 0
        for string in sequence:
            size += len(string) + len(sep)

        result = malloc(size)

        for string in sequence:
            copy string into result
            copy sep into result

        return result

    因此,这将需要或多或少的O(S)步(其中S是连接的所有字符串长度的总和)。


    字符串连接比python 2.5慢得多,因为它仍然为每个字符串连接创建一个新的副本,而不是附加到原始的字符串连接,这导致join()成为一个流行的解决方案。

    下面是一个老基准,展示了老问题:http://www.skymind.com/~ocrow/python_-string/