C#/ XNA巨型内存泄漏

时间:2012-06-06 22:31:06

标签: c# memory-leaks xna

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

2 个答案:

答案 0 :(得分:5)

您尚未发布足够的信息来为您提供更多有根据的猜测。这是我受过教育的猜测:

如果你在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;
    }
}

答案 1 :(得分:2)

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

如果要强制GC循环使用GC.Collect()