Почему соединение быстрее, чем нормальная конкатенация

Я видел несколько примеров с различных языков, которые однозначно доказывают, что присоединение к элементам списка (массив) является временами быстрее это просто связывающее строку. К сожалению, я не нашел объяснение почему? Кто-то может объяснить внутренний алгоритм, который работает при обеих операциях и почему один быстрее, чем другой.

Вот пример Python того, что я имею в виду:

# This is slow
x = 'a'
x += 'b'
...
x += 'z'

# This is fast
x = ['a', 'b', ... 'z']
x = ''.join(x)

Благодарность является усовершенствованием),

11
задан holographic-principle 15 July 2013 в 12:50
поделиться

7 ответов

В нашем приложении мы внедрили шарики как простое окно WPF. Расположение окна ограничено некоторыми свойствами родительской управляющей модели. Вот пример кода (где BallingingContainerWindow наследует от Window):

        BaloonContainterWindow newBalloon = new BaloonContainterWindow();
        newBalloon.CreateBaloon(balloonType, balloonData);

        // Allow input and output when theis window is on top of winforms window
        SetBalloonLocation(newBalloon, sequenceId, stepId, rulerModel);

        newBalloon.Show();
        newBalloon.CloseOnDeactivation = false;
        newBalloon.Activate();
-121--2544836-

Причина в том, что строки на Python (и многих других языках) являются неизменяемыми объектами - то есть после создания их нельзя изменить. Вместо этого конкатенация строки фактически делает новую строку, которая состоит из содержимого двух объединяемых строк меньшего размера, а затем заменяет старую строку новой.

Поскольку создание строки занимает определенное время (необходимо выделить память, скопировать содержимое строки в эту память и так далее), выполнение многих строк занимает больше времени, чем создание одной строки. Выполнение конкатенаций N требует создания в процессе новых строк N . join () , с другой стороны, необходимо создать только одну строку (конечный результат) и, таким образом, работать гораздо быстрее.

-121--2898871-

Код в функции соединения заранее знает все строки, которые его просят объединить, и, следовательно, насколько велики эти строки, он может вычислить конечную длину строки перед началом операции. Следовательно, ему нужно выделить память для последней строки только один раз, и тогда он может поместить каждую исходную строку (и разделитель) в правильное место в памяти.

С другой стороны, у одной операции + = со строкой нет другого выбора, кроме как просто выделить достаточно памяти для последней строки, которая является конкатенацией только двух строк. Последующие + = должны делать то же самое, каждая выделяющая память, которая на следующем + = будет отброшена. Каждый раз постоянно растущая строка копируется из одного места памяти в другое.

14
ответ дан 3 December 2019 в 02:10
поделиться

Другие ответы в основном касались этого, но если вы хотите еще больше подробностей, у Джоэла Спольски есть статья, в которой он описывает " Алгоритм художника Шлемиля ", что чрезвычайно актуально и прекрасно объясняет, почему понимание такого рода деталей низкоуровневой реализации по-прежнему очень важно, даже если вы работаете на языке высокого уровня, таком как Python.

1
ответ дан 3 December 2019 в 02:10
поделиться

Это связано с тем, что для конкатенации последовательностей должен быть выделен больший и больший объем памяти:

x = 'a' # String of size 1 allocated
x += 'b' # String of size 2 allocated, x copied, and 'b' added. Old x discarded
x += 'b' # String of size 3 allocated, x copied, and 'c' added. Old x discarded
x += 'b' # String of size 4 allocated, x copied, and 'd' added. Old x discarded
x += 'b' # String of size 5 allocated, x copied, and 'e' added. Old x discarded

Так что происходит, вы выполняете большие выделения и копии, но затем разворачиваетесь и выбрасываете их. Очень расточительно.

x = ['a', 'b', ..., 'z'] # 26 small allocations
x = ''.join(x) # A single, large allocation
-121--2898872-

В нашем приложении мы реализовали шарики как простое окно WPF. Расположение окна ограничено некоторыми свойствами родительской управляющей модели. Вот пример кода (где BalledContainerWindow наследует от Window):

        BaloonContainterWindow newBalloon = new BaloonContainterWindow();
        newBalloon.CreateBaloon(balloonType, balloonData);

        // Allow input and output when theis window is on top of winforms window
        SetBalloonLocation(newBalloon, sequenceId, stepId, rulerModel);

        newBalloon.Show();
        newBalloon.CloseOnDeactivation = false;
        newBalloon.Activate();
-121--2544836-

Я не знаю внутренних элементов соединения, но в первой версии вы создаете новую последовательность при каждом вызове оператора + =. Так как последовательности являются неизменяемыми, каждый раз при выделении новой памяти и создании копии.

Теперь соединение (которое является методом последовательности) может выполнять только одно распределение, поскольку оно может рассчитать размер заранее.

0
ответ дан 3 December 2019 в 02:10
поделиться

Причина в том, что строки в Python (и многих других языках) являются неизменяемыми объектами , то есть однажды созданные, они не могут быть изменены. Вместо этого объединение строки фактически создает новую строку, которая состоит из содержимого двух объединяемых строк меньшего размера, а затем заменяет старую строку новой.

Поскольку создание строки занимает определенное время (необходимо выделить память, скопировать содержимое строки в эту память и т. Д.), Создание множества строк занимает больше времени, чем создание одной строки. Выполнение конкатенаций N требует создания в процессе N новых строк. join () , с другой стороны, должен создать только одну строку (конечный результат) и, таким образом, работает намного быстрее.

13
ответ дан 3 December 2019 в 02:10
поделиться

Это связано с тем, что для конкатенации строк должен быть выделен все больший и больший фрагмент памяти:

x = 'a' # String of size 1 allocated
x += 'b' # String of size 2 allocated, x copied, and 'b' added. Old x discarded
x += 'b' # String of size 3 allocated, x copied, and 'c' added. Old x discarded
x += 'b' # String of size 4 allocated, x copied, and 'd' added. Old x discarded
x += 'b' # String of size 5 allocated, x copied, and 'e' added. Old x discarded

Итак, что происходит, вы выполняете большие выделения и копии , но затем развернитесь и выбросьте их. Очень расточительно.

x = ['a', 'b', ..., 'z'] # 26 small allocations
x = ''.join(x) # A single, large allocation
3
ответ дан 3 December 2019 в 02:10
поделиться

Ну, это сильно зависит от языка, но в целом идея заключается в том, что одна большая операция выполняется быстрее, чем множество мелких. Во втором примере соединение знает все элементы, к которым оно должно присоединиться, и поэтому может просто выделить необходимые ресурсы и вставить символы. Объединение в первом примере должно перераспределять ресурсы на каждом отдельном шаге (худший случай).

0
ответ дан 3 December 2019 в 02:10
поделиться

См. производительность соединения строк Python и один конкретный ответ, который очень хорошо описывает это:

Совет касается объединения большого количества строк.

Чтобы вычислить s = s1 + s2 + ... + sn,

1) с помощью +. Создается новая строка s1 + s2, затем создается новая строка s1 + s2 + s3, ... и т. Д., Поэтому требуется много операций выделения памяти и копирования. Фактически, s1 копируется n-1 раз, s2 копируется n-2 раза, ... и т. Д.

2) с использованием "" .join ([s1, s2, ..., sn]). Объединение выполняется за один проход, и каждый символ в строках копируется только один раз.

3
ответ дан 3 December 2019 в 02:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: