Чому конкатенація рядків швидша за приєднання масиву?


114

Сьогодні я прочитав цю тему про швидкість конкатенації рядків.

Дивно, але переможець став струнним конкатенацією:

http://jsben.ch/#/OJ3vo

Результат був протилежним тому, що я думав. Крім того, є багато статей з цього приводу , які пояснюють , як противно це .

Я можу здогадатися, що браузери оптимізовані для наведення рядків concatна останню версію, але як це зробити? Чи можна сказати, що краще використовувати +при об'єднанні рядків?


Оновлення

Так, у сучасних браузерах конкатенація рядків оптимізована, тому використання +знаків швидше, ніж використання, joinколи потрібно об'єднати рядки.

Але @Arthur зазначив, що joinце швидше, якщо ви насправді хочете з'єднати рядки з роздільником.


Оновлення -
Chrome Chrome 2020 : Масив joinмайже 2 times fasterє рядковим. + См .: https://stackoverflow.com/a/54970240/984471

Як зауваження:

  • Масив joinкраще, якщо єlarge strings
  • Якщо нам потрібно генерувати several small stringsв кінцевому висновку, то краще перейти з рядком concat +, оскільки в іншому випадку для Array знадобиться кілька перетворень Array to String в кінці, що є перевантаженням продуктивності.


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.cc,RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat) щоб побачити код.


4
Ви залишили справді цікаву річ, масив використовується лише для виклику Runtime_StringBuilderConcat з різними підрахунками аргументів. Але справжня робота там робиться.
злий

41
Оптимізація 101: Ви повинні прагнути якнайменше повільно! наприклад, arr.join vs str+на chrome ви отримуєте (в операціях на секунду) 25k/s vs 52k/s. на Firefox new ви отримаєте 76k/s vs 212k/s. так str+швидше. але давайте шукати інші веб-переглядачі. Opera дає 43 к / с проти 26 к / с. IE дає 1300/s vs 1002/s. бачите, що відбувається? тільки браузер, оптимізація НЕОБХІДНІСТЬ буде краще використовувати те , що відбувається повільніше всіх інших, де це не має значення. Отже, жодна з цих статей нічого не розуміє про продуктивність.
gcb

45
@gcb, єдині веб-переглядачі, для яких об’єднання швидше, не слід використовувати. 95% моїх користувачів мають FF та Chrome. Я збираюся оптимізувати для 95% випадків використання.
Пол Дрейпер

7
@PaulDraper, якщо 90% користувачів переглядають швидкий веб-переглядач, і будь-який варіант, який ви обрали, отримає їх 0,001, але 10% ваших користувачів отримають 2 секунди, якщо ви вирішите штрафувати інших користувачів із того 0,001s ... рішення зрозуміло. якщо ви не можете його побачити, мені шкода того, кого ви кодуєте.
gcb

7
Старі веб-переглядачі згодом відійдуть, але шанси на те, що хтось повернеться назад, щоб перетворити всі ці масиви, приєднується. Краще кодувати майбутнє до тих пір, поки це не складе особливих незручностей вашим поточним користувачам. Шанси, що тут важливіше турбуватися, ніж ефективність конкатенації при роботі зі старими браузерами.
Томас Хіггінботам

23

Firefox швидкий, тому що він використовує щось, що називається канати ( Ropes: альтернатива струнним ). Мотузка - це лише 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

Це дві операції замість однієї на кожній ітерації, яка потребує більше часу.
Артур

1
І навіщо нам це ставити? Ми вже склеюємо предмети outputбез нього.
Sanghyun Lee

Тому що так працює приєднання. Наприклад, ви також можете зробити те, Array.join(",")що не буде працювати зі своєю forпетлею
Артур

О, я це зрозумів. Ви вже перевірили, чи приєднується () (швидше)?
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 Час приєднання: 462. Строка Concat (+) час: 827. Приєднання майже в 2 рази швидше.
Манохар Редді Поредді

3

Орієнтири там тривіальні. Об’єднання трьох і тих же предметів повторно буде накреслено, результати виявляться детермінованими та запам'ятовуються, обробник сміття буде просто викидати об’єкти масиву (який буде майже нічого за розміром) і, ймовірно, просто штовхається та вискакує зі стека через відсутність зовнішні посилання і тому, що рядки ніколи не змінюються. Мене б більше вразило, якби тест був великою кількістю випадково згенерованих рядків. Як у концерті чи двох струнних струн.

Array.join FTW!


2

Я б сказав, що за допомогою рядків простіше виділити більший буфер. Кожен елемент має лише 2 байти (якщо UNICODE), тому навіть якщо ви консервативні, ви можете виділити досить великий буфер для рядка. З arraysкожним елементом є більш "складним", оскільки кожен елемент є Object, тому консервативна реалізація попередньо виділить простір для менше елементів.

Якщо ви спробуєте додати a for(j=0;j<1000;j++)перед кожним, forви побачите, що (під хромом) різниця у швидкості стає меншою. Зрештою, це було ще 1,5x для конкатенації рядків, але менше, ніж 2.6, що було раніше.

І що потребує копіювання елементів, символ Unicode, ймовірно, менший, ніж посилання на JS-об’єкт.

Пам’ятайте, що існує можливість, що багато реалізацій двигунів JS мають оптимізацію для масивів одного типу, що зробить все, що я написав, марним :-)


1

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


0

Це явно залежить від реалізації механізму javascript. Навіть для різних версій одного двигуна можна отримати суттєво різні результати. Ви повинні зробити свій власний орієнтир, щоб підтвердити це.

Я б сказав, що String.concatв останніх версіях V8 кращі показники. Але для Firefox та Opera Array.joinє переможцем.


-1

Я здогадуюсь, що, хоча кожна версія коштує багато конкатенацій, версії приєднання додатково створюють масиви.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.