Ефективність масиву проти об’єктів у JavaScript


145

У мене є модель з можливо тисячами об’єктів. Мені було цікаво, що було б найефективнішим способом їх зберігання та отримання одного об’єкта, як тільки у мене є його id. Ідентифікатори - це довгі цифри.

Отже, це два варіанти, про які я думав. у варіанті перший - це простий масив з індексом збільшення. у варіанті 2 - це асоціативний масив і, можливо, об'єкт, якщо він має значення. Моє питання - який з них більш ефективний, коли мені в основному потрібно отримати один об'єкт, але також іноді перебирати їх і сортувати.

Варіант перший з неасоціативним масивом:

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

Варіант другий з асоціативним масивом:

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

Оновлення:

Гаразд, я розумію, що використання масиву у другому варіанті не виникає сумніву. Таким чином, у рядку декларації другим варіантом має бути справді: var a = {};і питання лише в тому, що ефективніше отримувати об'єкт із заданим ідентифікатором: масив або об’єкт, де ідентифікатор є ключовим.

а також зміниться відповідь, якщо мені доведеться сортувати список багато разів?


1
це допомагає , може бути :: stackoverflow.com/questions/13309464 / ...
Судір Bastakoti

Вам потрібна відсортована колекція у всі часи? Якщо це так, немає іншого варіанту, крім масиву (хоча не використовуйте індекси, як ви зараз).
Джон

@Jon фактично, я. що ви маєте на увазі під "як ти зараз"?
Моше Шахам

1
@MosheShaham: Масиви (повинні) мають безперервні індекси, починаючи з 0. Якщо ви використовуєте масиви, не робіть нічого іншого.
Джон

Я думаю, що цей орієнтир відповість на першу частину вашого питання: jsben.ch/#/Y9jDP
EscapeNetscape

Відповіді:


143

Коротка версія: масиви здебільшого швидші за об'єкти. Але немає 100% правильного рішення.

Оновлення 2017 - Тест та результати

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

налаштування тесту результати тесту

Оригінальний пост - Пояснення

У вашому запитанні є деякі помилки.

У Javascript немає асоціативних масивів. Тільки масиви та об’єкти.

Це масиви:

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

Це також масив:

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

Це в основному масив з отворами в ньому, тому що кожен масив має нескінченну індексацію. Це повільніше, ніж масиви без дірок. Але ітерація вручну через масив ще повільніше (в основному).

Це об’єкт:

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

Ось перевірка працездатності з трьох можливостей:

Пошук масиву проти Holey Array проти тесту на продуктивність об'єкта

Чудово прочитав про ці теми в журналі Smashing: Написання швидкого JavaScript, ефективного використання пам'яті


1
@Moshe І тим самим слід вести всю дискусію про продуктивність у Javascript. : P
деге

9
Це дійсно залежить від даних та розміру даних, з якими ви працюєте. Дуже малі набори даних та невеликі об’єкти будуть працювати набагато краще з масивами. Якщо ви говорите про пошук у великому наборі даних, де ви використовуєте об'єкт як карту, то об'єкт є більш ефективним. jsperf.com/array-vs-object-performance/35
f1v

5
Погодьтеся з f1v, але у редакції 35 є недолік у тесті: if (a1[i].id = id) result = a1[i];має бути: if (a1[i].id === id) result = a1[i];Тест http://jsperf.com/array-vs-object-performance/37 виправляє це
Чарльз Бірн

1
Див. Http://jsperf.com/array-vs-object-performance/71 . Має менший підмножина даних (я повинен був зациклитися на створенні даних, але я хотів дірок у масиві) приблизно з 93 об'єктів порівняно з 5000. Зовнішнім циклом є ідентифікатори для пошуку розпорошених в об’єктному масиві (починаючи з середини та кінця) та Я також додав ідентифікатор, що відсутній, щоб пошук масиву повинен був пройти всі елементи. Holey Array, об’єкт за ключем, а потім ручний масив. Отже, як f1v заявив, це дійсно залежить від розміру даних та місця, де дані для ручного пошуку масиву.
Чарльз Бірн

4
Цю відповідь можна було б покращити шляхом узагальнення висновків jsPerf у цій публікації - тим більше, що результати jsPerf - це справжня відповідь на питання. Решта - додатково. Це більш актуально в часи, коли jsPerf вниз (як зараз). meta.stackexchange.com/questions/8231/…
Jeff

23

Це насправді не питання про ефективність взагалі, оскільки масиви та об'єкти працюють дуже по-різному (або, принаймні, повинні бути). Масиви мають суцільний індекс 0..n, в той час як об'єкти відображають довільні ключі до довільних значень. Якщо ви хочете поставити конкретні ключі, єдиний вибір - це об'єкт. Якщо вам не байдуже клавіші, це масив.

Якщо ви спробуєте встановити довільні (числові) ключі для масиву, у вас дійсно є втрата продуктивності , оскільки поведінковий масив заповнить усі індекси між ними:

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

