哪些因素影响Java调用的性能?
from http://www.codeceo.com/article/java-performance-inspect.html
- 最快与最短的方法调用的类型之间存在巨大的性能差别。
- 在实际应用中,添加或删除
final
关键字并不会真正影响性能。但如果除此以外,你还在层级结构上进行某些操作,那这些行为则可能导致性能下降。 - 更深的类的层次结构并不会真正影响到调用的性能。
- 单态调用比双态调用更快。
- 双态调用比超多状态调用更快。
- 我们在能够进行剖析(profile-ably),但是不能进行查验的单态调用点中看到类型保护,这种保护会使得这些调用点的调用性能低于那些能够进行查验的单态调用点。
因为在子类中可以重写一个非
final
方法,这使得调用点可以调用不同的方法。现假设我传入一个 Foo 的实例或一个重写了 bar
子类—— Baz的实例,编译器如何得知要调用哪一个 bar
方法呢?在默认情况下,方法将在Java中被虚拟化(可重写)。对于任一调用点,编译器需要在一个称为虚拟表(vtable)的表中寻找与其对应的方法。这是个非常耗时的过程,所以,能进行优化的编译器,总是会试图减少这种查询带来的开销。一种方法就是先前提到的内联,这的确是个良策,但前提是编译器能证明在给定的调用点上调用的方法唯一。而这样的调用点我们称为单态(monomorphic)调用点。
不幸的是,进行这种分析需要耗费大量时间。所以在实际过程中,确定一个调用点是否单态是个不太可取的方法。对此,JIT编译器倾向于使用一种替代方法:列出哪些类可以在此调用点被调用,接着根据之前的N个相同的调用猜测此调用点是否是单态的。以假定某个调用点永远为单态,来进行投机性质的优化往往是可取的行为。因为这样的优化往往都是正确的,但也因它无法确保永远正确,编译器需要在方法调用之前注入一个用于检查方法类型的防护机制。
除了单态的调用点以外,还有两种调用点我们希望对其进行优化。一种称为双态(bimorphic)调用点,在该点上有两个候选方法。对此你依然可以实现内联——借助防护代码,让其检测应调用哪一个方法,并引导程序跳转至内联在调用点的两个方法体中真正对应的那一个。这样的方式还是比查看所有虚拟表的方式要快得多。但在某些情况下,我们得利用内联缓存来进行优化。内联缓存需要借助一张特定的跳转表( jump table),这种表类似于对虚拟表查找做的一份缓存。hotsopt JIT编译器支持双态内联缓存,并定义那些拥有三个及三个以上候选方法的调用点为超多状态(megamorphic)调用点。
这就使得我在基准测试与探究当中,需要额外地把调用情况划分为三类:单态、双态、超多状态。
没有评论:
发表评论