Efficacité des tableaux et des objets en JavaScript

145

J'ai un modèle avec peut-être des milliers d'objets. Je me demandais quel serait le moyen le plus efficace de les stocker et de récupérer un seul objet une fois que j'ai son identifiant. Les identifiants sont des nombres longs.

Voici donc les 2 options auxquelles je pensais. dans la première option, c'est un simple tableau avec un index incrémentiel. dans l'option 2, c'est un tableau associatif et peut-être un objet, si cela fait une différence. Ma question est de savoir lequel est le plus efficace, lorsque j'ai principalement besoin de récupérer un seul objet, mais aussi parfois de les parcourir et de les trier.

Option 1 avec tableau non associatif:

var a = [{id: 29938, name: 'name1'},
         {id: 32994, name: 'name1'}];
function getObject(id) {
    for (var i=0; i < a.length; i++) {
        if (a[i].id == id) 
            return a[i];
    }
}

Option deux avec tableau associatif:

var a = [];  // maybe {} makes a difference?
a[29938] = {id: 29938, name: 'name1'};
a[32994] = {id: 32994, name: 'name1'};
function getObject(id) {
    return a[id];
}

Mettre à jour:

OK, je comprends que l'utilisation d'un tableau dans la deuxième option est hors de question. Donc, la ligne de déclaration, la deuxième option devrait vraiment être: var a = {};et la seule question est: qu'est-ce qui fonctionne le mieux pour récupérer un objet avec un id donné: un tableau ou un objet où l'id est la clé.

et aussi, la réponse changera-t-elle si je devrai trier la liste plusieurs fois?

Moshe Shaham
la source
1
cela aide peut-être :: stackoverflow.com/questions/13309464/…
Sudhir Bastakoti
Avez-vous besoin d'une collection triée à tout moment? Si c'est le cas, il n'y a pas d'autre option qu'un tableau (bien que n'utilisant pas les index comme vous le faites actuellement).
Jon
@Jon en fait, je le fais. qu'entendez-vous par «comme vous le faites actuellement»?
Moshe Shaham
1
@MosheShaham: Les tableaux (devraient) avoir des index continus à partir de 0. Si vous utilisez des tableaux, ne faites rien d'autre.
Jon
Je suppose que ce benchmark répondra à la première partie de votre question: jsben.ch/#/Y9jDP
EscapeNetscape

Réponses:

143

La version courte: les tableaux sont généralement plus rapides que les objets. Mais il n'y a pas de solution correcte à 100%.

Mise à jour 2017 - Test et résultats

var a1 = [{id: 29938, name: 'name1'}, {id: 32994, name: 'name1'}];

var a2 = [];
a2[29938] = {id: 29938, name: 'name1'};
a2[32994] = {id: 32994, name: 'name1'};

var o = {};
o['29938'] = {id: 29938, name: 'name1'};
o['32994'] = {id: 32994, name: 'name1'};

for (var f = 0; f < 2000; f++) {
    var newNo = Math.floor(Math.random()*60000+10000);
    if (!o[newNo.toString()]) o[newNo.toString()] = {id: newNo, name: 'test'};
    if (!a2[newNo]) a2[newNo] = {id: newNo, name: 'test' };
    a1.push({id: newNo, name: 'test'});
}

configuration de test résultats de test

Message original - Explication

Il y a des idées fausses dans votre question.

Il n'y a pas de tableaux associatifs en Javascript. Seuls les tableaux et les objets.

Ce sont des tableaux:

var a1 = [1, 2, 3];
var a2 = ["a", "b", "c"];
var a3 = [];
a3[0] = "a";
a3[1] = "b";
a3[2] = "c";

C'est aussi un tableau:

var a3 = [];
a3[29938] = "a";
a3[32994] = "b";

Il s'agit essentiellement d'un tableau avec des trous, car chaque tableau a une indexation continue. C'est plus lent que les tableaux sans trous. Mais l'itération manuelle dans le tableau est encore plus lente (surtout).

Ceci est un objet:

var a3 = {};
a3[29938] = "a";
a3[32994] = "b";

Voici un test de performance de trois possibilités:

