Bộ Javascript so với hiệu suất của mảng


87

Có thể vì Bộ tương đối mới đối với Javascript nhưng tôi không thể tìm thấy một bài báo, trên StackO hoặc bất kỳ nơi nào khác, nói về sự khác biệt hiệu suất giữa hai trong Javascript. Vì vậy, sự khác biệt, về mặt hiệu suất, giữa hai là gì? Cụ thể, khi nói đến việc loại bỏ, thêm và lặp lại.


1
Bạn không thể sử dụng chúng thay thế cho nhau. Vì vậy, rất ít ý nghĩa khi so sánh chúng.
zerkms

bạn đang nói về sự so sánh giữa Set[]hoặc {}?
xuất hiện

2
Thêm và lặp lại không tạo ra nhiều khác biệt, việc xóa và - quan trọng nhất - tra cứu sẽ tạo ra sự khác biệt.
Bergi


3
@ zerkms — thực ra, Array cũng không được sắp xếp theo thứ tự, nhưng việc sử dụng chỉ mục của chúng cho phép chúng được đối xử như thể chúng vốn có. ;-) Chuỗi giá trị trong Tập hợp được giữ theo thứ tự chèn.
RobG

Câu trả lời:


98

Được rồi, tôi đã thử nghiệm thêm, lặp và xóa các phần tử khỏi cả mảng và tập hợp. Tôi đã chạy thử nghiệm "nhỏ", sử dụng 10.000 phần tử và thử nghiệm "lớn", sử dụng 100.000 phần tử. Đây là kết quả.

Thêm các phần tử vào một bộ sưu tập

Có vẻ như .pushphương thức mảng nhanh hơn khoảng 4 lần so với .addphương thức set, bất kể số phần tử được thêm vào.

Lặp lại và sửa đổi các phần tử trong một bộ sưu tập

Đối với phần này của bài kiểm tra, tôi đã sử dụng một forvòng lặp để lặp qua mảng và một for ofvòng lặp để lặp qua tập hợp. Một lần nữa, việc lặp lại trên mảng nhanh hơn. Lần này có vẻ như nó sẽ theo cấp số nhân vì nó mất gấp đôi thời gian trong các bài kiểm tra "nhỏ" và gần gấp bốn lần trong các bài kiểm tra "lớn".

Xóa các phần tử khỏi bộ sưu tập

Bây giờ đây là nơi nó trở nên thú vị. Tôi đã sử dụng kết hợp một forvòng lặp và .spliceđể xóa một số phần tử khỏi mảng và tôi đã sử dụng for of.deleteđể xóa một số phần tử khỏi tập hợp. Đối với các bài kiểm tra "nhỏ", việc xóa các mục khỏi tập hợp nhanh hơn khoảng ba lần (2,6 mili giây so với 7,1 mili giây) nhưng mọi thứ đã thay đổi đáng kể đối với bài kiểm tra "lớn", nơi nó mất tới 1955,1 mili giây để xóa các mục khỏi mảng trong khi chỉ mất 83,6 mili giây để xóa chúng khỏi tập hợp, nhanh hơn 23 lần.

Kết luận

Ở 10k phần tử, cả hai bài kiểm tra đều chạy với thời gian tương đương (mảng: 16,6 ms, set: 20,7 ms) nhưng khi xử lý với 100k phần tử, tập hợp là người chiến thắng rõ ràng (mảng: 1974,8 ms, set: 83,6 ms) nhưng chỉ do loại bỏ hoạt động. Nếu không thì mảng nhanh hơn. Tôi không thể nói chính xác tại sao lại như vậy.

Tôi đã thử với một số kịch bản kết hợp trong đó một mảng được tạo và điền, sau đó được chuyển đổi thành một tập hợp trong đó một số phần tử sẽ bị loại bỏ, tập hợp sau đó sẽ được chuyển đổi lại thành một mảng. Mặc dù làm điều này sẽ mang lại hiệu suất tốt hơn nhiều so với việc xóa các phần tử trong mảng, nhưng thời gian xử lý bổ sung cần thiết để chuyển đến và đi từ một tập hợp lớn hơn lợi ích của việc điền một mảng thay vì một tập hợp. Cuối cùng, sẽ nhanh hơn nếu chỉ giải quyết một tập hợp. Tuy nhiên, đó là một ý tưởng thú vị, rằng nếu người ta chọn sử dụng một mảng làm tập hợp dữ liệu cho một số dữ liệu lớn không có bản sao, thì đó có thể là một lợi thế về hiệu suất khôn ngoan, nếu có nhu cầu loại bỏ nhiều phần tử trong một để chuyển đổi mảng thành một tập hợp, thực hiện thao tác loại bỏ và chuyển đổi tập hợp trở lại một mảng.

