【发布时间】:2009-03-03 21:57:55
【问题描述】:
当您需要非常小的对象时,比如说包含 2 个浮点属性,并且您将拥有数百万不会立即“销毁”的对象,是结构体还是类更好的选择?
就像在 xna 中作为库一样,有 point3s 等作为结构体,但如果您需要长时间保留这些值,会不会对性能造成威胁?
【问题讨论】:
当您需要非常小的对象时,比如说包含 2 个浮点属性,并且您将拥有数百万不会立即“销毁”的对象,是结构体还是类更好的选择?
就像在 xna 中作为库一样,有 point3s 等作为结构体,但如果您需要长时间保留这些值,会不会对性能造成威胁?
【问题讨论】:
与大多数关于结构的问题相反,这实际上似乎是结构的一个很好的用途。如果它包含的数据是值类型,并且您将使用其中的很多,那么结构会很好地工作。
一些提示:
:: 结构体不应大于 16 字节,否则将失去性能优势。
:: 使结构不可变。这使得用法更清晰。
例子:
public struct Point3D {
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
public Point3D(float x, float y, float z) {
X = x;
Y = y;
Z = z;
}
public Point3D Invert() {
return new Point3D(-X, -Y, -Z);
}
}
【讨论】:
myRect = new Rectangle(myRect.X, myRect.Y+4, myRect.Width, myRect.Height); 真的比myRect.Y += 4; 更清晰吗?改变结构的方法是有问题的,但在许多情况下,暴露的字段应该是常态。
myRect = myRect.AddY(4);。
var tempRect = ListOfRects[index]; tempRect.Y += 4; ListOfRects[index] = tempRect; 仍然比 var tempRect = ListOfRects[index]; ListOfRects[index] = new Rectangle(tempRect.X, tempRect.Y+4, tempRect.Width, tempRect.Height); 更清晰。除其他外,一眼就能看出它正在修改哪些字段......
ListOfRects[index].Y += 4 会生成虚假代码而不是产生编译时错误。 PODS(普通旧数据结构)具有与类类型对象不同的语义,但它们都具有相同的语义。假设有一个List<CRect>,其中CRect 是一个与Rectangle 具有相同属性的可变类。怎么会...
答案取决于最终存储对象/值的位置。如果要将它们存储在像 ArrayList 这样的无类型集合中,那么您最终将它们装箱。装箱为结构创建对象包装器,其占用空间与类对象相同。另一方面,如果您使用 T[] 或 List 之类的类型化数组,那么使用结构将仅存储每个元素的实际数据,并且仅存储整个集合而不是其元素的足迹。
因此,在 T[] 数组中使用结构更有效。
【讨论】:
最大的问题是内存是分配在栈上还是堆上。默认情况下,结构体进入堆栈,并且堆栈通常在空间方面受到更多限制。所以像这样创建一大堆结构可能是个问题。
不过,在实践中,我真的认为这没什么大不了的。如果你有那么多它们,它们很可能是某个类实例(在堆上)的一部分。
【讨论】:
Struct 似乎适合这个应用程序。
请记住,“需要保留这些值”意味着它们存储在堆中某处,可能是类实例的数组字段。
需要注意的一点是,这会导致在大对象堆上进行分配。目前尚不清楚这个堆如何进行碎片整理(如果有的话),但是对于寿命很长的对象来说,这可能不是问题。
对数以百万计的这些数据类型使用类可能会因为取消引用的剪切量而代价高昂,而对这种类型的操作可能会发生这种情况。
【讨论】:
通常,相同类型的非别名(即非共享)数据的大型数组最好存储在结构中以提高性能,因为您减少了间接次数。 (另见when-are-structs-the-answer)。 class 和 struct 之间的确切性能差异取决于您的使用情况。 (例如,在操作中,你只访问结构的一部分吗?你做很多临时复制吗?如果结构很小,它可能总是更好用,但如果它很大,创建临时副本可能会减慢你的速度。如果你使其不可变,您将必须始终复制整个内容以更改值。)
如有疑问,请测量。
由于您对此类测量可能不明显的可能的长期影响感兴趣,因此请注意此类数组可能存储在大对象堆中,应该重复使用而不是销毁和重新分配. (见CRL Inside Out: Large Object Heap Uncovered。)
在调用中传递较大尺寸的结构时,您可能希望使用 ref 参数传递它们以避免复制。
【讨论】:
值类型(结构)适用于不经常在堆上分配的类型,即它们大多包含在另一个引用或值类型中。
您给出的 Vector3 示例是一个完美的示例。您很少会在堆中悬挂 Vector3,它们大部分时间会包含在本身在堆中的类型中,或者用作局部变量,在这种情况下,它将分配在堆栈上。
【讨论】: