อาร์เรย์กับประสิทธิภาพของวัตถุใน JavaScript


145

ฉันมีแบบจำลองที่มีวัตถุหลายพันรายการ ฉันสงสัยว่าสิ่งใดจะเป็นวิธีที่มีประสิทธิภาพมากที่สุดในการจัดเก็บและเรียกคืนวัตถุเดียวเมื่อฉันมีรหัส ID เป็นตัวเลขที่ยาว

ดังนั้นนี่คือ 2 ตัวเลือกที่ฉันคิด ในตัวเลือกที่หนึ่งมันเป็นอาร์เรย์ที่เรียบง่ายพร้อมดัชนีที่เพิ่มขึ้น ในตัวเลือกที่ 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 = {};และคำถามเดียวคือ: สิ่งที่ทำงานได้ดีขึ้นในการดึงวัตถุที่มี id ที่กำหนด: อาร์เรย์หรือวัตถุที่ id เป็นกุญแจสำคัญ

และคำตอบจะเปลี่ยนไปหรือไม่ถ้าฉันต้องเรียงลำดับรายการหลายครั้ง?


1
ความช่วยเหลือนี้อาจเป็น :: stackoverflow.com/questions/13309464/…
Sudhir Bastakoti

คุณต้องการคอลเล็กชันที่เรียงลำดับตลอดเวลาหรือไม่? ถ้าเป็นเช่นนั้นจะไม่มีตัวเลือกอื่นนอกจากอาร์เรย์ (แม้ว่าจะไม่ได้ใช้ดัชนีเหมือนที่คุณทำอยู่ในปัจจุบัน)
Jon

@ จอนจริงๆแล้วฉันทำ คุณหมายถึง "ชอบคุณทำอะไร"
Moshe Shaham

1
@MosheShaham: อาร์เรย์ (ควร) มีดัชนีต่อเนื่องเริ่มต้นจาก 0 หากคุณใช้อาร์เรย์อย่าทำอะไรอย่างอื่น
Jon

ฉันเดาว่ามาตรฐานนี้จะตอบคำถามแรกของคุณ: 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";

นี่คือการทดสอบประสิทธิภาพของความเป็นไปได้สามประการ:

Lookup Array กับ Holey Array กับการทดสอบประสิทธิภาพวัตถุ

อ่านยอดเยี่ยมเกี่ยวกับหัวข้อเหล่านี้ได้ที่ 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 มีชุดย่อยของข้อมูลขนาดเล็ก (ฉันควรวนซ้ำสำหรับการสร้างข้อมูล แต่ฉันต้องการรูในอาร์เรย์) ของวัตถุประมาณ 93 เทียบกับ 5000 วงรอบนอกเป็นรหัสเพื่อค้นหากระจัดกระจายในอาร์เรย์วัตถุ (เริ่มต้นกลางและปลาย) และ ฉันเพิ่ม id ที่หายไปเช่นกันดังนั้นการค้นหา Array จะต้องสำรวจองค์ประกอบทั้งหมด Holey Array, Object by Key, Manual Array ดังนั้นตามที่ f1v ระบุว่าจริง ๆ แล้วมันขึ้นอยู่กับขนาดของข้อมูลและตำแหน่งของข้อมูลสำหรับการค้นหาอาเรย์ด้วยตนเอง
Charles Byrne

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ค่า แต่มันจะทำงานในลักษณะนี้ตั้งแต่คุณ [ควรจะเป็น] iteratingอาร์เรย์ในบางจุด.)

ตัวอักษรสำหรับทั้งสองตัวเลือกควรทำให้ชัดเจนว่าจะใช้อย่างไร:

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

ฉันไม่ต้องการระบุคีย์เฉพาะ ฉันต้องการรู้ว่าอะไรที่ทำงานได้ดีขึ้นและฉันจะทำงานกับมัน ตกลงดังนั้นในตัวเลือกที่สองอาร์เรย์ไม่อยู่ในคำถาม แต่สิ่งที่เกี่ยวกับวัตถุกับอาเรย์ที่ไม่เกี่ยวข้อง?
Moshe Shaham

1
@Moshe ไม่มีสิ่งใดเป็นอาร์เรย์ที่ไม่เกี่ยวข้องใน Javascript หากคุณต้องการคีย์ (ตัวเลขหรือสตริง) ให้ใช้วัตถุ หากคุณต้องการรายการ (สั่งซื้อ) ให้ใช้อาร์เรย์ ระยะเวลา ประสิทธิภาพไม่ได้เข้าสู่การสนทนา หากประสิทธิภาพเป็นสิ่งสำคัญและคุณสามารถใช้ชีวิตร่วมกับกุญแจของคุณได้ด้วยวิธีใดลองใช้อันไหนดีกว่าสำหรับคุณ
หลอกลวง

