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

这是我在网上发现的一篇有趣的文章 。

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

这是非常令人印象深刻的,我想知道这里是否有其他人有关于如何做到这一点的更详细的指导 。 首先,我想知道如何避免使用字符串,当消息中的某些数据是字符串时,无论客户端应用程序正在查看消息,都希望传递这些字符串? 另外,你在启动阶段分配了什么? 你怎么知道它够了? 声明一大块内存并保留对它的引用以便GC不会启动它是否很简单? 客户端应用程序使用这些消息怎么样? 是否还需要按照这些严格的标准编写?

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

我发现你链接的纸张相当不足:

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

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

从一开始就要注意的一点是,他们说“传统智慧一直在开发低延迟消息传递技术,需要使用非托管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(); } } 

如果你是multithreading的话,可以添加更多的复杂function(虽然在非交互式环境中非常高性能,你可能希望每个线程有一个线程或单独的Foo类存储),如果你不能提前预测MOST_POSSIBLY_NEEDED(最简单的是根据需要创建新的Foo(),但不能为GC释放它们,如果FREE._next为null,则可以通过创建新的Foo在上面的代码中轻松完成。

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

这是否是这些人实际做的事情,我当然不能说,但上述确实阻止了GC的激活。 这只会在非常高的吞吐量条件下更快,如果不是那么让GC做它的东西可能更好(GC确实对你有所帮助,尽管有90%的问题将它视为一个大坏事)。

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

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

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

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

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

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

以下是他们网站的一些信息:

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

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

其次,当我们使用对象时,我们会小心它们的创建和破坏。 我们倾向于汇集对象而不是释放它们以进行垃圾收集。 我们尝试管理对象生命周期,以便对象在年轻一代中被垃圾收集器捕获,或者永远保留。

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

在99%的时间里,当你试图实现这一目标时,你会浪费你的老板的钱。 本文描述了一个绝对极端的场景,他们需要最后一滴性能。 正如您可以在文章中看到的那样,.NET框架的很多部分在尝试无GC时无法使用。 BCL的一些最基本的部分使用内存分配(或“生成垃圾”,正如论文所称)。 您需要找到解决这些方法的方法。 即使您需要绝对超快的应用程序,最好还是先尝试构建一个可以扩展(使用多台计算机)的应用程序/体系结构,然后再尝试使用无GC路由。 他们使用无GC路线的唯一原因是他们需要绝对的低延迟。 IMO,当您需要绝对速度但不关心绝对最小响应时间时,很难certificate无GC架构的合理性。 除此之外,如果您尝试构建一个不含GC的客户端应用程序(例如Windows Forms或WPF App); 忘掉它,那些演示框架不断创造新的对象。

但如果你真的想要这个,那实际上很简单。 这是一个简单的如何:

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

祝好运