这些人如何避免制造任何垃圾?

Posted

技术标签:

【中文标题】这些人如何避免制造任何垃圾?【英文标题】:How do these people avoid creating any garbage? 【发布时间】:2011-03-26 17:05:52 【问题描述】:

这是我在网上找到的一个有趣的article。

它讨论了这家公司如何能够在托管环境中解析大量财务数据,主要是通过对象重用和避免字符串等不可变数据。然后他们继续表明他们的程序在连续运行阶段没有执行任何 GC

这令人印象深刻,我想知道这里的其他人是否有更多关于如何执行此操作的详细指南。一方面,我想知道你怎么能避免使用字符串,当消息中的一些数据是字符串时,任何正在查看消息的客户端应用程序都希望传递这些字符串?另外,你在启动阶段分配了什么?你怎么知道这就够了?声明一大块内存并保留对它的引用以便GC不会启动是否简单? 任何客户端应用程序正在使用这些消息呢?是否也需要按照这些严格的标准来编写?

另外,我需要一个特殊的工具来查看内存吗?到目前为止,我一直在使用 SciTech 内存分析器。

【问题讨论】:

您真的需要这样做吗?大多数近实时系统只使用非托管代码。 不,我不需要这样做。但我很想知道怎么做。 【参考方案1】:

我发现你链接的论文相当不足:

它假设并且希望您假设垃圾收集是最终的延迟杀手。他们没有解释他们为什么这么认为,也没有解释他们的系统以何种方式基本上不是变相的定制垃圾收集器。 它谈到了在垃圾收集中清理的 内存量,这无关紧要:垃圾收集所花费的时间更多地取决于 对象的数量,与它们的大小。 底部的“结果”表无法与使用 .NET 垃圾收集器的系统进行比较。

当然,这并不意味着他们在撒谎,也与垃圾收集无关,但这基本上意味着这篇论文只是试图听起来令人印象深刻,而没有真正泄露任何你可以用来构建自己的有用的东西.

【讨论】:

你认为这篇论文是红鲱鱼吗?当我看到他们使用 .NET 的原因(“MSFT 管理硬件​​更改”)时,我倾向于这样想,这并不是一个真正的好处。 管理硬件更改对于他们所说的吞吐量来说可能是一个很大的好处。在那个级别上,人们会希望重新编译——甚至重写——为新架构使用新的编译器优化,优化 JITting 应该为你做的事情。 大多数 JIT 没有做足够的优化来与配置文件引导优化的静态编译竞争。使用 .net 的原因是生成托管代码要便宜得多。做这样的事情并不是很复杂。您预先分配所有资源,然后不运行 GC。许多人使用对象池来实现这种类型的架构。【参考方案2】:

从一开始就需要注意的一点是,他们说“传统智慧一直在开发低延迟消息传递技术,需要使用非托管 C++ 或汇编语言”。特别是,他们正在谈论一种人们经常会忽视 .NET(或 Java)解决方案的情况。就此而言,一个相对幼稚的 C++ 解决方案可能也达不到要求。

这里要考虑的另一件事是,他们基本上没有摆脱 GC,而是取代了它 - 那里有管理对象生命周期的代码,但这是他们自己的代码。

有几种不同的方法可以代替。这是一个。假设我需要在应用程序运行时创建和销毁几个 Foo 对象。 Foo 创建由 int 参数化,因此正常代码为:

public class Foo

    private readonly int _bar;
    Foo(int bar)
    
        _bar = bar;
    
    /* other code that makes this class actually interesting. */


public class UsesFoo

    public void FooUsedHere(int param)
    
        Foo baz = new Foo(param)
        //Do something here
        //baz falls out of scope and is liable to GC colleciton
    

一种截然不同的方法是:

