在v8实现中,检索/查找是否是O(1)的公平假设?
是的。 V8使用一种变体的哈希表,通常对于这些操作具有O(1)
的复杂度。
有关详细信息,您可能需要查看https://codereview.chromium.org/220293002/,其中基于https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables 实现了OrderedHashTable
。
src/collections.js
迁移到了 src/builtins/builtins-collections-gen.cc
https://github.com/v8/v8/blob/master/src/builtins/builtins-collections-gen.cc#L1424 - protoEvangelion对于不想深入了解细节的人:
1:我们可以假设好的哈希表实现的时间复杂度几乎为O(1)。
2:这篇博客是由V8团队发布的,其中解释了对其 Map
、Set
、WeakSet
和WeakMap
的哈希表实现进行的一些内存优化: 优化哈希表:隐藏哈希码
基于1和2:V8的Set和Map的get
& set
& add
& has
时间复杂度几乎为O(1)。
set
或get
)的O(1)时间复杂度。让我们考虑最坏情况,当表中有N个条目(它满了),所有条目都属于单个桶,并且所需的条目位于尾部。在这种情况下,查找需要通过链元素移动N次。
另一方面,在最佳情况下,当表已满,但每个桶都有2个条目时,查找最多需要2次移动。
众所周知,哈希表中的各个操作非常“廉价”,但重新哈希是不廉价的。重新哈希具有O(N)时间复杂度,并需要在堆上分配新哈希表。此外,重新哈希作为插入或删除操作的一部分执行,必要时进行。因此,例如,map.set()调用可能比您预期的更昂贵。幸运的是,重新哈希是相对较少的操作。
除此之外,内存布局或哈希函数等细节也很重要,但我不准备在这里详细介绍。如果你好奇V8 Maps是如何工作的,你可以在这里找到更多细节here。我曾经对这个主题感兴趣,并尝试以易读的方式分享我的发现。let map = new Map();
let obj = {};
const benchMarkMapSet = size => {
console.time("benchMarkMapSet");
for (let i = 0; i < size; i++) {
map.set(i, i);
}
console.timeEnd("benchMarkMapSet");
};
const benchMarkMapGet = size => {
console.time("benchMarkMapGet");
for (let i = 0; i < size; i++) {
let x = map.get(i);
}
console.timeEnd("benchMarkMapGet");
};
const benchMarkObjSet = size => {
console.time("benchMarkObjSet");
for (let i = 0; i < size; i++) {
obj[i] = i;
}
console.timeEnd("benchMarkObjSet");
};
const benchMarkObjGet = size => {
console.time("benchMarkObjGet");
for (let i = 0; i < size; i++) {
let x = obj[i];
}
console.timeEnd("benchMarkObjGet");
};
let size = 2e6;
benchMarkMapSet(size);
benchMarkObjSet(size);
benchMarkMapGet(size);
benchMarkObjGet(size);
benchMarkMapSet: 382.935毫秒
benchMarkObjSet: 76.077毫秒
benchMarkMapGet: 125.478毫秒
benchMarkObjGet: 2.764毫秒
benchMarkMapSet: 373.172毫秒
benchMarkObjSet: 77.192毫秒
benchMarkMapGet: 123.035毫秒
benchMarkObjGet: 2.638毫秒
map.set('k' + i, i)
,obj['k' + i] = i
等时,对象的速度比 map 显著慢。 - Victor为什么我们不进行测试呢?
var size_1 = 1000,
size_2 = 1000000,
map_sm = new Map(Array.from({length: size_1}, (_,i) => [++i,i])),
map_lg = new Map(Array.from({length: size_2}, (_,i) => [++i,i])),
i = size_1,
j = size_2,
s;
s = performance.now();
while (i) map_sm.get(i--);
console.log(`Size ${size_1} map returns an item in average ${(performance.now()-s)/size_1}ms`);
s = performance.now();
while (j) map_lg.get(j--);
console.log(`Size ${size_2} map returns an item in average ${(performance.now()-s)/size_2}ms`);
所以对我来说,随着规模增长,它似乎会收敛于O(1)。那么我们就称之为O(1)吧。