最快的指针散列函数是什么?

新手上路,请多包涵

基于哈希表的容器是非常快的关联数组(例如 unordered_mapunordered_set )。

它们的性能高度依赖于用于为每个条目创建索引的散列函数。随着哈希表的增长,元素会一次又一次地重新哈希。

指针是简单类型,基本上是唯一标识对象的 48 字节值。问题在于,由于几个 LSB 为零,因此使用哈希函数作为结果的地址效率不高。

例子:

 struct MyVoidPointerHash {
    size_t operator()(const void* val) const {
        return (size_t)val;
    }
};

更快的实现是丢失一些位:

 struct MyVoidPointerHash2 {
    size_t operator()(const void* val) const {
        return ((size_t)val) >> 3; // 3 on 64 bit, 1 on 32 bit
    }
};

后者在使用散列集和映射的大型应用程序上产生了 10-20% 的性能提升,其中包含数以万计的元素,这些元素经常被构建和清除。

有人可以为散列指针提供更好的方案吗?

该功能需要是:

  1. 快速地!并且必须很好地内联。
  2. 提供合理的分配,允许罕见的冲突。

更新 - 基准测试结果

我运行了两组测试,一组用于 int* 和一个大小为 4KB 的类指针。结果非常有趣。

我使用 std::unordered_set 用于所有测试,数据大小为 16MB,在单个 new 调用中分配。第一个算法运行了两次,以确保缓存尽可能热并且 CPU 全速运行。

设置:VS2013 (x64)、i7-2600、Windows 8.1 x64。

  • VS2013默认哈希函数
  • 哈希1: return (size_t)(val);
  • 哈希2: return '(size_t)(val) >> 3;
  • 哈希3(@BasileStarynkevitch): uintptr_t ad = (uintptr_t)val; return (size_t)((13 * ad) ^ (ad >> 15));
  • 哈希4(@Roddy): uintptr_t ad = (uintptr_t)val; return (size_t)(ad ^ (ad >> 16));
  • 哈希5(@egur):

代码:

 template<typename Tval>
struct MyTemplatePointerHash1 {
    size_t operator()(const Tval* val) const {
        static const size_t shift = (size_t)log2(1 + sizeof(Tval));
        return (size_t)(val) >> shift;
    }
};

测试 1 - int*

  • VS2013 默认耗时 1292ms
  • Hash1 耗时 742ms
  • Hash2 耗时 343ms
  • Hash3 耗时 1008ms
  • Hash4 耗时 629ms
  • Hash5 耗时 350 毫秒

测试 1 - 4K_class*

  • VS2013 默认耗时 0.423ms
  • Hash1 耗时 23.889 毫秒
  • Hash2 耗时 6.331ms
  • Hash3 耗时 0.366ms
  • Hash4 耗时 0.390ms
  • Hash5 耗时 0.290ms

更新2:

到目前为止,获胜者是模板化哈希 (Hash5) 函数。各种块大小的最佳速度性能水平。

更新 3: 为基线添加了默认哈希函数。事实证明它远非最佳。

原文由 egur 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 670
2 个回答

让这个问题搁置一段时间后,我将发布迄今为止我最好的指针哈希函数:

 template<typename Tval>
struct MyTemplatePointerHash1 {
    size_t operator()(const Tval* val) const {
        static const size_t shift = (size_t)log2(1 + sizeof(Tval));
        return (size_t)(val) >> shift;
    }
};

它对各种块大小都具有高性能。

如果有人有更好的功能,我会改变接受的答案。

原文由 egur 发布,翻译遵循 CC BY-SA 3.0 许可协议

从理论的角度来看,正确的答案是:使用 std::hash 这可能是专门为尽可能好,如果这不适用,请使用一个好的散列函数而不是一个快速的散列函数。散列函数的速度与其质量无关。

实际 的答案是:使用 std::hash ,这很糟糕,但性能却出奇的好。

