如何处理返回结构的永恒?如何处理、结构

2023-09-04 01:41:32 作者:冗默詮释①苆

我正在写一个游戏,有细胞的一个巨大的二维数组。一个单元只需要3个字节。我也有一类称为CellMap,其中包含了二维数组作为私人领域,并通过一个公共索引提供对它的访问。

I'm writing a game that has a huge 2D array of "cells". A cell takes only 3 bytes. I also have a class called CellMap, which contains the 2D array as a private field, and provides access to it via a public indexer.

探查表明,性能问题是由于过多的Cell对象的垃圾回收。所以,我决定做一个细胞结构(这是一个类)。

Profiling showed that a performance problem is caused by garbage collection of too many Cell objects. So I decided to make Cell a struct (it was a class).

但是,现在code这样不起作用:

But now code like this doesn't work:

cellMap[x, y].Population++;

我能想到的很多选择,但我真的不喜欢任何人。

I can think of many options, but I don't really like any of them.

使数组公众,然后写 cellMap.Data [X,Y]。人口= 5; 停止使用CellMap类,然后只需直接使用二维数组。但CellMap非常方便,因为它实现了自己的优化序列化,它暴露了宽度身高属性都比较方便不是写 cellMap.GetLength(0) 制作单元一成不变。不过,再怎么会在code看? cellMap [X,Y] = IncrementCellPopulation(cellMap [X,Y])?非常详细。 系统像两个实用功能 cellMap.SetPopulationAt(X,Y​​,5) 在拥有CellMap每类中,添加一个实用性能如私人细胞[,] CellData {{返回this.CellMap.GetInternalArray(); }} ,所以当时我的code可以像 CellData [X,Y]。人口++ Make the array public, and write cellMap.Data[x, y].Population = 5; Stop using a CellMap class, and just use a 2D array directly. But CellMap is very convenient because it implements its own optimized serialization, and it exposes Width and Height properties that are more convenient than writing cellMap.GetLength(0) Make Cell immutable. But then how would the code look? cellMap[x, y] = IncrementCellPopulation(cellMap[x, y])? Very verbose. A couple of utility functions like cellMap.SetPopulationAt(x, y, 5) In every class that owns a CellMap, add a utility property like private Cell[,] CellData { get { return this.CellMap.GetInternalArray(); } }, so then my code can look like CellData[x, y].Population++

如何在这个问题传统上解决了吗?

How is this problem traditionally solved?

推荐答案

所以,实际上有两个问题在这里。还有你实际上问的问题:什么是技术处理的事实,结构应该是一成不变的,因为它们是按值复制,但要变异之一。再有就是它激励这一块,这是一个问题我怎样才能让我的程序的性能是否可以接受?

So there are actually two problems here. There's the question you actually asked: what are techniques to deal with the fact that structs ought to be immutable because they are copied by value, but you want to mutate one. And then there's the question which is motivating this one, which is "how can I make the performance of my program acceptable?"

我的其他回答解决了第一个问题,但第二个问题是有趣的为好。

My other answer addresses the first question, but the second question is interesting as well.

首先,如果探查实际上已经确定了所述性能问题是由于细胞的垃圾收集,那么它是即使细胞成一个结构将有助于可能的。这也有可能,它不会在所有帮助,这是可能的,这样做将会使情况变得更糟。

First off, if the profiler has actually identified that the performance problem is due to garbage collection of cells, then it is possible that making cell into a struct will help. It is also possible that it will not help at all, and it is possible that doing so will make it worse.

您细胞不包含任何引用类型;我们知道这一点,因为你已经表示,他们只有三个字节。如果别人看,这是认为他们可以通过把一个类为一个结构,那么它可能没有帮助,因为在所有进行性能优化的类可能包含引用类型的字段的,在这种情况下,垃圾收集器仍然能够收集每个实例,即使是变成一个值类型。在它的引用类型需要收集呢!我只想建议尝试这种出于性能的考虑,如果单元格中只包含值类型,这显然它的作用。

