Скажем, у Вас есть этот код для создания строки от трех строк:
x = 'foo'
x += 'bar' # 'foobar'
x += 'baz' # 'foobarbaz'
В этом случае, Python сначала должен выделить и создать 'foobar'
, прежде чем он сможет выделить и создать 'foobarbaz'
.
Так для каждого +=
, который называют, все содержание строки и независимо от того, что становится добавленным к ней, должно быть скопировано в совершенно новый буфер памяти. Другими словами, если Вы имеете N
строки, к которым присоединятся, необходимо выделить приблизительно N
, временные строки и первая подстрока копируются ~N времена. Последняя подстрока только копируется однажды, но в среднем, каждая подстрока становится скопированной ~N/2
времена.
С .join
, Python может играть много приемов, так как промежуточные строки не должны быть созданы. CPython выясняет, в каком количестве памяти он нуждается впереди и затем выделяет правильно измеренный буфер. Наконец, это затем копирует каждую часть в новый буфер, что означает, что каждая часть только копируется однажды.
существуют другие жизнеспособные подходы, которые могли привести к лучшей производительности для +=
в некоторых случаях. Например, если внутреннее строковое представление на самом деле rope
или если время выполнения достаточно на самом деле умно, чтобы так или иначе выяснить, что временные строки бесполезны к программе и оптимизируют их далеко.
Однако CPython, конечно, делает не , делают эту оптимизацию надежно (хотя это май для немного угловых случаев ) и так как это - наиболее распространенная используемая реализация, много лучших практик на основе какой работы хорошо для CPython. Наличие стандартизированного набора норм также помогает другим реализациям сфокусировать их усилия по оптимизации также.
Я думаю, что это поведение лучше всего объяснено в строковая буферная глава .
Lua Для перезаписи того объяснения в контексте Python, давайте запустимся с невинного фрагмента кода (производная той в документах Lua):
s = ""
for l in some_list:
s += l
Предполагают, что каждый l
- 20 байтов, и эти s
был уже проанализирован к размеру 50 КБ. Когда Python конкатенирует s + l
, он создает новую строку с 50 020 байтами и копирует 50 КБ от s
в эту новую строку. Таким образом, для каждой новой строки программа перемещает 50 КБ памяти и рост. После чтения 100 новых строк (только 2 КБ) отрывок уже переместил больше чем 5 МБ памяти. Ко всем неприятностям после присвоения
s += l
старая строка является теперь мусором. После двух циклов цикла существует две старых строки, делающие в общей сложности больше чем 100 КБ мусора. Так, языковой компилятор решает запустить свой сборщик "мусора" и освобождает тех 100 КБ. Проблема состоит в том, что это произойдет, каждые два цикла и программа запустят ее сборщик "мусора" две тысячи раз прежде, чем прочитать целый список. Даже со всей этой работой, ее использование памяти будет большим несколько из размера списка.
И, в конце:
Эта проблема не специфична для Lua: Другие языки с истинной сборкой "мусора", и где строки являются неизменными объектами, представляют подобное поведение, при этом Java является самым известным примером. (Java предлагает структуру
StringBuffer
для улучшения проблемы.)
строки Python также неизменные объекты .