자바 스크립트 세트 대 어레이 성능


87

세트가 Javascript에 비교적 새롭기 때문일 수 있지만 StackO 또는 다른 곳에서 Javascript에서 둘의 성능 차이에 대해 설명하는 기사를 찾을 수 없었습니다. 그렇다면 성능 측면에서 둘 사이의 차이점은 무엇입니까? 특히 제거, 추가 및 반복과 관련하여.


1
서로 바꿔서 사용할 수 없습니다. 따라서 그것들을 비교하는 것은 거의 의미가 없습니다.
zerkms

Set[]또는 사이의 비교에 대해 이야기 하고 {}있습니까?
eithed a

2
추가하고 반복하는 것은 큰 차이를 만들지 않습니다. 제거하고 가장 중요한 것은 조회가 차이를 만듭니다.
Bergi


3
@ zerkms— 엄격하게도 Array도 순서가 지정되어 있지 않지만 인덱스를 사용하면 마치있는 것처럼 처리 할 수 ​​있습니다. ;-) Set의 값 시퀀스는 삽입 순서로 유지됩니다.
RobG

답변:


98

좋아, 배열과 집합 모두에서 요소를 추가, 반복 및 제거하는 것을 테스트했습니다. 나는 10,000 개의 요소를 사용하는 "작은"테스트와 100,000 개의 요소를 사용하는 "큰"테스트를 실행했습니다. 결과는 다음과 같습니다.

컬렉션에 요소 추가

추가되는 요소의 수에 관계없이 .push배열 방법은 .addset 방법 보다 약 4 배 빠른 것 같습니다 .

컬렉션의 요소 반복 및 수정

테스트의이 부분에서는 for루프를 사용하여 배열 for of을 반복 하고 루프를 사용하여 세트를 반복했습니다. 다시 말하지만 어레이를 반복하는 것이 더 빨랐습니다. 이번에는 "작은"테스트에서는 두 배, "큰"테스트에서는 거의 4 배 더 오래 걸리므로 기하 급수적으로 보일 것입니다.

컬렉션에서 요소 제거

이제 이것이 흥미로워지는 곳입니다. 나는 for루프 의 조합을 .splice사용 for of하고 배열에서 일부 요소 .delete를 제거하고 세트에서 일부 요소를 사용 하고 제거했습니다. "작은"테스트의 경우 세트에서 항목을 제거하는 것이 약 3 배 더 빨랐지만 (2.6ms 대 7.1ms) "대형"테스트의 경우에는 항목을 배열에서 제거하는 데 1955.1ms가 소요되었지만 세트에서 제거하는 데 83.6ms가 걸렸습니다. 23 배 더 빨랐습니다.

결론

10k 요소에서 두 테스트 모두 비슷한 시간 (배열 : 16.6ms, 세트 : 20.7ms)을 실행했지만 100k 요소를 처리 할 때 세트가 확실한 승자였습니다 (배열 : 1974.8ms, 세트 : 83.6ms). 조작. 그렇지 않으면 어레이가 더 빨랐습니다. 그 이유를 정확히 말할 수 없었습니다.

배열이 생성되고 채워진 다음 일부 요소가 제거되는 집합으로 변환 된 다음 집합이 배열로 다시 변환되는 하이브리드 시나리오를 가지고 놀았습니다. 이렇게하면 배열에서 요소를 제거하는 것보다 훨씬 더 나은 성능을 제공 할 수 있지만 세트로 /에서 전송하는 데 필요한 추가 처리 시간이 세트 대신 배열을 채우는 이점보다 큽니다. 결국 세트 만 처리하는 것이 더 빠릅니다. 그럼에도 불구하고, 중복이없는 일부 빅 데이터에 대한 데이터 수집으로 어레이를 사용하기로 선택하면 하나에서 많은 요소를 제거해야하는 경우 성능 측면에서 유리할 수 있다는 것은 흥미로운 아이디어입니다. 배열을 집합으로 변환하고 제거 작업을 수행 한 다음 집합을 다시 배열로 변환합니다.

어레이 코드 :

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

코드 설정 :

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
집합의 값은 기본적으로 고유합니다. 따라서 [1,1,1,1,1,1]배열의 길이가 6 인 경우 세트는 크기 1을 갖습니다. 이러한 세트의 특성 때문에 코드가 실제로 각 실행에서 크기가 100,000 개 항목과 크게 다른 크기의 세트를 생성 할 수있는 것처럼 보입니다. 전체 스크립트가 실행될 때까지 세트의 크기를 표시하지 않기 때문에 아마 눈치 채지 못했을 것입니다.
KyleFarris 2016 년

6
@KyleFarris 내가 잘못하지 않는 한, 귀하의 예제와 같이 세트에 중복이 있으면 사실 [1, 1, 1, 1, 1]이지만 세트의 각 항목은 실제로 목록에서 무작위로 생성 된 이름과 성을 포함한 다양한 속성을 가진 객체이기 때문에 수백 개의 가능한 이름, 무작위로 생성 된 나이, 무작위로 생성 된 성별 및 기타 무작위로 생성 된 속성 ... 세트에 두 개의 동일한 개체가있을 가능성은 거의 없습니다.
snowfrogdev

3
사실, 세트가 세트의 객체와 실제로 구별되지 않는 것처럼 보이기 때문에이 경우에 맞습니다. 따라서 실제로 {foo: 'bar'}세트에 정확히 동일한 객체 10,000x를 가질 수 있으며 크기는 10,000입니다. 배열도 마찬가지입니다. 스칼라 값 (문자열, 숫자, 부울 등)에서만 고유 한 것 같습니다.
KyleFarris

