【问题标题】:Where is the maximum capacity of a C# Collection<T> defined?C# Collection<T> 的最大容量在哪里定义?
【发布时间】:2012-12-06 22:23:12
【问题描述】:

我尝试在一个Collection中添加大量元素,每个元素都是简单的数据传输对象,具有基本数据类型的五个属性,没什么特别的。

在循环中添加新条目时,我总是会收到 OutOfMemoryException。有趣的是,在尝试添加第 8388608 个元素(即 8*1024*1024)时,我总是遇到异常。因此,我假设此类集合中允许的容量(元素数量)存在内置限制,但我找不到任何相关信息。

这个限制真的存在吗?我在哪里可以找到此文档?

【问题讨论】:

  • 正如标题中所说,CollectionT 如问题中所述。
  • 你的“T”是什么?执行 sizeof 并查看该大小乘以数组长度是否可以达到 2 GB。
  • 不,原因在下面的答案中描述
  • 8*1024*1024 = 2 到 23 日

标签: .net collections


【解决方案1】:

这是一个 OutOfMemoryException,所以这不是这里所讨论的集合的大小或容量:它是您的应用程序中的内存使用。诀窍是您不必用尽机器甚至进程中的内存来获取此异常。

我认为正在发生的事情是您正在填满大型对象堆。随着收藏的增长,他们需要在后台添加存储以容纳新项目。一旦分配了新的存储并复制了项目,旧的存储就会被释放并且应该可以进行垃圾回收。

问题在于,一旦超出特定大小(过去是 85000 字节,但现在可能不同),垃圾收集器 (GC) 就会使用称为大对象堆 (LOH) 的东西来跟踪您的内存。当 GC 从 LOH 中释放内存时(这种情况开始时很少发生),内存将返回到您的操作系统并可供其他进程使用,但该内存中的 虚拟地址空间仍将在您自己的流程中使用。您的程序地址表中会出现一个巨大的漏洞,并且因为这个漏洞位于大对象堆上,所以它永远不会被压缩或回收。

您在 2 的精确幂次方上看到此异常的原因是大多数 .Net 集合使用加倍算法来向集合添加存储。它总是会在需要再次加倍的地方抛出,因为在那之前已经分配了 RAM。

因此,一个快速的解决方案是利用大多数 .Net 集合中很少使用的功能。如果您查看构造函数重载,大多数集合类型都会有一个允许您在初始构造期间设置容量。此容量不是硬性限制——它只是一个起点——但它在少数情况下很有用,包括当你的集合会变得非常大时。您可以将初始容量设置为淫秽的东西……希望足够大以容纳所有物品,或者至少只需要“加倍”一次或两次。

您可以通过在控制台应用程序中运行以下代码来看到这种效果:

var x = new List<int>();
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

在我的系统上,在 134217728 个项目之后会引发 OutOfMemory 异常。 134217728 * 每个 int 4 个字节仅(并且确切地说)512MB 的 RAM。它不应该抛出,因为这是该过程中唯一具有任何实际大小的东西,但无论如何它都会因为地址空间丢失给旧版本的集合。

现在让我们更改代码来设置容量,如下所示:

var x = new List<int>(134217728 * 2);
for (long y = 0; y < long.MaxValue; y++)
    x.Add(0);

现在我的系统在抛出时会一直运行到 268435456 个项目(1GB 的 RAM),因为它不能将 1GB 翻倍,这要归功于进程使用的其他 ram 吃掉了 2GB 虚拟地址表的一部分限制(即:循环计数器以及集合对象和进程本身的任何开销)。

我无法解释的是,它不允许我使用 3 作为乘数,即使那只是(!)1.5GB。一个使用不同乘数的小实验试图找出我能得到多大,结果表明这个数字并不一致。在某一时刻,我能够达到 2.6 以上,但后来不得不回落到 2.4 以下。我猜是要发现一些新东西。

如果此解决方案确实为您提供了足够的空间,那么还有一个trick you can use to get 3GB of virtual address space,或者您可以强制您的应用程序编译为 x64 而不是 x86 或 AnyCPU。如果您使用的是基于 2.0 运行时的框架版本(通过 .Net 3.5 的任何版本),您可能会尝试更新到 .Net 4.0 或更高版本,据报道这会更好一些。如果做不到这一点,您将不得不完全重写您如何处理数据,这可能涉及将其保存在磁盘上,并且一次只在内存中保存一个项目或项目的小样本(缓存)。我真的推荐最后一个选项,因为其他任何东西最终都可能会再次意外中断(如果你的数据集一开始就这么大,它也可能会增长)。

【讨论】:

  • 感谢您的全面回答,每天总有新东西要学:-)
  • 刚刚通过 LINQPad 运行了您的示例。我第一次运行得到 33554432,第二次运行得到 (33554432*2)。当我乘以 4 得到地址的数量并看到它们是 2 的幂时,这非常有意义。感谢您的出色回答。
  • 不要在这里开始讨论,但我一直在关注您的答案的所有更改。谢谢你这么彻底。
【解决方案2】:

在此处查看 Marc 的答案What's the max items in a List<T>?

您可能确实只有当前进程的 OutOfMemoryException。

【讨论】:

  • 我的脑子现在在旋转。我在没有完全理解 为什么 的情况下发布了指向 Marc 答案的链接。谁能解释或链接到好的解释?
  • 我想我的回答有你的解释。
【解决方案3】:

OutOfMemoryException 并不意味着你已经达到了集合中元素数量的硬性限制,这意味着你已经达到了可以保存在内存中的数据量的硬性限制在当前进程

根据可用内存、当前使用的内存等,它会因机器而异。

【讨论】:

  • 它是当前进程上的内存(甚至只是内存地址空间),而不是当前机器。他的机器上可能仍有大量可用内存。
  • 我不确定是不是这样,因为它总是发生在 8388608,即 2^23 - 或者这真的是巧合吗?
  • @Gorgsenegger - 我的赌注是巧合
  • 也许现在 - 上面有 Norla 的回答(或更好的:链接) - 我到了某个地方......它是否总是 2^n 因为每次达到当前限制时都会加倍大小?
【解决方案4】:

限制是您可以为集合类设置的容量,很可能是它的int.MaxValue,即2147483647(在您的情况下肯定是)。但是,无论您是否已达到该硬限制,当您用完内存时都会出现 OOM 异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 2011-01-14
    • 1970-01-01
    相关资源
    最近更新 更多