JVM JIT可以专门化子类中的非重写方法吗?

时间:2017-02-20 18:55:39

标签: java performance inheritance optimization jvm

嗯,这个标题并不能理解,但基本上我的意思是在类m()中给出了一些方法Base,它在某些子类{{1}中没有被覆盖当前JVM 1 中的JIT编译器能够“专门化” 0 Derived无论如何有意义,或者派生谁继承而不是覆盖m()共享相同的编译代码?

这种专业化是有道理的,其中派生的类定义了使Base.m()更简单的东西。例如,为了讨论的目的,让我们说m()调用另一个成员函数m(),并在派生类n()中定义,以便n()内联到{{}时1}}后者大大简化了。

具体来说,请考虑遵循以下类中的两个非抽象方法(它们都是n() - 类型方法,而抽象方法是相应的m()方法):

m()

两者都依赖于抽象方法。让我们说你现在有n()这样:

public class Base {

  abstract int divisor();
  abstract boolean isSomethingEnabled();

  int divide(int p) {
    return p / divisor();
  }

  Object doSomething() {
    if (isSomethingEnabled()) {
      return slowFunction();
    } else {
      return null;
  }
}

现在Derivedpublic class Derived extends Base { final int divisor() { return 2; } final boolean isSomethingEnabled() { return false; } } 方法的有效行为非常简单,divide()不是任意数字的完全除法,而是只需将位数减半即可通过位操作完成。 doSomething()方法始终返回 false 。我假设divide编译doSomething()JIT如果divide() only 子类,那么一切都很好:存在(目前存在) )两个抽象调用只有一个可能的实现,CHA将启动并内联唯一可能的实现,一切都很好。

在更一般的情况下,存在其他派生类,但我不清楚JVM是否只编译一个 2 版本的方法doSomething()对{1}}调用抽象方法,或者如果它足够聪明,可以说:“嘿,即使Derived没有覆盖Derived我应该专门为它编译一个版本“因为它会变得更简单”。

当然,即使没有专门的重新编译,攻击性内联通常也会使其工作正常(即,当您在已知或甚至可能只是Base的类上调用invokevirtual时,内联无论如何,很可能会给你很好的实现,但同样地,有很多情况下没有这样的内联。

0 我的专长我没有任何具体的内容,除了编译适用于某些受限域的函数的另一个版本,就像说 inlining 是一个表单一样对特定调用站点的特化,或者大多数函数在某种程度上专门针对当前上下文的方式(例如,加载的类,关于null的假设等)。

1 特别是,当一个人说“JVM可以,等等吗?”一个人通常在谈论Hotspot,我也主要在Hotspot,但也有任何其他JVM也可以这样做。

2 好的,你可能有多个版本的函数,用于堆栈替换,不同的编译器级别,进行去优化等等...

4 个答案:

答案 0 :(得分:3)

  1. HotSpot JVM最多只有一个当前的,进入版本的编译方法。从源代码中的Methodnmethod实体之间的一对一关系可以看出这一点。但是,可能存在多个非进入的先前版本(例如,在较低层和OSR存根中编译的nmethods)。
  2. 此单个编译版本通常针对基于运行时分析的最常见情况进行优化。例如,在Base.doSomething() JIT的分析期间,isSomethingEnabled()实例上始终会调用Derived(即使有更多子类),它会优化快速案例的调用,一个不常见的陷阱,一个缓慢的陷阱。优化后doSomething()看起来像

        if (this.getClass() != Derived.class) {
            uncommon_trap();  // this causes deoptimization
        }
        return false;
    
    1. 为每个分支和每个呼叫站点单独收集配置文件数据。这使得可以针对一个接收器优化(专门化)方法的一部分,并且针对不同的接收器优化(专门化)另一部分。
    2. 如果在分析期间检测到两个不同的接收器,JIT可以通过类型检查来内联两个被监护的接收者。
    3. 将使用vtable lookup编译具有两个以上接收器的虚拟调用。
    4. 要查看方法配置文件数据,请使用JVM调试版本中提供的-XX:+PrintMethodData选项。

答案 1 :(得分:2)

不,我的理解是JVM本身不会专门化一个方法,而是优化基类功能,如果它在配置文件优化期间发现divisor()经常解析为某个方法。

您是否尝试过诊断打印以查看会发生什么?

  

你可以偷看,而不是试图猜测JIT在做什么   通过打开java命令行标志发生了什么:   -XX:+ PrintCompilation -XX:+ UnlockDiagnosticVMOptions -XX:+ PrintInlining(来自Java JIT compiler inlining

答案 2 :(得分:1)

根据OpenJDK Wiki

  
      
  • 通常会内联方法。这增加了编译器"地平线"优化。
  •   
  • 静态,私密,最终和/或"特殊"调用很容易内联。
  •   
  • 虚拟(和界面)调用通常被降级为"特殊"调用,如果类层次结构允许它。如果进一步的类加载会破坏事物,则会注册依赖项。
  •   
  • 使用乐观检查编译具有不平衡类型配置文件的虚拟(和接口)调用,以支持历史上常见的类型(或两种类型)。
  •   

也就是说,对于两种最常见的接收器类型,派生的方法将被内联到它们的调用者中(如果足够小,这应该是这种情况),并且修剪了无法访问的分支。

此外,如果基本方法足够小以便内联到其调用者中,则它将针对该调用者的两种最常见的接收器类型进行优化。

也就是说,Hotspot JVM专门用于代码,如果它足够小,可以内联到该呼叫站点的两种最常见的接收器类型。

答案 3 :(得分:-1)

JVM没有定义或重新定义类型。它解释了行为的运行实现。它是编译器,即处理类型的源语言。 JVM是低级别的,"金属" Java世界。类型及其实例是创建受输入影响的一系列可观察事件的指令。随着时间的推移,这一系列的输入和可观察事件构成了计算机科学家所称的语义和#34;一个程序。

由JVM决定在保留语义的同时执行这些指令的方法。它有时会完全破坏一个阶级结构。从概念上讲,类实例存在于具有标记属性的堆内存中。在一段时间内,直到语义因某些状态改变而禁止它,JVM可能会在寄存器中保留两个活动值,甚至不在RAM中,并忽略已定义类的其余部分。这是"专业"方法?

不,不是。 JVM中没有新的定义,没有新的Java级指令集,没有短暂的类型。现在只需要一种临时的,经过编译和优化的方式来完成指令。当优化不再起作用或重要时,JVM甚至会恢复解释字节码。并且字节码也不包含任何新类型。它是汇编语言,重新定义高级代码所要求的高于其薪资等级。

最重要的是,程序中唯一的类型是源代码中强制要求的类型,而不是字节码或JVM。所有语义都来自源。