我正在编写一个金融 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 进行垃圾收集”

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

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

问: 如果时间关键,您的意思是管道中有大量消息,并且您不想让垃圾收集器减慢速度?

A: 如果是这样,那你的担心是多余的。从声音来看,您的对象的寿命非常短,这意味着垃圾收集器将非常有效地回收它们,而不会出现任何明显的性能滞后。

然而,唯一确定的方法是测试它,将其设置为整夜运行,处理持续的测试消息流,如果你的性能统计数据可以发现 GC 何时启动(即使你可以发现它,如果它真的很重要,我会更加惊讶)。

充分了解和感受垃圾收集器的行为方式,您就会明白为什么不推荐您在这里想到的内容。除非你真的喜欢 CLR 花时间重新排列内存中的对象 很多.

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

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

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

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

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

从声音上看,您似乎在谈论确定性终结(C++ 中的析构函数),而 C# 中不存在这种确定性终结。C# 中最接近的就是一次性模式。基本上你实现了 I一次性 界面。

基本模式是这样的:

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 压缩。”

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top