C# · 12月 26, 2021

.net – 定义了C#集合的最大容量在哪里?

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

在循环中添加新条目时,我总是得到一个OutOfMemoryException.有趣的是,当尝试添加第8388608个元素(这是8 * 1024 * 1024)时,我总是得到异常.因此,我认为在这种集合中允许的容量(元素数量)方面存在内置限制,但是我找不到任何关于它的信息.

这个限制确实存在吗?我在哪里可以找到这个记录?

解决方法 这是一个OutOfMemoryException,所以它不是在这个问题的集合的大小或容量:它是应用程序中的内存使用.诀窍在于,您不必在机器中甚至在您的进程中使用内存来获取此异常.

我认为发生的是你正在填补大对象堆.随着集合的增长,他们需要在后台添加存储以适应新项目.一旦分配了新的存储空间并复制了这些项目,旧存储将被释放,并且应该有资格进行垃圾收集.

问题是,一旦你超过一定的大小(以前是85000字节,但现在可能会有所不同),垃圾收集器(GC)使用大对象堆(LOH)来跟踪你的内存.当GC从LOH释放内存(这仅仅是很少开始)时,内存将返回到您的操作系统并可用于其他进程,但该内存的虚拟地址空间仍将在您自己的进程中使用.您的程序地址表中将有一个很大的漏洞,并且由于此漏洞位于“大对象堆”上,因此将永远不会被压缩或回收.

您以两个确切的权力看到这个异常的原因是大多数.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的virutal地址表限制(即:循环计数器和收集对象和进程本身的任何开销).

我不能解释的是,它不允许我使用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或更高版本,据报道,这将更好一点.如果没有这些,您将不得不重新编写如何处理可能涉及将其保留在磁盘上的数据,并且一次只能将内容中的项目(缓存)的单个项目或小样本.我真的推荐这个最后一个选项,因为其他任何事情可能会最终再次意外崩溃(如果你的数据集是这么大的开始,它也可能会增长).