Зачем быть.'' join () быстрее, чем += в Python?
Я могу найти множество информации в интернете (о переполнении стека и в противном случае) о том, как это очень неэффективно и плохо использовать +
или +=
для конкатенации в Python.
Я не могу понять, почему +=
неэффективно. Помимо упоминания здесь ,что" он был оптимизирован для улучшения 20% в некоторых случаях " (все еще не ясно, что это за случаи), я не могу найти никакой дополнительной информации.
что происходит на более техническом уровень, который делает ''.join()
превосходит другие методы конкатенации Python?
2 ответа:
допустим, у вас есть этот код, чтобы построить строку из трех строк:
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 также неизменяемые объекты.