Hiệu suất mảng so với đối tượng trong JavaScript


144

Tôi có một mô hình với hàng ngàn đối tượng. Tôi đã tự hỏi điều gì sẽ là cách hiệu quả nhất để lưu trữ chúng và truy xuất một đối tượng một khi tôi có id. Các id là số dài.

Vì vậy, đây là 2 lựa chọn tôi đã suy nghĩ. trong tùy chọn một, đó là một mảng đơn giản với chỉ số tăng dần. trong tùy chọn 2, nó là một mảng kết hợp và có thể là một đối tượng, nếu nó tạo ra sự khác biệt. Câu hỏi của tôi là cái nào hiệu quả hơn, khi tôi chủ yếu cần lấy một đối tượng, nhưng đôi khi cũng lặp qua chúng và sắp xếp.

Tùy chọn một với mảng không liên kết:

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

Tùy chọn hai với mảng kết hợp:

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

Cập nhật:

OK, tôi hiểu rằng sử dụng một mảng trong tùy chọn thứ hai là không cần thiết. Vì vậy, dòng khai báo tùy chọn thứ hai nên thực sự là: var a = {};và câu hỏi duy nhất là: điều gì đang hoạt động tốt hơn trong việc truy xuất một đối tượng với một id đã cho: một mảng hoặc một đối tượng trong đó id là khóa.

và ngoài ra, câu trả lời sẽ thay đổi nếu tôi sẽ phải sắp xếp danh sách nhiều lần?



Bạn có cần một bộ sưu tập được sắp xếp mọi lúc? Nếu vậy, không có tùy chọn nào khác ngoài một mảng (mặc dù không sử dụng các chỉ mục như bạn hiện đang làm).
Jon

@Jon thực sự, tôi làm. bạn có ý nghĩa gì bởi "như bạn hiện đang làm"?
Moshe Shaham

1
@MosheShaham: Mảng (nên) có các chỉ mục liên tục bắt đầu từ 0. Nếu bạn sử dụng mảng, đừng làm gì khác.
Jon

Tôi đoán điểm chuẩn này sẽ trả lời phần đầu tiên của câu hỏi của bạn: jsben.ch/#/Y9jDP
EscapeNetscape

Câu trả lời:


143

Phiên bản ngắn: Mảng hầu hết nhanh hơn vật thể. Nhưng không có giải pháp đúng 100%.

Cập nhật 2017 - Kiểm tra và kết quả

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

thiết lập thử nghiệm kết quả kiểm tra

Bài gốc - Giải thích

Có một số quan niệm sai lầm trong câu hỏi của bạn.

Không có mảng kết hợp trong Javascript. Chỉ mảng và đối tượng.

Đây là các mảng:

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

Đây cũng là một mảng:

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

Về cơ bản, đó là một mảng có các lỗ hổng trong đó, bởi vì mọi mảng đều có lập chỉ mục liên tục. Nó chậm hơn mảng không có lỗ. Nhưng lặp lại thủ công thông qua mảng thậm chí còn chậm hơn (chủ yếu).

Đây là một đối tượng:

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

Đây là một bài kiểm tra hiệu suất của ba khả năng:

Tra cứu mảng vs Holey Array vs Kiểm tra hiệu năng đối tượng

Một bài đọc tuyệt vời về các chủ đề này tại Tạp chí Smashing: Viết JavaScript hiệu quả bộ nhớ nhanh


1
@Moshe Và do đó, tất cả các cuộc thảo luận về hiệu suất trong Javascript nên được thực hiện. : P
lừa dối

9
Điều này thực sự phụ thuộc vào dữ liệu và kích thước của dữ liệu bạn đang làm việc. Các tập dữ liệu rất nhỏ và các đối tượng nhỏ sẽ hoạt động tốt hơn nhiều với các mảng. Nếu bạn nói về việc tra cứu trong một tập dữ liệu lớn nơi bạn sử dụng một đối tượng làm bản đồ thì một đối tượng sẽ hiệu quả hơn. jsperf.com/array-vs-object-performance353
F1v

5
Đồng ý với F1v, nhưng Bản sửa đổi 35 có một lỗ hổng trong bài kiểm tra: if (a1[i].id = id) result = a1[i];Nên là: if (a1[i].id === id) result = a1[i];Kiểm tra http://jsperf.com/array-vs-object-performance/37 sửa lỗi đó
Charles Byrne

