[英]Javascript Set vs. Array performance
这可能是因为 Sets 对于 Javascript 来说相对较新,但我无法在 StackO 或其他任何地方找到一篇文章,讨论 Javascript 中两者之间的性能差异。 那么,就性能而言,两者之间有什么区别? 具体来说,当涉及到删除、添加和迭代时。
好的,我已经测试了从数组和集合中添加、迭代和删除元素。 我使用 10 000 个元素进行了“小”测试,并使用 100 000 个元素进行了“大”测试。 这是结果。
无论添加多少元素, .push
数组方法似乎比.add
set 方法快 4 倍。
对于测试的这一部分,我使用了for
循环来遍历数组,并使用for of
循环来遍历集合。 同样,对数组的迭代速度更快。 这一次似乎是指数级的,因为在“小”测试期间花费了两倍的时间,在“大”测试期间花费的时间几乎是四倍。
现在这就是它变得有趣的地方。 我使用for
循环和.splice
的组合从数组中删除一些元素,我使用for of
和.delete
从集合中删除一些元素。 对于“小”测试,从集合中删除项目的速度大约快三倍(2.6 毫秒对 7.1 毫秒),但“大”测试的情况发生了巨大变化,从数组中删除项目需要 1955.1 毫秒,而它只需要花了 83.6 毫秒将它们从集合中移除,快了 23 倍。
在 10k 个元素时,两个测试的运行时间相当(数组:16.6 毫秒,集合:20.7 毫秒)但在处理 100k 个元素时,集合是明显的赢家(数组:1974.8 毫秒,集合:83.6 毫秒)但仅仅是因为删除操作。 否则阵列更快。 我不能确切地说出这是为什么。
我尝试了一些混合场景,其中创建并填充了一个数组,然后将其转换为一个集合,其中一些元素将被删除,然后该集合将重新转换为一个数组。 尽管这样做会比删除数组中的元素提供更好的性能,但在集合之间进行传输所需的额外处理时间超过了填充数组而不是集合的收益。 最后,只处理一个集合会更快。 尽管如此,这是一个有趣的想法,如果人们选择使用数组作为一些没有重复项的大数据的数据集合,那么如果需要在一个元素中删除许多元素,那么在性能方面可能是有利的操作,将数组转换为集合,执行移除操作,并将集合转换回数组。
数组代码:
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.")
观察:
- 设置操作可以理解为执行流中的快照。
- 我们不是一个确定的替代品。
- 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 对象的 add 和 push 方法:
数组/推送(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 和 Set 不是在相等的条件下开始的。 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
循环中完成。
慢(例如new Set(largeArray)
快速(例如 6ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }
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/
与具有 40.000 个元素的difference()
、 intersection()
、 union()
和uniq()
(+ 他们的 iteratee 伙伴等)进行现实生活比较
对于您问题的迭代部分,我最近运行了此测试,发现 Set 的性能远远优于包含 10,000 个项目的 Array(在同一时间范围内可能发生的操作大约是 10 倍)。 并且取决于浏览器在类似测试中击败或输给 Object.hasOwnProperty。
Set 和 Object 都有它们的“has”方法,它们似乎以 O(1) 的方式执行,但取决于浏览器的实现,单个操作可能需要更长或更短的时间。 似乎大多数浏览器在 Object 中实现 key 的速度比 Set.has() 快。 甚至 Object.hasOwnProperty 包括对密钥的额外检查,至少比我在 Chrome v86 上的 Set.has() 快约 5%。
https://jsperf.com/set-has-vs-object-hasownproperty-vs-array-includes/1
更新:11/11/2020: https ://jsbench.me/irkhdxnoqa/2
如果您想使用不同的浏览器/环境运行自己的测试。
同样,我将添加一个基准,用于将项目添加到数组与设置和删除。
如果属性查找是您的主要关注点,这里有一些数字。
JSBench 测试https://jsbench.me/3pkjlwzhbr/1
// https://jsbench.me/3pkjlwzhbr/1 // https://docs.google.com/spreadsheets/d/1WucECh5uHlKGCCGYvEKn6ORrQ_9RS6BubO208nXkozk/edit?usp=sharing // JSBench forked from https://jsbench.me/irkhdxnoqa/2 var theArr = Array.from({ length: 10000 }, (_, el) => el) var theSet = new Set(theArr) var theObject = Object.assign({}, ...theArr.map(num => ({ [num]: true }))) var theMap = new Map(theArr.map(num => [num, true])) var theTarget = 9000 // Array function isTargetThereFor(arr, target) { const len = arr.length for (let i = 0; i < len; i++) { if (arr[i] === target) { return true } } return false } function isTargetThereForReverse(arr, target) { const len = arr.length for (let i = len; i > 0; i--) { if (arr[i] === target) { return true } } return false } function isTargetThereIncludes(arr, target) { return arr.includes(target) } // Set function isTargetThereSet(numberSet, target) { return numberSet.has(target) } // Object function isTargetThereHasOwnProperty(obj, target) { return obj.hasOwnProperty(target) } function isTargetThereIn(obj, target) { return target in obj } function isTargetThereSelectKey(obj, target) { return obj[target] } // Map function isTargetThereMap(numberMap, target) { return numberMap.has(target) }
for
循环for
循环(反向)array.includes(target)
set.has(target)
obj.hasOwnProperty(target)
target in obj
<- 慢 1.29%obj[target]
<- 最快map.has(target)
<- 慢 2.94% 非常欢迎来自其他浏览器的结果,请更新此答案。
您可以使用此电子表格制作精美的屏幕截图。
JSBench 测试分叉自Zargold 的回答。
在本文中,您将通过示例JavaScript 搜索优化了解差异及其对性能的影响,还有一些示例解释了在搜索过程中使用set.has()
时性能如何变化。
10 users in DB and 2 online-users
。100k users in DB and 1k online users
。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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.