web-dev-qa-db-fra.com

Quelles sont les performances des objets / tableaux en JavaScript? (spécifiquement pour Google V8)

Les performances associées aux tableaux et aux objets en JavaScript (en particulier Google V8) seraient très intéressantes à documenter. Je ne trouve aucun article complet sur ce sujet sur Internet.

Je comprends que certains objets utilisent des classes comme structure de données sous-jacente. S'il y a beaucoup de propriétés, elle est parfois traitée comme une table de hachage?

Je comprends également que les tableaux sont parfois traités comme des tableaux C++ (c'est-à-dire indexation aléatoire rapide, suppression lente et redimensionnement). Et, à d'autres moments, ils sont traités plus comme des objets (indexation rapide, insertion/retrait rapide, plus de mémoire). Et, parfois, ils sont stockés sous forme de listes chaînées (c'est-à-dire indexation aléatoire lente, suppression/insertion rapide au début/à la fin)

Quelles sont les performances précises des extractions et manipulations de tableaux/objets en JavaScript? (spécifiquement pour Google V8)

Plus précisément, quel est l'impact sur les performances de:

  • Ajout d'une propriété à un objet
  • Suppression d'une propriété d'un objet
  • Indexation d'une propriété dans un objet
  • Ajout d'un élément à un tableau
  • Suppression d'un élément d'un tableau
  • Indexation d'un élément dans un tableau
  • Appel de Array.pop ()
  • Appel de Array.Push ()
  • Appel de Array.shift ()
  • Appel de Array.unshift ()
  • Appel de Array.slice ()

Tout article ou lien pour plus de détails serait également apprécié. :)

EDIT: Je me demande vraiment comment les tableaux et objets JavaScript fonctionnent sous le capot. De plus, dans quel contexte le moteur V8 "sait" -il "basculer" vers une autre structure de données?

Par exemple, supposons que je crée un tableau avec ...

var arr = [];
arr[10000000] = 20;
arr.Push(21);

Que se passe-t-il vraiment ici?

Ou ... et ça ... ???

var arr = [];
//Add lots of items
for(var i = 0; i < 1000000; i++)
    arr[i] = Math.random();
//Now I use it like a queue...
for(var i = 0; i < arr.length; i++)
{
    var item = arr[i].shift();
    //Do something with item...
}

Pour les baies conventionnelles, les performances seraient terribles; alors que si une LinkedList était utilisée ... pas si mal.

103
BMiner

MISE À JOUR: Notez que JSPref est actuellement en panne

(j'ai enregistré une copie du cas de test, et je mettrai à jour la réponse une fois que JSPref sera corrigé/qu'un successeur sera trouvé)


Hmm ... peut-être une surpuissance pour la réponse ... mais j'ai créé ne suite de tests, précisément pour explorer ces problèmes (et plus) ( copie archivée ).

Et dans ce sens, vous pouvez voir les problèmes de performances dans ce testeur de cas de test 50+ (cela prendra beaucoup de temps).

De plus, comme son nom l'indique, il explore l'utilisation de la nature de liste chaînée native de la structure DOM.

(Actuellement en panne, reconstruit en cours) Plus de détails sur mon blog à ce sujet .

Le résumé est le suivant

  • La baie V8 est rapide, TRÈS RAPIDE
  • Array Push/pop/shift est ~ environ 20x + plus rapide que tout équivalent d'objet.
  • Étonnamment, Array.shift() est rapide ~ environ 6 fois plus lentement qu'un pop de tableau, mais est ~ environ 100 fois plus rapide qu'une suppression d'attribut d'objet.
  • De manière amusante, Array.Push( data ); est plus rapide que Array[nextIndex] = data De près de 20 (tableau dynamique) à 10 (tableau fixe) fois.
  • Array.unshift(data) est plus lent que prévu et ~ environ 5 fois plus lent qu'une nouvelle propriété ajoutée.
  • L'annulation de la valeur array[index] = null Est plus rapide que sa suppression delete array[index] (Non défini) dans un tableau par ~ environ 4x ++ plus rapide.
  • Étonnamment, la suppression d'une valeur dans un objet est obj[attr] = null ~ Environ 2x plus lente que la simple suppression de l'attribut delete obj[attr]
  • Sans surprise, le milieu du tableau Array.splice(index,0,data) est lent, très lent.
  • Étonnamment, Array.splice(index,1,data) a été optimisée (pas de changement de longueur) et est 100 fois plus rapide qu'une simple épissure Array.splice(index,0,data)
  • sans surprise, la divLinkedList est inférieure à un tableau sur tous les secteurs, à l'exception de la suppression de dll.splice(index,1) (où elle a cassé le système de test).
  • PLUS GRANDE SURPRISE de tout cela [comme l'a souligné jjrv], les écritures du tableau V8 sont légèrement plus rapides que les lectures V8 = O

Remarque: Ces mesures s'appliquent uniquement aux grands tableaux/objets que la v8 n'optimise pas entièrement. Il peut y avoir des cas de performances optimisés très isolés pour une taille de tableau/objet inférieure à une taille arbitraire (24?). Plus de détails peuvent être consultés de manière approfondie sur plusieurs vidéos google IO.

Remarque 2: Ces merveilleux résultats de performances ne sont pas partagés entre les navigateurs, en particulier *cough* IE. De plus, le test est énorme, donc je n'ai pas encore analysé et évalué complètement les résultats: veuillez le modifier dans =)

Note mise à jour (décembre 2012): Les représentants de Google ont des vidéos sur youtubes décrivant le fonctionnement interne de chrome lui-même (comme lorsque il passe d'un tableau de liste liée à un tableau fixe, etc.) et comment les optimiser. Voir GDC 2012: de la console à Chrome pour en savoir plus.

Note mise à jour (février 2013): Thx @badunk, pour fournir le lien vidéo au point exact

Note mise à jour (juin 2016): Thx @Benedikt, concernant la différence de performance Push de tableau dans les tableaux fixes/dynamiques.

274
PicoCreator

À un niveau de base qui reste dans les domaines de JavaScript, les propriétés des objets sont des entités beaucoup plus complexes. Vous pouvez créer des propriétés avec des setters/getters, avec une énumération, une écriture et une configurabilité différentes. Un élément d'un tableau ne peut pas être personnalisé de cette manière: il existe ou il n'existe pas. Au niveau du moteur sous-jacent, cela permet une optimisation beaucoup plus en termes d'organisation de la mémoire qui représente la structure.

En termes d'identification d'un tableau à partir d'un objet (dictionnaire), les moteurs JS ont toujours fait des lignes explicites entre les deux. C'est pourquoi il existe une multitude d'articles sur les méthodes pour essayer de créer un objet semi-faux de type tableau qui se comporte comme un mais autorise d'autres fonctionnalités. La raison pour laquelle cette séparation existe même est que les moteurs JS eux-mêmes stockent les deux différemment.

Les propriétés peuvent être stockées sur un objet tableau, mais cela montre simplement comment JavaScript insiste pour faire de tout un objet. Les valeurs indexées dans un tableau sont stockées différemment des propriétés que vous décidez de définir sur l'objet tableau qui représente les données du tableau sous-jacent.

Chaque fois que vous utilisez un objet tableau légitime et que vous utilisez l'une des méthodes standard de manipulation de ce tableau, vous allez toucher les données du tableau sous-jacent. Dans V8 en particulier, ce sont essentiellement les mêmes qu'un tableau C++, donc ces règles s'appliqueront. Si, pour une raison quelconque, vous travaillez avec un tableau que le moteur n'est pas en mesure de déterminer en toute confiance, c'est un tableau, vous êtes sur un terrain beaucoup plus fragile. Avec les versions récentes de V8, il y a cependant plus de place pour travailler. Par exemple, il est possible de créer une classe qui a Array.prototype comme prototype et d'avoir toujours un accès efficace aux différentes méthodes de manipulation de tableaux natifs. Mais c'est un changement récent.

Des liens spécifiques vers des modifications récentes de la manipulation de tableaux peuvent être utiles ici:

En plus, voici Array Pop et Array Push directement à partir de la source de V8, tous deux implémentés dans JS lui-même:

function ArrayPop() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.pop"]);
  }

  var n = TO_UINT32(this.length);
  if (n == 0) {
    this.length = n;
    return;
  }
  n--;
  var value = this[n];
  this.length = n;
  delete this[n];
  return value;
}


function ArrayPush() {
  if (IS_NULL_OR_UNDEFINED(this) && !IS_UNDETECTABLE(this)) {
    throw MakeTypeError("called_on_null_or_undefined",
                        ["Array.prototype.Push"]);
  }

  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();
  for (var i = 0; i < m; i++) {
    this[i+n] = %_Arguments(i);
  }
  this.length = n + m;
  return this.length;
}
5
user748221

Lors de l'exécution sous node.js 0.10 (construit sur la v8), je voyais une utilisation du processeur qui semblait excessive pour la charge de travail. J'ai tracé un problème de performances à une fonction qui vérifiait l'existence d'une chaîne dans un tableau. J'ai donc fait quelques tests.

  • chargé 90822 hôtes
  • le chargement de la configuration a pris 0,087 seconde (tableau)
  • le chargement de la configuration a pris 0,152 seconde (objet)

Le chargement de 91k entrées dans un tableau (avec validate & Push) est plus rapide que de définir obj [clé] = valeur.

Dans le test suivant, j'ai recherché une fois chaque nom d'hôte dans la liste (91k itérations, pour faire la moyenne du temps de recherche):

  • la configuration de la recherche a pris 87,56 secondes (tableau)
  • la recherche de la configuration a pris 0,21 seconde (objet)

L'application est ici Haraka (un serveur SMTP) et il charge la liste Host_list une fois au démarrage (et après les modifications) et effectue ensuite cette recherche des millions de fois pendant le fonctionnement. Passer à un objet a été une énorme victoire de performance.

0
Matt Simerson

Je voudrais compléter les réponses existantes par une enquête sur la question de savoir comment les implémentations se comportent concernant les baies en croissance: si elles les implémentent de la manière "habituelle", on verrait de nombreuses poussées rapides avec des poussées lentes rares et entrecoupées à quel point les copies d'implémentation la représentation interne du tableau d'un tampon à un plus grand.

Vous pouvez très bien voir cet effet, cela vient de Chrome:

16: 4ms
40: 8ms 2.5
76: 20ms 1.9
130: 31ms 1.7105263157894737
211: 14ms 1.623076923076923
332: 55ms 1.5734597156398105
514: 44ms 1.5481927710843373
787: 61ms 1.5311284046692606
1196: 138ms 1.5196950444726811
1810: 139ms 1.5133779264214047
2731: 299ms 1.5088397790055248
4112: 341ms 1.5056755767118273
6184: 681ms 1.5038910505836576
9292: 1324ms 1.5025873221216042

Même si chaque Push est profilé, la sortie contient uniquement celles qui prennent du temps au-dessus d'un certain seuil. Pour chaque test, j'ai personnalisé le seuil pour exclure toutes les poussées qui semblent représenter les poussées rapides.

Ainsi, le premier nombre représente quel élément a été inséré (la première ligne est pour le 17e élément), le second est le temps qu'il a fallu (pour de nombreux tableaux, le benchmark se fait en parallèle), et la dernière valeur est la division du premier numéro par celui de l'ancienne ligne.

Toutes les lignes dont le temps d'exécution est inférieur à 2 ms sont exclues pour Chrome.

Vous pouvez voir que Chrome augmente la taille du tableau en puissances de 1,5, plus un certain décalage pour tenir compte des petits tableaux.

Pour Firefox, c'est une puissance de deux:

126: 284ms
254: 65ms 2.015873015873016
510: 28ms 2.0078740157480315
1022: 58ms 2.003921568627451
2046: 89ms 2.0019569471624266
4094: 191ms 2.0009775171065494
8190: 364ms 2.0004885197850513

J'ai dû augmenter un peu le seuil dans Firefox, c'est pourquoi nous commençons à # 126.

Avec IE, nous obtenons un mélange:

256: 11ms 256
512: 26ms 2
1024: 77ms 2
1708: 113ms 1.66796875
2848: 154ms 1.6674473067915691
4748: 423ms 1.6671348314606742
7916: 944ms 1.6672283066554338

C'est un pouvoir de deux au début, puis il passe à des pouvoirs de cinq tiers.

Ainsi, toutes les implémentations courantes utilisent la manière "normale" pour les tableaux (au lieu de devenir fous avec cordes , par exemple).

Voici le code de référence et voici le violon il est dedans.

var arrayCount = 10000;

var dynamicArrays = [];

for(var j=0;j<arrayCount;j++)
    dynamicArrays[j] = [];

var lastLongI = 1;

for(var i=0;i<10000;i++)
{
    var before = Date.now();
    for(var j=0;j<arrayCount;j++)
        dynamicArrays[j][i] = i;
    var span = Date.now() - before;
    if (span > 10)
    {
      console.log(i + ": " + span + "ms" + " " + (i / lastLongI));
      lastLongI = i;
    }
}
0
John