C#/XNA 巨大的内存泄漏

本文关键字:内存 泄漏 巨大 XNA | 更新日期: 2023-09-27 18:36:34

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

C#/XNA 巨大的内存泄漏

您没有发布足够的信息来为您提供超出根据的猜测。这是我有根据的猜测:

如果您在 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);

然后,这些调用中的每一个都将创建新的string,并在每次运行时将对象StringBuilder在背后。因为它们在您的Draw方法中,所以每个方法每秒可能运行 60 次。这是很多临时对象被分配!

若要验证是否是这种情况,请使用 CLR 探查器。听起来你已经这样做了。

虽然这不是真正的"泄漏"——垃圾收集器最终会清理它们——但这种分配模式在游戏中是不可取的。请参阅这篇博客文章,了解在游戏中处理垃圾收集的两种方法。方法 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;
    }
}

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

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