如何在C#中有效地处理许多更新对象?

如何在C#中有效地处理许多更新对象?,c#,garbage-collection,xna,C#,Garbage Collection,Xna,我正在用C#和XNA开发一个2D高空射击游戏。我有一个我称之为“bullet”的类,需要每几分之一秒更新其中的许多实例 我做这件事的第一个方法是有一个通用的项目符号列表,并根据需要简单地删除和添加新项目符号。但是在这样做的过程中,GC经常起作用,我的游戏有一些周期性的急促滞后。(删去了很多代码,但只想显示一个简单的片段) 我的第二个也是当前的尝试是,创建一个单独的通用子弹堆栈,当我使用完一颗子弹时,我会将其推到该堆栈中,当我需要一颗新的子弹时,如果堆栈中有任何东西,我会弹出一颗子弹。如果堆栈中没

我正在用C#和XNA开发一个2D高空射击游戏。我有一个我称之为“bullet”的类,需要每几分之一秒更新其中的许多实例

我做这件事的第一个方法是有一个通用的项目符号列表,并根据需要简单地删除和添加新项目符号。但是在这样做的过程中,GC经常起作用,我的游戏有一些周期性的急促滞后。(删去了很多代码,但只想显示一个简单的片段)

我的第二个也是当前的尝试是,创建一个单独的通用子弹堆栈,当我使用完一颗子弹时,我会将其推到该堆栈中,当我需要一颗新的子弹时,如果堆栈中有任何东西,我会弹出一颗子弹。如果堆栈中没有任何内容,那么我会在列表中添加一个新项目符号。它似乎减少了急促的滞后,但又一次,有时仍然会出现一些急促的滞后(尽管我不知道这是否相关)


所以,我知道过早优化是万恶之源,但这是非常明显的低效,我可以及早发现(这甚至是在不得不担心敌人的子弹填满屏幕之前)。所以我的问题是:将未使用的对象推送到堆栈会调用垃圾收集吗?引用将保持活动状态还是对象仍在被销毁?有没有更好的方法来处理更新许多不同的对象?比如说,我是不是太花哨了?只需反复浏览列表并找到一个未使用的项目符号就可以了吗?

这里有很多问题,很难判断

首先,
bullet
是结构还是类?如果bullet是一个类,则在任何时候构造一个类,然后取消它的根(让它超出范围或将其设置为null),您将向集合添加GC需要的内容

如果你要做很多这些,并将它们更新到每一帧,你可能想考虑使用<代码>列表<代码> > <代码>弹头<代码>作为一个结构,列表被预先分配(生成一个大到足以容纳你所有的子弹的大小,这样就不会在你调用<代码>列表时被重新创建。这将大大有助于降低GC压力

而且,仅仅因为我需要咆哮:

所以,我知道过早优化是万恶之源,但这是非常明显的低效


永远不要害怕优化一个你知道会导致问题的例程。如果您看到性能问题(即:您的滞后),这不再是过早的优化。是的,您不想优化每一行代码,但是您确实需要优化代码,尤其是当您看到真正的性能问题时。一旦发现问题就立即对其进行优化要比以后尝试对其进行优化容易得多,因为在添加大量使用
bullet
类的其他代码之前,所需的任何设计更改都将更容易实现。

我承认我在这方面没有任何经验,但是我会考虑使用传统的数组。将数组初始化为大于所需的大小,即理论上的最大子弹数,例如100。然后从0开始分配数组开头的项目符号,将最后一个元素保留为null。因此,如果您有四个活动项目符号,您的阵列将如下所示:

0 B 1b 2 B 3 B 4空 ... 99空

这样做的好处是数组总是被分配的,因此您不会处理更复杂的数据结构的开销。这实际上相当类似于字符串的工作方式,因为它们实际上是带有空终止符的char[]

也许值得一试。一个缺点是,在取出子弹时,你必须进行一些手动操作,可能会将子弹之后的所有东西都移动到插槽中。但此时您只是在移动指针,因此我认为这不会像分配内存或GC那样带来很高的代价。

您可能会发现这个方法很有用。只需要一个子弹对象,但多个飞锤可能会为其指定不同的位置和速度。飞锤可以存储在预先分配的数组(例如100)中,并标记为活动或不活动


这将完全消除垃圾收集,并可能减少跟踪每个项目符号可延展属性所需的空间。

您正确地假设,将未使用的项目符号保留在堆栈中可以防止它们被垃圾收集


至于延迟的原因,您是否尝试过任何分析工具?只是为了找出问题所在。

列表实际上有一个内置的容量,以防止每次添加/删除都分配。一旦超过容量,它会增加更多(我想我每次都会加倍)。问题可能更多的是删除而不是添加。Add只会出现在第一个按大小跟踪的开放点上。要删除,必须压缩列表以填充现在已空的插槽。如果总是删除列表前面的元素,则每个元素都需要向下滑动

堆栈仍然使用数组作为其内部存储机制。因此,您仍然受到数组的添加/删除属性的约束

要使数组工作,您需要为每个项目创建一个活动属性的所有项目符号。当需要新项目符号时,将活动标志填充为true并设置所有新项目符号属性。完成后,将活动标志翻转为false

如果您想尝试消除每次重绘都需要迭代列表(列表可能非常大,具体取决于您将允许的内容),那么可以尝试在数组中实现一个双链接列表。当需要一个新的项目符号时,要求数组提供第一个可用的免费条目。转到最后一个活动项目符号(变量),并将新项目符号数组位置添加到其下一个活动项目符号属性中。当需要删除它时,转到上一个项目符号并将其活动项目符号属性更改为已删除的下一个活动项目符号

//I am using public fields for demonstration.  You will want to make them properties
public class Bullet {
  public bool Active;
  public int thisPosition;
  public int PrevBullet = -1;
  public int NextBullet = -1;
  public List<Bullet> list;

  public void Activate(Bullet lastBullet) {
    this.Active = true;
    this.PrevBullet = lastBullet.thisPosition;
    list[this.PrevBullet].NextBullet = this.thisPosition;
  }

  public void Deactivate() {
    this.Active = false;
    list[PrevBullet].NextBullet = this.NextBullet;
    list[NextBullet].PrevBullet= this.PrevBullet;
  }
}
//我正在使用公共字段进行演示。你会想要我的
if (triggerButton)
{
    if (bulletStack.Count > 0)
    {
        bullet temp = bulletStack.Pop();
        temp.resetPosition();
        bullets.Add(temp);
    }
    else
    {
        bullets.Add(new bullet());
    }
}
if (bulletDestroyed)
{
    bulletStack.Push(bullet);
    bullets.Remove(bullet);
}
//I am using public fields for demonstration.  You will want to make them properties
public class Bullet {
  public bool Active;
  public int thisPosition;
  public int PrevBullet = -1;
  public int NextBullet = -1;
  public List<Bullet> list;

  public void Activate(Bullet lastBullet) {
    this.Active = true;
    this.PrevBullet = lastBullet.thisPosition;
    list[this.PrevBullet].NextBullet = this.thisPosition;
  }

  public void Deactivate() {
    this.Active = false;
    list[PrevBullet].NextBullet = this.NextBullet;
    list[NextBullet].PrevBullet= this.PrevBullet;
  }
}