Matrice vs. efficienza dell'oggetto in JavaScript


145

Ho un modello con forse migliaia di oggetti. Mi chiedevo quale sarebbe stato il modo più efficiente per archiviarli e recuperare un singolo oggetto una volta che ho il suo ID. Gli ID sono numeri lunghi.

Quindi queste sono le 2 opzioni a cui stavo pensando. nell'opzione uno è un array semplice con un indice incrementale. nell'opzione 2 è un array associativo e forse un oggetto, se fa la differenza. La mia domanda è quale sia il più efficiente, quando ho principalmente bisogno di recuperare un singolo oggetto, ma a volte anche passare attraverso di essi e ordinare.

Opzione 1 con array non associativo:

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];
    }
}

Opzione due con array associativo:

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

Aggiornare:

OK, capisco che l'uso di un array nella seconda opzione è fuori discussione. Quindi la riga di dichiarazione dovrebbe essere la seconda opzione: var a = {};e l'unica domanda è: cosa sta andando meglio nel recupero di un oggetto con un dato id: un array o un oggetto in cui l'id è la chiave.

e inoltre, la risposta cambierà se dovrò ordinare l'elenco più volte?



Hai bisogno di una raccolta differenziata in ogni momento? In tal caso, non esiste altra opzione che un array (anche se non si utilizzano gli indici come si fa attualmente).
Jon,

@Jon in realtà, lo faccio. cosa intendi con "come fai attualmente"?
Moshe Shaham,

1
@MosheShaham: le matrici (dovrebbero) avere indici continui a partire da 0. Se usi le matrici, non fare nient'altro.
Jon il

Immagino che questo benchmark risponderà alla prima parte della tua domanda: jsben.ch/#/Y9jDP
EscapeNetscape

Risposte:


143

La versione breve: gli array sono per lo più più veloci degli oggetti. Ma non esiste una soluzione corretta al 100%.

Aggiornamento 2017 - Test e risultati

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

configurazione di prova risultati del test

Posta originale - Spiegazione

Ci sono alcune idee sbagliate nella tua domanda.

Non ci sono array associativi in ​​Javascript. Solo matrici e oggetti.

Questi sono array:

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

Anche questo è un array:

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

Fondamentalmente è un array con buchi, perché ogni array ha un indicizzazione continua. È più lento degli array senza fori. Ma iterare manualmente attraverso l'array è ancora più lento (principalmente).

Questo è un oggetto:

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

Ecco un test delle prestazioni di tre possibilità:

Lookup Array vs Holey Array vs Object Performance Test

Una lettura eccellente su questi argomenti su Smashing Magazine: Scrivere JavaScript con memoria veloce ed efficiente


1
@Moshe E quindi tutte le discussioni sulle prestazioni in Javascript dovrebbero essere fatte. : P
inganno

9
Questo dipende davvero dai dati e dalle dimensioni dei dati con cui stai lavorando. Set di dati molto piccoli e piccoli oggetti avranno prestazioni molto migliori con gli array. Se si parla di ricerca in un set di dati di grandi dimensioni in cui si utilizza un oggetto come mappa, un oggetto è più efficiente. jsperf.com/array-vs-object-performance/35
f1v

5
Concordo con f1v, ma la Revisione 35 ha un difetto nel test: if (a1[i].id = id) result = a1[i];Dovrebbe essere: if (a1[i].id === id) result = a1[i];Test http://jsperf.com/array-vs-object-performance/37 corregge che
Charles Byrne,

1
Vedi http://jsperf.com/array-vs-object-performance/71 . Ha un sottoinsieme più piccolo di dati (avrei dovuto eseguire il ciclo per la creazione dei dati, ma volevo buchi nell'array) di circa 93 oggetti rispetto a 5000. I loop esterni sono ID per cercare sparsi nell'array di oggetti (inizio metà e fine) e Ho aggiunto anche un ID mancante in modo che la ricerca di array debba attraversare tutti gli elementi. Holey Array, Object by Key, quindi Manual Array. Quindi, come ha affermato f1v, dipende davvero dalla dimensione dei dati e da dove si trovano i dati per le ricerche manuali dell'array.
Charles Byrne

4
Questa risposta potrebbe essere migliorata riassumendo le conclusioni di jsPerf qui in questo post - specialmente perché i risultati di jsPerf sono la vera risposta alla domanda. Il resto è extra. Questo è più rilevante nei momenti in cui jsPerf è inattivo (come in questo momento). meta.stackexchange.com/questions/8231/…
Jeff

24

Non è affatto una questione di prestazioni, dal momento che array e oggetti funzionano in modo molto diverso (o almeno si suppone che lo facciano). Le matrici hanno un indice continuo 0..n, mentre gli oggetti associano chiavi arbitrarie a valori arbitrari. Se si desidera fornire chiavi specifiche, l'unica scelta è un oggetto. Se non ti interessano le chiavi, è un array.

Se si tenta di impostare chiavi arbitrarie (numeriche) su un array, si ha davvero una perdita di prestazioni , dal momento che l'array riempirà comportamentalmente tutti gli indici tra:

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

(Si noti che l'array non contiene effettivamente 99 undefinedvalori, ma si comporterà in questo modo poiché [si suppone che stia iterando l'array a un certo punto.)

