Python的字符串连接与str.join有多慢?


74

由于我对此线程的回答中有评论,因此我想知道+=操作员和操作员之间的速度差是多少。''.join()

那么两者的速度比较是多少?


2
你在测试什么?两个弦?两百万根琴弦?
SilentGhost

是的,我一直忘了timeit的语法-和time.time()-开始变得容易得多:P
Wayne Werner 2010年

这个问题是类似的,并且具有更好的答案:stackoverflow.com/questions/1349311/…–
Frank

Answers:


108

来自:有效的字符串连接

方法1:

def method1():
  out_str = ''
  for num in xrange(loop_count):
    out_str += 'num'
  return out_str

方法4:

def method4():
  str_list = []
  for num in xrange(loop_count):
    str_list.append('num')
  return ''.join(str_list)

现在,我意识到它们并不是严格的代表,第4种方法会在遍历和加入每个项目之前追加到列表中,但这是一个合理的指示。

字符串连接要比串联快得多。

为什么?字符串是不可变的,不能在原位置更改。要更改一个,需要创建一个新的表示形式(两者的串联)。

替代文字


3
好吧,我本人要自己回答(因此标记),但是看来您击败了我!+1,特别是有用的链接!
韦恩·沃纳

2
@Wayne:有用的链接是从您链接到的问题复制而来的!
SilentGhost

6
-1。string.join和+串联之间的速度差异没有固定的比率,因为它们的**增长率** /复杂度完全不同。随着要串联的字符串数量的增加,string.join与字符串串联相比将具有越来越大的裕度。
Lie Ryan

1
@nate c:方法1现在仅比方法6(使用Python 2.6)慢一点,但这仅在CPython中。我相信,在Jython中,它并没有被这样的优化,因此''.join(list)仍然相当快-看到PEP 8.在“编程建议”第一点
克里斯·摩根

10
从PEP 8:“例如,对于形式为a + = b或a = a + b的语句,请不要依赖CPython对原位字符串连接的有效实现。这些语句在Jython中的运行速度较慢。在库的性能敏感部分中,应改用''.join()形式。这将确保在各种实现中,连接在线性时间内发生。”
尼尔·G,

9

我的原始代码有误,看来 +连接通常更快(尤其是在较新的硬件上使用较新版本的Python)

时间如下:

Iterations: 1,000,000       

Windows 7,Core i7上的Python 3.3

String of len:   1 took:     0.5710     0.2880 seconds
String of len:   4 took:     0.9480     0.5830 seconds
String of len:   6 took:     1.2770     0.8130 seconds
String of len:  12 took:     2.0610     1.5930 seconds
String of len:  80 took:    10.5140    37.8590 seconds
String of len: 222 took:    27.3400   134.7440 seconds
String of len: 443 took:    52.9640   170.6440 seconds

Windows 7,Core i7上的Python 2.7

String of len:   1 took:     0.7190     0.4960 seconds
String of len:   4 took:     1.0660     0.6920 seconds
String of len:   6 took:     1.3300     0.8560 seconds
String of len:  12 took:     1.9980     1.5330 seconds
String of len:  80 took:     9.0520    25.7190 seconds
String of len: 222 took:    23.1620    71.3620 seconds
String of len: 443 took:    44.3620   117.1510 seconds

在Linux Mint,Python 2.7和较慢的处理器上

String of len:   1 took:     1.8840     1.2990 seconds
String of len:   4 took:     2.8394     1.9663 seconds
String of len:   6 took:     3.5177     2.4162 seconds
String of len:  12 took:     5.5456     4.1695 seconds
String of len:  80 took:    27.8813    19.2180 seconds
String of len: 222 took:    69.5679    55.7790 seconds
String of len: 443 took:   135.6101   153.8212 seconds

这是代码:

from __future__ import print_function
import time

def strcat(string):
    newstr = ''
    for char in string:
        newstr += char
    return newstr

def listcat(string):
    chars = []
    for char in string:
        chars.append(char)
    return ''.join(chars)

def test(fn, times, *args):
    start = time.time()
    for x in range(times):
        fn(*args)
    return "{:>10.4f}".format(time.time() - start)

def testall():
    strings = ['a', 'long', 'longer', 'a bit longer', 
               '''adjkrsn widn fskejwoskemwkoskdfisdfasdfjiz  oijewf sdkjjka dsf sdk siasjk dfwijs''',
               '''this is a really long string that's so long
               it had to be triple quoted  and contains lots of
               superflous characters for kicks and gigles
               @!#(*_#)(*$(*!#@&)(*E\xc4\x32\xff\x92\x23\xDF\xDFk^%#$!)%#^(*#''',
              '''I needed another long string but this one won't have any new lines or crazy characters in it, I'm just going to type normal characters that I would usually write blah blah blah blah this is some more text hey cool what's crazy is that it looks that the str += is really close to the O(n^2) worst case performance, but it looks more like the other method increases in a perhaps linear scale? I don't know but I think this is enough text I hope.''']

    for string in strings:
        print("String of len:", len(string), "took:", test(listcat, 1000000, string), test(strcat, 1000000, string), "seconds")

testall()