1
Xem http://jsperf.com/array-vs-object-performance/71 . Có một tập hợp dữ liệu nhỏ hơn (đáng lẽ tôi phải lặp để tạo dữ liệu, nhưng tôi muốn có lỗ hổng trong mảng) khoảng 93 đối tượng so với 5000. Vòng ngoài là Id để tìm kiếm rải rác trong mảng đối tượng (bắt đầu giữa và cuối) và Tôi cũng đã thêm một id bị thiếu để tra cứu Array sẽ phải duyệt qua tất cả các phần tử. Holey Array, Object by Key, sau đó là Array thủ công. Vì vậy, như F1v đã nói, nó thực sự phụ thuộc vào kích thước của dữ liệu và nơi dữ liệu dành cho tìm kiếm mảng thủ công.
Charles Byrne

4
Câu trả lời này có thể được cải thiện bằng cách tóm tắt các kết luận của jsPerf ở đây trong bài đăng này - đặc biệt vì kết quả jsPerf là ​​câu trả lời thực sự cho câu hỏi. Phần còn lại là thêm. Điều này có liên quan nhiều hơn trong thời gian khi jsPerf ngừng hoạt động (như ngay bây giờ). meta.stackexchange.com/questions/8231/ Mạnh
Jeff

23

Đây thực sự không phải là một câu hỏi về hiệu năng, vì các mảng và các đối tượng hoạt động rất khác nhau (hoặc ít nhất là được cho là). Mảng có một chỉ mục liên tục 0..n, trong khi các đối tượng ánh xạ các khóa tùy ý thành các giá trị tùy ý. Nếu bạn muốn cung cấp các khóa cụ thể, lựa chọn duy nhất là một đối tượng. Nếu bạn không quan tâm đến các phím, thì đó là một mảng.

Nếu bạn cố gắng đặt các khóa (số) tùy ý trên một mảng, bạn thực sự bị mất hiệu năng , vì về mặt hành vi, mảng sẽ điền vào tất cả các chỉ mục ở giữa:

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

(Lưu ý rằng mảng thực sự không chứa 99 undefinedgiá trị, nhưng nó sẽ hoạt động theo cách này vì bạn [được cho là] lặp lại mảng tại một số điểm.)

Các chữ cho cả hai tùy chọn sẽ làm cho nó rất rõ ràng làm thế nào chúng có thể được sử dụng:

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

Tôi không muốn cung cấp khóa cụ thể. Tôi muốn biết những gì đang hoạt động tốt hơn, và tôi sẽ làm việc với điều đó. OK, vì vậy trong tùy chọn thứ hai, một mảng nằm ngoài câu hỏi. Nhưng những gì về một đối tượng so với mảng không liên kết?
Moshe Shaham

1
@Moshe Không có thứ gọi là mảng không liên kết trong Javascript. Nếu bạn cần khóa (số hoặc chuỗi), hãy sử dụng một đối tượng. Nếu bạn chỉ cần một danh sách (đã ra lệnh), hãy sử dụng mảng. Giai đoạn = Stage. Hiệu suất không tham gia vào cuộc thảo luận. Nếu hiệu suất là rất quan trọng và bạn có thể sống với các phím của mình bằng mọi cách, hãy thử cách nào hiệu quả hơn với bạn.
lừa dối

5
Nhưng tôi muốn biết những gì đang hoạt động tốt hơn: lấy một đối tượng từ một mảng (bằng cách lặp qua nó) hoặc từ một đối tượng "kết hợp" trong đó id là khóa. Tôi xin lỗi nếu câu hỏi của tôi không rõ ràng ...
Moshe Shaham

2
@Moshe Nếu bạn truy cập bất cứ thứ gì bằng khóa, trong một đối tượng hoặc mảng, nó sẽ luôn nhanh hơn vô hạn so với việc lặp qua container cố gắng tìm thứ bạn muốn. Sự khác biệt của việc truy cập một mục theo khóa trong một mảng hoặc trong một đối tượng có thể không đáng kể. Looping rõ ràng là tồi tệ hơn cả hai cách.
lừa dối

