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

114

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

Étonnamment, la concaténation de chaînes a été le gagnant:

http://jsben.ch/#/OJ3vo

Le résultat était à l'opposé de ce que je pensais. Par ailleurs, il y a beaucoup d' articles sur ce qui expliquent de manière opposée comme cela .

Je peux deviner que les navigateurs sont optimisés pour la chaîne concatsur la dernière version, mais comment font-ils cela? Peut-on dire qu'il est préférable d'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, donc l'utilisation de +signes est plus rapide que l'utilisation joinlorsque vous souhaitez concaténer des chaînes.

Mais @Arthur a souligné que joinc'est plus rapide si vous voulez réellement joindre des chaînes avec un séparateur.


Mise à jour - 2020
Chrome: Array joinest presque 2 times fasterString concat + Voir: https://stackoverflow.com/a/54970240/984471

À noter:

  • Array joinest meilleur si vous avezlarge strings
  • Si nous avons besoin de générer several small stringsdans la sortie finale, il est préférable d'utiliser string concat +, car sinon, utiliser Array nécessitera plusieurs conversions de tableau en chaîne à la fin, ce qui est une surcharge de performances.

Sanghyun Lee
la source
1
Ce code est censé produire 500 téraoctets de déchets, mais il s'exécute en 200 ms. Je pense qu'ils allouent juste un peu plus d'espace pour une chaîne, et lorsque vous y ajoutez une courte chaîne, elle tient généralement dans un espace supplémentaire.
Ivan Kuckir

Réponses:

149

Les optimisations de chaînes du navigateur ont modifié l'image de concaténation de chaînes.

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 fait 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, de sorte que Safari, Opera, Chrome et Internet Explorer 8 affichent également de meilleures performances en utilisant l'opérateur plus. Internet Explorer avant la version 8 n'avait pas une telle optimisation, et donc la technique de tableau est 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, "");
}

Ainsi, en interne, ils l'optimisent en créant un InternalArray (la partsvariable), qui est ensuite rempli. La fonction StringBuilderConcat est appelée avec ces pièces. 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)voir le code.

Daan
la source
4
Vous avez laissé de côté la chose vraiment intéressante, le tableau n'est utilisé que pour appeler Runtime_StringBuilderConcat avec différents nombres d'arguments. Mais le vrai travail se fait là-bas.
evilpie
41
Optimisation 101: vous devez viser le moins lent! par exemple, arr.join vs str+sur chrome, vous obtenez (en opérations par seconde) 25k/s vs 52k/s. sur Firefox nouveau que vous obtenez 76k/s vs 212k/s. str+est donc PLUS RAPIDE. mais regardons d'autres navigateurs. Opera donne 43k / s contre 26k / s. IE donne 1300/s vs 1002/s. voyez ce qui se passe? le seul navigateur nécessitant une optimisation serait mieux d'utiliser ce qui est plus lent sur tous les autres, là où cela n'a pas d'importance. Ainsi, aucun de ces articles ne comprend quoi que ce soit sur les performances.
gcb
45
@gcb, les seuls navigateurs pour lesquels la jointure est plus rapide ne doivent pas être utilisés. 95% de mes utilisateurs ont FF et Chrome. Je vais optimiser pour le cas d'utilisation à 95%.
Paul Draper le
7
@PaulDraper si 90% des utilisateurs utilisent un navigateur rapide et que l'une ou l'autre des options que vous choisissez leur rapportera 0,001 s, mais 10% de vos utilisateurs gagneront 2 s si vous choisissez de pénaliser les autres utilisateurs sur ces 0,001 s ... la décision est clair. si vous ne pouvez pas le voir, je suis désolé pour celui pour qui vous codez.
gcb
7
Les navigateurs plus anciens finiront par disparaître, mais il est peu probable que quelqu'un revienne convertir toutes ces jointures de tableau. Il vaut mieux coder pour l'avenir tant que ce n'est pas un inconvénient majeur pour vos utilisateurs actuels. Il y a de fortes chances qu'il y ait des choses plus importantes à craindre que les performances de concaténation lorsqu'il s'agit d'anciens navigateurs.
Thomas Higginbotham
23

