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

114

Сегодня я прочитал эту ветку о скорости конкатенации строк.

Удивительно, но конкатенация строк оказалась победителем:

http://jsben.ch/#/OJ3vo

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

Я могу предположить, что браузеры оптимизированы для работы с строками concatв последней версии, но как они это делают? Можно ли сказать, что лучше использовать +при конкатенации строк?


Обновить

Таким образом, в современных браузерах конкатенация строк оптимизирована, поэтому использование +знаков выполняется быстрее, чем использование, joinкогда вы хотите объединить строки.

Но @Arthur указал, что joinэто быстрее, если вы действительно хотите объединить строки с разделителем.


Обновление - 2020
Chrome: массив joinпочти 2 times fasterравен String concat + См .: https://stackoverflow.com/a/54970240/984471

В качестве примечания:

  • Массив joinлучше, если у вас естьlarge strings
  • Если нам нужно сгенерировать several small stringsв окончательном выводе, лучше использовать конкатенацию строк +, так как в противном случае для использования массива потребуется несколько преобразований массива в строку в конце, что является перегрузкой производительности.

Санхюн Ли
источник
1
Этот код должен произвести 500 терабайт мусора, но он выполняется за 200 мс. Я думаю, что они просто выделяют немного больше места для строки, и когда вы добавляете к ней короткую строку, она обычно помещается в дополнительное пространство.
Иван Кукир

Ответы:

149

Оптимизация строк в браузере изменила картину конкатенации строк.

Firefox был первым браузером, который оптимизировал конкатенацию строк. Начиная с версии 1.0, метод массива фактически во всех случаях медленнее, чем использование оператора плюс. Другие браузеры также оптимизировали конкатенацию строк, поэтому Safari, Opera, Chrome и Internet Explorer 8 также показывают лучшую производительность при использовании оператора «плюс». В Internet Explorer до версии 8 такой оптимизации не было, поэтому метод массива всегда работает быстрее, чем оператор «плюс».

- Написание эффективного JavaScript: Глава 7 - Еще более быстрые сайты

Механизм JavaScript V8 (используемый в Google Chrome) использует этот код для объединения строк:

// ECMA-262, section 15.5.4.6
function StringConcat() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined", ["String.prototype.concat"]);
  }
  var len = %_ArgumentsLength();
  var this_as_string = TO_STRING_INLINE(this);
  if (len === 1) {
    return this_as_string + %_Arguments(0);
  }
  var parts = new InternalArray(len + 1);
  parts[0] = this_as_string;
  for (var i = 0; i < len; i++) {
    var part = %_Arguments(i);
    parts[i + 1] = TO_STRING_INLINE(part);
  }
  return %StringBuilderConcat(parts, len + 1, "");
}

Итак, внутренне они оптимизируют его, создавая InternalArray ( partsпеременную), которая затем заполняется. С этими частями вызывается функция StringBuilderConcat. Это быстро, потому что функция StringBuilderConcat представляет собой сильно оптимизированный код C ++. Здесь слишком долго цитировать, но найдите код в файле runtime.ccRUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat) .

Даан
источник
4
Вы упустили действительно интересную вещь, массив используется только для вызова Runtime_StringBuilderConcat с другим количеством аргументов. Но настоящая работа там ведется.
Evilpie
41
Оптимизация 101: вы должны стремиться к наименьшей медлительности! например, arr.join vs str+на chrome вы получите (в операциях в секунду) 25k/s vs 52k/s. на firefox новый вы получаете 76k/s vs 212k/s. так str+БЫСТРЕЕ. но давайте посмотрим другие браузеры. Opera выдает 43 к / с против 26 к / с. IE дает 1300/s vs 1002/s. посмотреть, что происходит? только браузер, оптимизация НЕОБХОДИМОСТЬ будет лучше использовать то , что происходит медленнее всех остальных, где это не имеет значения. Итак, ни одна из этих статей ничего не понимает о производительности.
gcb
45
@gcb, не следует использовать только браузеры, для которых соединение выполняется быстрее. 95% моих пользователей имеют FF и Chrome. Я собираюсь оптимизировать для случая использования 95%.
Paul Draper
7
@PaulDraper, если 90% пользователей используют быстрый браузер, и любой из выбранных вами вариантов даст им 0,001 с, но 10% ваших пользователей получат 2 с, если вы решите наказать других пользователей из этих 0,001 с ... решение ясно. если вы его не видите, мне очень жаль того, для кого вы кодируете.
gcb
7
Старые браузеры в конечном итоге уйдут, но вероятность того, что кто-то вернется, чтобы преобразовать все эти объединения массивов, маловероятна. Лучше писать код для будущего, если это не будет серьезным неудобством для ваших нынешних пользователей. Скорее всего, есть более важные вещи, о которых нужно беспокоиться, чем производительность конкатенации при работе со старыми браузерами.
Томас Хиггинботэм
23

