Node.js映射中的最大条目数?

时间:2019-01-31 03:30:56

标签: javascript node.js dictionary v8

我在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的限制。)

3 个答案:

答案 0 :(得分:6)

V8开发人员在这里。我可以证实,2 ^ 24是中的条目的最大数目的Map。这是不是一个错误,它只是实现定义的限制。

极限由下式确定:

  • FixedArray的{​​{1}}后备存储区的最大大小为1GB(与总堆大小限制无关)
  • 在64位系统上,这意味着每Map最多有1GB / 8B = 2 ^ 30/2 ^ 3 = 2 ^ 27〜= 134M个最大元素
  • A 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