Python字符串“join”更快(?)不是“+”,但这里怎么了?

2024-04-26 23:36:40 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我定义了这些功能:

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位计算机上使用Python3.1.1 IDLE):

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是最快的。虽然时间上的差异没有那么大(除了最后一次),但我想知道为什么会发生这种情况。这是因为我使用的是Python3.1.1,而且它提供了最有效的“+”吗?我是否应该使用“+”来替代连接?或者,我做了一些非常愚蠢的事情吗?或者什么?请解释清楚。


Tags: 方法字符串inforreturnheredef文章
3条回答

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

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

我们中的一些Python提交者,我相信主要是Rigo和Hettinger,特意优化了一些非常常见的alas的特殊情况(我相信是在2.5的路上),他们认为已经证明了初学者永远不会相信s += something枯萎病是正确的,而且+=可能给Python起了个坏名字。我们中的其他人并没有那么火爆,因为他们不可能将每一次事件(甚至只是其中的大多数)都优化到合适的性能;但是我们在这个问题上没有足够的热情去尝试和积极地阻止它们。

我相信这条线索证明我们应该更严厉地反对他们。现在,他们在一个很难预测的案例子集中优化了+=,对于某些愚蠢的案例,优化速度可能比正确的方法快20%(仍然是''.join)--这是一个完美的方法,可以诱使初学者通过使用错误的习语来追求那些不相关的20%的收益。。。代价是,偶尔从他们的POV中脱颖而出,遭受200%的表演损失(或者更多,因为非线性行为仍然潜伏在赫廷格和里戈预先绑好并放上鲜花的角落外面;—)——一个重要的,一个会让他们痛苦的。这与Python的“理想情况下只有一种显而易见的方法”背道而驰,在我看来,我们共同为初学者设置了一个陷阱——这也是最好的一种。。。那些不只是接受他们的“好手”告诉他们的,而是好奇地去提问和探索的人。

我放弃了。OP,@mshsayem,继续,到处使用+=在琐碎的、微小的、不相关的情况下享受你不相关的20%加速,你最好尽情享受它们——因为有一天,当你看不到它的到来时,在一个重要的、大的行动中,你会被迎面而来的200%减速的拖车撞到腹部(除非你运气不好,而且是2000%减速)。只要记住:如果你觉得“Python的速度太慢了”,记住,它很可能是你最喜欢的一个循环,即转身咬着喂它的手。

对于我们其他人——那些明白说We should forget about small efficiencies, say about 97% of the time意味着什么的人,我将继续衷心地推荐''.join,这样我们都可以睡得很安稳,知道当我们最不期望、最不负担得起你的时候,我们不会受到超线性减速的打击。但对你来说,阿米恩·里戈和雷蒙德·赫廷格(最后两位,我亲爱的私人朋友,顺便说一句,不仅仅是共犯;—)——愿你的+=一帆风顺,你的大O永远不会比N差!-)

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

$ 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个字符,直接加入列表当然是最快的,但操作人员害怕必须在那之前添加。但是:

$ 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也是非常好的。此外,它显然很容易优化:

$ 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要慢得多:当你说

l += "a"

您将字符串"a"附加到l的末尾,但是当您说

l = l + ["a"]

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

相关问题 更多 >