public class Foo

    private static readonly Foo[] FOO_STORE = new Foo[MOST_POSSIBLY_NEEDED];
    private static Foo FREE;
    static Foo()
    
        Foo last = FOO_STORE[MOST_POSSIBLY_NEEDED -1] = new Foo();
        int idx = MOST_POSSIBLY_NEEDED - 1;
        while(idx != 0)
        
            Foo newFoo = FOO_STORE[--idx] = new Foo();
            newFoo._next = FOO_STORE[idx + 1];
        
        FREE = last._next = FOO_STORE[0];
    
    private Foo _next;
    //Note _bar is no longer readonly. We lose the advantages
    //as a cost of reusing objects. Even if Foo acts immutable
    //it isn't really.
    private int _bar;
    public static Foo GetFoo(int bar)
    
        Foo ret = FREE;
        FREE = ret._next;
        return ret;
    
    public void Release()
    
        _next = FREE;
        FREE = this;
    
    /* other code that makes this class actually interesting. */


public class UsesFoo

    public void FooUsedHere(int param)
    
        Foo baz = Foo.GetFoo(param)
        //Do something here
        baz.Release();
    

如果您是多线程的(尽管为了在非交互式环境中获得真正的高性能,您可能希望拥有一个线程,或者每个线程单独存储 Foo 类),并且如果您无法预测 MOST_POSSIBLY_NEEDED提前(最简单的方法是根据需要创建 new Foo(),但不为 GC 释放它们,如果 FREE._next 为 null,则可以在上面的代码中通过创建新的 Foo 轻松完成)。

如果我们允许不安全的代码,我们可以有更大的优势,让 Foo 成为一个结构体(因此数组持有一个连续的堆栈内存区域),_next 是一个指向 Foo 的指针,而 GetFoo() 返回一个指针。

这是否是这些人实际在做的事情,我当然不能说,但上面确实阻止了 GC 的激活。这只会在吞吐量非常高的情况下更快,如果不是,那么让 GC 完成它的工作可能会更好(GC 确实对你有帮助,尽管 90% 的问题都认为它是个大问题)。

还有其他类似的方法可以避免 GC。在 C++ 中,new 和 delete 运算符可以被覆盖,这允许更改默认的创建和销毁行为,并且您可能会对如何以及为什么这样做的讨论感兴趣。

一个实际的收获是,当对象要么持有除内存之外的昂贵资源(例如与数据库的连接),要么在继续使用它们时“学习”(例如 XmlNameTables)。在这种情况下,池化对象很有用(默认情况下,ADO.NET 连接在后台执行此操作)。在这种情况下,虽然一个简单的队列是可行的方法,因为内存方面的额外开销并不重要。您还可以在锁争用时放弃对象(您希望获得性能,而锁争用对它的伤害比放弃对象更大),我怀疑这在他们的情况下会起作用。

【讨论】:

哎呀,虽然有时这样的东西真的很有用,但大多数篡夺 GC 的方法都属于“有趣,现在永远不会这样做”的类别,而大多数篡夺它的尝试都属于进入“你有一个问题,你做了一些事情,现在你有两个问题”的类别。除了让 GC 在真实代码中做它的事情之外,我只有一次有理由做任何事情,而那一次是非常局部的,应用程序的内存使用模式短暂地与它的正常操作完全不同。 【参考方案3】:

据我了解,文章并没有说他们不使用字符串。他们不使用 immutable 字符串。不可变字符串的问题在于,当您进行解析时,生成的大多数字符串只是丢弃的字符串。

我猜他们正在使用某种预分配与可变字符串的free lists 相结合。

【讨论】:

【参考方案4】:

我曾使用名为 StreamBase 的 CEP 产品工作了一段时间。他们的一位工程师告诉我,他们正在将 C++ 代码迁移到 Java,因为通过完全避免 GC,他们在 JVM 上获得了更好的性能、更少的错误和更好的可移植性。我想这些论点也适用于 CLR。​​

这似乎违反直觉,但他们的产品速度非常快。