产生兴趣后,我在周末运行了大约 30 小时的基准测试。除其他外,我试图得到一个平均情况与最坏情况,并试图强制 std::unordered_map 进入最坏情况的行为,方法是故意给出关于插入的集合大小的桶计数的错误提示。

我比较了较差的哈希值( std::hash<T*> )与众所周知的总体质量好的通用哈希值(djb2,sdbm)以及这些导致输入长度非常短的变体,以及明确的哈希值被认为用于散列表(murmur2 和 murmur3),以及实际上比根本不散列更糟糕的散列,因为它们丢弃了熵。

由于由于对齐,指针上的最低 2-3 位始终为零,我认为值得将简单的右移测试为“哈希”,因此仅使用非零信息,以防哈希表仅使用最低的 N 位。事实证明, _对于合理的班次_(我也尝试过不合理的班次!),这实际上表现得很好。

发现

我的一些发现是众所周知且不足为奇的,而另一些则 非常 令人惊讶:

  • 很难预测什么是“好”哈希。编写好的散列函数很难。不足为奇,众所周知的事实,并再次得到证明。
  • 在每种情况下,没有一个哈希值明显优于所有其他哈希值。没有一个哈希值在 80% 的时间里都显着优于所有其他哈希值。第一个结果是意料之中的,但第二个结果却令人惊讶。
  • 真的很难让 std::unordered_map 表现得不好。即使故意对桶计数给出不好的提示,这将强制进行多次重新哈希,整体性能也不会差多少。只有以几乎荒谬的方式丢弃大部分熵的最 糟糕 的哈希函数才能显着影响性能超过 10-20%(例如 right_shift_12 ,实际上只导致 12 个不同的哈希50,000 个输入的值!在这种情况下,哈希映射的运行速度慢了大约 100 倍也就不足为奇了——我们基本上是在链表上进行随机访问查找。)。
  • 一些“有趣”的结果肯定是由于实现细节。我的实现(GCC)使用略大于 2^N 的素数桶数,并将具有相同哈希值的值 插入到链表中。
  • std::hash<T*> 的专业化对于 GCC 来说是完全可悲的(一个简单的 reinterpret_cast )。有趣的是,执行相同操作的仿函数 _在插入时始终执行得更快,而在随机访问时执行得更慢_。差异很小(在 8-10 秒的测试运行中需要十几毫秒),但它不是噪音,它始终存在——可能与指令重新排序或流水线有关。令人惊讶的是,完全相同的代码(也是无操作代码)在两种不同的场景中 始终 表现不同。
  • 可悲散列的性能 并不 比“好”散列或专门为散列表设计的散列差得多。事实上,有一半的时候,他们是表现最好的,或者是前三名。
  • “最好的”散列函数很少会导致最好的整体性能。
  • 在这个 SO 问题中作为答案发布的哈希值通常是可以的。它们的平均水平很好,但并不优于 std::hash 。通常他们会进入前 3-4 名。
  • 差的散列在某种程度上易受插入顺序的影响(在随机插入和随机插入后的随机查找上表现更差),而“好”的散列对插入顺序的影响更具弹性(差异很小或没有差异),但整体性能是还是慢了一点。

测试设置

测试不仅针对任何 4 字节或 8 字节(或其他)对齐的值进行,还针对通过在堆上分配完整的元素集并将分配器提供的地址存储在 std::vector 中获得的实际地址进行测试 --- (对象随后被删除,不再需要)。

地址被插入到新分配的 std::unordered_map 中,以存储在向量中的顺序进行每个测试,一次以原始顺序(“顺序”),一次在向量上应用 std::random_shuffle 之后.

对大小为 4、16、64、256 和 1024 的 50,000 和 1,000,000 个对象的集合进行了测试(为简洁起见,此处省略了 64 的结果,它们正如您所期望的那样,介于 16 和 256 之间——StackOverflow只允许发布 30k 个字符)。

