web-dev-qa-db-fra.com

Pourquoi la concaténation de chaînes est-elle plus rapide que la jointure de tableaux?

Aujourd'hui, j'ai lu ce fil à propos de la vitesse de concaténation de chaînes.

Étonnamment, la concaténation des cordes a été gagnante:

http://jsben.ch/#/OJ3vo

Le résultat était opposé à ce que je pensais. En outre, il existe de nombreux articles à ce sujet qui expliquent de manière opposée comme this .

Je peux deviner que les navigateurs sont optimisés pour chainer concat sur la dernière version, mais comment font-ils cela? Peut-on dire qu'il vaut mieux utiliser + lors de la concaténation de chaînes?

Mettre à jour

Ainsi, dans les navigateurs modernes, la concaténation de chaînes est optimisée, ainsi + signes est plus rapide que d'utiliser join lorsque vous voulez concaténer chaînes.

Mais @ Arthur a souligné que join est plus rapide si vous voulez réellement rejoindre chaînes avec un séparateur.

107
Sanghyun Lee

Les optimisations de chaîne du navigateur ont modifié l’image de concaténation de chaîne.

Firefox a été le premier navigateur à optimiser la concaténation de chaînes. À partir de la version 1.0, la technique de tableau est en réalité plus lente que l'utilisation de l'opérateur plus dans tous les cas. D'autres navigateurs ont également optimisé la concaténation de chaînes. Ainsi, Safari, Opera, Chrome et Internet Explorer 8 affichent également de meilleures performances en utilisant l'opérateur plus. Avant la version 8, Internet Explorer n’avait pas cette optimisation et la technique de la matrice est donc toujours plus rapide que l’opérateur Plus.

- Écriture de JavaScript efficace: Chapitre 7 - Sites Web encore plus rapides

Le moteur JavaScript V8 (utilisé dans Google Chrome) utilise ce code pour effectuer la concaténation de chaînes:

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

Donc, ils l’optimisent en interne en créant un InternalArray (la variable parts), qui est ensuite rempli. La fonction StringBuilderConcat est appelée avec ces parties. C'est rapide car la fonction StringBuilderConcat est un code C++ fortement optimisé. Il est trop long de citer ici, mais recherchez dans le fichier runtime.cc pour RUNTIME_FUNCTION(MaybeObject*, Runtime_StringBuilderConcat) pour voir le code.

143
Daan

Firefox est rapide car il utilise quelque chose appelé Ropes ( Ropes: une alternative aux chaînes ). Une corde est fondamentalement juste un DAG, où chaque Node est une chaîne).

Ainsi, par exemple, si vous faites a = 'abc'.concat('def'), l'objet nouvellement créé ressemblera à ceci. Bien sûr, ce n’est pas exactement ce à quoi cela ressemble en mémoire, car vous devez toujours avoir un champ pour le type de chaîne, la longueur et peut-être autre.

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

Et b = a.concat('123')

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

Ainsi, dans le cas le plus simple, VM ne doit effectuer pratiquement aucun travail. Le seul problème est que cela ralentit un peu les autres opérations sur la chaîne résultante. Cela réduit bien entendu également la surcharge de mémoire.

Par contre, ['abc', 'def'].join('') allouait généralement de la mémoire pour mettre à plat la nouvelle chaîne en mémoire. (Peut-être que cela devrait être optimisé)

22
evilpie

Je sais que c'est un vieux fil, mais votre test est incorrect. Vous faites output += myarray[i]; Alors que cela devrait ressembler davantage à output += "" + myarray[i]; Car vous avez oublié de coller des éléments avec quelque chose. Le code concat devrait être quelque chose comme:

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

De cette façon, vous effectuez deux opérations au lieu d’une en raison de l’assemblage d’éléments.

Array.join() est plus rapide.

5
Arthur

Les repères y sont triviaux. La concaténation des trois mêmes éléments sera répétitivement alignée, les résultats s'avéreront déterministes et mémorisés, le gestionnaire de déchets jettera simplement des objets de tableau (dont la taille sera presque nulle) et probablement simplement poussé et sorti de la pile car aucun références externes et parce que les chaînes ne changent jamais. Je serais plus impressionné si le test consistait en un grand nombre de chaînes générées aléatoirement. Comme dans un ou deux concerts de cordes.

Array.join FTW!

3
Jeremy Moritz

Je dirais qu'avec des chaînes, il est plus facile de préallouer un tampon plus grand. Chaque élément ne contient que 2 octets (si UNICODE), donc même si vous êtes conservateur, vous pouvez préallouer un assez gros tampon pour la chaîne. Avec arrays, chaque élément est plus "complexe", car chaque élément est un Object, de sorte qu'une implémentation conservatrice va préallouer de l'espace pour moins d'éléments.

Si vous essayez d'ajouter une for(j=0;j<1000;j++) avant chaque for, vous verrez que (sous chrome) la différence de vitesse devient plus petite. À la fin, il restait 1,5 fois la concaténation de chaînes, mais plus petit que la version 2.6 précédente.

ET ayant à copier les éléments, un caractère Unicode est probablement plus petit qu'une référence à un objet JS.

Sachez qu’il est possible que de nombreuses implémentations de moteurs JS offrent une optimisation pour des tableaux de type unique qui rendrait tout ce que j’aurais écrit inutile :-)

2
xanatos

Pour une grande quantité de données, la jointure est plus rapide, donc la question est posée de manière incorrecte.

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);

Testé sur Chrome 72.0.3626.119, Firefox 65.0.1, Edge 42.17134.1.0. Notez qu’il est plus rapide même avec la création de tableau inclus!

1
almostA

This test montre la pénalité d'utiliser réellement une chaîne faite avec une concaténation d'affectation contre une méthode array.join. Bien que la vitesse globale d’assignation soit toujours deux fois plus rapide en Chrome v31, elle n’est plus aussi énorme que lorsque vous n’utilisez pas la chaîne résultante.

1
srgstm

Cela dépend clairement de la mise en œuvre du moteur javascript. Même pour différentes versions d'un moteur, vous pouvez obtenir des résultats très différents. Vous devriez faire votre propre point de repère pour vérifier cela.

Je dirais que String.concat a de meilleures performances dans les versions récentes de V8. Mais pour Firefox et Opera, Array.join est un gagnant.

0
Vanuan