Lookup Array vs Holey Array vs Test de performances des objets

Une excellente lecture sur ces sujets dans Smashing Magazine: Ecrire du JavaScript rapide et efficace en mémoire

Alp
la source
1
@Moshe Et ainsi toute discussion sur les performances en Javascript doit être faite. : P
deceze
9
Cela dépend vraiment des données et de la taille des données avec lesquelles vous travaillez. Les très petits ensembles de données et les petits objets fonctionneront beaucoup mieux avec les tableaux. Si vous parlez de recherche dans un grand ensemble de données où vous utilisez un objet comme carte, un objet est plus efficace. jsperf.com/array-vs-object-performance/35
f1v
5
D'accord avec f1v, mais la révision 35 a un défaut dans le test: if (a1[i].id = id) result = a1[i];Devrait être: if (a1[i].id === id) result = a1[i];Test http://jsperf.com/array-vs-object-performance/37 corrige cela
Charles Byrne
1
Voir http://jsperf.com/array-vs-object-performance/71 . A un sous-ensemble plus petit de données (j'aurais dû boucler pour la création de données, mais je voulais des trous dans le tableau) d'environ 93 objets par rapport à 5000. La boucle externe sont des identifiants à rechercher dispersés dans le tableau d'objets (début milieu et fin) et J'ai également ajouté un identifiant manquant pour que la recherche Array doive traverser tous les éléments. Holey Array, l'objet par clé, puis Manual Array. Ainsi, comme l'a déclaré f1v, cela dépend vraiment de la taille des données et de l'emplacement des données pour les recherches de tableaux manuelles.
Charles Byrne
4
Cette réponse pourrait être améliorée en résumant les conclusions de jsPerf ici dans cet article - d'autant plus que les résultats de jsPerf sont la vraie réponse à la question. Le reste est extra. Ceci est plus pertinent dans les moments où jsPerf est en panne (comme maintenant). meta.stackexchange.com/questions/8231/…
Jeff
24

Ce n'est pas du tout une question de performance, car les tableaux et les objets fonctionnent très différemment (ou sont censés le faire, du moins). Les tableaux ont un index continu 0..n, tandis que les objets mappent des clés arbitraires à des valeurs arbitraires. Si vous souhaitez fournir des clés spécifiques, le seul choix est un objet. Si vous ne vous souciez pas des clés, c'est un tableau.

Si vous essayez de définir des clés arbitraires (numériques) sur un tableau, vous avez vraiment une perte de performances , car comportementalement le tableau remplira tous les index intermédiaires:

> foo = [];
  []
> foo[100] = 'a';
  "a"
> foo
  [undefined, undefined, undefined, ..., "a"]

(Notez que le tableau ne contient pas réellement 99 undefinedvaleurs, mais il se comportera de cette façon puisque vous [censé être] itérer le tableau à un moment donné.)

Les littéraux pour les deux options devraient indiquer très clairement comment elles peuvent être utilisées:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature
déceler
la source
Je ne veux pas fournir de clés spécifiques. Je veux savoir ce qui fonctionne le mieux et je travaillerai avec cela. OK, donc dans la deuxième option, un tableau est hors de question. mais qu'en est-il d'un objet par rapport au tableau non associatif?
Moshe Shaham
1
@Moshe Il n'y a pas de tableau non associatif en Javascript. Si vous avez besoin de clés (nombres ou chaînes), utilisez un objet. Si vous avez juste besoin d'une liste (ordonnée), utilisez des tableaux. Période. La performance n'entre pas dans la discussion. Si la performance est cruciale et que vous pouvez vivre avec vos clés de toute façon, essayez celle qui vous convient le mieux.
deceze
5
Mais je veux savoir ce qui fonctionne le mieux: récupérer un objet d'un tableau (en le parcourant en boucle) ou d'un objet "associatif" où l'id est la clé. Je suis désolé si ma question n'était pas claire ...
Moshe Shaham
2
@Moshe Si vous accédez à quelque chose par clé, que ce soit dans un objet ou un tableau, ce sera toujours infiniment plus rapide que de parcourir le conteneur en essayant de trouver ce que vous voulez. La différence d'accès à un élément par clé dans un tableau ou dans un objet est probablement négligeable. La boucle est évidemment pire de toute façon.
deceze
1
@deceze - Que diriez-vous "d'un tableau contenant des objets utilisateur et pour obtenir l'objet de l'utilisateur, une boucle est nécessaire pour obtenir un objet utilisateur basé sur un objet user_id" vs "ayant des clés, user_idce qui permet d'accéder à l'objet utilisateur en user_idtant que clé"? Lequel est le meilleur en termes de performances? Toutes les suggestions à ce sujet sont appréciées :)
Rayon
14