测试套件执行了 3 次,结果随处变化 3 或 4 毫秒,但总体相同。此处发布的结果是最后一次运行。

“随机”测试中的插入顺序以及访问模式(在每个测试中)是伪随机的,但对于测试运行中的每个散列函数来说完全相同。

哈希基准下的时间是在一个整数变量中总结 4,000,000,000 个哈希值。

insert 列是创建 std::unordered_map ,分别插入 50,000 和 1,000,000 个元素并破坏地图的 50 次迭代的时间(以毫秒为单位)。

access 是以毫秒为单位对“向量”中的伪随机元素进行 100,000,000 次查找,然后在 unordered_map 中查找该地址的时间。

此时间平均包括访问 vector 中的随机元素的一次缓存未命中,至少对于大型数据集(小型数据集完全适合 L2)。

2.66GHz Intel Core2、Windows 7、gcc 4.8.1/MinGW-w64_32 上的所有时序。定时器粒度@ 1ms。

源代码

由于 Stackoverflow 的 30k 字符限制,源代码 在 Ideone 上可用。

注意: 在台式 PC 上运行完整的测试套件需要 2 个多小时,因此如果您想重现结果,请准备好散步。

试验结果

Benchmarking hash funcs...
std::hash                 2576
reinterpret_cast          2561
djb2                      13970
djb2_mod                  13969
sdbm                      10246
yet_another_lc            13966
murmur2                   11373
murmur3                   15129
simple_xorshift           7829
double_xorshift           13567
right_shift_2             5806
right_shift_3             5866
right_shift_4             5705
right_shift_5             5691
right_shift_8             5795
right_shift_12            5728
MyTemplatePointerHash1    5652
BasileStarynkevitch       4315

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access
std::hash                 421        6988
reinterpret_cast          408        7083
djb2                      451        8875
djb2_mod                  450        8815
sdbm                      455        8673
yet_another_lc            443        8292
murmur2                   478        9006
murmur3                   490        9213
simple_xorshift           460        8591
double_xorshift           477        8839
right_shift_2             416        7144
right_shift_3             422        7145
right_shift_4             414        6811
right_shift_5             425        8006
right_shift_8             540        11787
right_shift_12            1501       49604
MyTemplatePointerHash1    410        7138
BasileStarynkevitch       445        8014

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access
std::hash                 443        7570
reinterpret_cast          436        7658
djb2                      473        8791
djb2_mod                  472        8766
sdbm                      472        8817
yet_another_lc            458        8419
murmur2                   479        9005
murmur3                   491        9205
simple_xorshift           464        8591
double_xorshift           476        8821
right_shift_2             441        7724
right_shift_3             440        7716
right_shift_4             450        8061
right_shift_5             463        8653
right_shift_8             649        16320
right_shift_12            3052       114185
MyTemplatePointerHash1    438        7718
BasileStarynkevitch       453        8140

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access
std::hash                 8945       32801
reinterpret_cast          8796       33251
djb2                      11139      54855
djb2_mod                  11041      54831
sdbm                      11459      36849
yet_another_lc            14258      57350
murmur2                   16300      39024
murmur3                   16572      39221
simple_xorshift           14930      38509
double_xorshift           16192      38762
right_shift_2             8843       33325
right_shift_3             8791       32979
right_shift_4             8818       32510
right_shift_5             8775       30436
right_shift_8             10505      35960
right_shift_12            30481      91350
MyTemplatePointerHash1    8800       33287
BasileStarynkevitch       12885      37829

