从一开始就需要注意的一点是,他们说“传统智慧一直在开发低延迟消息传递技术,需要使用非托管 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 连接在后台执行此操作)。在这种情况下,虽然一个简单的队列是可行的方法,因为内存方面的额外开销并不重要。您还可以在锁争用时放弃对象(您希望获得性能,而锁争用对它的伤害比放弃对象更大),我怀疑这在他们的情况下会起作用。