python教程—为什么variable1 += variable2比variable1 = variable1 + variable2快得多?-Python实用宝典

python教程—为什么variable1 += variable2比variable1 = variable1 + variable2快得多?

我继承了一些用于创建大型表的Python代码(最多19列宽5000行)。这张桌子在屏幕上画了9秒钟。我注意到每一行都是用这个代码添加的:

我继承了一些用于创建大型表的Python代码(最多19列宽5000行)。 9秒将表格绘制到屏幕上。我注意到每一行都是用这个代码添加的:

    sTable = sTable + 'n' + GetRow()

其中稳定是一个字符串。

我把它改成:

    sTable += 'n' + GetRow()

我注意到表格现在出现在six seconds > /strong>。

然后我把它改成:

    sTable += 'n%s' % GetRow()

基于(仍然是6秒)。

由于调用了大约5000次,它突出了性能问题。但是为什么会有这么大的差异呢?为什么编译器在第一个版本中没有发现问题并进行优化呢?

回答

这不是关于使用inplace +=与+ binary add的比较,您并没有告诉我们全部内容。你的原始版本连接了三个字符串,而不是两个:

    sTable = sTable + 'n' + sRow # simplified, sRow is a function call

Python试图帮助优化字符串连接;当使用strobj += otherstrobj和strobj = strobj + otherstringobj时都是如此,但是当涉及到两个以上字符串时,它不能应用这种优化。

Python字符串是不可变的通常,但是如果没有对左边字符串对象的其他引用,无论如何它正在被反弹,那么Python欺骗和修改字符串。这样就避免了每次连接时都必须创建一个新字符串,这将大大提高速度。

这是在字节码计算循环中实现的。都在使用< a href = " http://hg.python.org/cpython/file/23a60d89dbd4/Python/ceval.c l1202”> BINARY_ADD两个字符串< / >,当使用< a href = " http://hg.python.org/cpython/file/23a60d89dbd4/Python/ceval.c l1406 " > INPLACE_ADD两个字符串< / >,Python代表连接一个特殊的helper函数< a href = " http://hg.python.org/cpython/file/23a60d89dbd4/Python/ceval.c l4507 " > string_concatenate () < / >。要通过修改字符串来优化连接,首先需要确保字符串没有其他引用;如果只有堆栈和原始变量引用它,那么这是可以做到的,and,接下来的操作将替换原始变量引用。

如果只有2引用字符串,和下一个操作符是STORE_FAST之一(设置一个局部变量),STORE_DEREF(设置一个变量引用了封闭的函数)或STORE_NAME(设置一个全局变量),目前影响变量引用相同的字符串,那么目标变量可以减少引用的数量是1,堆栈。

这就是为什么您的原始代码不能完全使用这种优化。表达式的第一部分是稳定的+ 'n',接下来 操作是另一个BINARY_ADD:

    >>> import dis >>> dis.dis(compile(r"sTable = sTable + 'n' + sRow", '<stdin>', 'exec')) 1 0 LOAD_NAME 0 (sTable) 3 LOAD_CONST 0 ('n') 6 BINARY_ADD 7 LOAD_NAME 1 (sRow) 10 BINARY_ADD 11 STORE_NAME 0 (sTable) 14 LOAD_CONST 1 (None) 17 RETURN_VALUE

第一个BINARY_ADD后面跟着LOAD_NAME来访问sRow变量,而不是存储操作。第一个BINARY_ADD必须始终生成一个新的字符串对象,随着稳定的增长,它会变得越来越大,并且创建这个新的字符串对象需要越来越多的时间。

您将此代码更改为:

    sTable += 'n%s' % sRow

其中删除了第二个连接。现在字节码是:

    >>> dis.dis(compile(r"sTable += 'n%s' % sRow", '<stdin>', 'exec')) 1 0 LOAD_NAME 0 (sTable) 3 LOAD_CONST 0 ('n%s') 6 LOAD_NAME 1 (sRow) 9 BINARY_MODULO 10 INPLACE_ADD 11 STORE_NAME 0 (sTable) 14 LOAD_CONST 1 (None) 17 RETURN_VALUE

我们只剩下一个INPLACE_ADD和一个存储。现在可以就地修改sTable,而不会产生更大的新string对象。

你会得到相同的速度差:

    sTable = sTable + ('n%s' % sRow)

在这里。

时间试验显示了这种差异:

    >>> import random >>> from timeit import timeit >>> testlist = [''.join([chr(random.randint(48, 127)) for _ in range(random.randrange(10, 30))]) for _ in range(1000)] >>> def str_threevalue_concat(lst): ... res = '' ... for elem in lst: ... res = res + 'n' + elem ... >>> def str_twovalue_concat(lst): ... res = '' ... for elem in lst: ... res = res + ('n%s' % elem) ... >>> timeit('f(l)', 'from __main__ import testlist as l, str_threevalue_concat as f', number=10000) 6.196403980255127 >>> timeit('f(l)', 'from __main__ import testlist as l, str_twovalue_concat as f', number=10000) 2.3599119186401367

这个故事的寓意是,首先不应该使用字符串连接。从其他字符串加载构建新字符串的正确方法是使用列表,然后使用string .join():

    table_rows = [] for something in something_else: table_rows += ['n', GetRow()] sTable = ''.join(table_rows)

这更快:

    >>> def str_join_concat(lst): ... res = ''.join(['n%s' % elem for elem in lst]) ... >>> timeit('f(l)', 'from __main__ import testlist as l, str_join_concat as f', number=10000) 1.7978830337524414

但是你不能只使用'n'.join(lst):

    >>> timeit('f(l)', 'from __main__ import testlist as l, nl_join_concat as f', number=10000) 0.23735499382019043

​Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

本文由 Python实用宝典 作者:Python实用宝典 发表,其版权均为 Python实用宝典 所有,文章内容系作者个人观点,不代表 Python实用宝典 对观点赞同或支持。如需转载,请注明文章来源。
0

发表评论