Avec ES6, le moyen le plus performant serait d'utiliser une carte.

var myMap = new Map();

myMap.set(1, 'myVal');
myMap.set(2, { catName: 'Meow', age: 3 });

myMap.get(1);
myMap.get(2);

Vous pouvez utiliser les fonctionnalités ES6 aujourd'hui en utilisant un shim ( https://github.com/es-shims/es6-shim ).

Les performances varient en fonction du navigateur et du scénario. Mais voici un exemple où Mapest le plus performant: https://jsperf.com/es6-map-vs-object-properties/2


RÉFÉRENCE https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map

Sandstrom
la source
11
Vous avez une ressource pour le sauvegarder? D'après mes observations jusqu'à présent, les ensembles ES6 sont plus rapides que les tableaux, mais les cartes ES6 sont plus lentes que les objets et les tableaux
Steel Brain
1
C'est plus «sémantique», pas plus performant, c'était la question.
AlexG le
3
@AlexG à peu près sûr que le titre l'indique clairement efficiency.
Qix - MONICA A ÉTÉ BRUYÉE
@Qix Oui, mon mauvais: o
AlexG
8

Dans NodeJS, si vous connaissez le ID, le bouclage dans le tableau est très lent par rapport à object[ID].

const uniqueString = require('unique-string');
const obj = {};
const arr = [];
var seeking;

//create data
for(var i=0;i<1000000;i++){
  var getUnique = `${uniqueString()}`;
  if(i===888555) seeking = getUnique;
  arr.push(getUnique);
  obj[getUnique] = true;
}

//retrieve item from array
console.time('arrTimer');
for(var x=0;x<arr.length;x++){
  if(arr[x]===seeking){
    console.log('Array result:');
    console.timeEnd('arrTimer');
    break;
  }
}

//retrieve item from object
console.time('objTimer');
var hasKey = !!obj[seeking];
console.log('Object result:');
console.timeEnd('objTimer');

Et les résultats:

Array result:
arrTimer: 12.857ms
Object result:
objTimer: 0.051ms

Même si l'ID de recherche est le premier dans le tableau / objet:

Array result:
arrTimer: 2.975ms
Object result:
objTimer: 0.068ms
Paweł
la source
5

J'ai essayé de faire passer cela à la dimension suivante, littéralement.

Étant donné un tableau à 2 dimensions, dans lequel les axes x et y ont toujours la même longueur, est-il plus rapide de:

a) recherchez la cellule en créant un tableau à deux dimensions et en recherchant le premier index, suivi du deuxième index, c'est-à-dire:

var arr=[][]    
var cell=[x][y]    

ou

b) créez un objet avec une représentation sous forme de chaîne des coordonnées x et y, puis effectuez une seule recherche sur cet objet, c'est-à-dire:

var obj={}    
var cell = obj['x,y']    

Résultat: il
s'avère qu'il est beaucoup plus rapide d'effectuer deux recherches d'index numériques sur les tableaux qu'une seule recherche de propriété sur l'objet.

Résultats ici:

http://jsperf.com/arr-vs-obj-lookup-2

Davem M
la source
3

Cela dépend de l'utilisation. Si le cas est la recherche d'objets est très rapide.

Voici un exemple de Plunker pour tester les performances des recherches de tableaux et d'objets.

https://plnkr.co/edit/n2expPWVmsdR3zmXvX4C?p=preview

Vous verrez cela; Recherche de 5.000 articles dans une collection de 5.000 longueurs, prenez le contrôle de 3000millisecondes

