JavaScriptにおける配列とオブジェクトの効率


144

おそらく何千ものオブジェクトを持つモデルがあります。それらを格納し、IDを取得したら単一のオブジェクトを取得する最も効率的な方法は何だろうと思っていました。IDは長い数字です。

これらは私が考えていた2つのオプションです。オプション1では、インデックスが増加する単純な配列です。オプション2では、これは連想配列であり、違いがある場合はオブジェクトです。私の質問は、ほとんどの場合単一のオブジェクトを取得する必要があるが、時にはそれらをループしてソートする必要がある場合、どちらがより効率的かということです。

非連想配列のオプション1:

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

連想配列を使用したオプション2:

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

更新:

はい、2番目のオプションで配列を使用することは問題外です。したがって、2番目のオプションの宣言行は、実際には次のようになります。var a = {};唯一の問題は、指定されたIDを持つオブジェクトを取得する際のパフォーマンスが向上していることです。IDがキーである配列またはオブジェクトです。

また、リストを何度も並べ替える必要がある場合、答えは変わりますか?


1
これはかもしれ役立ちます:: stackoverflow.com/questions/13309464/...
のSudhir Bastakoti

常にソートされたコレクションが必要ですか?その場合、配列以外のオプションはありません(ただし、現在のようにインデックスを使用していません)。
Jon

@ジョン実際には、私はします。「現在のように」とはどういう意味ですか?
Moshe Shaham 2013年

1
@MosheShaham:配列には(0から始まる)連続したインデックスが必要です。配列を使用する場合は、他に何もしないでください。
Jon

このベンチマークが質問の最初の部分に答えると思います:jsben.ch
#

回答:


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";

以下は、3つの可能性のパフォーマンステストです。

ルックアップアレイとホーリーアレイとオブジェクトのパフォーマンステスト

Smashing Magazineでこれらのトピックに関する優れた記事:高速メモリ効率の高い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で修正されます
Charles Byrne

1
http://jsperf.com/array-vs-object-performance/71を参照してください。データのサブセットが小さい(データ作成のためにループする必要があったが、配列に穴が必要だった)のは、5000と比較して約93オブジェクトです。外側のループは、オブジェクト配列内に散在して検索されるID(中央と末尾から始まります)および欠落しているIDも追加したので、配列検索はすべての要素をトラバースする必要があります。ホーリー配列、キーによるオブジェクト、次に手動配列。したがって、f1vが述べたように、それは実際にはデータのサイズと、手動の配列検索用のデータの場所に依存します。
Charles Byrne

4
この答えは、この投稿でjsPerfの結論を要約することで改善できます。特に、jsPerfの結果が質問に対する実際の答えであるためです。残りは余分です。これは、jsPerfがダウンしている場合(現在のように)により関連します。meta.stackexchange.com/questions/8231/...
ジェフ

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

特定のキーを指定したくありません。パフォーマンスの向上を知りたいと思っています。OK、2番目のオプションでは、配列は問題外です。しかし、オブジェクトと非連想配列はどうですか?
Moshe Shaham 2013年

1
@Moshe Javascriptには、非連想配列のようなものはありません。キー(数値または文字列)が必要な場合は、オブジェクトを使用します。(順序付けられた)リストだけが必要な場合は、配列を使用します。限目。パフォーマンスは議論に入りません。パフォーマンスが重要であり、どちらの方法でもキーを使用できる場合は、どちらの方法が適しているか試してください。
だます

5
しかし、私は何がより良いパフォーマンスをしているのか知りたいです:オブジェクトを配列から(ループして)取得するか、IDがキーである "連想"オブジェクトから取得します。私の質問が明確でなかったとしたら申し訳ありません...
Moshe Shaham 2013年

2
@Mosheオブジェクトまたは配列内のキーで何かにアクセスする場合、コンテナをループして目的のものを見つけるよりも常に無限に高速になります。配列内またはオブジェクト内のキーによって項目にアクセスする違いは、ごくわずかです。ループは明らかにどちらの方法でも悪化します。
だます

1
@deceze -どのように「配列は、ユーザーオブジェクトを保持し、ユーザーのオブジェクトを取得するについて、ループが基づいてユーザーオブジェクトを取得するために必要とされるuser_id」として、オブジェクトがキーを持つ対「user_idユーザオブジェクトを使用してアクセスすることができ、したがってuser_id、キーとして」?パフォーマンスの面でどちらが優れていますか?これについての提案はありがたいです:)
Rayon

13

ES6で最もパフォーマンスの高い方法は、マップを使用することです。

var myMap = new Map();

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

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

シム(https://github.com/es-shims/es6-shim)を使用して、今すぐES6機能を使用できます。

パフォーマンスは、ブラウザーとシナリオによって異なります。しかし、これMapが最もパフォーマンスが高い1つの例です: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

シークIDが配列/オブジェクトの最初のIDである場合でも、

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

5

私はこれを文字通り次の次元に持っていこうとしました。

x軸とy軸が常に同じ長さである2次元配列が与えられた場合、以下の方が高速です

a)2次元配列を作成してセルを検索し、最初のインデックスとそれに続く2番目のインデックスを検索します。

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

または

b)x座標とy座標の文字列表現でオブジェクトを作成し、そのobjで単一のルックアップを実行します。

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

結果:
オブジェクトに対して1つのプロパティルックアップを行うよりも、配列に対して2つの数値インデックスルックアップを行う方がはるかに高速であることがわかります。

ここでの結果:

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


3

使い方次第です。ケースがルックアップオブジェクトの場合、非常に高速です。

これは、配列とオブジェクトのルックアップのパフォーマンスをテストするPlunkerの例です。

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

あなたはそれを見るでしょう。以下のために見上げる5.000内の項目5.000引き継ぐ、長アレイコレクション3000milisecons

ただし、オブジェクトで5.000アイテムを検索すると、プロパティは5.000になり、take only 2または3miliseconsになります。

オブジェクトツリーを作成しても大きな違いはありません


0

x個のアイテムに限定されたイベントソースからのライブローソク足を保存する必要がある場所で直面している同様の問題がありました。各キャンドルのタイムスタンプがキーとして機能し、キャンドル自体が値として機能するオブジェクトにそれらを格納することができます。もう1つの可能性は、各アイテムがキャンドル自体である配列に格納できることでした。ライブキャンドルに関する1つの問題は、最新の更新が最新のデータを保持する同じタイムスタンプで更新を送信し続けるため、既存のアイテムを更新するか、新しいアイテムを追加することです。したがって、ここに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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.