当需要更多空间时,列表会在c#中占用一倍的空间。 在某些时候,加倍说1024到2048会变得不那么有效吗?

当数字较小时,可以快速将数组列表的大小从2个增加到4个内存地址,但是当它开始增加空间量时,更接近数组列表中允许的最大空间量(接近2MB限制) 。 如果只是将arrays的大小增加到某个时刻所需尺寸的一小部分,那么改变这些更大区域中分配的空间会更有效吗? 显然,从1mb增加到2mb的大小现在并不是什么大不了的事情 – 但是如果你每小时有5万人每小时运行一次这样做会使arrays大小增加一倍,我很好奇这是否足够好有理由改变其运作方式。 更不用说减少不需要的内存空间(理论上)。

我的意思的小图形表示.ArrayList a中有4个元素,这是它目前的最大大小

|||| 

现在让我们向arraylist添加另一个项目,内部代码将使数组的大小加倍,即使我们只向数组添加一个东西。 arraylist现在变成了8个元素

 |||||||| 

在这些大小级别,我怀疑它有什么不同,但是当你每次分配1mb到2mb时,有人会做一些事情,比如将一些文件添加到一个arraylist或大约1.25mb的东西,那么分配了0.75mb的不需要的空间。

为了让您更好地了解System.Collections.Generic类当前在c#中运行的代码。 它现在的工作方式是每次用户尝试向数组太小的数据添加内容时,它会使数组列表(读取数组)的大小加倍。 加倍尺寸是一个很好的解决方案并且有意义,直到你实际上增长它远远大于技术上需要它。

以下是该类特定部分的来源:

 private void EnsureCapacity(int min) { if (this._items.Length >= min) return; // This is what I'm refering to int num = this._items.Length == 0 ? 4 : this._items.Length * 2; if ((uint) num > 2146435071U) num = 2146435071; if (num < min) num = min; this.Capacity = num; } 

我猜这就是许多编程语言中处理内存管理的方式所以以前可能已经考虑了很多次,只是想知道这是否是一种可以节省大量系统资源的效率保护程序规模。

随着集合的大小变大,创建新缓冲区的成本也随之增加,因为您需要复制所有现有元素。 需要完成的这些副本的数量与每个副本的费用间接成比例的事实正是为什么向List添加项目的摊销成本是O(1)。 如果缓冲区的大小线性增加,则将项目添加到List分摊成本实际上变为O(n)。

节省内存,允许“浪费”的内存从O(n)变为O(1)。 与几乎所有性能/算法决策一样,我们再次面临着为速度交换内存的典型决策。 我们可以节省内存并且加速速度较慢(因为更多的复制)或者我们可以使用更多内存来获得更快的添加速度。 当然,没有一个普遍正确的答案。 有些人真的更喜欢使用较慢的添加速度来换取较少浪费的内存。 首先耗尽的特定资源将根据程序,运行的系统等而有所不同。 处于稀缺资源的情况下的那些人可能无法使用List ,其被设计为尽可能广泛适用,尽管它不是普遍的最佳选择。

动态数组(如List )的指数增长因子背后的想法是:

  1. 浪费的空间量总是仅与arrays中的数据量成比例。 因此,您永远不会浪费比正常使用更大规模的资源。

  2. 即使有许多重新分配,在创建大小为N的数组时复制所花费的总时间是单个元素的 O(N) – 或O(1) 。

  3. O(1)的访问时间非常快,系数很小。

这使得List非常适用于数据库对象的数据库,例如,数据库对象的引用内存表,需要近乎即时的访问,但数组元素本身很小。

相反,动态数组的线性增长会导致n平方的内存浪费 。 这种情况发生在以下情况:

  1. 你向数组中添加一些内容,将其扩展为大N的大小为N,从而释放小K的大小为NK的先前内存块(可能非常大)。

  2. 您分配了一些对象。 内存管理器把一些放在刚刚腾空的大内存块中,为什么不呢?

  3. 你向数组中添加了一些东西,将它扩展到N + K的大小为一些小K.因为以前释放的内存块现在是稀疏占用的,所以内存管理器没有足够大的连续空闲内存块,并且必须请求更多的虚拟内存来自操作系统。

  4. 因此,尽管所测量的对象的尺寸线性增长,但虚拟内存提交也是二次方的。

这不是理论上的可能性。 我实际上不得不修复一个n平方的内存泄漏,因为有人手动编码了一个线性增长的动态整数数组。 解决方法是丢弃手动代码并使用为此目的创建的几何增长数组库。

话虽这么说,我还看到了当需要的总内存需要增长时,32位进程中List的指数重新分配(以及Dictionary类似增长的内存缓冲区)的问题128 MB。 在这种情况下,即使剩余的虚拟地址空间足够多,列表或字典也经常无法分配256 MB的连续内存范围 。 然后,应用程序将向用户报告内存不足错误。 在我的情况下,客户抱怨这一点,因为任务管理器报告VM使用从未超过,例如,1.5GB。 如果我是微软,我会阻止’List’(以及Dictionary中类似的内存缓冲区)增长到总虚拟地址空间的1%。