Cependant, la recherche de 5.000 éléments dans l'objet a 5.000 propriétés, prendre seulement 2ou 3millisecons

Faire une arborescence d'objets ne fait pas non plus une énorme différence

Mehmet Otkun
la source
0

J'ai eu un problème similaire auquel je suis confronté où je dois stocker des chandeliers en direct à partir d'une source d'événements limitée à x éléments. Je pourrais les avoir stockées dans un objet où l'horodatage de chaque bougie agirait comme clé et la bougie elle-même agirait comme valeur. Une autre possibilité était que je puisse le stocker dans un tableau où chaque élément était la bougie elle-même. Un problème avec les bougies en direct est qu'elles continuent d'envoyer des mises à jour sur le même horodatage où la dernière mise à jour contient les données les plus récentes.Par conséquent, vous mettez à jour un élément existant ou en ajoutez un nouveau. Voici donc une belle référence qui tente de combiner les 3 possibilités. Les tableaux de la solution ci-dessous sont au moins 4x plus rapides en moyenne. N'hésitez pas à jouer

"use strict";

const EventEmitter = require("events");
let candleEmitter = new EventEmitter();

//Change this to set how fast the setInterval should run
const frequency = 1;

setInterval(() => {
    // Take the current timestamp and round it down to the nearest second
    let time = Math.floor(Date.now() / 1000) * 1000;
    let open = Math.random();
    let high = Math.random();
    let low = Math.random();
    let close = Math.random();
    let baseVolume = Math.random();
    let quoteVolume = Math.random();

    //Clear the console everytime before printing fresh values
    console.clear()

    candleEmitter.emit("candle", {
        symbol: "ABC:DEF",
        time: time,
        open: open,
        high: high,
        low: low,
        close: close,
        baseVolume: baseVolume,
        quoteVolume: quoteVolume
    });



}, frequency)

// Test 1 would involve storing the candle in an object
candleEmitter.on('candle', storeAsObject)

// Test 2 would involve storing the candle in an array
candleEmitter.on('candle', storeAsArray)

//Container for the object version of candles
let objectOhlc = {}

//Container for the array version of candles
let arrayOhlc = {}

//Store a max 30 candles and delete older ones
let limit = 30

function storeAsObject(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;

    // Create the object structure to store the current symbol
    if (typeof objectOhlc[symbol] === 'undefined') objectOhlc[symbol] = {}

    // The timestamp of the latest candle is used as key with the pair to store this symbol
    objectOhlc[symbol][time] = candle;

    // Remove entries if we exceed the limit
    const keys = Object.keys(objectOhlc[symbol]);
    if (keys.length > limit) {
        for (let i = 0; i < (keys.length - limit); i++) {
            delete objectOhlc[symbol][keys[i]];
        }
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]

    console.log("Storing as objects", end - start, Object.keys(objectOhlc[symbol]).length)
}

function storeAsArray(candle) {

    //measure the start time in nanoseconds
    const hrtime1 = process.hrtime()
    const start = hrtime1[0] * 1e9 + hrtime1[1]

    const { symbol, time } = candle;
    if (typeof arrayOhlc[symbol] === 'undefined') arrayOhlc[symbol] = []

    //Get the bunch of candles currently stored
    const candles = arrayOhlc[symbol];

    //Get the last candle if available
    const lastCandle = candles[candles.length - 1] || {};

    // Add a new entry for the newly arrived candle if it has a different timestamp from the latest one we storeds
    if (time !== lastCandle.time) {
        candles.push(candle);
    }

    //If our newly arrived candle has the same timestamp as the last stored candle, update the last stored candle
    else {
        candles[candles.length - 1] = candle
    }

    if (candles.length > limit) {
        candles.splice(0, candles.length - limit);
    }

    //measure the end time in nano seocnds
    const hrtime2 = process.hrtime()
    const end = hrtime2[0] * 1e9 + hrtime2[1]


    console.log("Storing as array", end - start, arrayOhlc[symbol].length)
}

La conclusion 10 est la limite ici

Storing as objects 4183 nanoseconds 10
Storing as array 373 nanoseconds 10
PirateApp
la source