1
@deceze - Làm thế nào "về mảng giữ các đối tượng người dùng và để lấy đối tượng của người dùng, một vòng lặp là cần thiết để có được đối tượng người dùng dựa trên đối tượng user_id" vs "có khóa user_idvì đối tượng người dùng có thể được truy cập bằng user_idkhóa"? Cái nào tốt hơn về hiệu suất? Bất kỳ đề xuất nào về điều này đều được đánh giá cao :)
Rayon

13

Với ES6, cách hiệu quả nhất là sử dụng Bản đồ.

var myMap = new Map();

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

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

Bạn có thể sử dụng các tính năng ES6 ngay hôm nay bằng cách sử dụng shim ( https://github.com/es-shims/es6-shim ).

Hiệu suất sẽ thay đổi tùy thuộc vào trình duyệt và kịch bản. Nhưng đây là một ví dụ có Maphiệu suất cao nhất: https://jsperf.com/es6-map-vs-object-properies/2


TÀI LIỆU THAM KHẢO https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map


11
Có bất kỳ nguồn lực để sao lưu này? Từ những quan sát của tôi cho đến nay Bộ ES6 nhanh hơn mảng nhưng Bản đồ ES6 chậm hơn cả đối tượng và mảng
Steel Brain

1
Đó là "ngữ nghĩa" hơn, không hiệu quả hơn, đó là câu hỏi.
AlexG

3
@AlexG khá chắc chắn tiêu đề rõ ràng efficiency.
Qix - MONICA ĐƯỢC PHÂN BIỆT

@Qix Vâng, xấu của tôi: o
AlexG

8

Trong NodeJS nếu bạn biết ID, việc lặp qua mảng rất chậm so với 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');

Và kết quả:

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

Ngay cả khi ID tìm kiếm là cái đầu tiên trong mảng / đối tượng:

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

5

Tôi đã cố gắng đưa điều này đến chiều tiếp theo, theo nghĩa đen.

Cho một mảng 2 chiều, trong đó các trục x và y luôn có cùng độ dài, có nhanh hơn không:

a) tra cứu ô bằng cách tạo một mảng hai chiều và tìm kiếm chỉ mục đầu tiên, theo sau là chỉ mục thứ hai, nghĩa là:

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

hoặc là

b) tạo một đối tượng với biểu diễn chuỗi của tọa độ x và y, sau đó thực hiện tra cứu trên obj đó, nghĩa là:

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

Kết quả:
Hóa ra việc thực hiện hai lần tra cứu chỉ mục số trên các mảng sẽ nhanh hơn nhiều so với việc tra cứu một thuộc tính trên đối tượng.

Kết quả tại đây:

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


3

Nó phụ thuộc vào cách sử dụng. Nếu trường hợp là tra cứu đối tượng là rất nhanh.

Dưới đây là một ví dụ về Plunker để kiểm tra hiệu năng của tra cứu mảng và đối tượng.

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

Bạn sẽ thấy rằng; Tìm kiếm 5.000 mặt hàng trong bộ sưu tập mảng dài 5.000 , tiếp quản 3000milisecons

Tuy nhiên, tìm kiếm 5.000 vật phẩm trong đối tượng có 5.000 thuộc tính, chỉ lấy 2hoặc 3milisecons

Cũng làm cho cây đối tượng không tạo ra sự khác biệt lớn


0

Tôi đã có một vấn đề tương tự mà tôi đang phải đối mặt khi tôi cần lưu trữ nến trực tiếp từ nguồn sự kiện giới hạn ở x vật phẩm. Tôi có thể lưu trữ chúng trong một đối tượng trong đó dấu thời gian của mỗi cây nến sẽ đóng vai trò là chìa khóa và chính cây nến sẽ đóng vai trò là giá trị. Một khả năng khác là tôi có thể lưu trữ nó trong một mảng trong đó mỗi vật phẩm là chính cây nến. Một vấn đề về nến sống là chúng liên tục gửi các bản cập nhật trên cùng dấu thời gian trong đó bản cập nhật mới nhất giữ dữ liệu gần đây nhất do đó bạn có thể cập nhật một mục hiện có hoặc thêm một mục mới. Vì vậy, đây là một điểm chuẩn tốt đẹp cố gắng kết hợp cả 3 khả năng. Mảng trong giải pháp dưới đây trung bình nhanh hơn gấp 4 lần. Thoải mái chơi

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

Kết luận 10 là giới hạn ở đây

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.