Mã mảng:

var timer = function(name) {
  var start = new Date();
  return {
    stop: function() {
      var end = new Date();
      var time = end.getTime() - start.getTime();
      console.log('Timer:', name, 'finished in', time, 'ms');
    }
  }
};

var getRandom = function(min, max) {
  return Math.random() * (max - min) + min;
};

var lastNames = ['SMITH', 'JOHNSON', 'WILLIAMS', 'JONES', 'BROWN', 'DAVIS', 'MILLER', 'WILSON', 'MOORE', 'TAYLOR', 'ANDERSON', 'THOMAS'];

var genLastName = function() {
  var index = Math.round(getRandom(0, lastNames.length - 1));
  return lastNames[index];
};

var sex = ["Male", "Female"];

var genSex = function() {
  var index = Math.round(getRandom(0, sex.length - 1));
  return sex[index];
};

var Person = function() {
  this.name = genLastName();
  this.age = Math.round(getRandom(0, 100))
  this.sex = "Male"
};

var genPersons = function() {
  for (var i = 0; i < 100000; i++)
    personArray.push(new Person());
};

var changeSex = function() {
  for (var i = 0; i < personArray.length; i++) {
    personArray[i].sex = genSex();
  }
};

var deleteMale = function() {
  for (var i = 0; i < personArray.length; i++) {
    if (personArray[i].sex === "Male") {
      personArray.splice(i, 1)
      i--
    }
  }
};

var t = timer("Array");

var personArray = [];

genPersons();

changeSex();

deleteMale();

t.stop();

console.log("Done! There are " + personArray.length + " persons.")

Đặt mã:

var timer = function(name) {
    var start = new Date();
    return {
        stop: function() {
            var end  = new Date();
            var time = end.getTime() - start.getTime();
            console.log('Timer:', name, 'finished in', time, 'ms');
        }
    }
};

var getRandom = function (min, max) {
  return Math.random() * (max - min) + min;
};

var lastNames = ['SMITH','JOHNSON','WILLIAMS','JONES','BROWN','DAVIS','MILLER','WILSON','MOORE','TAYLOR','ANDERSON','THOMAS'];

var genLastName = function() {
    var index = Math.round(getRandom(0, lastNames.length - 1));
    return lastNames[index];
};

var sex = ["Male", "Female"];

var genSex = function() {
    var index = Math.round(getRandom(0, sex.length - 1));
    return sex[index];
};

var Person = function() {
	this.name = genLastName();
	this.age = Math.round(getRandom(0,100))
	this.sex = "Male"
};

var genPersons = function() {
for (var i = 0; i < 100000; i++)
	personSet.add(new Person());
};

var changeSex = function() {
	for (var key of personSet) {
		key.sex = genSex();
	}
};

var deleteMale = function() {
	for (var key of personSet) {
		if (key.sex === "Male") {
			personSet.delete(key)
		}
	}
};

var t = timer("Set");

var personSet = new Set();

genPersons();

changeSex();

deleteMale();

t.stop();

console.log("Done! There are " + personSet.size + " persons.")


1
Xin lưu ý rằng các giá trị của một tập hợp là duy nhất theo mặc định. Vì vậy, [1,1,1,1,1,1]đối với một mảng sẽ có độ dài 6, một tập hợp sẽ có kích thước 1. Có vẻ như mã của bạn thực sự có thể tạo ra các tập hợp có kích thước rất khác nhau so với kích thước 100.000 mục trong mỗi lần chạy vì đặc điểm này của Bộ. Bạn có thể không bao giờ nhận thấy vì bạn không hiển thị kích thước của tập hợp cho đến khi toàn bộ tập lệnh được chạy.
KyleFarris