--------------------------------
sizeof(T)       = 4
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access
std::hash                 12183      33424
reinterpret_cast          12125      34000
djb2                      22693      51255
djb2_mod                  22722      51266
sdbm                      15160      37221
yet_another_lc            24125      51850
murmur2                   16273      39020
murmur3                   16587      39270
simple_xorshift           16031      38628
double_xorshift           16233      38757
right_shift_2             11181      33896
right_shift_3             10785      33660
right_shift_4             10615      33204
right_shift_5             10357      38216
right_shift_8             15445      100348
right_shift_12            73773      1044919
MyTemplatePointerHash1    11091      33883
BasileStarynkevitch       15701      38092

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access
std::hash                 415        8243
reinterpret_cast          422        8321
djb2                      445        8730
djb2_mod                  449        8696
sdbm                      460        9439
yet_another_lc            455        9003
murmur2                   475        9109
murmur3                   482        9313
simple_xorshift           463        8694
double_xorshift           465        8900
right_shift_2             416        8402
right_shift_3             418        8405
right_shift_4             423        8366
right_shift_5             421        8347
right_shift_8             453        9195
right_shift_12            666        18008
MyTemplatePointerHash1    433        8191
BasileStarynkevitch       466        8443

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access
std::hash                 450        8135
reinterpret_cast          457        8208
djb2                      470        8736
djb2_mod                  476        8698
sdbm                      483        9420
yet_another_lc            476        8953
murmur2                   481        9089
murmur3                   486        9283
simple_xorshift           466        8663
double_xorshift           468        8865
right_shift_2             456        8301
right_shift_3             456        8302
right_shift_4             453        8337
right_shift_5             457        8340
right_shift_8             505        10379
right_shift_12            1099       34923
MyTemplatePointerHash1    464        8226
BasileStarynkevitch       466        8372

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access
std::hash                 9548       35362
reinterpret_cast          9635       35869
djb2                      10668      37339
djb2_mod                  10763      37311
sdbm                      11126      37145
yet_another_lc            11597      39944
murmur2                   16296      39029
murmur3                   16432      39280
simple_xorshift           16066      38645
double_xorshift           16108      38778
right_shift_2             8966       35953
right_shift_3             8916       35949
right_shift_4             8973       35504
right_shift_5             8941       34997
right_shift_8             9356       31233
right_shift_12            13831      45799
MyTemplatePointerHash1    8839       31798
BasileStarynkevitch       15349      38223

--------------------------------
sizeof(T)       = 64
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access
std::hash                 14756      36237
reinterpret_cast          14763      36918
djb2                      15406      38771
djb2_mod                  15551      38765
sdbm                      14886      37078
yet_another_lc            15700      40290
murmur2                   16309      39024
murmur3                   16432      39381
simple_xorshift           16177      38625
double_xorshift           16073      38750
right_shift_2             14732      36961
right_shift_3             14170      36965
right_shift_4             13687      37295
right_shift_5             11978      35135
right_shift_8             11498      46930
right_shift_12            25845      268052
MyTemplatePointerHash1    10150      32046
BasileStarynkevitch       15981      38143

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access
std::hash                 432        7957
reinterpret_cast          429        8036
djb2                      462        8970
djb2_mod                  453        8884
sdbm                      460        9110
yet_another_lc            466        9015
murmur2                   495        9147
murmur3                   494        9300
simple_xorshift           479        8792
double_xorshift           477        8948
right_shift_2             430        8120
right_shift_3             429        8132
right_shift_4             432        8196
right_shift_5             437        8324
right_shift_8             425        8050
right_shift_12            519        11291
MyTemplatePointerHash1    425        8069
BasileStarynkevitch       468        8496

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access
std::hash                 462        7956
reinterpret_cast          456        8046
djb2                      490        9002
djb2_mod                  483        8905
sdbm                      482        9116
yet_another_lc            492        8982
murmur2                   492        9120
murmur3                   492        9276
simple_xorshift           477        8761
double_xorshift           477        8903
right_shift_2             458        8116
right_shift_3             459        8124
right_shift_4             462        8281
right_shift_5             463        8370
right_shift_8             458        8069
right_shift_12            662        16244
MyTemplatePointerHash1    459        8091
BasileStarynkevitch       472        8476

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access
std::hash                 9756       34368
reinterpret_cast          9718       34897
djb2                      10935      36894
djb2_mod                  10820      36788
sdbm                      11084      37857
yet_another_lc            11125      37996
murmur2                   16522      39078
murmur3                   16461      39314
simple_xorshift           15982      38722
double_xorshift           16151      38868
right_shift_2             9611       34997
right_shift_3             9571       35006
right_shift_4             9135       34750
right_shift_5             8978       32878
right_shift_8             8688       30276
right_shift_12            10591      35827
MyTemplatePointerHash1    8721       30265
BasileStarynkevitch       15524      38315

