Dlaczego łączenie łańcuchów jest szybsze niż łączenie tablic?

Dzisiaj przeczytałem Ten wątek o szybkości konkatenacji strun.

O dziwo, konkatenacja strun była zwycięzcą:

Http://jsben.ch/#/OJ3vo

Wynik był przeciwny do tego, co myślałem. Poza tym, istnieje wiele artykułów na ten temat, które wyjaśniają przeciwstawnie jak to .

Domyślam się, że przeglądarki są zoptymalizowane do stringów concat w najnowszej wersji, ale jak to zrobić? Czy możemy powiedzieć, że jest lepiej używać + Podczas łączenia strun?

Update

Tak więc w nowoczesnych przeglądarkach konkatenacja łańcuchów jest zoptymalizowana, więc używanie znaków + jest szybsze niż używanie znaków join, Gdy chcesz konkatenować.

Ale @Artur wskazał że join jest szybszy, jeśli rzeczywiście chcesz połączyć ciągi znaków z separatorem.

Author: Sanghyun Lee, 2011-09-04

8 answers

Optymalizacje ciągów przeglądarek zmieniły obraz konkatenacji ciągów.

Firefox był pierwszą przeglądarką, która zoptymalizowała łączenie łańcuchów. Począwszy od wersji 1.0, technika array jest we wszystkich przypadkach wolniejsza niż użycie operatora plus. Inne przeglądarki również zoptymalizowały łączenie ciągów, więc Safari, Opera, Chrome i Internet Explorer 8 również wykazują lepszą wydajność przy użyciu operatora plus. Internet Explorer przed wersją 8 nie miał takiego optymalizacja, a więc technika array jest zawsze szybsza niż operator plus.

- Pisanie Skutecznego JavaScript: Rozdział 7 – Jeszcze Szybsze Strony Internetowe

Silnik javascript V8 (używany w Google Chrome) używa tego kodu do łączenia łańcuchów:

// 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, "");
}

Tak więc wewnętrznie optymalizują ją, tworząc InternalArray( zmienną parts), która jest następnie wypełniana. Funkcja StringBuilderConcat jest wywoływana z tymi częściami. Jest szybki, ponieważ funkcja StringBuilderConcat to mocno zoptymalizowany kod C++. Za długo tu cytować, ale szukaj w runtime.cc plik dla {[2] } aby zobaczyć kod.

 136
Author: Daan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 15:18:59

Firefox jest szybki, ponieważ używa czegoś o nazwie liny (liny: alternatywa dla ciągów ). Lina jest w zasadzie tylko DAG, gdzie każdy węzeł jest ciągiem.

Na przykład, jeśli wykonasz a = 'abc'.concat('def'), nowo utworzony obiekt będzie wyglądał tak. oczywiście nie jest to dokładnie tak, jak to wygląda w pamięci, ponieważ nadal musisz mieć pole dla typu string, długość i może inne.

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

I b = a.concat('123')

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

Więc w najprostszym przypadku VM nie musi wykonywać prawie żadnej pracy. Jedynym problemem jest to, że spowalnia to inne operacje na wynikowym łańcuchu. Również to oczywiście zmniejsza napowietrzność pamięci.

Z drugiej strony ['abc', 'def'].join('') Zwykle przydzielał pamięć, aby nowy ciąg był płaski w pamięci. (Może to powinno być zoptymalizowane)

 22
Author: evilpie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-17 04:03:09

Wiem, że to stary wątek, ale twój test jest nieprawidłowy. Robisz output += myarray[i]; podczas gdy powinno być bardziej output += "" + myarray[i];, ponieważ zapomniałeś, że musisz sklejać przedmioty razem z czymś. Kod concat powinien być podobny do:

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

W ten sposób wykonujesz dwie operacje zamiast jednej z powodu klejenia elementów razem.

Array.join() jest szybszy.

 4
Author: Arthur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 13:47:16

Benchmarki tam są trywialne. Wielokrotne łączenie tych samych trzech elementów będzie inlinowane, wyniki zostaną udowodnione deterministycznie i zapamiętane, program obsługi śmieci będzie po prostu wyrzucał obiekty tablicy (które nie będą prawie niczym w rozmiarze) i prawdopodobnie po prostu wypchnął i wyskoczył ze stosu ze względu na brak zewnętrznych odniesień i ponieważ ciągi nigdy się nie zmieniają. Byłbym bardziej pod wrażeniem, gdyby test był dużą liczbą losowo generowanych ciągów. Jak na koncercie czy dwóch struny.

Array.dołącz do FTW!

 3
Author: Jeremy Moritz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-29 19:38:08

Powiedziałbym, że z łańcuchami łatwiej jest wstępnie przydzielić większy bufor. Każdy element ma tylko 2 bajty (jeśli UNICODE), więc nawet jeśli jesteś zachowawczy, możesz wstępnie przydzielić dość duży bufor dla ciągu. Z arrays każdy element jest bardziej "złożony", ponieważ każdy element jest Object, więc konserwatywna implementacja prealokuje przestrzeń dla mniejszej liczby elementów.

Jeśli spróbujesz dodać for(j=0;j<1000;j++) Przed każdym for zobaczysz, że (pod chrome) różnica prędkości staje się mniejsza. W koniec był jeszcze 1,5 x dla konkatenacji strun, ale mniejszy niż 2,6, który był wcześniej.

I konieczność kopiowania elementów, znak Unicode jest prawdopodobnie mniejszy niż odniesienie do obiektu JS.

Należy pamiętać, że istnieje możliwość, że wiele implementacji silników JS ma optymalizację dla tablic jednego typu, które uczyniłyby wszystko, co napisałem bezużytecznym: -)

 2
Author: xanatos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 12:08:30

Ten test pokazuje karę za rzeczywiste użycie ciągu wykonanego z przypisaniem konkatenacji vs wykonanego z tablicą.metoda join. Podczas gdy ogólna prędkość przypisania jest nadal dwa razy szybsza w Chrome v31, ale nie jest już tak duża, jak przy braku użycia wynikowego ciągu.

 1
Author: srgstm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-18 08:01:27

To zależy od implementacji silnika javascript. Nawet dla różnych wersji jednego silnika można uzyskać znacznie różne wyniki. Powinieneś zrobić swój własny benchmark, aby to zweryfikować.

Powiedziałbym, że String.concat ma lepszą wydajność w ostatnich wersjach V8. Ale dla Firefoksa i opery, Array.join jest zwycięzcą.

 0
Author: Vanuan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-09 13:16:11

Domyślam się, że podczas gdy każda wersja nosi koszt wielu konkatenacji, wersje join budują dodatkowo tablice.

 -1
Author: Marcelo Cantos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 11:55:20