12
Set에서 개체의 똑같은 내용을{foo: 'bar'} 여러 번 가질 수 있지만 똑같은 개체 (참조) 는 아닙니다 . 가치는 IMO 미묘한 차이를 지적
SimpleVar

14
Set을 사용하는 가장 중요한 이유 인 0 (1) 조회를 잊어 버렸습니다. hasIndexOf.
Magnus

64

관찰 :

  • 집합 작업은 실행 스트림 내에서 스냅 샷으로 이해할 수 있습니다.
  • 우리는 확실한 대체물이 아닙니다.
  • Set 클래스 의 요소 에는 액세스 가능한 인덱스가 없습니다.
  • Set 클래스Array 클래스 보완으로, 기본 추가, 삭제, 검사 및 반복 작업을 적용 할 컬렉션을 저장해야하는 시나리오에서 유용합니다.

성능 테스트를 공유합니다. 콘솔을 열고 아래 코드를 복사하여 붙여 넣으십시오.

어레이 만들기 (125000)

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

1. 색인 찾기

Set의 has 메소드를 Array indexOf와 비교했습니다.

배열 / 같이 IndexOf (0.281ms) | 세트 / (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. 새 요소 추가

Set 및 Array 개체의 추가 및 푸시 메서드를 각각 비교합니다.

어레이 / 푸시 (1.612ms) | 설정 / 추가 (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. 요소 삭제

요소를 삭제할 때 배열과 집합이 동일한 조건에서 시작되지 않는다는 점을 명심해야합니다. Array에는 네이티브 메서드가 없으므로 외부 함수가 필요합니다.

배열 / deleteFromArr (0.356ms) | 설정 / 제거 (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' );

여기 에서 전체 기사 읽기


4
Array.indexOf는 Array.includes 여야 동등합니다. Firefox에서 매우 다른 숫자가 나타납니다.
kagronick

2
Object.includes 대 Set.has 비교에 관심이 있습니다.
Leopold Kristjansson

1
@LeopoldKristjansson 저는 비교 테스트를 작성하지 않았지만, 우리는 24k 항목이있는 어레이가있는 프로덕션 사이트에서 타이밍을 수행했으며 Array.includes에서 Set.has 로의 전환은 엄청난 성능 향상이었습니다!
sedot

3

내 관찰은 큰 배열에 대해 두 가지 함정을 염두에두고 Set이 항상 더 낫다는 것입니다.

a) 배열에서 세트를 생성하는 작업 for은 미리 캐시 된 길이 의 루프 에서 수행되어야합니다 .

느림 (예 : 18ms) new Set(largeArray)

빠름 (예 : 6ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }

b) 반복은 for of루프 보다 빠르기 때문에 같은 방식으로 수행 할 수 있습니다 .

참조 https://jsfiddle.net/0j2gkae7/5/를

에 실제 비교 difference(), intersection(), union()uniq()40.000 요소 (+ 자신의 iteratee 동반자 등)


3

벤치마킹 된 반복 스크린 샷질문의 반복 부분에 대해 최근에이 테스트를 실행 한 결과 Set가 10,000 개 항목의 배열보다 훨씬 우수한 성능을 보였습니다 (동일한 시간 내에 작업이 약 10 배 발생할 수 있음). 그리고 브라우저에 따라 비슷한 테스트를 위해 Object.hasOwnProperty를이기거나 잃었습니다.

Set과 Object는 모두 O (1)로 상각되는 것처럼 보이는 "has"메서드를 가지고 있지만 브라우저의 구현에 따라 단일 작업이 더 오래 걸리거나 더 빨라질 수 있습니다. 대부분의 브라우저는 Set.has ()보다 빠르게 Object에 키를 구현하는 것 같습니다. 키에 대한 추가 검사를 포함하는 Object.hasOwnProperty조차도 Chrome v86에서 적어도 Set.has ()보다 약 5 % 빠릅니다.

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

업데이트 : 2020 년 11 월 11 일 : https://jsbench.me/irkhdxnoqa/2

다른 브라우저 / 환경에서 자체 테스트를 실행하려는 경우.


마찬가지로 배열과 세트에 항목을 추가하고 제거하는 벤치 마크를 추가합니다.


4
귀하의 경우와 같이 이러한 링크가 깨질 수 있으므로 답변에 링크를 사용하지 마십시오 (공식 라이브러리에 연결되지 않은 경우). 당신 링크는 404입니다
길 Epshtain

링크를 사용했지만 사용 가능할 때 출력도 복사했습니다. 그들이 연결 전략을 너무 빨리 바꾼 것은 유감입니다.
Zargold

: 스크린 샷 및 새로운 JS 성능 웹 사이트와 이제 게시물을 업데이트 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")

10K 항목에 대한 세 가지 작업은 다음과 같은 이점을 제공합니다.

set: 7.787ms
array: 2.388ms

@Bergi는 처음에 생각했던 것이기도하지만 그렇습니다.
zerkms

1
@zerkms : "work"정의 :-) 예, 배열은 뒤에 비어 forEach있지만 예상 한대로는 아닐 것입니다. 비슷한 행동을 원한다면 그것도 s.forEach(function(e) { s.clear(); })마찬가지입니다.
Bergi

1
음, 의도 한 것이 아니라 무언가를 합니다. 인덱스 i 와 끝 사이의 모든 요소를 ​​삭제합니다 . 그것은 delete세트에서하는 일과 비교되지 않습니다 .
trincot

@Bergi 아 맞다, 단 2 번의 반복으로 모든 것을 제거합니다. 내 잘못이야.
zerkms

4
1 회 반복. splice(0)배열을 비 웁니다.
trincot
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.