Почему переменная1 + = переменная2 намного быстрее, чем переменная1 = переменная1 + переменная2?

Я унаследовал некоторый код Python, который используется для создания огромных таблиц (до 19 столбцов по 5000 строк). На экране отобразилось девять секунд . Я заметил, что каждая строка была добавлена ​​с использованием этого кода:

sTable = sTable + '\n' + GetRow() 

где sTable – строка.

Я изменил это так:

 sTable += '\n' + GetRow() 

и я заметил, что таблица появилась через шесть секунд .

И затем я изменил его на:

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

на основе этих советов по производительности Python (еще шесть секунд).

Поскольку это было вызвано около 5000 раз, он подчеркнул проблему производительности. Но почему была такая большая разница? И почему компилятор не выявил проблему в первой версии и не оптимизировал ее?

One Solution collect form web for “Почему переменная1 + = переменная2 намного быстрее, чем переменная1 = переменная1 + переменная2?”

Речь идет не об использовании inplace += versus + binary add. Вы не рассказали нам всю историю. Ваша исходная версия объединила 3 ​​строки, а не только две:

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

Python пытается помочь и оптимизировать конкатенацию строк; как при использовании strobj += otherstrobj и strobj = strobj + otherstringobj , но он не может применить эту оптимизацию, когда задействовано более 2 строк.

Строки Python неизменяемы нормально , но если нет других ссылок на объект левой строки и он все равно отскакивается, Python обманывает и изменяет строку . Это позволяет избежать необходимости создавать новую строку каждый раз, когда вы объединяетесь, и это может привести к значительному улучшению скорости.

Это реализовано в цикле оценки байт-кода. Как при использовании BINARY_ADD в двух строках, так и при использовании INPLACE_ADD в двух строках , Python делегирует конкатенацию специальной вспомогательной функции string_concatenate() . Чтобы иметь возможность оптимизировать конкатенацию, изменяя строку, сначала нужно убедиться, что в строке нет других ссылок на нее; если только стек и исходная переменная ссылаются на это, это может быть сделано, и следующая операция заменит исходную ссылку на переменную.

Так что, если есть всего две ссылки на строку, а следующий оператор является одним из STORE_FAST (установить локальную переменную), STORE_DEREF (установить переменную, на которую ссылаются закрытые функции) или STORE_NAME (установить глобальную переменную), а затронутая переменная в настоящее время ссылается на одну и ту же строку, затем эта целевая переменная очищается, чтобы уменьшить количество ссылок только на 1, стек.

Вот почему ваш исходный код не смог полностью использовать эту оптимизацию. Первая часть вашего выражения – sTable + '\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 растет, и для создания этого нового строкового объекта требуется больше времени.

Вы изменили этот код на:

 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 может быть изменен на месте, не приводя к появлению более крупного нового строкового объекта.

У вас была бы такая же разница в скорости:

 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 

Мораль этой истории состоит в том, что вы не должны использовать конкатенацию строк в первую очередь. Правильный способ создания новой строки из множества других строк – использовать список, а затем использовать str.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
  • Как найти и подсчитать смайлики в строке с помощью python?
  • Преобразование объекта Python str / unicode в двоичный / hex-блок
  • Элементарная конкатенация строк в numpy
  • Расширение встроенных классов в python
  • Копировать копию RPython с кавычками из R в Python
  • Строковое манипулирование с помощью обычного выражения или выражений
  • строка байта и строка unicode. питон
  • Python - лучший язык программирования в мире.