【问题标题】:Why is concatenating strings running faster than joining them? [duplicate]为什么连接字符串比加入它们运行得更快? [复制]
【发布时间】:2013-12-09 18:38:59
【问题描述】:

据我了解,“”.join(iterable_of_strings) 是连接字符串的首选方式,因为它允许进行优化,避免不必要地将不可变对象重写到内存。

对于我来说,在表达式中添加字符串确实比加入它们运行得更快。

我在加入时获得了大约 2.9-3.2 秒的时间,在我的笔记本电脑上使用 Python 3.3 运行此代码时添加了 2.3-2.7 秒。我找不到一个好的答案谷歌搜索这个。有人可以解释一下可能发生的事情或指导我找到一个好的资源吗?

import uuid
import time

class mock:
    def __init__(self):
        self.name = "foo"
        self.address = "address"
        self.age = "age"
        self.primarykey = uuid.uuid4()

data_list = [mock() for x in range(2000000)]

def added():
    my_dict_list = {}
    t = time.time()
    new_dict = { item.primarykey: item.name + item.address + item.age for item in data_list }
    print(time.time() - t)

def joined():
    my_dict_list = {}
    t = time.time()
    new_dict = { item.primarykey: ''.join([item.name, item.address, item.age]) for item in data_list }
    print(time.time() - t)

joined()
added()

【问题讨论】:

  • cPython 具有非常有效的就地字符串连接。发现两个连接操作比具体化一个列表然后执行''.join 更快,这并不让我感到惊讶。

标签: python string loops memory python-3.3


【解决方案1】:

据我了解,“”.join(iterable_of_strings) 是连接字符串的首选方式,因为它允许进行优化,避免不必要地将不可变对象重写到内存。

您的理解有些不正确。出于您解释的原因,"".join(iterable_of_strings) 是连接可迭代字符串的首选方式。

但是,您没有可迭代的字符串。你只有三个字符串。连接三个字符串的最快方法是将它们与+ 相加,或者使用.format()%。这是因为在您的情况下,您必须首先创建可迭代对象,然后加入字符串,所有这些都是为了避免复制一些非常小的字符串。

.join() 不会变得更快,除非你有这么多的字符串,以至于愚蠢的代码无论如何都要使用其他方法。何时发生这种情况取决于您使用的 Python 实现、版本以及字符串的长度,但我们通常会讨论十多个字符串。

虽然确实不是所有实现都具有快速连接,但我已经测试了 CPython、PyPy 和 Jython,它们都具有更快的连接速度,或者仅对几个字符串的连接速度一样快。

本质上,在代码运行之前,您应该根据代码清晰度在+.join() 之间进行选择。然后,如果您关心速度:分析和基准测试您的代码。不要坐着猜。

一些时间安排:http://slides.colliberty.com/DjangoConEU-2013/#/step-40

附视频讲解:http://youtu.be/50OIO9ONmks?t=18m30s

【讨论】:

    【解决方案2】:

    您看到的时差来自创建要传递给join 的列表。虽然您可以通过使用元组来获得小幅加速,但当只有几个短字符串时,它仍然比仅与 + 连接要慢。

    如果你有一个可迭代的字符串开始,而不是一个以字符串作为属性的对象,那将是不同的。然后你可以直接在可迭代对象上调用join,而不需要为每个调用构建一个新的。

    这是我对timeit 模块所做的一些测试:

    import timeit
    
    short_strings = ["foo", "bar", "baz"]
    long_strings = [s*1000 for s in short_strings]
    
    def concat(a, b, c):
        return a + b + c
    
    def concat_from_list(lst):
        return lst[0] + lst[1] + lst[2]
    
    def join(a, b, c):
        return "".join([a, b, c])
    
    def join_tuple(a, b, c):
        return "".join((a, b, c))
    
    def join_from_list(lst):
        return "".join(lst)
    
    def test():
        print("Short strings")
        print("{:20}{}".format("concat:",
                               timeit.timeit(lambda: concat(*short_strings))))
        print("{:20}{}".format("concat_from_list:",
                               timeit.timeit(lambda: concat_from_list(short_strings))))
        print("{:20}{}".format("join:",
                               timeit.timeit(lambda: join(*short_strings))))
        print("{:20}{}".format("join_tuple:",
                               timeit.timeit(lambda: join_tuple(*short_strings))))
        print("{:20}{}\n".format("join_from_list:",
                                 timeit.timeit(lambda: join_from_list(short_strings))))
        print("Long Strings")
        print("{:20}{}".format("concat:",
                               timeit.timeit(lambda: concat(*long_strings))))
        print("{:20}{}".format("concat_from_list:",
                               timeit.timeit(lambda: concat_from_list(long_strings))))
        print("{:20}{}".format("join:",
                               timeit.timeit(lambda: join(*long_strings))))
        print("{:20}{}".format("join_tuple:",
                               timeit.timeit(lambda: join_tuple(*long_strings))))
        print("{:20}{}".format("join_from_list:",
                               timeit.timeit(lambda: join_from_list(long_strings))))
    

    输出:

    Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
    Type "copyright", "credits" or "license()" for more information.
    >>> ================================ RESTART ================================
    >>> 
    >>> test()
    Short strings
    concat:             0.5453461176251436
    concat_from_list:   0.5185697357936024
    join:               0.7099379456477868
    join_tuple:         0.5900842397209949
    join_from_list:     0.4177281794285359
    
    Long Strings
    concat:             2.002303591571888
    concat_from_list:   1.8898819841869416
    join:               1.5672863477837913
    join_tuple:         1.4343144915087596
    join_from_list:     1.231374639083505
    

    因此,从现有列表加入总是最快的。如果单个项目很短,则与 + 连接会更快,但对于长字符串,它总是最慢。我怀疑concatconcat_from_list 之间显示的差异来自于测试代码中函数调用中列表的解包。

    【讨论】:

    • 对。 concat_from_list 比 concat 快的事实表明测试代码有问题。正如您所说,它可能是拆包。无论如何,这些时间中的很多时间只是函数调用。我所做的测试表明,+ 对于三个长字符串也更快,尽管我手边没有数据。
    • 很有趣,感谢您的彻底回复。我肯定会采用您对 timeit 的使用,而不是我使用的那种笨拙的成语。
    • @TimWilder 不,不要。如果您关心使代码运行得更快,请对其进行分析。 timeit 如果使用正确,是一个有用的工具,但正确使用并不容易,而且您没有使用实际数据测试实际运行的代码,这意味着最好查看哪些类型的更改是一个很好的帮助值得一试,一旦你发现代码很慢。
    • @Lennart 我会记住这一点。我相信现在去读一些关于 Python 分析选项的公共汽车是值得的。
    猜你喜欢
    • 2020-12-18
    • 2011-11-10
    • 1970-01-01
    • 2017-06-30
    • 2016-12-26
    • 2022-03-30
    • 2014-06-14
    • 2017-04-14
    • 1970-01-01
    相关资源
    最近更新 更多