5
แต่ฉันต้องการที่จะรู้ว่าสิ่งที่มีประสิทธิภาพดีกว่า: การดึงวัตถุจากอาร์เรย์ (โดยการวนซ้ำมัน) หรือจากวัตถุ "เชื่อมโยง" ที่ id เป็นกุญแจสำคัญ ฉันขอโทษหากคำถามของฉันไม่ได้ล้าง ...
Moshe Shaham

2
@Moshe หากคุณเข้าถึงสิ่งใด ๆ ด้วยกุญแจไม่ว่าจะเป็นในวัตถุหรืออาร์เรย์มันจะเร็วกว่าการวนลูปผ่านคอนเทนเนอร์ที่พยายามค้นหาสิ่งที่คุณต้องการ ความแตกต่างของการเข้าถึงรายการตามคีย์ในอาร์เรย์หรือในวัตถุนั้นมีความสำคัญเล็กน้อย เห็นได้ชัดว่าการวนลูปแย่ลงทั้งสองทาง
หลอกลวง

1
@deceze - วิธี "เกี่ยวกับอาร์เรย์ที่ถือวัตถุผู้ใช้และเพื่อให้ได้วัตถุของผู้ใช้จำเป็นต้องมีการวนซ้ำเพื่อให้ได้วัตถุผู้ใช้ตามวัตถุuser_id" vs "ที่มีคีย์เนื่องจากuser_idวัตถุผู้ใช้สามารถเข้าถึงได้โดยใช้user_idเป็นคีย์" อันไหนดีกว่าในแง่ของประสิทธิภาพ? ข้อเสนอแนะใด ๆ เกี่ยวกับเรื่องนี้จะชื่นชม :)
เรยอน

13

ด้วย ES6 วิธีที่มีประสิทธิภาพมากที่สุดคือการใช้แผนที่

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 - MONICA ถูกยกเลิกเมื่อ

@Qix ใช่สิ่งที่ไม่ดีของฉัน: o
AlexG

8

ในNodeJSถ้าคุณรู้ว่าการวนลูปผ่านอาร์เรย์จะช้ามากเมื่อเทียบกับIDobject[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 การค้นหาเป็นรหัสแรกในอาร์เรย์ / วัตถุ:

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

5

ฉันพยายามนำสิ่งนี้ไปสู่มิติถัดไปอย่างแท้จริง

รับอาร์เรย์ 2 มิติซึ่งแกน x และ y มีความยาวเท่ากันเสมอไปหรือไม่:

a) ค้นหาเซลล์โดยสร้างอาร์เรย์สองมิติและค้นหาดัชนีแรกตามด้วยดัชนีที่สองเช่น:

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คอลเลกชันอาร์เรย์ยาวใช้เวลามากกว่า3000milisecons

อย่างไรก็ตามการค้นหารายการ5.000ในวัตถุมีคุณสมบัติ5.000ใช้เวลาเท่านั้น2หรือ3milisecons

การสร้างวัตถุต้นไม้ก็ไม่ได้สร้างความแตกต่างอย่างมาก


0

ฉันมีปัญหาที่คล้ายกันซึ่งฉันกำลังเผชิญซึ่งฉันต้องเก็บแท่งเทียนสดจากแหล่งเหตุการณ์ที่ จำกัด เฉพาะรายการ x ฉันสามารถให้พวกเขาเก็บไว้ในวัตถุที่เวลาประทับของเทียนแต่ละอันจะทำหน้าที่เป็นกุญแจและเทียนนั้นจะทำหน้าที่เป็นค่า ความเป็นไปได้อีกอย่างหนึ่งก็คือฉันสามารถเก็บมันไว้ในอาร์เรย์ที่แต่ละรายการเป็นเทียนเอง ปัญหาหนึ่งเกี่ยวกับเทียนไขแบบสดคือพวกเขาทำการส่งการอัปเดตในเวลาประทับเดียวกันกับที่การอัปเดตล่าสุดเก็บข้อมูลล่าสุดดังนั้นคุณจึงอัปเดตรายการที่มีอยู่หรือเพิ่มรายการใหม่ ดังนั้นนี่คือมาตรฐานที่ดีที่พยายามรวมความเป็นไปได้ทั้ง 3 อย่างเข้าด้วยกัน อาร์เรย์ในโซลูชันด้านล่างนั้นมีความเร็วอย่างน้อย 4x โดยเฉลี่ย รู้สึกอิสระที่จะเล่น

"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

0

หากคุณมีอาเรย์เรียงแล้วคุณสามารถทำการค้นหาแบบไบนารีและที่เร็วกว่าการค้นหาวัตถุคุณสามารถดูคำตอบของฉันที่นี่:
วิธีการค้นหาได้เร็วขึ้นในอาเรย์เรียงโดยใช้ Javascript

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.