我一直在考虑是否有一种方法可以加快 .NET 中的内存释放速度。我正在.NET(仅托管代码)中创建一个不需要重要图形的游戏,但我仍然想正确编写它,以免白白失去性能。
例如,将空值分配给不再需要的对象是否有用?我在 Internet 上的一些示例中看到了这一点。
我一直在考虑是否有一种方法可以加快 .NET 中的内存释放速度。我正在.NET(仅托管代码)中创建一个不需要重要图形的游戏,但我仍然想正确编写它,以免白白失去性能。
例如,将空值分配给不再需要的对象是否有用?我在 Internet 上的一些示例中看到了这一点。
将空值分配给不再需要的对象是否有用?
一般来说,没有。您将在网上看到的大多数执行此操作的示例都是由从 VB6 来到 .Net 的人提供的,这是一种常见的最佳实践。在 .Net 中,它的用处不大。如果你有一个运行时间很长的方法,它可能会让垃圾收集器更早地找到对象——但如果你的方法运行时间很长,你就会遇到其他问题。
相反,在 .Net 中,您应该构建简短的方法并在尽可能小的范围块中尽可能晚地定义变量。使用由范围确定的变量的“自然”生命周期,但保持自然生命周期较短。
正如此处至少一个其他答案所建议的那样,您不应该进行自己的垃圾收集。这实际上会使事情变慢。.Net 使用分代垃圾收集器。通过强制垃圾收集,您可能会收集目标对象(您也可能不会 - 垃圾收集无法保证)。但是您可能还会强制将一堆您还无法收集的其他对象存储在更高阶的代中,从而使它们将来更难收集。所以不要这样做。
你不应该太担心,也不要过早优化。.NET 中的内存管理是自动的并且非常高效。如果你确实开始“优化”,你需要确切地知道你在做什么,否则你可能会放慢速度。
所以先完成你的游戏,然后如果它太慢,使用分析器来查找问题。很可能不会是内存问题。
.net 中的大多数与图形相关的对象(Image 及其后代、Graphics、Pen、Brush 等)都实现了 IDisposable。如果您打算将对象用于特定操作,那么不要再使用以下模式:
using(var g = Graphics.FromBitmap(bmp))
{
//Do some stuff with the graphics object
}
这样做将确保任何非托管资源在超出范围时将被释放。
我发现在 .net 中分配对象是对性能影响最大的事情之一。为了解决这个问题,我使用工厂模式来回收我使用过的对象。这是简单的通用实现:
internal class ListFactory<T>
where T: IRecyclable, new()
{
private List<T> _internalList;
private int _pointer;
public ListFactory()
{
_internalList = new List<T>();
_pointer = 0;
}
public void Clear()
{
_pointer = 0;
}
public int Count
{
get
{
return _pointer;
}
}
public List<T> InnerList
{
get
{
return _internalList;
}
}
//Either return T form the list or add a new one
//Clear T when it is being reused
public T Create()
{
T t;
//If the pointer is less than the object count then return a recycled object
//Else return a new object
if (_pointer < _internalList.Count )
{
t = _internalList[_pointer];
t.Recycle();
}
else
{
t = new T();
_internalList.Add(t);
}
_pointer ++;
return t;
}
}
对于我的线路路由算法,我需要不断地保留许多值作为实现以下接口的RouteNode :
public interface IRecyclable
{
void Recycle();
}
这些被不断地创造和摧毁。要回收这些对象,请创建一个新工厂:
nodeFactory = new ListFactory<RouteNode>();
当你需要一个对象时,调用 create 方法:
RouteNode start = nodeFactory.Create();
RouteNode goal = nodeFactory.Create();
完成列表中的对象后,清除列表。指针被重置到列表的开头,但对象本身并没有被破坏。事实上,只有在对象被再次回收时才会调用 Recycle 方法。(如果您有其他对象引用,您可能希望更早执行此操作,请参阅下面的评论)
这是一个非常幼稚的实现,但它是一个开始的地方。
在某些情况下,将不再需要的对象设置为 null 会很有用。通常它是无用的或适得其反的,但并非总是如此。有帮助的四种主要情况:
要做到这一点(而且我不得不经常分配 >50mb 的内存),请致电:
myObj = null;
GC.Collect();
GC.WaitForPendingFinalizers();
我注意到应用程序的内存占用将大大减少。理论上,您不需要这样做。然而,在实践中,使用 32 位 Windows 操作系统,您可能会在任何给定时间获得 2 个 > 300mb 的连续块,并且该空间被大量小分配或一系列大分配占用可能意味着其他大分配将不必要地失败。垃圾收集器尽可能在后台运行,但如果您现在绝对必须进行大量分配,那么这组行有助于我实现这一点。
编辑:根据我在评论中的内容,对于反对者。
如果您阅读了Rico Mariani 撰写的关于垃圾收集的整篇文章,您会注意到大的、不频繁的、不可预测的内存分配属于场景 #2。到白衣:
规则 #2
如果一些非重复事件刚刚发生并且该事件很可能导致许多旧对象死亡,请考虑调用 GC.Collect()。
一个典型的例子是,如果您正在编写一个客户端应用程序,并且您显示一个非常大且复杂的表单,其中包含大量相关数据。您的用户刚刚与此表单进行了交互,可能会创建一些大型对象...诸如 XML 文档或一两个大型 DataSet 之类的东西。当表单关闭时,这些对象已经失效,因此 GC.Collect() 将回收与它们关联的内存。
现在为什么我会建议这是一个可能的时间打电话给收集器?我的意思是,我通常的建议是“收集器是自我调整的,所以不要乱用它”。你可能会问为什么态度的改变?
好吧,在这种情况下,收集者的倾向[原文如此] 试图根据过去预测未来可能会失败。
如果您在游戏中进行大量分配,则需要小心处理内存的方式。垃圾收集器根据过去的事件进行预测,如果管理不当,32 位机器上的大块内存可能对未来的分配造成毁灭性的影响。如果您还没有这样做,请不要自动假设我错了;如果你已经这样做了,我欢迎解释如何正确地做到这一点(即,如何对内存进行碎片整理以确保我总是可以在给定时间分配 50-100mb 的内存)。