세트가 Javascript에 비교적 새롭기 때문일 수 있지만 StackO 또는 다른 곳에서 Javascript에서 둘의 성능 차이에 대해 설명하는 기사를 찾을 수 없었습니다. 그렇다면 성능 측면에서 둘 사이의 차이점은 무엇입니까? 특히 제거, 추가 및 반복과 관련하여.
세트가 Javascript에 비교적 새롭기 때문일 수 있지만 StackO 또는 다른 곳에서 Javascript에서 둘의 성능 차이에 대해 설명하는 기사를 찾을 수 없었습니다. 그렇다면 성능 측면에서 둘 사이의 차이점은 무엇입니까? 특히 제거, 추가 및 반복과 관련하여.
Set
과 []
또는 사이의 비교에 대해 이야기 하고 {}
있습니까?
답변:
좋아, 배열과 집합 모두에서 요소를 추가, 반복 및 제거하는 것을 테스트했습니다. 나는 10,000 개의 요소를 사용하는 "작은"테스트와 100,000 개의 요소를 사용하는 "큰"테스트를 실행했습니다. 결과는 다음과 같습니다.
추가되는 요소의 수에 관계없이 .push
배열 방법은 .add
set 방법 보다 약 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]
배열의 길이가 6 인 경우 세트는 크기 1을 갖습니다. 이러한 세트의 특성 때문에 코드가 실제로 각 실행에서 크기가 100,000 개 항목과 크게 다른 크기의 세트를 생성 할 수있는 것처럼 보입니다. 전체 스크립트가 실행될 때까지 세트의 크기를 표시하지 않기 때문에 아마 눈치 채지 못했을 것입니다.
[1, 1, 1, 1, 1]
이지만 세트의 각 항목은 실제로 목록에서 무작위로 생성 된 이름과 성을 포함한 다양한 속성을 가진 객체이기 때문에 수백 개의 가능한 이름, 무작위로 생성 된 나이, 무작위로 생성 된 성별 및 기타 무작위로 생성 된 속성 ... 세트에 두 개의 동일한 개체가있을 가능성은 거의 없습니다.
{foo: 'bar'}
세트에 정확히 동일한 객체 10,000x를 가질 수 있으며 크기는 10,000입니다. 배열도 마찬가지입니다. 스칼라 값 (문자열, 숫자, 부울 등)에서만 고유 한 것 같습니다.
{foo: 'bar'}
여러 번 가질 수 있지만 똑같은 개체 (참조) 는 아닙니다 . 가치는 IMO 미묘한 차이를 지적
has
대 IndexOf
.
관찰 :
- 집합 작업은 실행 스트림 내에서 스냅 샷으로 이해할 수 있습니다.
- 우리는 확실한 대체물이 아닙니다.
- 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' );
여기 에서 전체 기사 읽기
내 관찰은 큰 배열에 대해 두 가지 함정을 염두에두고 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 동반자 등)
질문의 반복 부분에 대해 최근에이 테스트를 실행 한 결과 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
다른 브라우저 / 환경에서 자체 테스트를 실행하려는 경우.
마찬가지로 배열과 세트에 항목을 추가하고 제거하는 벤치 마크를 추가합니다.
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
forEach
있지만 예상 한대로는 아닐 것입니다. 비슷한 행동을 원한다면 그것도 s.forEach(function(e) { s.clear(); })
마찬가지입니다.
delete
세트에서하는 일과 비교되지 않습니다 .
splice(0)
배열을 비 웁니다.