【问题标题】:System.Collections.Generic.Dictionary = Ultimate performance?System.Collections.Generic.Dictionary = 终极性能?
【发布时间】:2011-06-08 13:36:30
【问题描述】:

我正在编写一个 Haxe C# 目标,我一直在研究 Haxe 标准库的性能差异,以便我们可以通过其跨平台代码提供最佳性能。

一个很好的例子是哈希表代码。我对使用 .NET 的字典有点不情愿,因为它看起来很笨重(由于内存对齐问题,键/值对的结构会占用大量内存,除了它所持有的不必要信息之外),并且因为在 std库中没有对象哈希之类的东西,我真的认为我可以通过不必调用 GetHashCode 来压缩一点性能,并且一直内联它。

此外,很明显 Dictionary 实现使用链表来处理冲突,这远非理想。

所以我们开始实现我们自己的解决方案,从 IntHash(字典)开始 我们首先实现了Hopscotch hashing,但结果确实不是很好,但很明显它不能很好地支持巨大的哈希表,因为 H 通常是一个机器字,并且随着 H / Length 的增加,性能越差。

然后我们开始实施khash-inspired 算法。这个具有很大的潜力,因为它的基准测试令人印象深刻,并且它可以处理同一阵列上的碰撞。它也有一些很棒的东西,比如在不需要两倍内存的情况下调整大小。

基准测试令人失望。当然,没有必要说我们实现的内存使用量比 Dictionary 的低得多。但我也希望能获得不错的性能提升,但不幸的是,事实并非如此。它并没有太低 - 不到一个数量级 - 但是对于 set 和 get 来说,.NET 的实现仍然表现得更好。

所以我的问题是:这是我们对 C# 最好的吗?我尝试寻找任何自定义解决方案,似乎几乎没有。有那个 C5 通用集合,但是代码太混乱了,我什至没有测试。而且我也没有找到任何基准。

所以...是这样吗?我应该直接绕过Dictionary<>吗?

【问题讨论】:

  • 字典不存储 KeyValuePairs。
  • 我已经体验到手动重新实现 .NET 集合无法与包含的实现竞争。我不知道为什么会这样,但我怀疑 CLR/JIT 在优化代码时会“作弊”,因为它对 .NET 容器有一些先验知识。
  • 康拉德:这实际上是我最喜欢的答案! :)
  • 查看相关:stackoverflow.com/questions/2151747/…。字典已经很快了。

标签: c# data-structures hash hashtable


【解决方案1】:

我发现 .NET Dictionary 在大多数情况下都表现良好,即使不是特别好。这是一个很好的通用实现。我最常遇到的问题是 2 GB 的限制。在 64 位系统上,您不能向字典添加超过大约 8950 万个项目(当键是整数或引用,而值是引用时)。字典开销似乎是每项 24 个字节。

这个限制以一种非常奇怪的方式为人所知。 Dictionary 似乎通过加倍增长——当它充满时,它会将容量增加到至少是当前大小两倍的下一个素数。因此,字典将增长到大约 4700 万,然后抛出异常,因为当它尝试加倍(达到 9400 万)时,内存分配失败(由于 2 GB 的限制)。我通过预先分配Dictionary(即调用允许您指定容量的构造函数)来解决这个问题。这也加快了填充字典的速度,因为它永远不必增长,这需要分配一个新数组并重新散列所有内容。

是什么让你说Dictionary 使用链表解决冲突?我很确定它使用开放寻址,但我不知道它是如何进行探测的。我想如果它进行线性探测,那么效果类似于您使用链表获得的效果。

我们编写了自己的 BigDictionary 类以超过 2 GB 的限制,并发现带有线性探测的直接开放寻址方案可以提供相当好的性能。它没有Dictionary 快,但它可以处理数亿个项目(如果我有记忆的话,数十亿个)。

也就是说,您应该能够编写更快的特定于任务的哈希表,在某些情况下它的性能优于 .NET 字典。但是对于通用哈希表,我认为你很难做得比 BCL 提供的更好。

【讨论】:

  • 知道每个项目的开销是 24 字节,我真的很惊讶!!!!对我来说,这已经证明了创建我自己的哈希版本是合理的,即使它有点慢。如果您使用的是 2gb 哈希,我想也许您也会从中受益!
  • 我也想知道实现是否根据平台(即紧凑/微框架)而变化
  • 对了,你是对的,它并没有真正使用链表,而是Entry结构体存储了下一次碰撞的数组索引
  • Dictionary 使用链表链接是数组的索引而不是对象引用。这实现了链桶散列的所有好处,并且对于从未从中删除任何内容的字典也将按插入顺序维护项目。这也意味着“哈希表”条目本身不需要包含条目数组中的条目以外的任何内容。
【解决方案2】:

在设计“更好”的哈希表时需要考虑很多事情。您尝试的自定义方法比 .NET 字典更慢或没有更好的原因之一是哈希表的性能通常非常依赖于:

  • 被散列的数据
  • 哈希函数的性能
  • 表格的负载系数
  • 碰撞次数与非碰撞次数
  • 冲突解决算法
  • 表中的数据量及其存储方式(通过指针/引用或直接在存储桶中)
  • 数据的访问模式
  • 插入/删除与检索的数量
  • 在封闭式散列/开放式寻址实现中需要调整大小
  • 以及许多其他因素...

有这么多需要调整和调整的东西,如果不付出大量的努力,很难想出一个通用的高性能(时间和速度)哈希表。这就是为什么,如果您要尝试创建自定义哈希表而不是内置在标准库(例如 .NET)中的哈希表,请准备好花费无数小时并意识到您的微调实现可能只针对您正在散列的特定类型和数量的数据。

因此,不,.NET 字典不是用于任何特定目的的最终哈希表。但是,鉴于字典的使用频率,我确信 Microsoft BCL(基类库)团队执行了大量的分析来选择他们为一般情况选择的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-05-12
    • 1970-01-01
    • 1970-01-01
    • 2016-08-10
    • 1970-01-01
    • 2022-12-21
    • 2019-08-28
    • 2011-08-13
    相关资源
    最近更新 更多