如何避免实时.NET应用程序中的垃圾收集?

时间:2008-09-17 16:54:11

标签: c# .net garbage-collection real-time finance

我正在编写一个金融C#应用程序,它接收来自网络的消息,根据消息类型将它们转换为不同的对象,最后将应用程序业务逻辑应用于它们。

重点是在应用业务逻辑之后,我非常确定我将永远不再需要这个实例。我不想等待垃圾收集器释放它们,而是明确地“删除”它们。

有没有更好的方法在C#中这样做,我应该使用一个对象池来重复使用同一组实例,还是有更好的策略。

目标是避免垃圾收集在时间关键过程中使用任何CPU。

13 个答案:

答案 0 :(得分:21)

不要马上删除它们。为每个对象调用垃圾收集器是个坏主意。通常你真的根本不想弄乱垃圾收集器,即使是时间关键的进程只是竞争条件等待发生,如果他们那么敏感。

但是如果你知道你的应用程序对于轻载时段很忙,那么当你到达一个光照期以鼓励在下一个繁忙时段之前进行清理时,你可以尝试更通用的GC.Collect()。

答案 1 :(得分:18)

请看这里:http://msdn.microsoft.com/en-us/library/bb384202.aspx

你可以告诉垃圾收集器你现在正在做一些关键的事情,它会对你很好。

答案 2 :(得分:11)

你自己打了 - 使用一个对象池并重用这些对象。对这些对象的调用的语义需要隐藏在工厂外观后面。您需要以某种预定义的方式扩展池。每次达到极限时可能会增加一倍的尺寸 - 高水算法或固定百分比。我强烈建议你不要调用GC.Collect()。

当您的池上的负载变得足够低时,您可以缩小池并最终触发垃圾收集 - 让CLR担心它。

答案 3 :(得分:7)

尝试对垃圾收集器进行二次猜测通常是一个非常糟糕的主意。在Windows上,垃圾收集器是a generational one,并且可以依赖它非常有效。这个一般规则有一些值得注意的例外 - 最常见的是一个事件的发生,你知道一个事实会导致很多旧的对象死亡 - 一旦对象被提升为Gen2(最长寿命)他们往往会四处闲逛。

在你提到的情况下,你听起来好像在生成一些短命的对象 - 这些将导致Gen0集合。无论如何,这些都经常发生,并且是最有效的。如果您愿意,可以通过拥有可重用的对象池来避免它们,但最好在采取此类操作之前确定GC是否是性能问题 - CLR分析器是执行此操作的工具。

应该注意的是,垃圾收集器在不同的.NET框架上是不同的 - 在紧凑的框架上(在Xbox 360和移动平台上运行)它是一个非世代GC,因此你必须更多小心程序生成的垃圾。

答案 4 :(得分:5)

强制GC.Collect()通常是一个坏主意,让GC做它最擅长的事情。听起来最好的解决方案是使用可以在必要时增长的对象池 - 我已经成功使用了这种模式。

这样,您不仅可以避免垃圾收集,还可以避免常规分配成本。

最后,您确定GC导致您出现问题吗?在实施任何节能解决方案之前,您应该测量并证明这一点 - 您可能会导致自己不必要的工作!

答案 5 :(得分:5)

“目标是避免垃圾收集在时间关键过程中使用任何CPU”

问:如果时间紧迫,你的意思是说你正在听一些神秘的硬件,你不能错过中断?

答:如果是这样,那么C#就不是要使用的语言,你需要汇编程序,C或C ++。

问:如果时间严重,那么你的意思是管道中有很多消息,你不想让垃圾收集器放慢速度吗?

A:如果是这样,你会不必要地担心。通过物体的声音,你的物体非常短暂,这意味着垃圾收集器将非常有效地回收它们,而没有任何明显的性能滞后。

但是,唯一可以确定的方法是测试它,将其设置为隔夜处理一系列测试消息,如果你的GC性能统计数据可以在GC启动时发现,我会感到震惊(甚至如果你能发现它,如果真的很重要,我会更加惊讶。)

答案 6 :(得分:3)

对垃圾收集器的行为有一个很好的理解和感受,并且您将理解为什么不推荐您在这里想到的。除非你真的希望CLR花时间在内存中重新排列对象很多

答案 7 :(得分:3)

应用程序的密集程度如何?我写了一个应用程序,以8KB块捕获3个声卡(Managed DirectX,44.1KHz,立体声,16位),并通过TCP / IP将3个流中的2个发送到另一台计算机。 UI为3个频道中的每个频道呈现音频电平表和(平滑)滚动标题/艺术家。这适用于具有XP,1.8GHz,512MB等的PC。该应用程序使用大约5%的CPU。

我没有动手调用GC方法。但我确实需要调整一些浪费的东西。我使用RedGate的Ant分析器来磨练浪费的部分。一个很棒的工具!

我想使用预先分配的字节数组池,但托管DX程序集在内部分配字节缓冲区,然后将其返回给App。事实证明,我没有。

答案 8 :(得分:2)

如果它绝对是时间关键,那么你应该使用像C / C ++这样的确定性平台。即使调用GC.Collect()也会生成CPU周期。

你的问题始于你想要节省内存但摆脱对象的建议。这是一个空间关键优化。您需要确定您真正想要的是什么,因为GC比人类更善于优化这种情况。

答案 9 :(得分:2)

从它的声音来看,似乎你在谈论确定性终结(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;
    }
}

答案 10 :(得分:1)

您可以在池中拥有有限数量的每种类型的实例,并重用已经完成的实例。池的大小取决于您要处理的消息量。

答案 11 :(得分:1)

不是每次收到消息时都创建对象的新实例,为什么不重用已经使用过的对象?这样你就不会与垃圾收集器作斗争,你的堆内存也不会碎片化。**

对于每种消息类型,您可以创建一个池来保存未使用的实例。每当收到网络消息时,您都会查看消息类型,从适当的池中提取等待的实例并应用业务逻辑。之后,将该消息对象的实例放回其池中。

您很可能希望使用实例“延迟加载”池,以便您的代码可以轻松扩展。因此,您的池类将需要检测何时已拉出空实例并在将其移出之前将其填满。然后当调用代码将它放回池中时,它就是一个真实的实例。

**“对象池是一种使用模式,允许重用对象而不是分配和释放对象,这有助于防止堆碎片以及昂贵的GC压缩。”

http://geekswithblogs.net/robp/archive/2008/08/07/speedy-c-part-2-optimizing-memory-allocations---pooling-and.aspx

答案 12 :(得分:0)

理论上,如果您的CPU处于高负载或除非确实需要,GC不应运行。但是如果你必须这样做,你可能只想将所有对象保存在内存中,也许只是一个单例实例,除非你准备好,否则永远不要清理它们。这可能是保证GC运行的唯一方法。