实现接口的开销

时间:2009-05-20 23:09:28

标签: c# java .net performance interface

我的一位同事告诉我,实现接口会带来开销。这是真的吗?

我不关心微观优化;我只是想知道这需要更深入的细节。

12 个答案:

答案 0 :(得分:18)

无法抗拒和测试它,看起来几乎没有开销。

参与者是:

Interface IFoo    defining a method
class Foo: IFoo   implements IFoo
class Bar         implements the same method as Foo, but no interface involved

所以我定义了

Foo realfoo = new Foo();
IFoo ifoo = new Foo();
Bar bar =  new Bar();

并调用该方法,该方法执行20个字符串连接,每个变量10,000,000次。

realfoo:   723 Milliseconds
ifoo:      732 Milliseconds
bar:       728 Milliseconds

如果该方法什么都不做,实际的呼叫会更突出。

  realfoo: 48 Milliseconds
  ifoo: 62 Milliseconds
  bar: 49 Milliseconds

答案 1 :(得分:12)

Java 的角度来讲,至少在Hotspot的最新版本中,答案通常是很少或没有开销

例如,假设您有一个如下方法:

public void doSomethingWith(CharSequence cs) {
  char ch = cs.charAt(0);
  ...
}

CharSequence当然是一个界面。所以你可能期望该方法必须做额外的工作,检查什么对象类型,找到方法,以及最后搜索界面等等 - 基本上你可以想象的所有恐怖故事......

但实际上,虚拟机可能比这更聪明。如果它确实在实践中你总是传递特定类型的对象,那么它不仅可以跳过对象类型检查,而且甚至可以内联该方法。例如,如果在一系列字符串的循环中调用上面的方法,Hotspot实际上可以内联对charAt()的调用,以便让字符逐字地成为一对MOV指令 - 换句话说,< strong>对接口的方法调用甚至可以变成甚至没有方法调用。 (P.S.此信息基于1.6更新版12的调试版本的汇编输出。)

答案 2 :(得分:9)

由于在调用方法或访问属性时执行了额外的间接操作,接口会产生开销。许多实现多态的系统,包括接口的实现,通常使用一个基于运行时类型映射函数调用的虚方法表。

理论上,编译器可以将虚函数调用优化为普通函数调用或内联代码,前提是编译器能够证明正在进行调用的对象的历史记录。

在绝大多数情况下,使用虚拟函数调用的好处远远超过了缺点。

答案 3 :(得分:6)

  

我不关心微观优化,只是想知道这需要更深入的细节。

存在开销,但它是微优化级别开销。例如,一个接口可以在IL切换中从呼叫到callvirt进行多次调用,但这非常小。

答案 4 :(得分:4)

是的,接口会产生开销。实际上,在逻辑和处理器之间添加的每一层都会增加开销。显然,你应该在汇编中编写所有东西,因为这是唯一不会产生开销的东西。 GUI也会产生开销,所以不要使用它们。

我很滑稽,但重点是你必须在清晰,易懂,可维护的代码和性能之间找到自己的平衡点。对于99.999%(当然重复)的应用程序,只要您注意避免不必要地重复执行任何更昂贵的方法,您就永远无法达到需要更难维护的程度只是为了让它跑得更快。

答案 5 :(得分:4)

除非你有非常具体的要求(即'我们正在制作游戏并且它必须以60fps运行,数据布局,缓存一致性和所述数据的转换非常重要'),我甚至都不会考虑编写软件时接口调用的额外性能成本。它只会在特定情况下引人注目。

例如:通过紧密循环中的接口进行数以万计的调用(即使这样,执行方法体的成本也可能使接口调用开销相形见绌),或者在处理数据时“指针追逐”很多。

此外,没有什么可以阻止您更改界面的合同以使呼叫更粗糙,例如IMyInterface.Process(Car[] cars)代替IMyInterface.Process(Car car)

