我正在编写一个金融 C# 应用程序,它从网络接收消息,根据消息类型将它们转换为不同的对象,最后在它们上应用应用程序业务逻辑。
关键是在应用了业务逻辑之后,我很确定我再也不需要这个实例了。我不想等待垃圾收集器释放它们,而是明确地“删除”它们。
在 C# 中是否有更好的方法可以做到这一点,我应该使用对象池来重用相同的实例集还是有更好的策略。
目标是避免垃圾收集在时间关键的过程中使用任何 CPU。
我正在编写一个金融 C# 应用程序,它从网络接收消息,根据消息类型将它们转换为不同的对象,最后在它们上应用应用程序业务逻辑。
关键是在应用了业务逻辑之后,我很确定我再也不需要这个实例了。我不想等待垃圾收集器释放它们,而是明确地“删除”它们。
在 C# 中是否有更好的方法可以做到这一点,我应该使用对象池来重用相同的实例集还是有更好的策略。
目标是避免垃圾收集在时间关键的过程中使用任何 CPU。
不要立即删除它们。为每个对象调用垃圾收集器是个坏主意。通常你真的不想和垃圾收集器混在一起,即使是时间关键的进程也只是等待发生的竞争条件,如果它们那么敏感的话。
但是,如果您知道您的应用程序将有繁忙和轻负载时段,您可以在达到轻负载时段时尝试更通用的 GC.Collect() 以鼓励在下一个繁忙时段之前进行清理。
看这里:http: //msdn.microsoft.com/en-us/library/bb384202.aspx
你可以告诉垃圾收集器你现在正在做一些关键的事情,它会尽量对你好。
你自己打——使用对象池并重用这些对象。对这些对象的调用的语义需要隐藏在工厂外观后面。您需要以某种预定义的方式扩展池。每次达到极限时,大小可能会翻倍——高水位算法或固定百分比。我真的强烈建议你不要调用 GC.Collect()。
当池上的负载变得足够低时,您可以缩小池并最终触发垃圾收集——让 CLR 担心它。
尝试事后猜测垃圾收集器通常是一个非常糟糕的主意。在 Windows 上,垃圾收集器是一个世代收集器,可以依靠它非常有效。这个一般规则有一些值得注意的例外 - 最常见的是发生一次性事件,您知道事实将导致大量旧对象死亡 - 一旦对象被提升到 Gen2(寿命最长)他们倾向于闲逛。
在您提到的情况下,您听起来好像正在生成许多短期对象 - 这些将导致 Gen0 集合。无论如何,这些都相对经常发生,并且是最有效的。如果您愿意,您可以通过拥有可重用的对象池来避免它们,但最好在采取此类操作之前确定 GC 是否是性能问题 - CLR 分析器是执行此操作的工具。
应该注意的是,垃圾收集器在不同的 .NET 框架上是不同的——在紧凑的框架上(在 Xbox 360 和移动平台上运行)它是一个非分代 GC,因此你必须更加小心什么你的程序产生的垃圾。
强制 GC.Collect() 通常是一个坏主意,让 GC 去做它最擅长的事情。听起来最好的解决方案是使用一个可以在必要时增长的对象池——我已经成功地使用了这种模式。
这样,您不仅可以避免垃圾收集,还可以避免常规分配成本。
最后,您确定 GC 给您带来了问题吗?在实施任何节省性能的解决方案之前,您可能应该测量并证明这一点 - 您可能会给自己造成不必要的工作!
“目标是避免垃圾收集在时间关键的过程中使用任何 CPU”
问:如果时间紧迫,你的意思是你正在听一些深奥的硬件,你不能错过中断?
答:如果是这样,那么 C# 不是要使用的语言,您需要 Assembler、C 或 C++。
问:如果按时间临界,您的意思是当管道中有很多消息时,您不想让垃圾收集器减慢速度?
答:如果是这样,您就不必担心了。从事物的声音来看,您的对象寿命很短,这意味着垃圾收集器将非常有效地回收它们,而不会出现任何明显的性能滞后。
但是,唯一可以确定的方法是对其进行测试,将其设置为在夜间运行以处理持续不断的测试消息流,如果您的性能统计数据可以在 GC 启动时发现(即使您可以发现它,如果它真的很重要,我会更加惊讶)。
对垃圾收集器的行为有一个很好的理解和感受,你就会明白为什么不推荐你在这里的想法。除非你真的很喜欢 CLR 花时间重新排列内存中的对象。
应用程序的密集程度如何?我编写了一个应用程序,它以 8KB 块捕获 3 个声卡(托管 DirectX,44.1KHz,立体声,16 位),并通过 TCP/IP 将 3 个流中的 2 个发送到另一台计算机。UI 为 3 个通道中的每一个呈现音频电平表和(平滑)滚动标题/艺术家。这在具有 XP、1.8GHz、512MB 等的 PC 上运行。该应用程序使用大约 5% 的 CPU。
我没有手动调用 GC 方法。但我确实不得不调整一些浪费的东西。我使用 RedGate 的 Ant profiler 来磨练浪费的部分。一个很棒的工具!
我想使用预先分配的字节数组池,但托管的 DX 程序集在内部分配字节缓冲区,然后将其返回给应用程序。事实证明我没有必要。
如果绝对时间紧迫,那么您应该使用像 C/C++ 这样的确定性平台。即使调用 GC.Collect() 也会产生 CPU 周期。
您的问题始于您想要节省内存但摆脱对象的建议。这是一个空间关键优化。你需要决定你真正想要什么,因为 GC 比人类更擅长优化这种情况。
从它的声音来看,您似乎在谈论 C# 中不存在的确定性终结(C++ 中的析构函数)。您会在 C# 中找到最接近的东西是 Disposable 模式。基本上你实现了IDisposable接口。
基本模式是这样的:
public class MyClass: IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
protected virtual void Dispose( bool disposing )
{
if( _disposed )
return;
if( disposing )
{
// Dispose managed resources here
}
_disposed = true;
}
}
您可以在池中拥有有限数量的每种类型的实例,并重用已经完成的实例。池的大小取决于您要处理的消息量。
与其每次收到消息都创建一个对象的新实例,不如重用已经使用过的对象?这样你就不会与垃圾收集器作斗争,你的堆内存也不会碎片化。**
对于每种消息类型,您可以创建一个池来保存未使用的实例。每当您收到网络消息时,您都会查看消息类型,从适当的池中拉出等待实例并应用您的业务逻辑。之后,您将该消息对象的实例放回它的池中。
您很可能希望使用实例“延迟加载”您的池,以便您的代码轻松扩展。因此,您的池类需要检测空实例何时被拉出并在分发之前将其填满。然后,当调用代码将其放回池中时,它就是一个真实的实例。
** “对象池是一种使用模式,它允许重用对象而不是分配和释放对象,这有助于防止堆碎片以及代价高昂的 GC 压缩。”
理论上,如果您的 CPU 负载过重或除非确实需要,GC 不应该运行。但是,如果必须,您可能只想将所有对象保存在内存中,也许是一个单例实例,除非您准备好,否则永远不要清理它们。这可能是保证 GC 何时运行的唯一方法。