流畅的接口是否会显着影响 .NET 应用程序的运行时性能?
我目前正致力于为现有技术实现一个流畅的接口,这将允许类似于以下代码片段的代码:
using (var directory = Open.Directory(@"path\to\some\directory"))
{
using (var file = Open.File("foobar.html").In(directory))
{
// ...
}
}
为了实现这样的构造,需要类来累积参数并将它们传递给其他对象。例如,要实现 Open.File(...).In(...)
构造,您需要两个类:
// handles 'Open.XXX':
public static class OpenPhrase
{
// handles 'Open.File(XXX)':
public static OpenFilePhrase File(string filename)
{
return new OpenFilePhrase(filename);
}
// handles 'Open.Directory(XXX)':
public static DirectoryObject Directory(string path)
{
// ...
}
}
// handles 'Open.File(XXX).XXX':
public class OpenFilePhrase
{
internal OpenFilePhrase(string filename)
{
_filename = filename
}
// handles 'Open.File(XXX).In(XXX):
public FileObject In(DirectoryObject directory)
{
// ...
}
private readonly string _filename;
}
也就是说,更多的组成部分语句(例如初始示例)具有,需要创建的对象越多,以便将参数传递给链中的后续对象,直到实际语句最终执行为止。
问题:
我对一些观点感兴趣:使用上述技术实现的流畅界面是否会显着影响使用它的应用程序的运行时性能?对于运行时性能,我指的是速度和内存使用方面。
请记住,只需在非常短的时间跨度内创建大量潜在的、节省参数的临时对象,我认为这可能会给垃圾收集器带来一定的压力。
如果您认为这对性能有显着影响,您是否知道实现流畅界面的更好方法?
I'm currently occupying myself with implementing a fluent interface for an existing technology, which would allow code similar to the following snippet:
using (var directory = Open.Directory(@"path\to\some\directory"))
{
using (var file = Open.File("foobar.html").In(directory))
{
// ...
}
}
In order to implement such constructs, classes are needed that accumulate arguments and pass them on to other objects. For example, to implement the Open.File(...).In(...)
construct, you would need two classes:
// handles 'Open.XXX':
public static class OpenPhrase
{
// handles 'Open.File(XXX)':
public static OpenFilePhrase File(string filename)
{
return new OpenFilePhrase(filename);
}
// handles 'Open.Directory(XXX)':
public static DirectoryObject Directory(string path)
{
// ...
}
}
// handles 'Open.File(XXX).XXX':
public class OpenFilePhrase
{
internal OpenFilePhrase(string filename)
{
_filename = filename
}
// handles 'Open.File(XXX).In(XXX):
public FileObject In(DirectoryObject directory)
{
// ...
}
private readonly string _filename;
}
That is, the more constituent parts statements such as the initial examples have, the more objects need to be created for passing on arguments to subsequent objects in the chain until the actual statement can finally execute.
Question:
I am interested in some opinions: Does a fluent interface which is implemented using the above technique significantly impact the runtime performance of an application that uses it? With runtime performance, I refer to both speed and memory usage aspects.
Bear in mind that a potentially large number of temporary, argument-saving objects would have to be created for only very brief timespans, which I assume may put a certain pressure on the garbage collector.
If you think there is significant performance impact, do you know of a better way to implement fluent interfaces?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
一般来说,生命周期非常短的对象正是 GC 最有效处理的对象类型,因为它们中的大多数将在下一次次要收集运行时死亡——并且在任何像样的 GC 实现中,次要集合与活动对象的总大小成正比。因此,短命对象的成本非常低,并且它们的分配意味着仅向上移动指针,这很快。
所以我想说:可能不会对性能产生重大影响。
Generally speaking, objects with a very small lifetime are exactly the kind of objects that the GC deals most efficiently with, because most of them will be dead at the time the next minor collection runs -- and on any decent GC implementation, the cost of a minor collection is proportional to the total size of live objects. Thus, short-lived objects cost very little, and their allocation means only bumping a pointer up, which is fast.
So I would say: probably no significant performance impact.
Thomas 说得非常正确,分代 GC 正是针对这种短期对象的分配和收集进行了优化。然而,像 OCaml 这样的函数式语言的 GC 对此比 .NET 进行了更严格的优化,而且,在将多个参数应用于柯里化函数的等效情况下,它们仍然竭尽全力避免这种情况。具体来说,他们使用一种称为大步语义的技术,其中编译器在编译时删除所有中间产物,因此 GC 永远不会看到任何这些。
在 .NET 上,值类型很可能让您自己解决这个问题。
Thomas is quite correct that generational GC is optimized for exactly this allocation and collection of short-lived objects. However, functional languages like OCaml have GCs that are much more heavily optimized for this than .NET is and, yet, they still go to great lengths to avoid this situation in the equivalent circumstance of applying multiple arguments to a curried function. Specifically, they use a technique called big-step semantics where the compiler removes all of the intermediates at compile time so the GC never sees any of this.
On .NET, value types may well let you solve this problem yourself.