Firefox est rapide car il utilise quelque chose appelé Ropes ( Ropes: an Alternative to Strings ). Une corde n'est en fait qu'un DAG, où chaque nœud est une chaîne.

Ainsi, par exemple, si vous le faites a = 'abc'.concat('def'), l'objet nouvellement créé ressemblerait à ceci. Bien sûr, ce n'est pas exactement à 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, la machine virtuelle ne doit pratiquement pas faire de travail. Le seul problème est que cela ralentit un peu les autres opérations sur la chaîne résultante. De plus, cela réduit naturellement la surcharge de la mémoire.

D'un autre côté, ['abc', 'def'].join('')il suffit généralement d'allouer de la mémoire pour disposer la nouvelle chaîne à plat en mémoire. (Peut-être que cela devrait être optimisé)

maléfique
la source
6

Je sais que c'est un vieux fil, mais votre test est incorrect. Vous faites output += myarray[i];alors que cela devrait être plus output += "" + myarray[i];parce que vous avez oublié, que vous devez 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 du collage d'éléments ensemble.

Array.join() est plus rapide.

Arthur
la source
Je ne comprends pas votre réponse. Quelle est la différence entre le putting "" +et l'original?
Sanghyun Lee
Il s'agit de deux opérations au lieu d'une à chaque itération, ce qui prend plus de temps.
Arthur
1
Et pourquoi devons-nous mettre cela? Nous collons déjà des articles outputsans cela.
Sanghyun Lee
Parce que c'est ainsi que fonctionne la jointure. Par exemple, vous pouvez également faire Array.join(",")ce qui ne fonctionnera pas avec votre forboucle
Arthur
Oh je l'ai. Avez-vous déjà testé pour voir si join () est plus rapide?
Sanghyun Lee
5

Pour une grande quantité de données, la jointure est plus rapide, la question est donc mal posée.

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 que c'est plus rapide même avec la création de tableau incluse!

presqueA
la source
~ Août 2020. Vrai. Dans Chrome: Array Join time: 462. String Concat (+) time: 827. Join est presque 2 fois plus rapide.
Manohar Reddy Poreddy
3

Les repères là-bas sont triviaux. La concaténation répétée des trois mêmes éléments sera intégrée, les résultats se révéleront déterministes et mémorisés, le gestionnaire de déchets rejettera simplement des objets de tableau (qui n'auront presque rien en taille) et probablement juste poussé et sauté de la pile en raison de non références externes et parce que les chaînes ne changent jamais. Je serais plus impressionné si le test était un grand nombre de chaînes générées aléatoirement. Comme dans un concert ou deux de cordes.

Array.join FTW!

Jeremy Moritz
la source
2

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

Si vous essayez d'ajouter un for(j=0;j<1000;j++)avant chacun, forvous verrez que (sous chrome) la différence de vitesse devient plus petite. En fin de compte, il était toujours 1,5x pour la concaténation de chaînes, mais plus petit que le 2,6 qui était avant.

ET avoir à 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 aient une optimisation pour les tableaux de type unique qui rendrait tout ce que j'ai écrit inutile :-)

xanatos
la source
1

Ce test montre la peine d'utiliser réellement une chaîne créée avec la concaténation d'affectation par rapport à la méthode array.join. Bien que la vitesse globale d'attribution soit toujours deux fois plus rapide dans Chrome v31, elle n'est plus aussi énorme que lorsque vous n'utilisez pas la chaîne résultante.

srgstm
la source
0

Cela dépend clairement de l'implémentation du moteur javascript. Même pour différentes versions d'un même moteur, vous pouvez obtenir des résultats significativement différents. Vous devez faire votre propre benchmark pour vérifier cela.

Je dirais que cela String.concata de meilleures performances dans les versions récentes du V8. Mais pour Firefox et Opera, Array.joinc'est un gagnant.

Vanuan
la source
-1

Je suppose que, bien que chaque version supporte le coût de nombreuses concaténations, les versions de jointure créent des tableaux en plus de cela.

Marcelo Cantos
la source