Your cells do not contain any reference types; we know this because you've said they are only three bytes. If someone else reading this is thinking that they could make a performance optimization by turning a class into a struct then it might not help at all because the class might contain a field of reference type, in which case the garbage collector still has to collect every instance, even if it is turned into a value type. The reference types in it need to be collected too! I would only recommend attempting this for performance reasons if Cell contains only value types, which apparently it does.

这可能使情况变得更糟,因为值类型不是万能的;他们有成本了。值类型往往更昂贵的复制比引用类型(其是pretty的多总是一个寄存器的大小,几乎总是适当的存储器边界上对齐,因此,该芯片是高度为复制这些优化)。和值类型复制所有的时间。

It might make it worse because value types are not a panacea; they have costs too. Value types are often more expensive to copy than reference types (which are pretty much always the size of a register, almost always aligned on the appropriate memory boundary, and therefore the chip is highly optimized for copying them). And value types are copied all the time.

现在,你的情况,你有一个结构它的小的比参考;引用是四个或八个字节典型。而你把它们放入数组,这意味着你正在收拾阵列下降;如果你有一个三千人,它会采取三千个字节。这意味着,三出于每四个结构的存在的对齐的,这意味着更多的时间(在许多芯片架构)来获取值从数组中。你可能会考虑测量的填充的你的结构的影响出四个字节,看看是否有差别,只要你仍然要保持他们在一个阵列,这使我想到我的下一个点。 ..

Now, in your case you have a struct which is smaller than a reference; references are four or eight bytes typically. And you're putting them in an array, which means that you are packing the array down; if you have a thousand of them, it'll take three thousand bytes. Which means that three out of every four structs in there are misaligned, meaning more time (on many chip architectures) to get the value out of the array. You might consider measuring the impact of padding your struct out to four bytes to see if that makes a difference, provided you're still going to keep them in an array, which brings me to my next point...

细胞抽象可能仅仅是一个坏的抽象用于存储数据有关地段的细胞的目的。如果问题是,细胞类,你保持了数千单元阵列,并收集他们是昂贵的,那么有办法总比让细胞变成一个结构等。假设例如一个细胞含有两个字节人口和颜色的一个字节。这是的机制细胞的,但肯定不是在的接口的要公开给用户。 我们没有理由为什么你的机构必须使用相同类型的接口。因此,你可以的 Cell类的需求制造实例的:

The Cell abstraction might simply be a bad abstraction for the purpose of storing data about lots of cells. If the problem is that Cells are classes, you're keeping an array of thousands of Cells, and collecting them is expensive, then there are solutions other than making Cell into a struct. Suppose for example that a Cell contains two bytes of Population and one byte of Color. That is the mechanism of Cell, but surely that is not the interface you want to expose to the users. There is no reason why your mechanism has to use the same type as the interface. And therefore you could manufacture instances of the Cell class on demand:

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是一个抽象的概念的,这样的使用抽象来隐藏凌乱的实施细则。的

and so on. Manufacture the cells on demand. They will almost immediately be collected if they are short-lived. CellMap is an abstraction, so use the abstraction to hide the messy implementation details.

在此架构下,你没有任何垃圾回收问题,因为你几乎没有活细胞的实例,但你仍然可以说

With this architecture you don't have any garbage collection problems because you have almost no live Cell instances, but you can still say

map[x,y].Population++;

没问题,因为第一个索引制造均的不可变对象知道如何更新地图的状态。所述的细胞的不需要是可变的;注意Cell类是完全不可变的。 (哎呀,单元格可能是一个结构在这里,当然,尽管它铸造ICELL只想反正框它)。它是地图的是可变的,而细胞变异的地图该用户。的

no problem, because the first indexer manufactures an immutable object which knows how to update the state of the map. The Cell doesn't need to be mutable; notice that the Cell class is completely immutable. (Heck, the Cell could be a struct here, though of course casting it to ICell would just box it anyway.) It is the map which is mutable, and the cell mutates the map for the user.