所以这里实际上有两个问题。您实际上问了一个问题:有哪些技术可以处理结构应该是不可变的这一事实,因为它们是按值复制的,但您想要改变一个。然后还有一个问题激发了这个问题,即“我怎样才能使我的程序的性能可以接受?”
我的另一个答案解决了第一个问题,但第二个问题也很有趣。
首先,如果探查器实际上已识别出性能问题是由于单元的垃圾收集造成的,那么它是possible将 cell 变成结构会有所帮助。也有可能它根本没有帮助,而且有可能这样做会让情况变得更糟。
您的单元格不包含任何引用类型;我们知道这一点是因为你说过它们只有三个字节。如果阅读本文的其他人认为他们可以通过将类转换为结构来进行性能优化,那么它可能根本没有帮助,因为该类可能包含引用类型的字段,在这种情况下,垃圾收集器仍然必须收集每个实例,即使它被转换为值类型。里面的参考类型也需要收集!如果 Cell 仅包含值类型(显然确实如此),出于性能原因,我建议尝试此操作。
这可能会让情况变得更糟,因为值类型并不是万能的。他们也有成本。复制值类型通常比引用类型更昂贵(引用类型几乎总是寄存器的大小,几乎总是在适当的内存边界上对齐,因此芯片针对复制它们进行了高度优化)。并且值类型始终被复制。
现在,在你的情况下,你有一个结构,它是smaller比参考;引用通常是四个或八个字节。并且您将它们放入一个数组中,这意味着您正在将数组打包;如果有一千个,则需要三千字节。这意味着每四个结构中有三个未对准的,意味着有更多时间(在许多芯片架构上)从阵列中获取值。您可以考虑衡量以下因素的影响padding你的结构体变成了四个字节,看看这是否有什么不同,前提是你仍然将它们保存在一个数组中,这让我想到了下一点......
Cell 抽象可能只是一个糟糕的抽象用于存储大量细胞的数据。如果问题是 Cell 是类,您要保留数千个 Cell 的数组,并且收集它们的成本很高,那么除了将 Cell 制作为结构体之外,还有其他解决方案。例如,假设一个 Cell 包含两个字节的 Population 和一个字节的 Color。那就是机制Cell,但肯定不是界面你想暴露给用户。您的机制没有理由必须使用与接口相同的类型。因此你可以按需制造 Cell 类的实例:
interface ICell
{
public int Population { get; set; }
public Color Color { get; set; }
}
private class CellMap
{
private ushort[,] populationData; // Profile the memory burden vs speed cost of ushort vs int
private byte[,] colorData; // Same here.
public ICell this[int x, int y]
{
get { return new Cell(this, x, y); }
}
private sealed class Cell : ICell
{
private CellMap map;
private int x;
private int y;
public Cell(CellMap map, int x, int y)
{
this.map = map; // etc
}
public int Population
{
get { return this.map.populationData[this.x, this.y]; }
set { this.map.populationData[this.x, this.y] = (ushort) value; }
}
等等。按需制造电池。如果它们的寿命很短,它们几乎会立即被收集。CellMap 是一个抽象, so 使用抽象来隐藏杂乱的实现细节。
使用这种架构,您不会遇到任何垃圾收集问题,因为您几乎没有活动的 Cell 实例,但您仍然可以说
map[x,y].Population++;
没问题,因为第一个索引器创建了一个不可变的对象知道如何更新地图的状态. The Cell不需要是可变的;请注意,Cell 类是完全不可变的。 (哎呀,这里的 Cell 可能是一个结构体,当然,将其转换为 ICell 无论如何都会将其装箱。)map这是可变的,并且单元格会为用户改变地图。