关于C#:如何避免在实时.NET应用程序中进行垃圾回收?

关于C#:如何避免在实时.NET应用程序中进行垃圾回收?

How to avoid garbage collection in real time .NET application?

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

重点是在应用了业务逻辑之后,我确定我将不再需要该实例。我不想等待垃圾回收器释放它们,而是要显式"删除"它们。

在C#中是否有更好的方法,应该使用对象池来重用总是相同的一组实例,还是有更好的策略?

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


请勿立即删除它们。为每个对象调用垃圾回收器是一个坏主意。通常,您真的根本不想弄乱垃圾收集器,即使时间紧迫的进程也是如此,如果它们如此敏感,它们就只是竞相等待的条件。

但是,如果您知道自己的应用在繁忙时段与轻负载时段之间,则可以在达到轻负载时段时尝试使用更通用的GC.Collect(),以鼓励在下一个繁忙时段之前进行清理。


查看此处:http://msdn.microsoft.com/zh-cn/library/bb384202.aspx

您可以告诉垃圾收集器您目前正在做一些重要的事情,它将对您有好处。


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

当池中的负载足够低时,您可以收缩池,最终将触发垃圾回收-让CLR担心它。


尝试猜测垃圾收集器通常是一个非常糟糕的主意。在Windows上,垃圾收集器是一个世代的垃圾收集器,可以依靠它来提高效率。此一般规则有一些值得注意的例外-最常见的是一次发生的事件,您知道一个事实会导致许多旧对象死亡-一旦将对象提升到Gen2(寿命最长)他们倾向于闲逛。

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

请注意,不同的.NET框架中的垃圾收集器有所不同-在紧凑型框架(可在Xbox 360和移动平台上运行)上,它是非世代的GC,因此您必须拥有更多的能力注意程序会产生什么垃圾。


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

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

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


"目标是避免垃圾收集在时间紧迫的过程中使用任何CPU "

问:如果时间紧迫,意味着您正在听一些深奥的硬件,您不能错过这个中断吗?

A:如果是这样,则C#不是要使用的语言,则需要使用汇编语言,C或C。

问:如果在关键时刻,您是指管道中有很多消息,而您又不想让垃圾收集器放慢速度吗?

A:如果是这样,您就不必担心了。从对象的声音来看,它们的寿命很短,这意味着垃圾回收器将非常有效地回收它们,而不会出现明显的性能延迟。

但是,唯一可以确定的方法就是对其进行测试,将其设置为运行一整夜以处理恒定的测试消息流,如果您的性能统计信息可以在GC启动时发现(并且即使您可以发现它,但如果它真的很重要,我会感到更加惊讶)。


应用程序强度有多大?我编写了一个应用程序,该应用程序以8KB的块捕获3个声卡(Managed DirectX,44.1KHz,立体声,16位),然后将3个流中的2个通过TCP / IP发送到另一台计算机。 UI会为3个通道的每个通道渲染一个音频电平表和(平滑的)滚动标题/艺术家。它可以在XP,1.8GHz,512MB等PC上运行。该应用使用大约5%的CPU。

我没有手动调用GC方法。但是我确实不得不调整一些浪费的东西。我使用RedGate的Ant探查器来细化那些浪费的部分。一个很棒的工具!

我想使用一个预先分配的字节数组池,但是托管DX程序集在内部分配字节缓冲区,然后将其返回给App。原来,我不必这样做。


对垃圾收集器的行为有一个很好的了解和感受,您将了解为什么不建议您在这里考虑什么。除非您真的很喜欢CLR花很多时间在内存中重新排列对象。

  • http://msdn.microsoft.com/zh-CN/magazine/bb985010.aspx
  • http://msdn.microsoft.com/zh-CN/magazine/bb985011.aspx

从它的声音看来,您似乎在谈论确定性终结(C中的析构函数),而C#中不存在。在C#中,最接近的东西是Disposable模式。基本上,您实现IDisposable接口。

基本模式是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
    }
}

如果绝对时间紧迫,那么您应该使用C / C之类的确定性平台。即使调用GC.Collect()也会生成CPU周期。

您的问题开始于关于要节省内存但要摆脱对象的建议。这是空间关键的优化。您需要决定自己真正想要的是什么,因为GC在优化这种情况方面比人为更好。


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

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

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

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

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


池中每种类型的实例数量可能有限,然后重复使用已经完成的实例。池的大小取决于您将处理的消息量。


理论上,如果您的CPU负载过重或除非确实需要,GC不应运行。但是如果有必要,您可能只想将所有对象都保存在内存中(也许是单例实例),并且除非准备好就不要清理它们。这可能是保证GC运行的唯一方法。


推荐阅读