【问题标题】:Can I pre-allocate memory for a Map/Set with a known number of elements?我可以为具有已知数量元素的 Map/Set 预先分配内存吗?
【发布时间】:2020-01-30 15:08:22
【问题描述】:

在 JS 数组的情况下,可以创建一个具有预定义长度的数组。如果我们将长度传递给构造函数,例如new Array(itemCount),JS 引擎可以为数组预先分配内存,因此在向数组添加新项目时不需要重新分配内存。

是否可以为MapSet 预先分配内存?它不像数组那样接受构造函数中的长度。如果您知道一个映射将包含 10 000 个项目,那么分配一次内存应该比多次重新分配内存要高效得多。

这是关于数组的same question


在 cmets 中有讨论,预定义的数组大小是否对性能有影响。我创建了一个simple test 来检查它,结果是:

  • Chrome - 使用预定义大小填充数组的速度大约快 3 倍
  • Firefox - 没有太大区别,但是预定义长度的数组要快一点
  • 边缘 - 填充预定义大小的数组大约快 1.5 倍

有一个建议是创建条目数组并将其传递给地图,因此地图可以从数组中获取长度。我也创建了这样的test,结果是:

  • Chrome - 构建接受条目数组的地图的速度大约慢 1.5 倍
  • Firefox - 没有区别
  • Edge - 将条目数组传递给 Map 构造函数的速度大约快 1.5 倍

【问题讨论】:

  • “JS 引擎可以为数组预先分配内存” 真的可以吗?数组可以包含异构数据。虽然我不得不承认对浏览器引擎没有洞察力,但看起来在那里可以做的事情并不多。但当然,我可能完全错了。编辑:我查看了链接的问题,但我仍然怀疑性能差异实际上是由于内存分配还是其他原因。
  • @ASDFGerte 你呢? Hashmaps 也有大小,增长它们比增长数组更复杂......
  • 我相信以前的 cmets 对 new Array(size) 为数组中的元素分配内存的想法感到不满。它没有。它为将放置数组元素的“槽”分配内存。这确实会为阵列带来性能优势。 Maps 和 Sets 使用不同的算法来存储他们的数据,如果我没记错的话,可能不会获得相同的性能优势。
  • 调用new Map(array) 可以将arrays 的长度作为大小的谓词。不确定是不是。
  • @JonasWilms 对实际发生的事情的回答将针对许多与实现相关的事情。有时我只是在戳戳,不管这是否有意义,开始深入了解所需的细节。我知道,哈希图也在增长。该评论不应暗示他们没有。异构数据结构,以及引擎如何对大量参数进行猜测,例如大小,或者它们将包含什么内容,以及将在内部分配什么结构,是一个很大的话题。

标签: javascript performance ecmascript-6


【解决方案1】:

没有办法影响引擎分配内存的方式,所以无论你做什么,你不能保证你使用的技巧适用于每个引擎,甚至是同一引擎的另一个版本.

在 JS 中有类型数组,它们是唯一具有固定大小的数据结构。使用它们,您可以实现自己的固定大小的哈希图。

一个非常幼稚的实现在插入时稍快²,不确定它在读取和其他方面的表现如何(它也只能存储 > 0 的 8 位整数):

function FixedMap(size) {
   const keys = new Uint8Array(size),
         values = new Uint8Array(size);
   
   return {
     get(key) {
        let pos = key % size;
        while(keys[pos] !== key) { 
          if(keys[pos] % size !== key % size) 
              return undefined;
          pos = (pos + 1) % size;
        }
        return values[pos];
     },
     set(key, value) {
       let pos = key % size;
       while(key[pos] && key[pos] !== key) pos = (pos + 1) % size;
       keys[pos] = value;
       values[pos] = value;
     }
   };
}
   

console.time("native");

const map = new Map();

for(let i = 0; i < 10000; i++)
  map.set(i, Math.floor(Math.random() * 1000));
  
console.timeEnd("native");

console.time("self");

const fixedMap = FixedMap(10000);

for(let i = 0; i < 10000; i++)
  fixedMap.set(i, Math.floor(Math.random() * 1000));
console.timeEnd("self");

² 营销人员会说 速度提高了 20%! 我会说 速度提高了 2 毫秒,我在这上面花了 10 多分钟...

【讨论】:

  • 对于固定大小/静态的地图,寻找一个完美的散列函数甚至可能是值得的。
  • @bergi 我用的那个有点完美......基准有点没用...... :)
  • 我喜欢这个答案,我觉得Javascript在这方面有点太松散了,至少需要提供一种指定值类型的方法,比如Uint32Array
【解决方案2】:

不,这是不可能的。即使Array(length) 技巧仍然有效,也值得怀疑,引擎在优化数组分配方面已经变得更好,并且可能选择了不同的策略来(预先)确定要分配的内存大小。

对于Maps 和Sets,不存在这样的技巧。你最好的办法是通过将一个可迭代对象传递给它们的构造函数来创建它们,比如new Map(array),而不是使用set/add 方法。这至少为引擎提供了机会将可迭代大小作为提示 - 尽管过滤掉重复项仍然会导致差异。
免责声明:我不知道是否有任何引擎实施或计划实施这样的优化。这样做可能不值得,特别是如果当前的内存分配策略已经足够好,可以减少任何改进。

【讨论】:

  • 感谢您的意见!我在问题中添加了一个测试,它检查Array(length) 技巧,它似乎有效。我也测试了您建议的解决方案,将一组条目放入 Map 构造函数,在 Chrome 中它比一个一个地放置元素更糟糕,所以我不推荐它。但我同意你回答的主要思想,谢谢。
  • @ValeriyKatkov 真可惜。我想这足以被报告为错误......
猜你喜欢
  • 1970-01-01
  • 2013-10-30
  • 2020-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多