为什么一个类的数组比结构数组消耗的内存多20%?

时间:2012-08-15 19:15:09

标签: c# arrays class struct

我正在制作一个2D平台游戏,并代表我正在使用2D数组的平铺,这些平铺是具有位置,类型和各种标志字段的类。当我将tile类中的class关键字更改为struct时,加载的地图会减少约20%的内存。

我不知道这个动作的正确与否,我只是想知道为什么内存消耗的差异。

编辑:数字为1038 MB,瓦片为类,845 MB为结构(没有大部分游戏数据)。​​

3 个答案:

答案 0 :(得分:10)

对象数组实际上是一个引用数组,对象存储在堆上。

这意味着引用有4或8个字节(取决于x86或x64)的开销,然后堆上的每个对象都有8或16个字节的开销。

在结构数组中,结构值直接存储在数组中,因此没有额外的开销。

因此,如果您的数据是例如48个字节,则额外的12个字节(在x86模式下)将是所用总内存的20%的开销。

请注意,它们的使用方式也有所不同。如果您移动切片或将其作为参数发送到方法,则在使用结构时将复制所有数据,但如果使用类,则仅复制引用。如果你可以保持结构小于16个字节,性能差异很小,但如果它更大,你可以通过使用类来获得更快的代码。

答案 1 :(得分:3)

每个对象都有一个8字节的头,其中包含指向类型句柄和同步块索引的指针,而结构是内联分配的。此外,您需要为正在使用的每个引用类型变量分配指针大小的引用。

This article详细介绍了如何在运行时创建对象。

答案 2 :(得分:1)

结构类型的存储位置(变量,参数,数组元素或字段)包含其所有公共字段和私有字段的值,通常是连接的,但可能包含一些填充。类类型的存储位置保存对堆对象(可能为空)的引用(4-8个字节)。堆对象保存8-16个字节的开销,以及对象及其祖先持有的所有公共,受保护和私有字段的内容。

如果您要存储的内容是固定大小的值包,则最有可能使用带有公开字段的结构。结构应该是不可变的概念可以追溯到C#编译器采用如下代码的日子:

  readonly Point Pt = new Point(3,4);
  void Yippie() { Pt.X += 5; }

并让Yippie构造一个新的临时Point,复制Pt,调用该临时实例上的X属性设置器,然后将其丢弃。有人认为防止这种无意义的正确方法不是让编译器说"抱歉 - 你不能在只读结构变量"上调用属性设置器,而是定义结构至于没有setter的只读属性。正确的做法是要求任何将要变异this的结构方法或属性在其声明中表明如此,并禁止在只读结构上使用此类方法或属性。

在属性中包装struct字段会影响性能,除了需要强制执行struct invariants的情况外,我建议不要这样做。我还建议避免声明结构readonly,因为任何声明的结构都会在任何字段被访问时被完整复制。

顺便提一下,要注意可变类类型是一件很重要的事情:可变类对象的状态不仅包括其字段的内容,还包括存在于其中的所有引用的集合 。在某些情况下,这可能很有用。通常,防止严重头痛的唯一方法是让持有可变对象引用的实体避免共享此类引用。如果制作某种类型的类型需要额外的工作来防止引用被滥用,那么这是一个好的迹象,表明所讨论的类型应该是一个结构。