【问题标题】:Collections and memory收藏和记忆
【发布时间】:2015-12-20 04:25:02
【问题描述】:

我有一个应用程序读取 3-4 GB 的数据,从每一行构建实体,然后将它们存储在列表中。

我遇到的问题是,内存疯狂地增长到 13 到 15 GB。为什么存储这些实体会占用这么多内存。

所以我构建了一个 Tree 并做了类似于 Huffman Encoding 的操作,总内存大小变成了大约 200 - 300 MB。

我明白,我压缩了数据。但我没想到将对象存储在列表中会大大增加内存。为什么会这样?

字典、堆栈、队列、数组等其他数据结构怎么样?

我在哪里可以找到有关数据结构的内部和内存分配的更多信息?

还是我做错了什么?

【问题讨论】:

  • 真的需要内存中的所有数据(3-4 GB 是巨大的)吗?为什么不使用数据库或按需只读所需的行?
  • 无关紧要。那不是我的问题。但是是的,我需要每一行。和文件是数据库。如果我逐行执行,我无法并行化程序。
  • "还是我做错了什么?"是的,你是。祝你好运!
  • 每当您需要将 3-4GB 的数据加载到内存中并将它们作为一个整体进行处理(并且您不能将它们切成碎片)时,99% 的时间您正在接近以错误的方式解决问题。
  • 也许您可以创建一个特殊的结构来处理这些类型的数据操作。一个基础级别的对象,用于管理检索、排序、并发、内存使用等操作。用于管理数据的基础对象可能称为“数据库”之类的东西。事实上,这些操作非常复杂,以至于一些有进取心的开发人员可能会通过销售能够做到这一点的整个产品来赚钱。呸……这都是疯话……

标签: c# .net memory collections


【解决方案1】:

在 .NET 中,大对象位于未压缩的大对象堆上。大是 85,000 字节以上的所有内容。当您增加列表时,它们可能会变得更大,并且一旦超过当前容量就必须重新分配。重定位意味着它们很可能被放在堆的末尾。所以你最终会得到一个非常分散的 LOH 和大量的内存使用。

更新:如果你用所需的容量(我猜你可以从数据库中确定)初始化列表,那么你的内存消耗应该会下降一点。

【讨论】:

    【解决方案2】:

    无论您要使用哪种数据结构,您的内存消耗都不会低于存储所有数据所需的内存。

    你计算过存储一个实例类对象需要多少内存吗?

    您的霍夫曼编码是一种节省空间的优化,这意味着您可以自己消除类对象中的大量重复数据。这与您用来保存数据的数据结构无关。这取决于您的数据本身的结构,以便您可以利用不同的空间节省策略(其中霍夫曼编码是众多可能性中的一种,适用于消除常见前缀并且用于存储它的数据结构是一棵树) .

    现在,回到你的问题。在不优化数据(即对象)的情况下,您可以注意一些事情来提高内存使用效率。

    我们所有的物体大小都差不多吗?

    您是否只是简单地运行一个循环,即时分配内存,然后将它们插入到列表中,如下所示:

    foreach (var obj in collection) { myList.Add(new myObject(obj)); }
    

    在这种情况下,您的列表对象会不断扩展。如果最后没有足够的可用内存来扩展列表,.NET 将分配一个新的、更大的内存并将原始数组复制到新内存。本质上,您最终会得到两块内存——原始内存和新扩展内存(现在保存列表)。这样做很多很多次(显然您需要处理 GB 的数据),您会看到 LOT碎片化 内存空间.

    你最好一次性为整个列表分配足够的内存。

    作为后记,我不禁想知道:世界你要如何搜索这个巨大的列表来找到你需要的东西?您不应该使用二叉树或哈希表之类的东西来帮助您进行搜索吗?也许您只是读入所有数据,对所有数据执行一些处理,然后将它们写回......

    【讨论】:

    • 哈哈。我没有从列表中搜索任何内容。一旦我填充了数据,我就会从中生成报告。汇总结果。我想一次读完,这样我就可以在内存中并行运行数据。我明白你的意思。
    • 如果我逐行处理这些废话,我必须等待 IO,实际上它比一次阅读整个内容然后分而治之要花费更长的时间。
    • Stephen 的观点是,在创建列表时,您应该估计甚至更好地了解行数,以便从一开始就构建具有正确大小的列表。知道大小会使原始列表设置变慢,但之后一切都会变得很快。
    • @user177883,在这种情况下,您可能希望一次导入 1000 行。处理它。然后导入接下来的 1000 行。只要您不是到处引用行,而只是汇总信息,就无需将整个内容读入内存。
    • @user177883,当您并行化处理时,一次处理文件一个块(比如 1000 行)也会工作得更好。例如,如果您有一个具有 8 个内核的系统,并且您将一千万个任务排入其中,那么每个内核将有一个包含 100 万个任务的内部队列——在等待时会消耗内存。如果你把它分成块,那么每个核心将只有 100 个任务排队。您没有比您的核心数量更快地完成它 - 一次是 8 条记录。
    【解决方案3】:

    如果您正在使用类,请阅读以下回复:Understanding CLR object size between 32 bit vs 64 bit

    在 64 位上(您使用的是 64 位,对吗?)对象开销是 16 个字节加上对对象的引用(有人在引用他,对吗?)所以还有 8 个字节。所以一个空对象会“吃掉”至少 24 个字节。

    如果您使用Lists,请记住Lists 会翻倍增长,因此您可能会浪费很多空间。其他 .NET 集合以同样的方式增长。

    我要补充一点,百万 Lists 的“纯”开销可能会让他的记忆崩溃。除了 List 对象“吃掉”的 16 + 8 字节空间外,它由 2 个整数(8 个字节)、一个 SyncLock 引用(8 个字节,通常为空)和一个引用内部数组(所以 8 + 16 字节 + 数组)

    【讨论】:

      猜你喜欢
      • 2022-12-03
      • 1970-01-01
      • 2012-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-13
      • 2021-06-17
      • 1970-01-01
      相关资源
      最近更新 更多