python - Python字符串添加优化失败案例

标签 python string optimization cpython

问题

为什么,在 CPython 中,

def add_string(n):
    s = ''
    for _ in range(n):
        s += ' '

需要线性时间,但是

def add_string_in_list(n):
    l = ['']
    for _ in range(n):
        l[0] += ' '

需要二次时间吗?


证明:

Timer(partial(add_string, 1000000)).timeit(1)
#>>> 0.1848409200028982
Timer(partial(add_string, 10000000)).timeit(1)
#>>> 1.1123797750042286
Timer(partial(add_string_in_list, 10000)).timeit(1)
#>>> 0.0033865350123960525
Timer(partial(add_string_in_list, 100000)).timeit(1)
#>>> 0.25131178900483064

我所知道的

当要添加的字符串的引用计数为 1 时,CPython 对字符串添加进行了优化。

这是因为 Python 中的字符串是不可变的,因此通常无法编辑它们。如果对一个字符串存在多个引用并且它发生了变化,两个 引用都会看到更改后的字符串。这显然是不希望的,因此多个引用不会发生突变。

但是,如果只有一个对字符串的引用,则改变该值只会更改该引用的字符串,而该引用希望它被更改。您可以这样测试这是否是一个可能的原因:

from timeit import Timer
from functools import partial

def add_string_two_references(n):
    s = ''
    for _ in range(n):
        s2 = s
        s += ' '

Timer(partial(add_string_two_references, 20000)).timeit(1)
#>>> 0.032532954995986074
Timer(partial(add_string_two_references, 200000)).timeit(1)
#>>> 1.0898985149979126

我不确定为什么这个因子只有 30 倍,而不是预期的 100 倍,但我相信这是开销。


我不知道的事

那么为什么列表版本会创建两个引用呢?这甚至是阻止优化的原因吗?

您可以检查它是否没有以任何不同方式对待普通对象:

class Counter:
    def __iadd__(self, other):
        print(sys.getrefcount(self))

s = Counter()
s += None
#>>> 6

class Counter:
    def __iadd__(self, other):
        print(sys.getrefcount(self))

l = [Counter()]
l[0] += None
#>>> 6

最佳答案

在基于列表的方法中,从列表的索引 0 开始的字符串在被放回索引 0 的列表之前被获取和修改。
对于这短暂的时刻,解释器在列表中仍然有旧版本的字符串,无法执行就地修改。
如果你看一下Python's source然后您会看到不支持就地修改列表的元素。因此对象(在本例中为字符串)必须从列表中检索、修改然后放回。
换句话说 list类型与 str 完全无关类型支持 +=运营商。

并考虑以下代码:

l = ['abc', 'def']
def nasty():
    global l
    l[0] = 'ghi'
    l[1] = 'jkl'
    return 'mno'
l[0] += nasty()

l 的值是['abcmno', 'jkl']这证明'abc'从列表中取出,然后 nasty()执行修改列表的内容,字符串 'abc''mno'连接起来,结果分配给 l[0] .如果nasty()在访问 l[0] 之前被评估就地修改它,那么结果将是 'ghimno' .

关于python - Python字符串添加优化失败案例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24040198/

相关文章:

python - 没有名为 'tensorflow_probability' 的模块

Java 程序从文本文件读取输入并进行相应修改

memory - 如何在给定特征值 1 的情况下找到特征向量,最大限度地减少内存使用

mysql - 跨多行优化 MySQL 查询

php - 在我的服务器中打开 zlib.output_compression 有什么注意事项吗?

python - 如何将数字组合成一行

python - Cython 扩展类 : How do I expose methods in the auto-generated C struct?

python - 如何使用 http.server 执行服务器端 python 脚本?

c# - 如何消除字符串中的所有换行符?

string - 如何在现代 Delphi 中将 AnsiString 转换为整数?