【问题标题】:List<T>.AddRange is causing a brief delayList<T>.AddRange 导致短暂的延迟
【发布时间】:2012-09-12 15:47:03
【问题描述】:

我有一个实现 ICollidable 接口的实体列表。该接口用于解决实体之间的冲突。因此,我的实体是:

  • 玩家
  • 敌人
  • 投射物
  • 项目
  • 瓷砖

在每次游戏更新(大约 60 t/s)时,我都会清除列表并根据游戏状态添加当前实体。我通过以下方式完成此操作:

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 秒打嗝,但从那里开始就很顺畅了。

感谢任何建议。

更新

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

更新

根据要求,这里是 camera.VisibleTiles 的代码

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

【问题讨论】:

  • 您可以访问camera.VisibleTiles 的代码吗?它可能不仅仅是为您提供对象引用吗?
  • 是的,它只是一个返回对 List 的引用的属性。
  • 按照@Brian 的建议,尝试用调用camera.VisibleTiles 但不将其添加到列表中的代码替换该行。这将确定是 AddRange 调用还是 VisibleTiles 调用慢。
  • 你能把这段代码粘贴进去吗? VisibleTiles 属性的代码?
  • 获取代码分析工具

标签: c# list xna profiling


【解决方案1】:

每当您遇到这样的性能问题时,正确的反应是不要猜测可能的原因;正确的回答是分析应用程序。有多种选择;我相信您可以在 StackOverflow 上找到建议。

不过,我今天感觉很幸运,所以我将忽略我刚刚给你的建议,大胆猜测一下。我将解释我的推理,希望对将来诊断此类问题有所帮助。

你描述的问题只发生一次并且只在游戏开始时让我相信这不是列表本身的功能所固有的任何东西,或碰撞逻辑。当然,这是假设列表中的对象数量在这段时间内基本保持不变。如果它由其中任何一个引起的,我希望它每一帧都会发生。

因此我怀疑垃圾收集器。您可能会在游戏开始时看到 GC 发生——换句话说,就在您将所有大量资产加载到内存之后。您也可能会在代码中看似随机的点看到它,因为您分配的任何对象理论上都可以将其推入集合中。

我的猜测是:当您加载游戏时,您创建的资产会产生很大的收集压力,但这不足以触发收集。当您在游戏过程中分配对象时(在这种情况下,由于调整列表的大小),它会增加收集压力到 GC 最终决定激活的点,从而导致您观察到的抖动。一旦 GC 运行,并且所有适当的对象都已降级到正确的代,抖动就会停止。

正如我所说,这只是一个猜测,尽管是受过教育的。但是,它很容易测试。在进入渲染循环之前添加对GC.Collect(2) 的调用。如果我是对的,抖动应该会消失。

如果您想比这更彻底——我强烈推荐它——微软提供了一个对调试内存问题很有用的工具,CLR Profiler。该工具将准确地向您显示收集发生的时间以及原因。它对 XNA 开发非常有用。

【讨论】:

  • 标记为答案,因为它让我朝着正确的方向思考。
【解决方案2】:

1-2秒可能是因为GC调整了每一代的大小。查看相应的 perf counters 并查看第一秒内是否有大量 Gen1/2 收集(最小化 GC 是游戏的有用目标)

额外的随机猜测:无论出于何种原因,您的所有对象都是struct。而且很大。所以复制它们需要很长时间。 (不会解释第一秒后的平滑)

注意事项:

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

【讨论】:

    【解决方案3】:

    您可以使用overloaded constructor for List&lt;T&gt; 以预定义的大小初始化列表:

    var collidableEntities = new List<object>(500);
    

    【讨论】:

    • 请注意,如果在循环中一次添加一个项目,这将是一笔更大的交易。 AddRange 将(尝试)确保所有要添加的项目都有足够的容量,这减少了可能调整缓冲区大小的次数。此外,500 真的没有那么大。您通常需要成千上万的人才能开始发现问题。
    猜你喜欢
    • 2017-11-01
    • 1970-01-01
    • 2010-09-11
    • 2018-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 1970-01-01
    相关资源
    最近更新 更多