我在Node.js v11.9.0中制作了一个大的Map
,它一直失败,并显示“致命错误:无效的表大小分配失败-JavaScript堆内存不足”。我的地图的键和值不应接近Node堆大小的任何地方,因此我尝试仅制作一个地图并将数字键和值插入其中:
var N = Math.pow(2, 26);
var map = new Map();
for (var i = 0; i < N; i++) {
map.set(i, i + 1);
if (i % 1e5 === 0) { console.log(i / 1e6); }
}
该程序在插入大约1,660万个条目之后使Node.n崩溃。这个数字似乎可疑地接近2 ^ 24,所以用if (i > 16777200) { console.log(i); }
替换上面的日志,我发现程序立即崩溃了成功打印“ 16777215”(小于2 ^ 24之一)后。
问题。 节点Map
中的条目数是否有记录的限制,接近2 ^ 24?有什么方法可以提高该限制?
(N.B。以node --max-old-space-size=4096
的身份运行Node不能防止崩溃,因为Node使用的内存远远少于4 GB。)
(注:2。我认为这不是哈希冲突问题,因为在我的实际代码中,地图包含(简短)字符串而不是数字。)
(N.B。3.在Firefox的JavaScript控制台中运行上述程序并不会杀死Firefox – Firefox不断添加超过3000万个条目。但是,Chrome像Node一样崩溃。因此这很可能是V8的限制。)
答案 0 :(得分:6)
V8开发人员在这里。我可以证实,2 ^ 24是中的条目的最大数目的Map
。这是不是一个错误,它只是实现定义的限制。
极限由下式确定:
FixedArray
的{{1}}后备存储区的最大大小为1GB(与总堆大小限制无关)Map
最多有1GB / 8B = 2 ^ 30/2 ^ 3 = 2 ^ 27〜= 134M个最大元素FixedArray
需要每个条目(键,值,下一个铲斗连杆)3层的元件,并且具有50%的最大负载因数(以避免因许多桶碰撞减速),和它的容量必须2的幂。2 ^ 27 /(3 * 2)向下舍入到2的下一个幂是2 ^ 24,这是您观察到的极限。 FWIW,是有限制的一切:除了最大堆尺寸,还有一个最大Map
长度,最大String
长度,最大Array
长度,最大{{1 }}大小,最大堆栈大小等。这些限制中的任何一个都有可能引起争议,有时提高它们是有道理的,但是这样的限制仍然存在。我不知道要把这个特定限制提高两倍,我不知道该怎么做,而且我也不知道两个因素是否足以满足您的期望。
答案 1 :(得分:3)
有趣的是,如果您更改代码以创建两个Map
对象并同时插入它们,它们都会在同一点崩溃,即16.7:
var N = Math.pow(2, 26);
var m1 = new Map();
var m2 = new Map();
for (var i = 0; i < N; i++) {
m2.set(i, i + 1);
m1.set(i, i + 1);
if (i % 1e5 === 0) { console.log(m1.size / 1e6); }
}
在任何给定的Map中创建超过2 24 个条目,而不是在所有Map对象中全局创建条目时,会发生奇怪的事情。
我认为您已经发现了一个V8错误,需要报告。
答案 2 :(得分:1)
我编写了BigMap和BigSet类,这些类在100%兼容的情况下可以超出该限制,并基于标准Map和Set建立,当达到限制时,我只是创建新的Map(或Set)。
const kMaxSize = Math.pow(2, 24)
const BigMap = class {
/*
public api, compatible with "Map"
*/
constructor (...parameters) {
this.maps = [new Map(...parameters)]
}
set (key, value) {
const map = this.maps[this.maps.length - 1]
if (map.size === kMaxSize) {
this.maps.push(new Map())
return this.set(key, value)
} else {
return map.set(key, value)
}
}
has (key) {
return _mapForKey(this.maps, key) !== undefined
}
get (key) {
return _valueForKey(this.maps, key)
}
delete (key) {
const map = _mapForKey(this.maps, key)
if (map !== undefined) {
return map.delete(key)
}
return false
}
clear () {
for (let map of this.maps) {
map.clear()
}
}
get size () {
let size = 0
for (let map of this.maps) {
size += map.size
}
return size
}
forEach (callbackFn, thisArg) {
if (thisArg) {
for (let value of this) {
callbackFn.call(thisArg, value)
}
} else {
for (let value of this) {
callbackFn(value)
}
}
}
entries () {
return _iterator(this.maps, 'entries')
}
keys () {
return _iterator(this.maps, 'keys')
}
values () {
return _iterator(this.maps, 'values')
}
[Symbol.iterator] () {
return _iterator(this.maps, Symbol.iterator)
}
}
/*
private function
*/
function _mapForKey (maps, key) {
for (let index = maps.length - 1; index >= 0; index--) {
const map = maps[index]
if (map.has(key)) {
return map
}
}
}
function _valueForKey (maps, key) {
for (let index = maps.length - 1; index >= 0; index--) {
const map = maps[index]
const value = map.get(key)
if (value !== undefined) {
return value
}
}
}
function _iterator (items, name) {
let index = 0
var iterator = items[index][name]()
return {
next: () => {
let result = iterator.next()
if (result.done && index < (items.length - 1)) {
index++
iterator = items[index][name]()
result = iterator.next()
}
return result
},
[Symbol.iterator]: function () {
return this
}
}
}
BigMap.length = 0
/*
Big Set
*/
const BigSet = class {
/*
public api, compatible with "Set"
*/
constructor (...parameters) {
this.sets = [new Set(...parameters)]
}
add (key) {
const set = this.sets[this.sets.length - 1]
if (set.size === kMaxSize) {
this.sets.push(new Set())
return this.add(key)
} else {
return set.add(key)
}
}
has (key) {
return _setForKey(this.sets, key) !== undefined
}
delete (key) {
const set = _setForKey(this.sets, key)
if (set !== undefined) {
return set.delete(key)
}
return false
}
clear () {
for (let set of this.sets) {
set.clear()
}
}
get size () {
let size = 0
for (let set of this.sets) {
size += set.size
}
return size
}
forEach (callbackFn, thisArg) {
if (thisArg) {
for (let value of this) {
callbackFn.call(thisArg, value)
}
} else {
for (let value of this) {
callbackFn(value)
}
}
}
entries () {
return _iterator(this.sets, 'entries')
}
keys () {
return _iterator(this.sets, 'keys')
}
values () {
return _iterator(this.sets, 'values')
}
[Symbol.iterator] () {
return _iterator(this.sets, Symbol.iterator)
}
}
/*
private function
*/
function _setForKey (sets, key) {
for (let index = sets.length - 1; index >= 0; index--) {
const set = sets[index]
if (set.has(key)) {
return set
}
}
}
function _iterator (items, name) {
let index = 0
var iterator = items[index][name]()
return {
next: () => {
let result = iterator.next()
if (result.done && index < (items.length - 1)) {
index++
iterator = items[index][name]()
result = iterator.next()
}
return result
},
[Symbol.iterator]: function () {
return this
}
}
}
BigSet.length = 0