C# 为什么类数组比结构数组多消耗约20%的内存?

C# 为什么类数组比结构数组多消耗约20%的内存?,c#,arrays,class,struct,C#,Arrays,Class,Struct,我正在制作一个2D platformer游戏,为了表示一个关卡,我使用了2D瓷砖数组,这些瓷砖是带有位置、类型和各种标志字段的类。当我将tile类中的class关键字更改为struct时,加载的映射消耗的内存大约减少了20% 我不知道这个行为的对错,我只想知道为什么内存消耗会有差异 编辑:数字为1038 MB,分片作为类,845 MB作为结构(没有大部分游戏数据)。对象数组实际上是引用数组,对象存储在堆上 这意味着引用的开销为4或8字节(取决于x86或x64),然后堆上的每个对象的开销为8或16

我正在制作一个2D platformer游戏,为了表示一个关卡,我使用了2D瓷砖数组,这些瓷砖是带有位置、类型和各种标志字段的类。当我将tile类中的
class
关键字更改为
struct
时,加载的映射消耗的内存大约减少了20%

我不知道这个行为的对错,我只想知道为什么内存消耗会有差异


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

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

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

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

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


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

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

详细介绍如何在运行时创建对象。结构类型的存储位置(变量、参数、数组元素或字段)包含其所有公共字段和私有字段的值,通常连接,但可能带有一些填充。类类型的存储位置保存对堆对象(4-8字节)的引用(可能为空)。一个heap对象拥有8-16字节的开销加上对象及其祖先拥有的所有公共、受保护和私有字段的内容

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

readonly Point Pt = new Point(3,4); void Yippie() { Pt.X += 5; } 只读点Pt=新点(3,4); void Yippie(){Pt.X+=5;} 并让
Yippie
构造一个新的临时
,将
Pt
复制到它,在该临时实例上调用
X
属性setter,然后放弃它。有人认为,防止这种胡说八道的正确方法不是让编译器说“对不起——您不能对只读结构变量调用属性setter”,而是定义结构,以便在没有setter的情况下拥有只读属性。正确的做法是要求任何将要变异的结构方法或属性在其声明中指明,并禁止在只读结构上使用此类方法或属性

在属性中包装结构字段会降低性能,我建议不要这样做,除非需要强制执行结构不变量。我还建议您避免声明structs
readonly
,因为这样声明的任何结构在访问任何字段时都会被完全复制


顺便说一句,对于可变类类型,需要注意一件重要的事情:可变类对象的状态不仅包括其字段的内容,还包括其所有引用的集合。在某些情况下,这可能很有用。通常,防止重大问题的唯一方法是让持有可变对象引用的实体避免共享此类引用。如果将某个对象设置为类类型需要额外的工作来防止引用被随意传递,那么这是一个好的迹象,说明所讨论的类型应该是一个结构。

因为.Net结构比.Net类“更轻”。20%似乎有点过分。但是创建(例如)一个“类”对象的1000K实例将比创建一个等效的“结构”对象的1000K实例使用更多的内存…看起来您可能正在考虑使用可变结构(如果我理解“标志”变量)。为了你自己的理智,不要。这里有大量关于类与结构的信息:你的20%的数字很难解释,因为你没有告诉我们每个结构中有多少字节的有效负载。游戏中一个平铺底图的1GB在任何情况下都有点愚蠢。