web-dev-qa-db-fra.com

Javascript Set vs Array performance

C’est peut-être parce que les ensembles sont relativement nouveaux en Javascript, mais je n’ai pas pu trouver d’article, sur StackO ou ailleurs, qui parle de la différence de performances entre les deux en Javascript. Alors, quelle est la différence, en termes de performance, entre les deux? Plus précisément, quand il s’agit de supprimer, d’ajouter et d’itérer.

61
snowfrogdev

Ok, j'ai testé l'ajout, l'itération et la suppression d'éléments d'un tableau et d'un ensemble. J'ai effectué un "petit" test utilisant 10 000 éléments et un "grand" test utilisant 100 000 éléments. Voici les résultats.

Ajout d'éléments à une collection

Il semblerait que la méthode du tableau .Push Soit environ 4 fois plus rapide que la méthode set .add, Quel que soit le nombre d'éléments ajoutés.

Itération et modification d'éléments dans une collection

Pour cette partie du test, j'ai utilisé une boucle for pour parcourir le tableau et une boucle for of Pour parcourir l'ensemble. Encore une fois, itérer sur le tableau était plus rapide. Cette fois, il semblerait que ce soit exponentiel, car cela a pris deux fois plus de temps lors des "petits" tests et presque quatre fois plus longtemps lors des "grands" tests.

Supprimer des éléments d'une collection

Maintenant, c'est là que ça devient intéressant. J'ai utilisé une combinaison d'une boucle for et de .splice Pour supprimer certains éléments du tableau et j'ai utilisé for of Et .delete Pour supprimer certains éléments de l'ensemble . Pour les "petits" tests, il était environ trois fois plus rapide de supprimer des éléments de l'ensemble (2,6 ms contre 7,1 ms), mais les choses ont radicalement changé pour le "grand" test, il a fallu 1955,1 ms pour supprimer des éléments du tableau alors qu'il ne Il a fallu 83,6 ms pour les supprimer de l’ensemble, 23 fois plus vite.

Conclusions

A 10k éléments, les deux tests ont exécuté des temps comparables (tableau: 16.6 ms, ensemble: 20.7 ms) mais s’agissant de 100k éléments, l’ensemble était le vainqueur (tableau: 1974.8 ms, ensemble: 83.6 ms) mais uniquement en raison de la suppression. opération. Sinon, le tableau était plus rapide. Je ne saurais dire exactement pourquoi.

J'ai joué avec des scénarios hybrides dans lesquels un tableau était créé et rempli, puis converti en un ensemble dans lequel certains éléments seraient supprimés, l'ensemble serait ensuite reconverti en un tableau. Bien que cela produise de bien meilleures performances que la suppression d'éléments dans le tableau, le temps de traitement supplémentaire nécessaire pour effectuer un transfert vers et depuis un ensemble l'emporte sur les gains de remplissage d'un tableau au lieu d'un ensemble. En fin de compte, il est plus rapide de ne traiter qu’un ensemble. Néanmoins, il est intéressant de noter que si on choisit d’utiliser un tableau comme collecte de données pour certaines mégadonnées ne contenant pas de doublons, il pourrait être avantageux en termes de performances, s’il est nécessaire de supprimer plusieurs éléments en même temps. operation, pour convertir le tableau en ensemble, effectuez l'opération de suppression et reconvertissez l'ensemble en tableau.

Code de tableau:

var timer = function(name) {
  var start = new Date();
  return {
    stop: function() {
      var end = new Date();
      var time = end.getTime() - start.getTime();
      console.log('Timer:', name, 'finished in', time, 'ms');
    }
  }
};

var getRandom = function(min, max) {
  return Math.random() * (max - min) + min;
};

var lastNames = ['SMITH', 'JOHNSON', 'WILLIAMS', 'JONES', 'BROWN', 'DAVIS', 'MILLER', 'WILSON', 'MOORE', 'TAYLOR', 'ANDERSON', 'THOMAS'];

var genLastName = function() {
  var index = Math.round(getRandom(0, lastNames.length - 1));
  return lastNames[index];
};

var sex = ["Male", "Female"];

var genSex = function() {
  var index = Math.round(getRandom(0, sex.length - 1));
  return sex[index];
};

var Person = function() {
  this.name = genLastName();
  this.age = Math.round(getRandom(0, 100))
  this.sex = "Male"
};

var genPersons = function() {
  for (var i = 0; i < 100000; i++)
    personArray.Push(new Person());
};

var changeSex = function() {
  for (var i = 0; i < personArray.length; i++) {
    personArray[i].sex = genSex();
  }
};

var deleteMale = function() {
  for (var i = 0; i < personArray.length; i++) {
    if (personArray[i].sex === "Male") {
      personArray.splice(i, 1)
      i--
    }
  }
};

var t = timer("Array");

var personArray = [];

genPersons();

changeSex();

deleteMale();

t.stop();

console.log("Done! There are " + personArray.length + " persons.")

Set code:

var timer = function(name) {
    var start = new Date();
    return {
        stop: function() {
            var end  = new Date();
            var time = end.getTime() - start.getTime();
            console.log('Timer:', name, 'finished in', time, 'ms');
        }
    }
};

var getRandom = function (min, max) {
  return Math.random() * (max - min) + min;
};

var lastNames = ['SMITH','JOHNSON','WILLIAMS','JONES','BROWN','DAVIS','MILLER','WILSON','MOORE','TAYLOR','ANDERSON','THOMAS'];

var genLastName = function() {
    var index = Math.round(getRandom(0, lastNames.length - 1));
    return lastNames[index];
};

var sex = ["Male", "Female"];

var genSex = function() {
    var index = Math.round(getRandom(0, sex.length - 1));
    return sex[index];
};

var Person = function() {
        this.name = genLastName();
        this.age = Math.round(getRandom(0,100))
        this.sex = "Male"
};

var genPersons = function() {
for (var i = 0; i < 100000; i++)
        personSet.add(new Person());
};

var changeSex = function() {
        for (var key of personSet) {
                key.sex = genSex();
        }
};

var deleteMale = function() {
        for (var key of personSet) {
                if (key.sex === "Male") {
                        personSet.delete(key)
                }
        }
};

var t = timer("Set");

var personSet = new Set();

genPersons();

changeSex();

deleteMale();

t.stop();

console.log("Done! There are " + personSet.size + " persons.")
72
snowfrogdev

[~ # ~] observations [~ # ~] :

  • Les opérations définies peuvent être comprises comme des instantanés dans le flux d'exécution.
  • Nous ne sommes pas avant un substitut définitif.
  • Les éléments d'un Set class n'ont pas d'index accessible.
  • Set class est un complément Array class, utile dans les scénarios dans lesquels il est nécessaire de stocker une collection sur laquelle appliquer des opérations d'ajout, de suppression, de vérification et d'itération de base .

Je partage quelques tests de performance. Essayez d’ouvrir votre console et copiez le code ci-dessous.

Création d'un tableau (125000)

var n = 125000;
var arr = Array.apply( null, Array( n ) ).map( ( x, i ) => i );
console.info( arr.length ); // 125000

1. Localisation d'un index

Nous avons comparé la méthode has de Set avec Array indexOf:

Tableau/ indexOf (0.281ms) | Définissez/ a (0.053ms)

// Helpers
var checkArr = ( arr, item ) => arr.indexOf( item ) !== -1;
var checkSet = ( set, item ) => set.has( item );

// Vars
var set, result;

console.time( 'timeTest' );
result = checkArr( arr, 123123 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
checkSet( set, 123123 );
console.timeEnd( 'timeTest' );

2. Ajouter un nouvel élément

Nous comparons les méthodes add et Push des objets Set et Array respectivement:

Array/ Push (1.612ms) | Définissez/ add (0.006ms)

console.time( 'timeTest' );
arr.Push( n + 1 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
set.add( n + 1 );
console.timeEnd( 'timeTest' );

console.info( arr.length ); // 125001
console.info( set.size ); // 125001

3. Supprimer un élément

Lors de la suppression d'éléments, nous devons garder à l'esprit que Array et Set ne démarrent pas dans des conditions identiques. Array n'a pas de méthode native, une fonction externe est donc nécessaire.

Array/ deleteFromArr (0.356ms) | Définissez/ remove (0.019ms)

var deleteFromArr = ( arr, item ) => {
    var i = arr.indexOf( item );
    i !== -1 && arr.splice( i, 1 );
};

console.time( 'timeTest' );
deleteFromArr( arr, 123123 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
set.delete( 123123 );
console.timeEnd( 'timeTest' );

Lire l'article complet ici

Mon observation est qu’un ensemble est toujours meilleur avec deux pièges à l’esprit pour les grands tableaux:

a) La création d'ensembles à partir de tableaux doit être effectuée dans une boucle for avec une longueur préchargée.

lent (18 ms, par exemple) new Set(largeArray)

rapide (par exemple 6 ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }

b) La itération pourrait se faire de la même manière car elle est aussi plus rapide qu'une boucle for of ...

Voir https://jsfiddle.net/0j2gkae7/5/

pour une comparaison réelle à difference(), intersection(), union() et uniq() (+ leurs compagnons itératifs, etc.) avec 40 000 éléments

3
sebilasse

J'ai récemment effectué ce test et constaté que Set avait largement surperformé un tableau de 1 000 éléments (environ 10 fois plus d'opérations possibles dans le même laps de temps). Et en fonction du navigateur, battre ou perdre pour Object.hasOwnProperty dans un test similaire.

Set et Object ont tous deux leur méthode "has" dans ce qui semble être amorti à O (1), mais en fonction de la mise en œuvre du navigateur, une opération unique peut durer plus longtemps ou plus rapidement.

https://jsperf.com/set-has-vs-object-hasownproperty-vs-array-includes/1 Si vous souhaitez exécuter vos propres tests avec différents navigateurs/environnements.

1
Zargold