Matriz vs. Eficiencia de objeto en JavaScript

145

Tengo un modelo con posiblemente miles de objetos. Me preguntaba cuál sería la forma más eficiente de almacenarlos y recuperar un solo objeto una vez que tenga su identificación. Las identificaciones son números largos.

Estas son las 2 opciones en las que estaba pensando. en la opción uno es una matriz simple con un índice incremental. en la opción 2 es una matriz asociativa y tal vez un objeto, si hace la diferencia. Mi pregunta es cuál es más eficiente, cuando en su mayoría necesito recuperar un solo objeto, pero también a veces recorrerlo y ordenarlo.

Opción uno con matriz no asociativa:

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

Opción dos con matriz asociativa:

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

Actualizar:

OK, entiendo que usar una matriz en la segunda opción está fuera de discusión. Entonces, la línea de declaración, la segunda opción realmente debería ser: var a = {};y la única pregunta es: qué está funcionando mejor al recuperar un objeto con una identificación dada: una matriz o un objeto donde la identificación es la clave.

y también, ¿cambiará la respuesta si tendré que ordenar la lista muchas veces?

Moshe Shaham
fuente
1
esta ayuda puede ser :: stackoverflow.com/questions/13309464/…
Sudhir Bastakoti
¿Necesita una colección ordenada en todo momento? Si es así, no hay otra opción que una matriz (aunque no use los índices como lo hace actualmente).
Jon
@ Jon en realidad, lo hago. ¿Qué quieres decir con "como lo haces actualmente"?
Moshe Shaham
1
@MosheShaham: Las matrices (deberían) tener índices continuos a partir de 0. Si usa matrices, no haga nada más.
Jon
Creo que este punto de referencia será responder a la primera parte de su pregunta: jsben.ch/#/Y9jDP
EscapeNetscape

Respuestas:

143

La versión corta: las matrices son en su mayoría más rápidas que los objetos. Pero no hay una solución 100% correcta.

Actualización 2017 - Prueba y resultados

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

configuración de prueba resultados de la prueba

Publicación original - Explicación

Hay algunas ideas falsas en su pregunta.

No hay matrices asociativas en Javascript. Solo matrices y objetos.

Estas son matrices:

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

Esta también es una matriz:

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

Básicamente es una matriz con agujeros, porque cada matriz tiene una indexación continua. Es más lento que las matrices sin agujeros. Pero iterar manualmente a través de la matriz es aún más lento (principalmente).

Este es un objeto:

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

Aquí hay una prueba de rendimiento de tres posibilidades:

Lookup Array vs Holey Array vs Object Performance Test

Una excelente lectura sobre estos temas en Smashing Magazine: escribir JavaScript eficiente en memoria rápida

Montaña
fuente
1
@Moshe Y, por lo tanto, toda discusión sobre el rendimiento en Javascript debe hacerse. : P
deceze
9
Esto realmente depende de los datos y el tamaño de los datos con los que está trabajando. Los conjuntos de datos muy pequeños y los objetos pequeños funcionarán mucho mejor con las matrices. Si habla de búsquedas en un gran conjunto de datos donde utiliza un objeto como mapa, entonces un objeto es más eficiente. jsperf.com/array-vs-object-performance/35
f1v
55
De acuerdo con f1v, pero la Revisión 35 tiene una falla en la prueba: if (a1[i].id = id) result = a1[i];Debería ser: if (a1[i].id === id) result = a1[i];Prueba http://jsperf.com/array-vs-object-performance/37 corrige eso
Charles Byrne
1
Ver http://jsperf.com/array-vs-object-performance/71 . Tiene un subconjunto de datos más pequeño (debería haber hecho un bucle para la creación de datos, pero quería agujeros en la matriz) de aproximadamente 93 objetos en comparación con 5000. El bucle externo son ID para buscar dispersos en la matriz de objetos (comenzando en el medio y al final) y También agregué una identificación faltante para que la búsqueda de matriz tuviera que atravesar todos los elementos. Holey Array, el Objeto por Clave, luego Manual Array. Entonces, como dijo f1v, realmente depende del tamaño de los datos y de dónde están los datos para las búsquedas de matrices manuales.
Charles Byrne
44
Esta respuesta podría mejorarse resumiendo las conclusiones de jsPerf aquí en esta publicación, especialmente porque los resultados de jsPerf son la respuesta real a la pregunta. El resto es extra. Esto es más relevante en momentos en que jsPerf está inactivo (como en este momento). meta.stackexchange.com/questions/8231/…
Jeff
23

En realidad, no es una pregunta de rendimiento, ya que las matrices y los objetos funcionan de manera muy diferente (o se supone que lo hacen, al menos). Las matrices tienen un índice continuo 0..n, mientras que los objetos asignan claves arbitrarias a valores arbitrarios. Si se desea proporcionar claves específicas, la única opción es un objeto. Si no te importan las claves, es una matriz.

Si intenta establecer claves arbitrarias (numéricas) en una matriz, realmente tiene una pérdida de rendimiento , ya que, en términos de comportamiento, la matriz completará todos los índices intermedios:

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