I valori letterali di entrambe le opzioni dovrebbero chiarire in che modo possono essere utilizzati:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature

Non voglio fornire chiavi specifiche. Voglio sapere cosa sta andando meglio e ci lavorerò. OK, quindi nella seconda opzione un array è fuori discussione. ma che dire di un oggetto rispetto all'array non associativo?
Moshe Shaham,

1
@Moshe Non esiste un array non associativo in Javascript. Se hai bisogno di chiavi (numeri o stringhe), usa un oggetto. Se hai solo bisogno di un elenco (ordinato), usa le matrici. Periodo. La performance non entra nella discussione. Se le prestazioni sono cruciali e puoi vivere con le tue chiavi in ​​entrambi i modi, prova quale funziona meglio per te.
Inganno

5
Ma voglio sapere cosa sta funzionando meglio: recuperare un oggetto da un array (facendolo scorrere attraverso di esso) o da un oggetto "associativo" in cui l'id è la chiave. Mi dispiace se la mia domanda non era chiara ...
Moshe Shaham,

2
@Moshe Se accedete a qualcosa tramite chiave, in un oggetto o in un array, sarà sempre infinitamente più veloce che passare attraverso il contenitore cercando di trovare ciò che volete. La differenza di accesso a un elemento tramite chiave in un array o in un oggetto è probabilmente trascurabile. Il looping è ovviamente peggiore in entrambi i casi.
Inganno

1
@deceze - Che ne dici di "per quanto riguarda l'array che contiene oggetti utente e per ottenere l'oggetto dell'utente, è necessario un ciclo per ottenere l'oggetto utente basato sull'oggetto user_id" vs "che ha le chiavi, user_idquindi è possibile accedere all'oggetto utente usando user_idcome chiave"? Qual è il migliore in termini di prestazioni? Tutti i suggerimenti su questo sono apprezzati :)
Rayon

14

Con ES6 il modo più efficace sarebbe usare una mappa.

var myMap = new Map();

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

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

È possibile utilizzare le funzionalità ES6 oggi usando uno shim ( https://github.com/es-shims/es6-shim ).

Le prestazioni possono variare in base al browser e allo scenario. Ma ecco un esempio in cui Mapè più performante: https://jsperf.com/es6-map-vs-object-properties/2


RIFERIMENTO https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map


11
Hai qualche risorsa per eseguire il backup? Dalle mie osservazioni finora i set ES6 sono più veloci degli array, ma le mappe ES6 sono più lente sia degli oggetti che degli array
Steel Brain,

1
È più "semantico", non più performante, che era la domanda.
AlexG

3
@AlexG è abbastanza sicuro che il titolo indichi chiaramente efficiency.
Qix - MONICA È STATA MISTREATA il

@Qix Sì, il mio male: o
AlexG

8

In NodeJS se si conosce il ID, il looping nell'array è molto lento rispetto a 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');

E i risultati:

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

Anche se l'ID di ricerca è il primo nell'array / oggetto:

Array result:
arrTimer: 2.975ms
Object result:
objTimer: 0.068ms

5

Ho cercato di portarlo alla dimensione successiva, letteralmente.

Dato un array bidimensionale, in cui gli assi xey hanno sempre la stessa lunghezza, è più veloce:

a) cercare la cella creando un array bidimensionale e cercando il primo indice, seguito dal secondo indice, ovvero:

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

o

b) creare un oggetto con una rappresentazione in forma di stringa delle coordinate xey, quindi eseguire una singola ricerca su quell'oggetto, ovvero:

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

Risultato:
risulta che è molto più veloce eseguire due ricerche di indici numerici sugli array rispetto a una ricerca di proprietà sull'oggetto.

Risultati qui:

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


3

Dipende dall'uso. Se il caso è la ricerca, gli oggetti è molto più veloce.

Ecco un esempio di Plunker per testare le prestazioni di ricerche di array e oggetti.

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

Lo vedrai; Cercando 5.000 articoli in una raccolta di array di 5.000 lunghezze, 3000acquisisci milisecon

Tuttavia, cercando 5.000 oggetti nell'oggetto ha 5.000 proprietà, solo prendere 2o 3milisecon

Anche fare l'albero degli oggetti non fa molta differenza


0

Ho avuto un problema simile che sto affrontando dove devo conservare candelabri dal vivo da una fonte di eventi limitata a x articoli. Potrei averli memorizzati in un oggetto in cui il timestamp di ogni candela fungerebbe da chiave e la candela stessa fungerebbe da valore. Un'altra possibilità era che potevo conservarlo in un array in cui ogni oggetto era la candela stessa. Un problema relativo alle candele attive è che continuano a inviare aggiornamenti nello stesso timestamp in cui l'ultimo aggiornamento contiene i dati più recenti, pertanto è necessario aggiornare un elemento esistente o aggiungerne uno nuovo. Quindi ecco un bel benchmark che tenta di combinare tutte e 3 le possibilità. Le matrici nella soluzione seguente sono almeno quattro volte più veloci in media. Sentiti libero di giocare

"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 conclusione 10 è il limite qui

Storing as objects 4183 nanoseconds 10
Storing as array 373 nanoseconds 10

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.