.NET中Generic和非泛型集合之间的内存使用差异

İlk*_*mov 5 .net c# memory generics collections

我现在读到.NET中的集合.众所周知,使用泛型集合泛型集合有一些优点:它们是类型安全的,没有强制转换,没有装箱/拆箱.这就是通用集合具有性能优势的原因.

如果我们认为非泛型集合存储每个成员object,那么我们可以认为泛型也具有内存优势.但是,我没有找到任何有关内存使用差异的信息.

有人可以澄清这一点吗?

Eri*_*ert 18

如果我们认为非泛型集合将每个成员存储为对象,那么我们可以认为泛型也具有内存优势.但是,我没有找到任何有关内存使用差异的信息.有人可以澄清这一点吗?

当然.让我们考虑ArrayList包含ints vs a的List<int>.假设int每个列表中有1000 秒.

在两者中,集合类型是数组周围的薄包装 - 因此得名ArrayList.在这种情况下ArrayList,底层object[]包含至少1000个盒装整数.在这种情况下List<int>,底层int[]包含至少1000 int秒.

为什么我说"至少"?因为两者都使用双倍完整策略.如果在创建集合时设置集合的容量,则会为该集合分配足够的空间.如果你不这样做,那么集合必须猜测,如果猜错了,你需要更多容量,那么它的容量就会增加一倍.所以,最好的情况是,我们的集合数组大小合适.最糟糕的情况是,它们可能是它们需要的两倍; 数组中可能有2000个对象或2000个整数的空间.

但是,为简单起见,我们很幸运,每个人大约有1000个.

首先,只是阵列的内存负担是什么?一个object[1000]占用一个32位的系统上4000个字节和一个64位系统上8000个字节,只是为了参考,这是指针尺寸.一个int[1000]无论占用4000个字节.(阵列簿记也占用了一些额外的字节,但与边际成本相比,这些成本很小.)

因此,我们已经看到非通用解决方案可能仅消耗两倍于阵列的内存.那个数组的内容怎么样?

那么,关于值类型的事情是它们存储在它们自己的变量中.除了用于存储1000个整数的4000个字节之外,没有额外的空间; 他们被打包到阵列中.因此,通用案例的额外成本为零.

对于这种object[]情况,数组的每个成员都是一个引用,该引用引用一个对象; 在这种情况下,一个盒装整数.盒装整数的大小是多少?

未装箱的值类型不需要存储有关其类型的任何信息,因为其类型由其所在的存储类型以及运行时已知的类型确定.盒装值类型需要在某处存储框中事物的类型,并占用空间.事实证明,32位.NET中对象的簿记开销是8字节,64位系统上是16.这只是开销; 我们当然需要4个字节用于int.但等等,情况变得更糟:在64位系统上,盒子必须与8字节边界对齐,所以我们需要64位系统上另外 4个字节的填充.

加起来:我们int[]在64位和32位系统上大约需要4KB.我们的object[]包含1000个int在32位系统上大约需要16KB,在64位系统上需要32K.因此,对于非通用情况,int[]vs 的内存效率要object[]差4或8倍.

但等等,情况变得更糟.那只是尺寸.访问时间怎么样?

要从整数数组中访问整数,运行时必须:

  • 验证数组是否有效
  • 验证索引是否有效
  • 从给定索引处的变量中获取值

要从盒装整数数组中访问整数,运行时必须:

  • 验证数组是否有效
  • 验证索引是否有效
  • 从给定索引处的变量中获取引用
  • 验证引用不为null
  • 验证引用是否为盒装整数
  • 从框中提取整数

这是更多的步骤,因此需要更长的时间.

但等待它会变得更糟糕.

现代处理器在芯片本身上使用缓存以避免返回主存储器.1000个普通整数的数组很可能最终进入高速缓存,以便快速连续地对数组的第一个,第二个,第三个等成员的访问全部从同一个高速缓存行中提取; 这太疯狂了.但是盒装整数可以遍布整个堆,这会增加缓存未命中率,这会进一步降低访问速度.

希望这足以澄清你对拳击惩罚的理解.

那些非盒装类型呢?字符串数组列表与a之间是否存在显着差异List<string>

这里的惩罚要小得多,因为a object[]和a string[]具有相似的性能特征和存储器布局.在这种情况下唯一的额外惩罚是(1)在运行时没有捕获你的错误,(2)使代码更难阅读和编辑,以及(3)运行时类型检查的轻微惩罚.