gpt4 book ai didi

c++ - 指针的最快哈希函数是什么?

转载 作者:行者123 更新时间:2023-12-02 07:09:07 27 4
gpt4 key购买 nike

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

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

指针是简单类型,基本上是唯一标识对象的 4/8 字节值。问题在于,由于多个 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% 的性能提升,其中包含数万个频繁构建和清除的元素。

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

该功能需要是:
  • 快速地!并且必须很好地内联。
  • 提供合理的分布,允许罕见的碰撞。

  • 更新 - 基准测试结果

    我运行了两组测试,一组用于 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;
  • Hash3(@BasileStarynkevitch):uintptr_t ad = (uintptr_t)val;
    return (size_t)((13 * ad) ^ (ad >> 15));
  • Hash4(@Roddy):uintptr_t ad = (uintptr_t)val;
    return (size_t)(ad ^ (ad >> 16));
  • Hash5(@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 耗时 742 毫秒
  • Hash2 拿走了 343 毫秒
  • Hash3 耗时 1008 毫秒
  • Hash4 耗时 629 毫秒
  • Hash5 拿走了 350 毫秒

  • 测试 1 - 4K_class* :
  • VS2013 默认耗时 0.423 毫秒
  • Hash1 耗时 23.889 毫秒
  • Hash2 耗时 6.331 毫秒
  • Hash3 耗时 0.366 毫秒
  • Hash4 耗时 0.390 毫秒
  • Hash5 拿走了 0.290 毫秒

  • 更新 2:

    迄今为止的赢家是模板化哈希 (Hash5) 函数。各种块大小的最佳速度性能水平。

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

    最佳答案

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

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

    TL;DR
    在变得好奇之后,我在周末运行了大约 30 个小时的基准测试。除其他事项外,我试图获得平均情况与最坏情况,并试图通过故意针对插入的集合大小提供有关桶数的错误提示,将 std::unordered_map 强制为最坏情况行为。

    我将较差的散列 ( std::hash<T*> ) 与众所周知的整体良好质量的通用散列 (djb2、sdbm) 以及它们的变体进行了比较,这些散列解释了非常短的输入长度,并与明确认为用于散列表的散列进行了比较(murmur2 和 murmur3),以及实际上比不进行散列更糟糕的 piss-poor 散列,因为它们丢弃了熵。
    由于由于对齐,指针上的最低 2-3 位始终为零,因此我认为值得将简单的右移测试为“散列”,因此仅使用非零信息,以防散列表,例如仅使用最低的 N 位。事实证明,对于合理的类次(我也尝试过不合理的类次!),这实际上表现得很好。

    发现

    我的一些发现是众所周知的,并不令人惊讶,另一些则非常令人惊讶:

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

  • 测试设置

    测试不仅针对任何 4 字节或 8 字节(或其他)对齐的值进行,而且针对通过在堆上分配完整元素集并将分配器提供的地址存储在 std::vector(然后删除了对象,不需要它们)。
    地址被插入到新分配的 std::unordered_map 中,按照 vector 中存储的顺序,一次是原始顺序(“顺序”),一次是在 vector 上应用 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 个元素以及销毁 map 的 50 次迭代的时间(以毫秒为单位)。
    access 列是在 'vector' 中对伪随机元素进行 100,000,000 次查找,然后在 unordered_map 中查找该地址的时间(以毫秒为单位)。
    这个时间包括平均一个缓存未命中,用于访问 vector 中的随机元素,至少对于大数据集(小数据集完全适合 L2)。

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

    源代码

    源代码可用 on Ideone ,同样是因为 Stackoverflow 的 30k 字符限制。

    注意: 在台式 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

    关于c++ - 指针的最快哈希函数是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20953390/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com