--------------------------------
sizeof(T)       = 256
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access
std::hash                 14169      36078
reinterpret_cast          14096      36637
djb2                      15373      37492
djb2_mod                  15279      37438
sdbm                      15531      38247
yet_another_lc            15924      38779
murmur2                   16524      39109
murmur3                   16422      39280
simple_xorshift           16119      38735
double_xorshift           16136      38875
right_shift_2             14319      36692
right_shift_3             14311      36776
right_shift_4             13932      35682
right_shift_5             12736      34530
right_shift_8             9221       30663
right_shift_12            15506      98465
MyTemplatePointerHash1    9268       30697
BasileStarynkevitch       15952      38349

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = sequential

dataset size    =    50000 elements
name                      insert     access
std::hash                 421        7863
reinterpret_cast          419        7953
djb2                      457        8983
djb2_mod                  455        8927
sdbm                      445        8609
yet_another_lc            446        8892
murmur2                   492        9090
murmur3                   507        9294
simple_xorshift           467        8687
double_xorshift           472        8872
right_shift_2             432        8009
right_shift_3             432        8014
right_shift_4             435        7998
right_shift_5             442        8099
right_shift_8             432        7914
right_shift_12            462        8911
MyTemplatePointerHash1    426        7744
BasileStarynkevitch       467        8417

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = random

dataset size    =    50000 elements
name                      insert     access
std::hash                 452        7948
reinterpret_cast          456        8018
djb2                      489        9037
djb2_mod                  490        8992
sdbm                      477        8795
yet_another_lc            491        9179
murmur2                   502        9078
murmur3                   507        9273
simple_xorshift           473        8671
double_xorshift           480        8873
right_shift_2             470        8105
right_shift_3             470        8100
right_shift_4             476        8333
right_shift_5             468        8065
right_shift_8             469        8094
right_shift_12            524        10216
MyTemplatePointerHash1    451        7826
BasileStarynkevitch       472        8419

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = sequential

dataset size    =  1000000 elements
name                      insert     access
std::hash                 10910      38432
reinterpret_cast          10892      38994
djb2                      10499      38985
djb2_mod                  10507      38983
sdbm                      11318      37450
yet_another_lc            11740      38260
murmur2                   16960      39544
murmur3                   16816      39647
simple_xorshift           16096      39021
double_xorshift           16419      39183
right_shift_2             10219      38909
right_shift_3             10012      39036
right_shift_4             10642      40284
right_shift_5             10116      38678
right_shift_8             9083       32914
right_shift_12            9376       31586
MyTemplatePointerHash1    8777       30129
BasileStarynkevitch       16036      38913

--------------------------------
sizeof(T)       = 1024
sizeof(T*)      = 4
insertion order = random

dataset size    =  1000000 elements
name                      insert     access
std::hash                 16304      38695
reinterpret_cast          16241      39641
djb2                      16377      39533
djb2_mod                  16428      39526
sdbm                      15402      37811
yet_another_lc            16180      38730
murmur2                   16679      39355
murmur3                   16792      39586
simple_xorshift           16196      38965
double_xorshift           16371      39127
right_shift_2             16445      39263
right_shift_3             16598      39421
right_shift_4             16378      39839
right_shift_5             15517      38442
right_shift_8             11589      33547
right_shift_12            11992      49041
MyTemplatePointerHash1    9052       30222
BasileStarynkevitch       16163      38829

原文由 Damon 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