这里有一些信息from their site:

StreamBase 通过两种方式避免垃圾收集:不使用对象,并且只使用我们需要的最小对象集。

首先,我们通过使用 Java 基本类型(布尔、字节、整数、双精度和长整数)来避免使用对象来表示要处理的数据。每个 StreamBase 数据类型由一个或多个原始类型表示。通过仅操作原始类型,我们可以将数据有效地存储在堆栈或数组分配的内存区域中。然后我们可以使用并行数组或方法调用等技术来有效地传递数据。

其次,当我们使用对象时,我们要小心它们的创建和销毁。我们倾向于将对象池化而不是释放它们以进行垃圾收集。我们尝试管理对象生命周期,以便对象要么被新生代的垃圾收集器捕获,要么永远保留。

最后,我们使用衡量每个元组垃圾收集的基准测试工具在内部对此进行了测试。为了达到我们的高速度,我们尝试消除所有每元组的垃圾收集,通常都取得了很好的成功。

【讨论】:

老实说,我不想在这个代码库上工作。没有对象模型,没有代码结构,哇。这太可怕了。如果他们非常想避免 GC,那么为什么首先要切换到 Java? 就像我说的,这是违反直觉的。他们有一个很棒的产品,虽然性能很好,但由一些聪明的人开发。我想他们有他们的理由。并不是说他们没有对象模型,也不是代码结构。只是他们尽可能地重用对象,当需要 GC 时,他们确保对象在 Gen0 中(无论如何都是好的做法)。我不是 C++ 大师,但我认为我宁愿用 C# 编程而不是 C++,即使有他们为自己设置的约束。 当然。 C++ 在这方面优势不大,而 C# 在内存安全和 .NET 互操作方面具有巨大优势。【参考方案5】:

在 99% 的情况下,当你试图实现这一目标时,你会浪费你老板的钱。这篇文章描述了一个绝对极端的场景,他们需要最后的性能下降。正如您在文章中所读到的,在尝试无 GC 时无法使用 .NET 框架的大部分内容。 BCL 的一些最基本的部分使用内存分配(或“产生垃圾”,正如论文所说)。您将需要找到绕过这些方法的方法。即使您需要绝对超快的应用程序,您最好先尝试构建一个可以横向扩展(使用多台机器)的应用程序/架构,然后再尝试走无 GC 路线。他们使用 no-GC 路由的唯一原因是他们需要绝对低延迟。 IMO,当您需要绝对速度但不关心绝对最小响应时间时,很难证明无 GC 架构的合理性。除此之外,如果您尝试构建一个无 GC 的客户端应用程序(例如 Windows Forms 或 WPF App);算了,那些表示框架不断地创建新对象。

但如果你真的想要这个,其实很简单。这是一个简单的方法:

找出 .NET API 的哪些部分不能使用(您可以编写一个工具,使用 introspection engine 分析 .NET 程序集)。 编写一个程序来验证您或您的开发人员编写的代码,以确保他们不会直接分配或使用前一点创建的安全列表(FxCop 是一个很好的工具)或使用“禁止”的 .NET 方法. 创建在启动时初始化的对象池。程序的其余部分可以重用现有对象,这样他们就不必执行任何new 操作。 如果您需要操作字符串,请为此使用字节数组并将字节数组存储在池中(WCF 也使用此技术)。您必须创建一个允许操作这些字节数组的 API。 最后但同样重要的是,个人资料、个人资料、个人资料。

祝你好运

【讨论】:

以上是关于这些人如何避免制造任何垃圾?的主要内容,如果未能解决你的问题,请参考以下文章

JS制造的垃圾桶

制造业ERP系统是什么?制造业ERP软件系统有哪些功能

Linus怒批GitHub:制造了毫无用处的垃圾合并信息!

最为精密的MRAM芯片制造系统

云计算对企业来说是一个不可避免的趋势

制造员工如何改写血汗命运?