在Code Complete中,Steve McConnell建议不要进行微观优化。他说,最好使用良好实践编写程序(即专注于可维护性,可读性等),然后,一旦完成,如果性能不够好,请对其进行分析并采取措施来修复主要瓶颈。

推测性地优化所有代码是没有意义的,因为可能更快。如果80%的执行时间花在执行20%的代码上,那么在任何地方牺牲松耦合显然是愚蠢的,因为'它可能会在这里或那里刮掉10微秒。所以你节省了10微秒,但是如果其他功能吞噬CPU,你的程序将不会更快。

如果您正在研究非性能关键软件,我会将接口视为微优化。如果需要,它也可以很容易地被剥离(假设您不会很快发布软件)。大多数软件都不需要blazin的快速速度。有例外(游戏,模拟器,实时股票交易等),但即便如此,它并不总是接口往往是罪魁祸首。

答案 6 :(得分:3)

与什么相比开销?

通过接口进行的呼叫比对非虚拟方法的呼叫更昂贵,是的。我没有亲自测试过,但我认为它的大小与虚拟通话相似。

也就是说,大多数情况下,性能通常不是不使用接口的有效理由 - 大多数情况下,呼叫量不足以解决问题。

答案 7 :(得分:3)

不幸的是,对于Java,还有一些优化可以用来提高接口性能。是的,与invokespecial相比,invokevirtual和invokeinterface指令“几乎没有任何开销”,但有一个Da Vinci project针对接口非常常见的简单使用中的性能缺点:一个只实现单个接口的对象永远不会超负荷。

有关您可能希望的所有技术细节,请参阅this Java bugparade request for enchancement

一如既往(似乎你明白这一点),在讨论像这样的微优化时,请咨询Amdahl's Law。如果您正在进行许多方法调用并且需要速度考虑重构并结合彻底的基准测试。

答案 8 :(得分:2)

虽然接口不应该产生开销,但不管怎么说。我不直接知道这一点,但二手,我们在有线电视盒上工作,它们的功能不足以至于我们测试了不同的性能场景。 (实例化的类计数有巨大差异)。

它不应该因为接口在运行时的影响非常小,它不像调用“通过”接口,接口就像编译器链接两段代码的方式 - 在运行时它不应该是不仅仅是一个指针调度表。

然而,它确实会影响性能。我猜它与元数据/反射有关,因为如果你不需要元数据,那么使用接口的唯一时间是从一个不太具体的接口转换到该接口,然后它只需要是一个标签,检查是否可能。

我会关注这个问题,因为我很想知道是否有人知道技术原因。您可能希望将其扩展为Java,因为它将完全相同,并且您更有可能通过Java获得答案,因为一切都是开源的。

答案 9 :(得分:2)

出于好奇,我实施了small benchmark。根据我的想法,代码应该阻止JVM缓存方法解析结果,因此应该在Java中显示 invokeinterface invokevirtual 之间的明显区别。

基准测试的结果是 invokeinterface invokevirtual 慢38%。

答案 10 :(得分:1)

通过接口调用比其他形式的虚拟方法调用稍微昂贵,因为vtable中有额外的间接层。在大多数情况下,这应该不重要,所以你不要过分担心性能并坚持良好的设计。

最近说过,我通过引入接口并通过接口进行所有调用来重构几个类。我非常自信(或懒惰),如果没有性能检查,我们就会释放它。事实证明,这对整个应用程序(不仅仅是通话)的性能有10%的影响。我们做了很多改变,这是我们怀疑的最后一件事。最后,当我们切换回具体课程时,原来的表现得以恢复。

这是一个经过大量优化的应用程序,上面可能不适用于其他情况。

答案 11 :(得分:1)

Virtual Stub dispatch与Interface Dispatch不同。 CLR JIT负责人Vance Morrison在这篇博文中详细描述了这一点。 http://blogs.msdn.com/vancem/archive/2006/03/13/550529.aspx