【问题标题】:C#/XNA Giant Memory LeakC#/XNA 巨大的内存泄漏
【发布时间】:2012-06-06 22:31:05
【问题描述】:

这是我在这里的第一篇文章。 我正在使用 XNA 在 Visual Studio 2010 中制作游戏,但我遇到了巨大的内存泄漏。我的游戏开始使用 17k 内存,然后在十分钟后达到 65k。我运行了一些内存分析器,它们都说正在创建 String 对象的新实例,但它们不是实时的。 String 的活动实例的数量根本没有改变。它还创建了 Char[](我期望它)、Object[] 和 StringBuilder 的实例。我的游戏很新,但是这里发布的代码太多。我不知道如何摆脱不活动的实例,请帮助!

【问题讨论】:

  • 我不确定我是否会将 65k 视为“巨大的内存泄漏”。你几乎可以把它装在 30 年前的个人电脑上。如果它爆炸到 650MB,我可能会有点担心。
  • 我猜你的意思一定是高达 65,000K,因为我认为你甚至不能再降低到 17K 的托管进程大小。即便如此,按照现代标准,65M 也不是那么大。
  • 好的,如果没关系,我们就让它滑动。它偶尔会重置。非常感谢!是的,我的意思是 65,000k 哈哈

标签: c# memory-leaks xna


【解决方案1】:

您发布的信息不足以为您提供有根据的猜测。这是我有根据的猜测:

如果你在 Draw 方法中做这样的事情:

spriteBatch.DrawString(font, "Score: " + score, location, Color.Black);
spriteBatch.DrawString(font, "Something else: " + foo, overHere, Color.Black);
spriteBatch.DrawString(font, "And also: " + bar, overThere, Color.Black);

然后,每个调用都会在每次运行时在您背后创建新的 stringStringBuilder 对象。因为它们在您的 Draw 方法中,所以每个可能每秒运行 60 次。那是分配了很多临时对象!

要验证是否是这种情况 - 使用 CLR Profiler。听起来你已经这样做了。

虽然这并不是真正的“泄漏”——垃圾收集器最终会清理它们——但这种分配模式在游戏中是不可取的。请参阅this blog post,了解游戏中处理垃圾收集的两种方法。方法 1 通常更容易并且提供更好的结果 - 所以我在这里讨论它。

此时值得一提的是,PC 上的 GC 速度足够快,这样的分配并不重要。 GC 会以很少的开销清理微小的对象(例如您的临时字符串)。

另一方面,在 Xbox 360 上,即使像这样定期产生少量垃圾也会导致一些严重的性能问题。 (我不确定 WP7,但我个人会像对待 Xbox 一样对待它——小心!)

我们如何解决这个问题?

答案很简单:DrawString 将接受StringBuilder 的实例来代替string。创建StringBuilder 的一个实例,然后在每次需要组合自定义字符串时重用它。

请注意,将数字或其他对象隐式或通过其ToString() 方法转换为字符串也会导致分配。因此,您可能必须编写自己的自定义代码以附加到 StringBuilder 而不会导致分配。

这是我使用的一种扩展方法,用于在不分配的情况下将整数附加到字符串:

public static class StringBuilderExtensions
{
    // 11 characters will fit -4294967296
    static char[] numberBuffer = new char[11];

    /// <summary>Append an integer without generating any garbage.</summary>
    public static StringBuilder AppendNumber(this StringBuilder sb, Int32 number)
    {
        bool negative = (number < 0);
        if(negative)
            number = -number;

        int i = numberBuffer.Length;
        do
        {
            numberBuffer[--i] = (char)('0' + (number % 10));
            number /= 10;
        }
        while(number > 0);

        if(negative)
            numberBuffer[--i] = '-';

        sb.Append(numberBuffer, i, numberBuffer.Length - i);

        return sb;
    }
}

【讨论】:

  • 技术上你不需要 numberBuffer;一次只追加 1 个字符(由于减少缓存命中而更快)。如果您担心调整大小,请使用 sb.EnsureCapacity(sb.Length + 16) 来防止它(16 保留缓存行对齐)。
  • 哦,哇,这很有道理。我会尽快尝试,非常感谢!
  • @GGulati 虽然你是对的,在字符上调用 Append 比在缓冲区中构建字符串并将其放置到位的性能略好,请注意,算法正在正确构建字符串 -向左。我将把找出从左到右的算法作为练习;)此外:不必担心EnsureCapacity。这里的重点是重用缓冲区。所以提高容量只会发生一次。 CPU时间的差异是如此微小。这是特定的 GC 优化。在顶部进行不必要的额外优化的错误形式。
【解决方案2】:

C# 中没有内存泄漏(或者说,它们非常很难获得)。你所经历的是正常的。垃圾收集器不会“感觉”它需要收集内存,所以它不需要。每当内存不足时,就会发生垃圾收集。如果您绝对确定没有保留对strings 的不必要引用,那么一切都很好。

如果您想强制执行 GC 循环,请使用 GC.Collect()

【讨论】:

  • 在游戏中使用GC.Collect 不是一个好主意,最好在游戏循环中使用对象池并避免动态分配。
  • XNA 的某些部分不是托管代码,因此 GC 将无法工作。像这样:stackoverflow.com/questions/3364481/…
猜你喜欢
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 2014-02-10
  • 2013-05-15
  • 1970-01-01
相关资源
最近更新 更多