(Зверніть увагу , що маса не на самому ділі містить 99 undefinedзначень, але він буде вести себе таким чином , так як ви [повинні бути] переборі масиву в якій - то момент.)

Буквали для обох варіантів повинні чітко пояснити, як їх можна використовувати:

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

Я не хочу вводити конкретні ключі. Я хочу знати, що краще, і я буду працювати з цим. Гаразд, тож у другому варіанті масив не виникає. а як щодо об’єкта проти неасоціативного масиву?
Моше Шахам

1
@Moshe У JavaScript не існує такого поняття, як неасоціативний масив. Якщо вам потрібні клавіші (цифри або рядки), використовуйте об’єкт. Якщо вам просто потрібен (упорядкований) список, використовуйте масиви. Період. Виступ не входить в дискусію. Якщо продуктивність має вирішальне значення, і ви можете жити з клавішами в будь-якому випадку, спробуйте, який з них працює краще.
деге

5
Але я хочу знати, що ефективніше: отримання об'єкта з масиву (циклічного перегляду через нього) або з "асоціативного" об'єкта, де ідентифікатор є ключовим. Вибачте, якщо моє питання не було зрозумілим ...
Моше Шахам

2
@Moshe Якщо ви отримуєте доступ до чого-небудь за ключем, або в об'єкті, або в масиві, це завжди буде нескінченно швидше, ніж перебирати контейнер, намагаючись знайти те, що ви хочете. Різниця доступу до елемента за ключем у масиві чи об'єкті, ймовірно, незначна. Цикл, очевидно, гірше і в будь-якому випадку.
деге

1
@deceze - Як "щодо масиву, що містить об'єкти користувача, і щоб отримати об'єкт користувача, потрібен цикл, щоб отримати об’єкт користувача на основі user_id" проти "об'єкта, який має ключі, user_idотже користувачеві об’єкт можна отримати доступ, використовуючи user_idяк ключ"? Хто з них кращий з точки зору продуктивності? Будь-які пропозиції з цього приводу вдячні :)
Район

13

Для ES6 найбільш ефективним способом було б використання Map.

var myMap = new Map();

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

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

Ви можете сьогодні використовувати функції ES6 за допомогою shim ( https://github.com/es-shims/es6-shim ).

Продуктивність залежить від браузера та сценарію. Але ось один із прикладів, де Mapнайбільш ефективно: https://jsperf.com/es6-map-vs-object-properties/2


СПРАВКА https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map


11
Маєте якийсь ресурс для резервного копіювання? З моїх спостережень Набори ES6 швидші за масиви, але карти ES6 повільніше, ніж об'єкти та масиви
Steel Brain

1
Це було більш "смислово", не більш виконавсько, про що йшлося.
AlexG

3
@AlexG впевнений, що в заголовку чітко зазначено efficiency.
Qix - МОНІКА ПОМИЛИЛА

@Qix Так, мій поганий: o
AlexG

8

У NodeJS, якщо ви знаєте ID, прокручування через масив відбувається дуже повільно в порівнянні з 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');

І результати:

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

Навіть якщо ідентифікатор, що шукає, є першим у масиві / об'єкті:

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

5

Я намагався перенести це на наступний вимір, буквально.

Враховуючи двовимірний масив, у якому осі x і y завжди однакової довжини, чи швидше:

а) шукати комірку, створюючи двовимірний масив і шукаючи перший індекс, а потім другий індекс, тобто:

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

або

b) створити об'єкт із строковим поданням координат x і y, а потім зробити єдиний пошук на цьому obj, тобто:

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

Результат:
виявляється, що набагато швидше зробити два числові пошукові індекси на масивах, ніж один пошук властивостей на об'єкті.

Результати тут:

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


3

Це залежить від використання. Якщо справа в пошуку об'єктів, це дуже швидше.

Ось приклад Plunker для тестування продуктивності масивів та пошуку об'єктів.

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

Ви побачите це; Шукаючи 5.000 предметів у колекції масивів довжиною 5.000 , візьміть на себе 3000мілісекони

Однак Дивлячись на 5000 елементів в об'єкті має 5.000 властивостями, приймають тільки 2або 3milisecons

Також створення об’єктного дерева не має великої різниці


0

У мене була подібна проблема, з якою я стикаюся, де мені потрібно зберігати живі свічники з джерела події, обмеженого x предметами. Я міг би їх зберігати в об'єкті, де часова марка кожної свічки буде виконувати роль ключа, а сама свічка виконуватиме значення. Інша можливість полягала в тому, що я міг зберігати його в масиві, де кожен предмет був самою свічкою. Одна з проблем, пов’язаних із живими свічками, полягає в тому, що вони постійно надсилають оновлення в ту саму часову позначку, де в останньому оновлення містяться найсвіжіші дані, тому ви або оновлюєте існуючий елемент, або додаєте новий. Тож ось приємний орієнтир, який намагається поєднати всі 3 можливості. Масиви в розчині нижче в середньому принаймні в 4 рази швидші. Сміливо грайте

"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)
}

Висновок 10 - це межа

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.