Firefox быстр, потому что он использует нечто, называемое веревками ( веревки: альтернатива строкам ). Веревка - это, по сути, просто DAG, где каждый узел представляет собой строку.

Так, например, если вы это сделаете a = 'abc'.concat('def'), вновь созданный объект будет выглядеть так. Конечно, это не совсем то, как это выглядит в памяти, потому что вам все равно нужно иметь поле для типа строки, длины и, возможно, другого.

a = {
 nodeA: 'abc',
 nodeB: 'def'
}

И b = a.concat('123')

b = {
  nodeA: a, /* {
             nodeA: 'abc',
             nodeB: 'def'
          } */
  nodeB: '123'
}           

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

С другой стороны ['abc', 'def'].join(''), обычно просто выделяет память, чтобы разместить новую строку в памяти. (Возможно, это стоит оптимизировать)

злодей
источник
6

Я знаю, что это старая ветка, но ваш тест неверен. Вы делаете, output += myarray[i];пока это должно быть больше похоже на то, output += "" + myarray[i];что вы забыли, что вам нужно чем-то склеивать предметы. Код concat должен быть примерно таким:

var output = myarray[0];
for (var i = 1, len = myarray.length; i<len; i++){
    output += "" + myarray[i];
}

Таким образом, вы выполняете две операции вместо одной из-за склеивания элементов вместе.

Array.join() быстрее.

Артур
источник
Я не понимаю твоего ответа. В чем разница между нанесением "" +и оригиналом?
Sanghyun Lee
Это две операции вместо одной на каждой итерации, что занимает больше времени.
Arthur
1
И зачем нам это ставить? Уже клеим предметы и outputбез него.
Sanghyun Lee
Потому что так работает join. Например, вы также можете сделать то, Array.join(",")что не будет работать с вашим forциклом
Артур
О, я понял. Вы уже тестировали, работает ли join () быстрее?
Sanghyun Lee
5

При большом объеме данных соединение происходит быстрее, поэтому вопрос сформулирован неверно.

let result = "";
let startTime = new Date().getTime();
for (let i = 0; i < 2000000; i++) {
    result += "x";
}
console.log("concatenation time: " + (new Date().getTime() - startTime));

startTime = new Date().getTime();
let array = new Array(2000000);
for (let i = 0; i < 2000000; i++) {
    array[i] = "x";
}
result = array.join("");
console.log("join time: " + (new Date().getTime() - startTime));

Проверено в Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. Обратите внимание, что это быстрее даже с включенным созданием массива!

почти
источник
~ Август 2020. Верно. В Chrome: Array Join time: 462. String Concat (+) time: 827. Join почти в 2 раза быстрее.
Manohar Reddy Poreddy,
3

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

Array.join FTW!

Джереми Мориц
источник
2

Я бы сказал, что со строками легче предварительно выделить больший буфер. Каждый элемент имеет размер всего 2 байта (если используется UNICODE), поэтому, даже если вы консервативны, вы можете предварительно выделить довольно большой буфер для строки. С arraysкаждым элементом все более «сложно», потому что каждый элемент является одним Object, поэтому консервативная реализация будет предварительно выделять пространство для меньшего количества элементов.

Если вы попытаетесь добавить for(j=0;j<1000;j++)перед каждым, forвы увидите, что (под хромом) разница в скорости становится меньше. В конце концов, конкатенация строк все еще была в 1,5 раза, но меньше, чем было до 2,6.

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

Имейте в виду, что существует вероятность того, что многие реализации JS-движков имеют оптимизацию для однотипных массивов, что сделает все, что я написал, бесполезным :-)

Ксанатос
источник
1

Этот тест показывает, что фактическое использование строки, созданной с помощью конкатенации присваиваний, по сравнению с методом array.join, не оправдано. Хотя общая скорость присваивания по-прежнему в два раза выше в Chrome v31, но она уже не так велика, как без использования результирующей строки.

srgstm
источник
0

Это явно зависит от реализации движка javascript. Даже для разных версий одного движка можно получить существенно разные результаты. Вы должны провести собственный тест, чтобы убедиться в этом.

Я бы сказал, что он String.concatимеет лучшую производительность в последних версиях V8. Но для Firefox и Opera Array.joinэто победитель.

Вануан
источник
-1

Я предполагаю, что, хотя каждая версия требует многих конкатенаций, версии соединения создают массивы в дополнение к этому.

Марсело Кантос
источник