(Tenga en cuenta que la matriz en realidad no contiene 99 undefinedvalores, pero se comportará de esta manera ya que [se supone que] está iterando la matriz en algún momento).

Los literales para ambas opciones deben dejar muy claro cómo se pueden usar:

var arr = ['foo', 'bar', 'baz'];     // no keys, not even the option for it
var obj = { foo : 'bar', baz : 42 }; // associative by its very nature
difunto
fuente
No quiero suministrar claves específicas. Quiero saber qué está funcionando mejor, y trabajaré con eso. Bien, en la segunda opción, una matriz está fuera de discusión. pero ¿qué pasa con un objeto frente a la matriz no asociativa?
Moshe Shaham
1
@Moshe No hay tal cosa como una matriz no asociativa en Javascript. Si necesita claves (números o cadenas), use un objeto. Si solo necesita una lista (ordenada), use matrices. Período. El rendimiento no entra en la discusión. Si el rendimiento es crucial y puedes vivir con tus llaves de cualquier manera, prueba cuál te funciona mejor.
deceze
55
Pero quiero saber qué está funcionando mejor: recuperar un objeto de una matriz (recorriéndolo) o de un objeto "asociativo" donde la identificación es la clave. Lo siento si mi pregunta no estaba clara ...
Moshe Shaham
2
@Moshe Si accede a cualquier cosa por clave, ya sea en un objeto o matriz, siempre será infinitamente más rápido que recorrer el contenedor tratando de encontrar lo que desea. La diferencia de acceder a un elemento por clave en una matriz o en un objeto es probablemente insignificante. Looping es obviamente peor de cualquier manera.
deceze
1
@deceze - ¿Qué tal "acerca de la matriz que contiene los objetos del usuario y para obtener el objeto del usuario, se necesita un bucle para obtener el objeto del usuario basado en el objeto user_id" vs "que tiene claves, user_idpor lo que se puede acceder al objeto del usuario usando user_idcomo clave"? ¿Cuál es mejor en términos de rendimiento? Cualquier sugerencia sobre esto es apreciada :)
Rayon
13

Con ES6, la forma más eficaz sería utilizar un mapa.

var myMap = new Map();

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

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

Puede usar las funciones de ES6 hoy usando una cuña ( https://github.com/es-shims/es6-shim ).

El rendimiento variará según el navegador y el escenario. Pero aquí hay un ejemplo donde Mapes más eficiente: https://jsperf.com/es6-map-vs-object-properties/2


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

Sandstrom
fuente
11
¿Tienes algún recurso para respaldar esto? Según mis observaciones hasta ahora, los conjuntos ES6 son más rápidos que las matrices, pero los mapas ES6 son más lentos que los objetos y las matrices
Steel Brain
1
Es más "semántico", no más eficaz, que era la cuestión.
AlexG
3
@AlexG bastante seguro de que el título lo indica claramente efficiency.
Qix - MONICA FUE MALTRATADA el
@Qix Sí, mi mal: o
AlexG
8

En NodeJS si conoce el ID, el bucle a través de la matriz es muy lento en comparación con 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');

Y los resultados:

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

Incluso si la ID de búsqueda es la primera en la matriz / objeto:

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

Traté de llevar esto a la siguiente dimensión, literalmente.

Dada una matriz bidimensional, en la que los ejes xey son siempre de la misma longitud, ¿es más rápido:

a) busque la celda creando una matriz bidimensional y buscando el primer índice, seguido del segundo índice, es decir:

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

o

b) cree un objeto con una representación de cadena de las coordenadas xey, y luego haga una sola búsqueda en ese obj, es decir:

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

Resultado:
Resulta que es mucho más rápido hacer dos búsquedas de índice numérico en las matrices, que una búsqueda de propiedad en el objeto.

Resultados aquí:

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

Davem M
fuente
3

Depende del uso. Si el caso es buscar objetos es muy rápido.

Aquí hay un ejemplo de Plunker para probar el rendimiento de la búsqueda de matrices y objetos.

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

Verás eso; Mirando hacia arriba para 5.000 artículos en la colección de matriz de longitud 5.000 , tome el control de 3000miliseconos

Sin embargo , la búsqueda de 5.000 elementos en el objeto tiene 5.000 propiedades, take only 2o 3milisecons

También hacer que el árbol de objetos no haga una gran diferencia

Mehmet Otkun
fuente
0

Tuve un problema similar al que tengo que enfrentar cuando necesito almacenar velas en vivo de una fuente de eventos limitada a x elementos. Podría tenerlos almacenados en un objeto donde la marca de tiempo de cada vela actuaría como la clave y la vela misma actuaría como el valor. Otra posibilidad era que pudiera almacenarlo en una matriz donde cada elemento era la vela misma. Un problema con las velas en vivo es que siguen enviando actualizaciones en la misma marca de tiempo donde la última actualización contiene los datos más recientes, por lo tanto, actualiza un elemento existente o agrega uno nuevo. Así que aquí hay un buen punto de referencia que intenta combinar las 3 posibilidades. Las matrices en la solución a continuación son al menos 4 veces más rápidas en promedio. Siéntete libre de jugar

"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 conclusión 10 es el límite aquí

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