List<T>.AddRange 导致了短暂延迟

3

我有一个实现了ICollidable接口的实体列表。这个接口用于解决实体之间的碰撞。我的实体有:

  • 玩家
  • 敌人
  • 抛射物
  • 道具
  • 瓷砖

每次游戏更新(大约60次/秒),我都会清空列表并根据游戏状态添加当前实体。我是通过以下方式实现的:

collidableEntities.Clear();
collidableEntities.AddRange(players);
collidableEntities.AddRange(enemies);
collidableEntities.AddRange(projectiles);
collidableEntities.AddRange(items);
collidableEntities.AddRange(camera.VisibleTiles);

在将可见瓷砖添加到列表中之前,一切都很正常。运行游戏循环的前1-2秒会出现明显的抽搐,导致绘图延迟(因此我可以看到渲染中的抖动)。我可以删除/添加添加瓷砖的代码行,并看到抖动的发生和不发生,因此我已经将其缩小到了那一行。

我的问题是,为什么? VisibleTiles列表约有450-500个瓷砖,因此数据并不是那么多。每个瓷砖都包含一个Texture2D(图像)和一个Vector2(位置),以确定要呈现的内容和位置。我将继续寻找答案,但从我的角度来看,我无法理解为什么只有前1-2秒会抽搐,而后来就顺畅了。

感谢任何建议。

更新

我尝试将初始容量增加到大约元素数量,但未观察到任何差异。

更新

如请求所示,这是相机.VisibleTiles的代码

public List<Tile>
{
    get { return this.visibleTiles; }
}

2
你有camera.VisibleTiles的代码访问权限吗?它可能不仅仅是提供对象引用吗? - Brian Warshaw
是的,它只是返回对List<Tile>的引用的一个属性。 - Justin Skiles
2
根据@Brian的建议,尝试替换调用 camera.VisibleTiles 但不将其添加到列表的代码行。这将确定是“AddRange”调用还是“VisibleTiles”调用较慢。 - Rotem
请把那段代码粘贴进来,VisibleTiles 属性的代码? - Brian Warshaw
2
获取一个代码性能分析工具。 - asawyer
显示剩余6条评论
3个回答

5
每当你遇到类似这样的性能问题时,正确的做法不是猜测可能的原因;正确的做法是对应用程序进行性能分析。有许多选项可供选择;我相信你可以在StackOverflow上找到建议。
然而,今天我感觉很幸运,所以我要忽略我刚刚给你的建议,瞎猜一下。我会解释我的推理,希望这对于将来诊断这些问题有所帮助。
你描述的问题只发生一次,而且只在游戏开始时发生,这让我相信它不是列表本身或碰撞逻辑的固有功能。当然,这是在假设列表中的对象数量在此期间基本保持不变的情况下。如果是由这两者之一引起的,我会预计它会在每一帧都发生。
因此,我怀疑是“垃圾回收器”。你可能会在游戏开始时(也就是在将所有大型资产加载到内存中后)看到GC的发生。你也可能会在代码中看到似乎随机的点发生,因为你分配的任何对象理论上都可以将其推向集合的边缘。
我的猜测是:当你加载游戏时,你创建的资产正在生成大量的收集压力,但仍然不足以触发收集。在游戏过程中分配对象(在这种情况下,由于调整列表大小而导致),它会增加收集压力,直到GC最终决定激活并引起你观察到的抖动。一旦GC运行,并且所有适当的对象被归类到其正确的代中,抖动就会停止。
正如我所说,这只是一个猜测,尽管是一个受过教育的猜测。然而,这很容易测试。在进入呈现循环之前添加一个对“GC.Collect(2)”的调用。如果我是对的,抖动应该消失。
如果你想更加彻底,我强烈推荐使用微软提供的一款有用于调试内存问题的工具CLR Profiler。这个工具会准确地显示收集发生的时间和原因。对于XNA开发非常有用。

标记为答案,因为它让我朝着正确的方向思考。 - Justin Skiles

1

1-2秒可能是由于GC调整每个代的大小而引起的。查看相应的性能计数器,看看在前几秒中是否有大量的Gen1/2收集(最小化GC对游戏是有用的目标)

额外的随机猜测:由于某种原因,您的所有对象都是struct。并且非常大。因此,复制它们需要很长时间。(不能解释为什么在前几秒后平滑过渡)

注意:

  • 考虑创建迭代器而不是将项目合并到单个列表中,这样可以避免所有复制。
  • 获取分析器
  • 学习查看进程的性能计数器。有趣的类别包括内存、GC、CPU、处理程序。

0

请注意,如果单个项目在循环中逐个添加,这将是一个更大的问题。 AddRange将(尝试)确保有足够的容量来添加所有项目,从而减少了可能发生的缓冲区调整大小的次数。此外,500并不是那么大。通常需要达到数以万计才会开始出现问题。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接