6
@KyleFarris Trừ khi tôi nhầm lẫn, điều này sẽ đúng nếu có các bản sao trong tập hợp, như trong ví dụ của bạn [1, 1, 1, 1, 1], nhưng vì mỗi mục trong tập hợp thực sự là một đối tượng có các thuộc tính khác nhau bao gồm tên và họ được tạo ngẫu nhiên từ danh sách trong số hàng trăm tên có thể có, tuổi được tạo ngẫu nhiên, giới tính được tạo ngẫu nhiên và các thuộc tính được tạo ngẫu nhiên khác ... tỷ lệ có hai đối tượng giống hệt nhau trong tập hợp là rất nhỏ.
snowfrogdev

3
Trên thực tế, bạn đúng trong trường hợp này vì có vẻ như Bộ không thực sự phân biệt với các đối tượng trong bộ. Vì vậy, thực sự bạn thậm chí có thể có cùng một đối tượng chính xác {foo: 'bar'}10.000x trong tập hợp và nó sẽ có kích thước 10.000. Tương tự đối với mảng. Có vẻ như nó chỉ duy nhất với các giá trị vô hướng (chuỗi, số, boolean, v.v.).
KyleFarris

12
Bạn có thể có cùng một nội dung chính xác của một đối tượng{foo: 'bar'} nhiều lần trong Tập hợp, nhưng không phải cùng một đối tượng chính xác (tham chiếu). Đáng chỉ ra sự khác biệt tinh tế IMO
SimpleVar

14
Bạn đã quên thước đo lý do quan trọng nhất để sử dụng Bộ, tra cứu 0 (1). hasvs IndexOf.
Magnus

66

QUAN SÁT :

  • Hoạt động thiết lập có thể được hiểu là ảnh chụp nhanh trong luồng thực thi.
  • Chúng tôi không có trước một sự thay thế dứt khoát.
  • Các phần tử của một lớp Set không có chỉ mục nào có thể truy cập được.
  • Lớp tập hợp là phần bổ sung của lớp Mảng , hữu ích trong những trường hợp mà chúng ta cần lưu trữ một tập hợp để áp dụng các thao tác cộng, xóa, kiểm tra và lặp cơ bản.

Tôi chia sẻ một số thử nghiệm về hiệu suất. Hãy thử mở bảng điều khiển của bạn và sao chép mã bên dưới.

Tạo một mảng (125000)

var n = 125000;
var arr = Array.apply( null, Array( n ) ).map( ( x, i ) => i );
console.info( arr.length ); // 125000

1. Định vị chỉ mục

Chúng tôi đã so sánh phương thức has của Set với Array indexOf:

Array / indexOf (0,281ms) | Đặt / (0,053ms)

// Helpers
var checkArr = ( arr, item ) => arr.indexOf( item ) !== -1;
var checkSet = ( set, item ) => set.has( item );

// Vars
var set, result;

console.time( 'timeTest' );
result = checkArr( arr, 123123 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
checkSet( set, 123123 );
console.timeEnd( 'timeTest' );

2. Thêm một phần tử mới

Chúng tôi so sánh các phương thức thêm và đẩy của các đối tượng Set và Array tương ứng:

Mảng / đẩy (1.612ms) | Đặt / thêm (0,006ms)

console.time( 'timeTest' );
arr.push( n + 1 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
set.add( n + 1 );
console.timeEnd( 'timeTest' );

console.info( arr.length ); // 125001
console.info( set.size ); // 125001

3. Xóa một phần tử

Khi xóa các phần tử, chúng ta phải lưu ý rằng Mảng và Tập hợp không bắt đầu trong các điều kiện bằng nhau. Mảng không có một phương thức gốc, vì vậy một hàm bên ngoài là cần thiết.

Array / deleteFromArr (0.356ms) | Đặt / xóa (0,019ms)

var deleteFromArr = ( arr, item ) => {
    var i = arr.indexOf( item );
    i !== -1 && arr.splice( i, 1 );
};

console.time( 'timeTest' );
deleteFromArr( arr, 123123 );
console.timeEnd( 'timeTest' );

set = new Set( arr );

console.time( 'timeTest' );
set.delete( 123123 );
console.timeEnd( 'timeTest' );

Đọc toàn bộ bài viết tại đây


4
Array.indexOf phải là Array.includes để chúng tương đương nhau. Tôi nhận được những con số rất khác nhau trên Firefox.
kagronick

2
Tôi sẽ quan tâm đến so sánh Object.includes và Set.has ...
Leopold Kristjansson

1
@LeopoldKristjansson Tôi không viết một bài kiểm tra so sánh, nhưng chúng tôi đã tính thời gian trong một trang web sản xuất với các mảng có 24k mục và việc chuyển từ Array.includes sang Set. Đã là một hiệu suất tăng đáng kể!
sedot

3

Quan sát của tôi là một Bộ luôn tốt hơn với hai cạm bẫy cho các mảng lớn:

a) Việc tạo Bộ từ Mảng phải được thực hiện trong một forvòng lặp với độ dài được xóa trước.

chậm (ví dụ: 18ms) new Set(largeArray)

nhanh (ví dụ: 6ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }

b) Việc for oflặp lại có thể được thực hiện theo cách tương tự vì nó cũng nhanh hơn một vòng lặp ...

