实现接口的开销

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

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

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

参与者是:

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 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

此外,没有什么可以阻止你改变接口的合同,使调用更粗粒度,例如IMyInterface.Process(Car[] cars)而不是IMyInterface.Process(Car car)

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

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

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

开销比较什么?

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

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

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

请参阅此Java bugparade请求,以获取您希望获得的所有技术详细信息。

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

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

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

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

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

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

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

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

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

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

Virtual Stub调度与Interface Dispatch不同。 CLR JIT负责人Vance Morrison详细介绍了这篇博客文章。 http://blogs.msdn.com/vancem/archive/2006/03/13/550529.aspx