6

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

我不知道这个动作的对错,我只想知道为什么内存消耗的差异。

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

4

3 回答 3

11

对象数组其实就是引用数组,对象存放在堆上。

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

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

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

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

于 2012-08-15T19:20:29.193 回答
3

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

本文详细介绍了如何在运行时创建对象和布局。

于 2012-08-15T19:21:23.940 回答
1

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

如果您要存储的东西是固定大小的值包,那么您很可能应该使用带有暴露字段的结构。结构应该是不可变的概念可以追溯到 C# 编译器采用如下代码的时代:

  只读点 Pt = new Point(3,4);
  无效 Yippie() { Pt.X += 5; }

Yippie构造了一个新的临时对象Point,复制Pt到它,在该临时实例上调用X属性设置器,然后丢弃它。有人认为防止这种废话的正确方法不是让编译器说“对不起——你不能在只读结构变量上调用属性设置器”,而是定义结构以便具有只读属性没有二传手。正确的做法是要求任何将要发生变异的结构方法或属性this在其声明中表明这一点,并禁止在只读结构上使用此类方法或属性。

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

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

于 2012-08-15T23:08:52.070 回答