【问题标题】:Large Object Heap friendly IDictionary大对象堆友好字典
【发布时间】:2011-05-05 04:26:27
【问题描述】:

我们有一个应用程序,它在几个 Dictionarys 中保存大量对象,其中一些对象在应用程序的生命周期内不断增长(交易应用程序具有大量工具和不断增长的订单/交易)。

由于大型对象堆的碎片,我们遇到了OutOfMemoryExceptions 的问题。

为了解决这个问题,我尝试编写一个“大”字典,该字典被实现为两级字典,其中所有叶字典都不够大,无法在 LOH 上分配。我使用了一致的散列算法来避免当单个存储桶变得太大时必须重新散列整个字典。一致的散列“圆圈”是来自 C5 集合库的 TreeDictionary

我的问题是,C# 是否有更好的数据结构(或者可能是我所描述的更好的实现)?

更新

这是“大”字典的实现:https://gist.github.com/956621

我知道这不是万无一失的,因为规范中既没有 LOH 堆阈值,也没有每个 Dictionary 条目的大小或缩放算法。但是,这是目前我能想到的最好的方法,以避免应用程序在中午崩溃。

【问题讨论】:

  • 这可能有助于说明您在现有实施中看到了什么问题?
  • 如果您将字典用作平面对象的键值存储,您可能会考虑使用内存映射文件。
  • 您是否验证过您没有在任何地方“泄漏”内存(如在所持有的引用中“泄漏”内存)
  • @Mitch Wheat,我们花了很多时间研究内存分析器(特别是 redgate ANTS 内存分析器)。我们已将所有潜在的泄漏减少到实际使用的内存相对恒定的程度。分析器指出 LOH 特别分散。目前,运行 3 小时后,LOH 使用了 10.37MB,但分配了 435.4MB,最大空闲片段为 49.83MB。在使用“大”字典实现之前,情况要糟糕得多
  • 为什么不使用本地数据库,如 squlite 或 SQL 精简版?您将能够轻松地索引和查询您的需求,并且数据库已经根据情况管理缓存。这就是创建数据库的全部原因,如果您的应用程序崩溃,那么您会丢失所有内容,但数据库将维护所有内容。甚至像 Auto Cad 这样的应用程序也使用数据库来存储其复杂的 3D 结构。

标签: c# large-object-heap


【解决方案1】:

当字典是应用程序中最大的数据结构时,它是一种不幸的数据结构。当哈希表变得太满时,它的大小通常会翻倍,这需要在调整大小期间(在关键时刻)进行 150% 的过度分配。哈希表在庞大时工作得非常好,但它需要连续分配,这会对堆算法造成压力。

您可以使用多级哈希表来减少这些缺点,例如使用哈希码的一个字节作为 256 个哈希表的索引。这肯定会增加一些开销,但更重要的是,这种策略和其他策略充满了危险,因为它会摆弄诸如您获得的哈希码之类的随机性,并可能使性能变得更糟。使用这种方法需要良好的理论基础和扎实的经验测试。但它可以工作。

另一种策略是为最坏的情况预先分配最大的数据结构并尽早分配。不需要细粒度的分配,但现在如果它用完,您将面临灾难性故障的幽灵。这是一种选择。

【讨论】:

  • 我认为我拥有的多级散列算法在性能方面还可以。我们在可能的情况下预先分配数组,但在最坏的情况下直接分配数组并不总是可行/可取的。
  • 通过在 Dictionary 构造函数中指定容量来预先分配表是否有帮助?
  • @shmith:如果您知道可能会有一百万个条目,那么马上说出来会很有帮助。但是当你达到一百万零一时,真正的痛苦就开始了。
【解决方案2】:

我认为这需要改变算法。

据我了解和了解,GC 非常擅长打包和整理内存。因此,您的问题源于一个简单的事实,即您将太多数据保存到内存中。

你在内存中保留了多少数据?

您是否考虑过使用数据库?紧凑的可能就足够了。

或者简单地告诉您的客户,要正确运行您的应用程序,他需要 16 GB 的内存。如果您的应用需要所有这些 16 GB 内存,那么肯定有问题。

编辑: 从不同的角度看待你的问题,在阅读你的编辑后,我得到了一个问题:你的对象有多大?或者它们是否包含长列表或数组?您多久删除/添加这些对象?

我认为问题可能不在于字典本身,而在于对象太大并且过于频繁地被删除/添加。也许使用某种捕获或池可能是有利可图的。如果您使用列表,请使用预先分配的方式创建这些列表。

也许使用不可变结构而不是可变类可以缓解碎片化。

【讨论】:

  • +1 "你在内存中保留了多少数据?"与其调整你的字典,不如利用你最近获得的经验重新评估你的要求。
  • 长期目标是减少我们在内存中的交易/订单数量,但短期内不可行。不幸的是,两者都没有用 64 位操作系统替换所有交易者的 32 位 XP 机器。
  • @Akash,这并不是一个很好的解决方案,它只是将内存需求推到不同的进程。我所追求的是可以在数据库中使用的任何巧妙的数据结构来避免这种情况。话虽如此,我怀疑 CLR 上是否有任何 RDBMS 专门由于这个问题而编写......
  • -1 因为错过了 OP 问题中的一个关键点,并且包含关于 GC 和碎片整理的基本错误信息。正如 OP 所提到的,“大对象堆的碎片化”。在 .Net 4.5.1 之前,没有办法重新定位大对象 - 字典是创建大对象的最严重违规者之一,随着它的增长,除非事先知道最大大小。问题确实出在字典里。多年来,我已经多次遇到这个确切的问题(尽管直到今天,总是能够在不切换到 IDictionary 的自定义实现的情况下解决它)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-22
  • 2010-10-15
  • 1970-01-01
相关资源
最近更新 更多