es6 Map和Set复杂度,v8实现

时间:2015-11-09 14:36:51

标签: javascript ecmascript-6 v8

在v8实现检索/查找中是否公平假设是O(1)?

(我知道标准并不能保证)

5 个答案:

答案 0 :(得分:28)

  

在v8实现检索/查找中是否公平假设是O(1)?

是。 V8使用散列表的变体,这些散列表通常具有O(1)这些操作的复杂性。

有关详细信息,您可能需要查看基于https://codereview.chromium.org/220293002/实施OrderedHashTable的{​​{3}}。

答案 1 :(得分:3)

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.935ms benchMarkObjSet: 76.077ms benchMarkMapGet: 125.478ms benchMarkObjGet: 2.764ms

benchMarkMapSet: 373.172ms benchMarkObjSet: 77.192ms benchMarkMapGet: 123.035ms benchMarkObjGet: 2.638ms

答案 2 :(得分:2)

最初的问题已经回答,但是O(1)并没有说明实现的效率。

首先,我们需要了解哈希表用于Maps的哪些变体。 “经典”哈希表不会提供服务,因为它们不提供任何迭代顺序保证,而ES6规范要求插入必须以迭代顺序进行。因此,V8中的地图是建立在所谓的deterministic hash tables之上的。这个想法与经典算法相似,但是存储桶还有另一层间接层,所有条目都插入并存储在固定大小的连续数组中。确定性哈希表算法确实为基本操作(例如setget)保证了O(1)的时间复杂度。

接下来,我们需要知道哈希表的初始大小是多少,负载因子,以及它如何(以及何时)增长/收缩。简短的答案是:初始大小为4,负载因子为2,表(即Map)一旦达到其容量,其大小便会增长x2,而一旦删除的条目超过1/2,则该表(即地图)会缩小。 >

让我们考虑一下最坏的情况,即该表的N个条目中有N个(已满),所有条目都属于一个存储桶,而所需的条目位于表尾。在这种情况下,查找需要N个在链元素中移动。

另一方面,在最好的情况下,如果表已满,但每个存储桶都有2个条目,则查找最多需要2步。

众所周知的事实是,虽然哈希表中的各个操作是“便宜”的,但重新哈希不是。重新哈希的时间复杂度为O(N),需要在堆上分配新的哈希表。此外,在必要时,将重新哈希处理作为插入或删除操作的一部分进行。因此,例如,map.set()调用可能比您预期的昂贵。幸运的是,重新哈希处理是相对少见的操作。

除此之外,诸如内存布局或哈希函数之类的细节也很重要,但是我在这里不打算讨论这些细节。如果您对V8 Maps的工作原理感到好奇,则可以找到更多详细信息here。不久前,我对该主题感兴趣,并试图以可读的方式分享我的发现。

答案 3 :(得分:0)

对于那些不想钻入兔子洞太深的人:

1:我们可以假设良好的哈希表实现实际上具有O(1)时间复杂度。
2:这是V8小组发布的博客,解释了如何针对MapSetWeakSet和{{ 1}}:Optimizing hash tables: hiding the hash code

基于1和2:V8的Set和Map的WeakMapgetsetadd的时间复杂度实际上是O(1)。

答案 4 :(得分:0)

我们为什么不直接测试。

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)。