您的测试是错误的。您的strcat将以字符串* len(string)返回输出,而您的listcat将始终仅返回字符串。您如何比较它们?使用newstr + = char或chars.append(string)进行测试。这实际上证明了@bwawok的观点,即+比列表追加要快。
thiruvenkadam

好的收获-应该是newstr + = char。哎呀 固定,并更新。
韦恩·沃纳

在Win 10,台式机Haswell i5Python 2.7.103.5.2计算机上,结果却相反:strcat的速度稍快:pastebin.com/sVVuExBa
Dan M.

1
@DanM .:您的意思listcat是速度稍快吗?因为这就是Pastebin中显示的内容。
ShadowRanger 2016年

第二列中的@ShadowRanger值较低,不是应该显示strcat(根据打印)吗?
Dan M.

8

现有答案的编写和研究都非常好,但是这是Python 3.6时代的另一个答案,因为现在我们有了文字字符串插值(AKA,f-strings):

>>> import timeit
>>> timeit.timeit('f\'{"a"}{"b"}{"c"}\'', number=1000000)
0.14618930302094668
>>> timeit.timeit('"".join(["a", "b", "c"])', number=1000000)
0.23334730707574636
>>> timeit.timeit('a = "a"; a += "b"; a += "c"', number=1000000)
0.14985873899422586

在配备2.3 GHz英特尔酷睿i7的2012 Retina MacBook Pro上使用CPython 3.6.5执行了测试。

这绝不是任何正式的基准测试,但是使用f-strings看起来和使用串联差不多+=。当然,任何改进的指标或建议都是欢迎的。


1
请参阅类似问题的答案:stackoverflow.com/a/1350289/1202214 + =不应使用,其性能提升只是一种幻想。
安德烈亚斯·伯格斯特伦

@AndreasBergström很高兴找到。使用会在同一台计算机上重新运行非正式基准测试,会导致a = "a"; a = a + "b"; a = a + "c"速度略有下降0.1739
朱尔斯

0

我改写了最后一个答案,乔能否请您就我的测试方式分享您的看法?

import time

start1 = time.clock()
for x in range (10000000):
    dog1 = ' and '.join(['spam', 'eggs', 'spam', 'spam', 'eggs', 'spam','spam', 'eggs', 'spam', 'spam', 'eggs', 'spam'])

end1 = time.clock()
print("Time to run Joiner = ", end1 - start1, "seconds")


start2 = time.clock()
for x in range (10000000):
    dog2 = 'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'+' and '+'spam'+' and '+'eggs'+' and '+'spam'

end2 = time.clock()
print("Time to run + = ", end2 - start2, "seconds")

注意:此示例是用Python 3.5编写的,其中range()的行为类似于以前的xrange()

我得到的输出:

Time to run Joiner =  27.086106206103153 seconds
Time to run + =  69.79100515996426 seconds

我个人更喜欢''.join([])而不是'Plusser way',因为它更干净,更易读。


-1

这就是傻程序旨在测试的内容:)

使用加号

import time

if __name__ == '__main__':
    start = time.clock()
    for x in range (1, 10000000):
        dog = "a" + "b"

    end = time.clock()
    print "Time to run Plusser = ", end - start, "seconds"

输出:

Time to run Plusser =  1.16350010965 seconds

现在加入。

import time
if __name__ == '__main__':
    start = time.clock()
    for x in range (1, 10000000):
        dog = "a".join("b")

    end = time.clock()
    print "Time to run Joiner = ", end - start, "seconds"

输出:

Time to run Joiner =  21.3877386651 seconds

因此,在Windows上的python 2.6上,我会说+大约比join快18倍:)


3
您的测试仅使用小字符串-这会产生误导性的输出,因为一旦尝试使用更长的字符串(请参阅我的答案),您可能会看到一些不同的结果。另外,您应该使用内存便宜的xrange,并且也可以1在对range的调用中省略。
韦恩·沃纳

感谢您的提示:)我仍在学习Python,这是我需要脱离Java时的另一项业余爱好。
bwawok

6
这在一个以上的地方都坏了。检查多少'a'.join('b')-是'b'。您的意思是''.join(['a','b'])。而且,'a'+'b'可能会在编译期间优化为常数,那么您要测试什么,赋值呢?
Nas Banov 2010年

即使您已将@NasBanov修复,也要对其进行修正,但是测试非常短的串联并不会测试的优势joinjoin将N个串联(1个分配,memcpy每个串联2个操作)减少为1个分配,然后进行N个memcpy操作,则获胜。因为它涉及(昂贵的)方法调用,所以在两个操作数的情况下它永远不会赢。但是至少在Python 3.5上,您实际上可以用低至4个操作数(在我的测试案例中)获胜。
ShadowRanger 2016年

另外,由于CPython的工作方式有一个怪异的结果,实际上它的执行速度mylist += (a,)比做起来要快(至少在CPython 3.5上如此)mylist.append(a)。创建一个匿名tuple(小元组被缓存在一个空闲列表中,因此不会发生分配)和调用operator +=,这两种语法都基于字节码解释器中的直接支持,比调用方法(泛型,无特殊优化)便宜。对于较小的串联,这样的东西的开销超过了实际串联的渐近开销。
ShadowRanger 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.