Xem https://jsfiddle.net/0j2gkae7/5/

để so sánh thực tế đời sống để difference(), intersection(), union()uniq()(+ đồng iteratee của họ vv) với 40.000 yếu tố


3

Ảnh chụp màn hình của Lặp lại được chuẩn hóaĐối với phần lặp lại của câu hỏi của bạn, gần đây tôi đã chạy thử nghiệm này và nhận thấy rằng Đặt hoạt động tốt hơn nhiều so với Mảng 10.000 mục (khoảng 10 lần các hoạt động có thể xảy ra trong cùng một khung thời gian). Và tùy thuộc vào trình duyệt hoặc đánh bại Object.hasOwnProperty trong một bài kiểm tra tương tự.

Cả Set và Object đều có phương thức "có" của chúng thực hiện theo thứ dường như được phân bổ thành O (1), nhưng tùy thuộc vào việc triển khai của trình duyệt, một thao tác có thể mất nhiều thời gian hơn hoặc nhanh hơn. Có vẻ như hầu hết các trình duyệt triển khai key trong Object nhanh hơn Set.has (). Ngay cả Object.hasOwnProperty bao gồm kiểm tra bổ sung về khóa nhanh hơn khoảng 5% so với Set.has () ít nhất đối với tôi trên Chrome v86.

https://jsperf.com/set-has-vs-object-hasownproperty-vs-array-includes/1

Cập nhật: 11/11/2020: https://jsbench.me/irkhdxnoqa/2

Trong trường hợp bạn muốn chạy thử nghiệm của riêng mình với các trình duyệt / môi trường khác nhau.


Tương tự, tôi sẽ thêm điểm chuẩn để thêm các mục vào mảng so với đặt và xóa.


4
Vui lòng không sử dụng các liên kết trong câu trả lời của bạn (trừ khi được liên kết với một thư viện chính thức) vì các liên kết này có thể bị hỏng - như đã xảy ra trong trường hợp của bạn. Liên kết của bạn là 404.
Gil Epshtain 17/02/19

Tôi đã sử dụng một liên kết nhưng cũng sao chép đầu ra khi nó có sẵn. Thật không may là họ đã thay đổi chiến lược liên kết của mình quá nhanh.
Zargold 19/02/19

Đã cập nhật bài đăng ngay bây giờ với ảnh chụp màn hình và trang web hiệu suất JS mới: jsbench.me
Zargold

-5
console.time("set")
var s = new Set()
for(var i = 0; i < 10000; i++)
  s.add(Math.random())
s.forEach(function(e){
  s.delete(e)
})
console.timeEnd("set")
console.time("array")
var s = new Array()
for(var i = 0; i < 10000; i++)
  s.push(Math.random())
s.forEach(function(e,i){
  s.splice(i)
})
console.timeEnd("array")

Ba phép toán trên 10K mục đã cho tôi:

set: 7.787ms
array: 2.388ms

@Bergi đó là những gì tôi nghĩ ban đầu, nhưng nó đã xảy ra.
zerkms

1
@zerkms: Xác định "công việc" :-) Có, mảng sẽ trống sau dấu forEach, nhưng có thể không theo cách bạn mong đợi. Nếu một người muốn hành vi có thể so sánh được, nó cũng nên s.forEach(function(e) { s.clear(); })như vậy.
Bergi

1
Chà, nó thực hiện một điều gì đó, không giống như những gì dự định: nó xóa tất cả các phần tử giữa chỉ mục i và phần cuối. Điều đó không so sánh với những gì deletehiện trên Bộ.
trincot

@Bergi à đúng rồi, nó xóa mọi thứ chỉ trong 2 lần lặp lại. Lỗi của tôi.
zerkms

4
Trong 1 lần lặp. splice(0)trống một mảng.
trincot
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.