如果考虑到非通用集合将每个成员都存储为“object”,那么可以认为通用集合也具有内存优势。然而,我没有找到关于内存使用差异的任何信息。
有人能澄清一下这一点吗?
所以我们可以看到,非泛型解决方案可能就为数组消耗了两倍的内存。那么数组的内容呢?
关于值类型的问题在于,它们直接存储在自己的变量中。除了用于存储1000个整数的那4000字节之外,没有额外的空间;它们被打包进数组里面。因此,对于泛型情况来说,额外的成本是零。
对于object[]
的情况,数组的每个成员都是一个引用,而该引用指向一个对象;在这种情况下,是装箱整型。一个装箱整型的大小是多少?
未装箱的值类型不需要存储其类型的任何信息,因为其类型由其存储的类型确定,并且运行时已知。装箱值类型需要在某个地方存储盒子中物品的类型,这需要空间。事实证明,在32位.NET中,对象的簿记开销为8字节,在64位系统上为16字节。这只是开销;我们当然需要4字节来存储整数。但是等等,情况变得更糟:在64位系统上,盒子必须对齐到8字节边界,因此在64位系统上,我们需要另外4字节的填充。
总结一下:在64位和32位系统上,我们的int[]
大约占用4KB。包含1000个整数的object[]
在32位系统上大约需要16KB,在64位系统上需要32K。因此,非泛型情况下,int[]
与object[]
的内存效率要差4到8倍。
但等等,情况变得更糟了。这仅仅是大小。那么访问时间呢?
要从整数数组中访问一个整数,运行时必须:
这需要更多的步骤,因此需要更长的时间。
但是等等,情况会变得更糟。
现代处理器在芯片本身上使用缓存来避免返回主内存。一个包含 1000 个普通整数的数组很可能会出现在高速缓存中,以便对数组的第一、第二、第三等成员进行快速连续访问,所有这些成员都是从同一高速缓存线路中提取的;这是非常快的。但是装箱整数可能分布在整个堆中,这增加了缓存失效的次数,进而大大降低了访问速度。
希望这足以澄清您对装箱惩罚的理解。
那么非装箱类型呢?一个字符串数组列表和一个 List<string>
之间有显著差异吗?
在这种情况下,惩罚要小得多,因为 object[]
和 string[]
具有类似的性能特征和内存布局。唯一的额外惩罚是:(1) 直到运行时才捕获错误,(2) 使代码更难以阅读和编辑,以及 (3) 运行时类型检查的轻微惩罚。
new ArrayList { 1, 2, 3 };
object
(称为装箱),以便将其存储到您的ArrayList
中。这将导致您在此处的内存开销,因为一个object
肯定比一个简单的int
大。ArrayList a = new ArrayList { 1, 2, 3};
对比。
List<object> a = new List<object> { 1, 2, 3 };
两个列表将消耗相同的内存,尽管第二个是通用的。这是因为它们都将您的整数装箱到object
中。因此,问题的答案与内存无关。
对于引用类型,完全没有内存差异:
ArrayList a = new ArrayList { myInstance, anotherInstance }
对比。
List<MyClass> a = new List<MyClass> { myInstance, anotherInstance }
这两个操作会产生相同的内存结果。但是第二个操作更容易维护,因为您可以直接使用实例而不需要转换它们。
List<byte>
会在内部存储一个 byte[]
来存储其值,而 List
则有一个 object[]
,其中必然包含4或8个字节的引用,加上装箱开销。 - Damien_The_Unbelieverint valueType = 1;
现在我们在堆栈上有一个值,如下所示:
i = 1
现在考虑我们立即执行以下操作:
object boxingObject = valueType;
现在我们在内存中有两个值,一个是堆栈中 valueType
的引用,另一个是堆中的 value 1
:
boxingObject
1
因此,根据 Microsoft 文档的说明,在对值类型进行装箱时会有额外的内存使用:
将值类型装箱会在堆上分配一个对象实例,并将值复制到新对象中。
请参阅 link 以获取完整信息。
object
总是比一个int
大。相关。 - Jeroen MostertList<byte>
占用每个元素 1 字节,而 ArrayList 在 32 位模式下占用 12 字节,在 64 位模式下占用 24 字节来存储一个字节。将 2400% 的存储开销视为有用是不明智的,最好忘记 .NET 1.0 曾经存在过。 - Hans Passant