【问题标题】:Are JavaScript Map Objects Indexed to Optimize map.get? [duplicate]JavaScript 地图对象是否被索引以优化 map.get? [复制]
【发布时间】:2016-03-23 12:58:21
【问题描述】:

在 V8 的幕后,JavaScript-Map-object 的键是否以某种优化map.get 方法的方式进行索引?还是map.get() 只是循环整个地图,直到找到关键匹配项?

我对 map.get 在 500,000 多个键/值对的更大映射上的效率感兴趣。我有这么多映射,我只想缓存在 RAM 中,而不是查询已经为快速检索值而索引键的数据库。在我看来,如果 Map 对象的键以某种方式在幕后建立索引,那么查询 RAM 而不是数据库会更快。

摘要:

function randomUniqueThing()
{
   // returns (magically) a unique random: 
   //  string, number, function, array or object.
}
var objMap = new Map();
var count = 0;
var thing1,thing2;
while(count < 500000)
{
    thing1 = randomUniqueThing();
    thing2 = randomUniqueThing();
    objMap.set(thing1, thing2);
    count++;
}
var lastValue = objMap.get(thing1); // Will getting this last
                                    // thing's value take longer
                                    // than getting other values?

【问题讨论】:

    标签: javascript node.js dictionary collections ecmascript-6


    【解决方案1】:

    是的,正如您对这种数据类型所期望的那样,Map 确实在后台使用了哈希表。

    来源证明:

    与往常一样,证据在源代码中:

    摘自V8源文件src/objects.hclass JSMap

    // The JSMap describes EcmaScript Harmony maps
    class JSMap : public JSCollection {
     public:
      DECLARE_CAST(JSMap)
    
      static void Initialize(Handle<JSMap> map, Isolate* isolate);
      static void Clear(Handle<JSMap> map);
    
      // Dispatched behavior.
      DECLARE_PRINTER(JSMap)
      DECLARE_VERIFIER(JSMap)
    
     private:
      DISALLOW_IMPLICIT_CONSTRUCTORS(JSMap);
    };
    

    如我们所见,JSMap 扩展了 JSCollection

    现在,如果我们看一下JSCollection 的声明:

    摘自V8源文件src/objects.hclass JSCollection

    class JSCollection : public JSObject {
     public:
      // [table]: the backing hash table
      DECL_ACCESSORS(table, Object)
    
      static const int kTableOffset = JSObject::kHeaderSize;
      static const int kSize = kTableOffset + kPointerSize;
    
     private:
      DISALLOW_IMPLICIT_CONSTRUCTORS(JSCollection);
    };
    

    在这里我们可以看到它使用了一个哈希表,并带有一个很好的注释来澄清它。


    关于该哈希表是否仅引用对象属性而不是 get 方法存在一些问题。正如我们可以从源代码到Map.prototype.get,正在使用哈希映射。

    摘自V8源文件src/js/collection.jsMapGet

    function MapGet(key) {
      if (!IS_MAP(this)) {
        throw MakeTypeError(kIncompatibleMethodReceiver,
                            'Map.prototype.get', this);
      }
      var table = %_JSCollectionGetTable(this);
      var numBuckets = ORDERED_HASH_TABLE_BUCKET_COUNT(table);
      var hash = GetExistingHash(key);
      if (IS_UNDEFINED(hash)) return UNDEFINED;
      var entry = MapFindEntry(table, numBuckets, key, hash);
      if (entry === NOT_FOUND) return UNDEFINED;
      return ORDERED_HASH_MAP_VALUE_AT(table, entry, numBuckets);
    }
    

    摘自V8源文件src/js/collection.jsMapFindEntry

    function MapFindEntry(table, numBuckets, key, hash) {
      var entry = HashToEntry(table, hash, numBuckets);
      if (entry === NOT_FOUND) return entry;
      var candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets);
      if (key === candidate) return entry;
      var keyIsNaN = NumberIsNaN(key);
      while (true) {
        if (keyIsNaN && NumberIsNaN(candidate)) {
          return entry;
        }
        entry = ORDERED_HASH_MAP_CHAIN_AT(table, entry, numBuckets);
        if (entry === NOT_FOUND) return entry;
        candidate = ORDERED_HASH_MAP_KEY_AT(table, entry, numBuckets);
        if (key === candidate) return entry;
      }
      return NOT_FOUND;
    }
    

    通过基准测试证明:

    还有另一种方法可以测试它是否使用哈希映射。输入许多条目,并测试最长和最短的查找时间是多少。像这样的:

    'use strict';
    
    let m = new Map();
    let a = [];
    
    for (let i = 0; i < 10000000; i++) {
        let o = {};
        m.set(o, i);
        a.push(o);
    }
    
    let lookupLongest = null;
    let lookupShortest = null;
    
    a.forEach(function(item) {
        let dummy;
        let before = Date.now();
        dummy = m.get(item);
        let after = Date.now();
        let diff = after - before;
        if (diff > lookupLongest || lookupLongest === null) {
            lookupLongest = diff;
        }
        if (diff < lookupShortest || lookupShortest === null) {
            lookupShortest = diff;
        }
    });
    console.log('Longest Lookup Time:', lookupLongest);
    console.log('Shortest Lookup Time:', lookupShortest);
    

    几秒钟后,我得到以下输出:

    $ node test.js
    Longest Lookup Time: 1
    Shortest Lookup Time: 0
    

    如果循环遍历每个条目,那么这种近距离查找时间肯定是不可能的。

    【讨论】:

    • 正因为如此,您是否希望 map.get 比为这些相同的 500,000 个键值中的任何一个查询索引数据库表更快?
    • @LonnieBest 我绝对希望这种情况发生,因为它们已经在内存中可用。取决于数据,数据库的查找速度可能会更快,但我想延迟会超过这个。如有疑问,请对其进行基准测试!
    • @LonnieBest 如果数据非常大,则不会。查询大型数据集的速度来自二进制搜索和/或哈希表。如果您必须遍历一个包含数千个结果的未排序数组,这可能比在索引数据数据库中进行二分搜索要花费更长的时间。
    • "其实 Map 是 Object 的子类。" - 当然是,因为所有的 map 对象都是对象。但这似乎只是表明在 Map 上使用属性(使用 .[])是使用哈希图实现的。 .get().set() 呢?如果 C++ 代码对此有更多说明,请原谅我。
    • @Bergi 够公平的。有关get 实施的更多信息,请参阅我的更新答案。
    猜你喜欢
    • 1970-01-01
    • 2012-02-08
    • 2014-09-06
    • 2021-12-03
    • 2021-07-31
    • 1970-01-01
    • 2015-02-28
    • 2018-07-14
    • 2023-01-30
    相关资源
    最近更新 更多