2008-09-17 67 views
11

我在写一个财务C#应用程序,它接收来自网络的消息,根据消息类型将它们翻译成不同的对象,并最终在它们上应用应用程序业务逻辑。如何避免实时.NET应用程序垃圾回收?

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

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

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

+0

在.Net中,当(a)创建给定对象速度较慢或(b)在32位应用程序中减少内存碎片时,使用数据的数组/列表/字典等块> 64 KB,例如>每个元素8K个元素x 8B,或者每个元素16K个元素x 4个B。 .Net **永远不会重新定位(移动)这样的大块**,如果您每次请求不同的大小,这会导致内存碎片化。更安全地分配足够大的#元素,并保留它们。 (但是在不使用时清除它们的字段,所以GC可以回收它们指向的任何内容。)如果超出分配的大小,请将#元素加倍。 – ToolmakerSteve 2014-03-22 20:34:36

+0

在极端情况下,我有一个32位应用程序(由于依赖关系在64位中不起作用),所以即使我在64位Windows上装载大量RAM,以避免内存碎片保留列表清单,以便每个子列表适合64 KB,以便.Net不会在“大对象堆”上分配。因此它可以在GC期间“压缩”内存,而不是在应用程序的4 GB地址空间中创建大型“漏洞”。这个特定的应用程序是有问题的,因为它包含了很多来自本地(非.Net)内存的DirectX分配。非.Net和.Net分配的混合导致了分裂。 – ToolmakerSteve 2014-03-22 20:41:13

回答

21

不要立即删除它们。为每个对象调用垃圾收集器是一个坏主意。通常情况下,你真的不想搞乱垃圾回收器,即使时间关键型进程只是在等待发生,如果他们是敏感的竞争条件。

但如果你知道你有VS忙你的应用程序轻载期间,你可能当你到达一个光周期下忙期之前,鼓励清理尝试更一般的GC.Collect的()。

+8

只要你明白GC.Collect没有提供确定性的垃圾收集,这很好。你只是告诉GC运行它的周期,在这种情况下决定是否收集内存。对GC.Collect的调用不保证内存收集。 – JeremiahClark 2008-10-18 03:57:33

+1

改编为显示非确定性。 – 2009-02-09 16:49:47

+1

请注意**调用GC.Collect()往往可以做得比弊大于利**:任何仍然有引用的对象将从第0代移至第1代,或者从第1代移至第2代。任何这样的对象将坚持更长的时间/需要更多GC去除的工作。只有在没有任何消息仍在工作时调用GC.Collect(),以便没有任何临时数据的引用。 – ToolmakerSteve 2014-03-22 20:22:41

1

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

5

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

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

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

0

从理论上讲,如果你的CPU负载很重或者除非真的需要,GC不应该运行。但是如果你必须这样做,你可能只想把所有的对象都放在内存中,也许是一个单例实例,除非你准备好了,否则不要清理它们。这可能是保证GC运行的唯一方法。

2

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

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

1

并非每次你得到一个消息时创建一个对象的新实例的,你为什么不重用已经被使用的对象?这样你就不会对付垃圾收集器,你的堆内存也不会变得分散。**

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

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

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

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

7

试图第二猜测垃圾收集器一般是一个非常糟糕的主意。在Windows上,垃圾收集器是a generational one,可以依靠它来非常高效。这个通用规则有一些例外,最常见的是发生一次事件,你知道事实会导致很多旧对象死亡 - 一旦对象被提升到Gen2(最长寿命)他们往往会流连忘返。

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

应该指出的是,垃圾收集器在不同的.NET框架上有所不同 - 在紧凑框架(运行在Xbox 360和移动平台上)上,它是非代人的GC,因此您必须多更加小心你的程序产生的垃圾。

11

你自己打了 - 使用一个对象池并重用这些对象。对这些对象的调用的语义需要隐藏在工厂外观之后。您需要以某种预先定义的方式增加游泳池。也许每当它达到极限时就将其大小增加一倍 - 高水算法或固定百分比。我真的强烈建议你不要调用GC.Collect()。

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

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; 
    } 
} 
2

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

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

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

5

“目的是为了避免垃圾收集过程中,时间关键进程使用任何CPU”

问:按时间要求严格,你的意思是你在听一些深奥的硬件如果,你不能错过中断?

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

问:如果时间紧迫,你的意思是管道中有很多消息,并且你不想让垃圾收集器减慢速度?

- 答:如果是这样,你是不必要的担心。通过事物的声音,您的对象非常短暂,这意味着垃圾收集器将非常有效地回收它们,而不会出现任何明显的性能滞后。但是,唯一可以确定的方法就是测试它,设置它在隔夜处理一个持续的测试消息流,如果你的性能数据可以在GC启动时发现,我会惊呆的即使你能发现它,如果它真的很重要,我会更加惊讶)。