64 位 OS/compile 的哈希函数,对于实际上只是 4 字节 int 的对象

hash function for a 64-bit OS/compile, for an object that's really just a 4-byte int

我有一个名为 Foo 的 class,它私下只不过是 4 字节的 int。如果我 return 它的值为 8 字节 size_t,我会搞砸 unordered_map<> 还是其他什么?我可以用 return foo + foo << 32; 之类的东西填充所有位。那会更好,还是更糟,因为现在所有哈希值都是 0x100000001 的倍数?或者 return ~foo + foo << 32; 会使用所有 64 位并且也没有公因数怎么样?

namespace std {
  template<> struct hash<MyNamespace::Foo> {
    typedef size_t result_type;
    typedef MyNamespace::Foo argument_tupe;
    size_t operator() (const MyNamespace::Foo& f ) const { return (size_t) f.u32InternalValue; }
  };
}

增量 uint32_t 密钥转换为 uint64_t 效果很好

unordered_map 将为 hash-table 增量保留 space。

key的低位用于确定bucket位置,以4entries/buckets为例,使用低位2位。 具有提供相同桶(桶数的倍数)的键的元素链接在链表中。这带有load-factor.

的概念
// 4 Buckets example

******** ******** ******** ******** ******** ******** ******** ******XX

bucket 00 would contains keys like {0, 256, 200000 ...}
bucket 01 would contains keys like {1, 513, 4008001 ...}
bucket 10 would contains keys like {2, 130, 10002 ...}
bucket 11 would contains keys like {3, 259, 1027, 20003, ...}

如果您尝试在桶中保存额外的值,并且加载因子超过限制,则 table 会调整大小(例如,您尝试在 4 桶中保存第 5 个元素 table 与 load_factor=1.0).

因此:

在达到 2^32 个元素之前,使用 uint32_tuint64_t 键几乎没有影响 hash-table。

Would that be better, or would it be worse as all hashes are now multiples of 0x100000001?

在达到 32 位溢出 (2^32) 之前,这不会产生任何影响 hash-table。

增量 uint32_t 和 uint64_t 之间的良好密钥转换:

key64 = static_cast<uint64>(key32);

增量 uint32_t 和 uint64_t 之间的错误密钥转换:

key64 = static_cast<uint64>(key32)<<32;

最好的办法是尽可能保持密钥均匀,避免一次又一次地使用相同的因子进行哈希。例如。在下面的代码中,所有因子为 7 的键都会发生冲突,直到大小调整为 16 个桶。

https://onlinegdb.com/r1N7TNySv

#include <iostream>
#include <unordered_map>

using namespace std;

// Print to std output the internal structure of an unordered_map.
template <typename K, typename T>
void printMapStruct(unordered_map<K, T>& map)
{
    cout << "The map has " << map.bucket_count()<< 
        " buckets and max load factor: " << map.max_load_factor() << endl;
    
    for (size_t i=0; i< map.bucket_count(); ++i)
    {
        cout << "    Bucket " << i << ": ";
        for (auto it=map.begin(i); it!=map.end(i); ++it)
        {
            cout << it->first << " ";
        }
        cout << endl;
    }
    cout << endl;
}

// Print the list of bucket sizes by this implementation
void printMapResizes()
{
    cout << "Map bucket counts:"<< endl;
    unordered_map<size_t, size_t> map;
    
    size_t lastBucketSize=0;
    for (size_t i=0; i<1024*1024; ++i)
    {
        if (lastBucketSize!=map.bucket_count())
        {
            cout << map.bucket_count() << " ";
            lastBucketSize = map.bucket_count();
        }
        map.emplace(i,i);
    }
    cout << endl;
}

int main()
{
    unordered_map<size_t,size_t> map;
    
    printMapStruct(map);
    
    map.emplace(0,0);
    map.emplace(1,1);
    printMapStruct(map);
    
    map.emplace(72,72);
    map.emplace(17,17);
    printMapStruct(map);
    
    map.emplace(7,7);
    map.emplace(14,14);
    printMapStruct(map);
    
    printMapResizes();

    return 0;
}

注意存储桶计数:

在上面的例子中,桶数如下:

1 3 7 17 37 79 167 337 709 1493 3209 6427 12983 26267 53201 107897 218971 444487 902483 1832561

这似乎是故意遵循一系列素数(尽量减少碰撞)。我不知道背后的功能。

std::unordered_map<> bucket_count() after default rehash