简体   繁体   English

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

[英]Can the JVM JIT specialize non-overridden methods in sub-classes?

Well, that title along can't get the idea across but basically what I mean is that given some method m() in a class Base , which is not overridden in some subclass Derived , are the JIT compilers in current JVMs 1 capable of "specializing" 0 m() anyway when it makes sense, or will derived who inherit and don't override Base.m() share the same compiled code? 嗯,这标题沿不能得到整个想法,但基本上我的意思是,鉴于一些方法m()中的一类Base ,这是不是在某些子类中重写Derived ,在目前的JVM 1能够在JIT编译“专门研究“ 0 m()无论如何有意义,或者派生谁继承并且不覆盖Base.m()共享相同的编译代码?

This specialization makes sense, where the derived class defines something that makes m() much simpler. 这种特殊化是有道理的, 派生类定义了使m()更简单的东西。 For example and for the purposes of discussion, let's say m() calls another member function n() and in the derived class n() is defined such that when n() is inlined into m() the latter is greatly simplified. 例如,为了讨论的目的,让我们说m()调用另一个成员函数n()并且在派生类中定义n() ,这样当n()内联到m() ,后者被大大简化。

To be concrete, consider following the two non-abstract methods in the following class (which are both m() -type methods, while the abstract methods are the corresponding n() methods): 具体来说,请考虑遵循以下类中的两个非抽象方法(它们都是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;
  }
}

Both rely on abstract methods. 两者都依赖于抽象方法。 Lets say you now have a Derived like this: 让我们说你现在有这样的Derived

public class Derived extends Base {

  final int divisor() {
    return 2;
  }

  final boolean isSomethingEnabled() {
    return false;
  }
}

Now the effective behavior of the divide() and doSomething() methods are very simply, the divide is not a full division by an arbitrary number, but a simply halving that can be done with bit-operations. 现在, divide()doSomething()方法的有效行为非常简单, divide不是任意数字的完全除法,而是可以通过位操作完成的一半。 The doSomething() method always returns false . doSomething()方法始终返回false I assume that when the JIT goes to compile divide() or doSomething() if Derived is the only subclass, all is good: there exists (currently) only one possible implementation for the two abstract calls, and CHA will kick in and inline the only possible implementations and all is good. 我假设当JIT去编译divide()doSomething()如果Derived唯一的子类,一切都很好:两个抽象调用只存在(当前)一个可能的实现,并且CHA将启动并内联只有可能的实现,一切都很好。

In the more general case that other derived classes exist, however, it isn't clear to me if the JVM will only compile one 2 version of the methods in Base with an invokevirtual call to the abstract methods, or if it is smart enough to say, "Hey, even though Derived doesn't override divisor() I should compile a version specifically for it 'cause it's going to be much simpler". 然而,在更一般的情况下,存在其他派生类,我不清楚JVM是否只使用对抽象方法的invokevirtual调用在Base编译方法的一个 2版本,或者它是否足够智能说,“嘿,即使Derived不会覆盖divisor()我应该专门为它编译一个版本,因为它会变得更简单”。

Of course, even without specialized recompilation aggressive inlining often makes it work out fine anyway (ie, when you call divide() on a class that is known or even just likely to be a Derived , inlining is likely to give you the good implementation anyway, but, equally, there are plenty of cases where such inlining isn't done. 当然,即使没有专门的重新编译,积极的内联通常也会使其工作正常(即,当你在已知或甚至可能是Derived的类上调用divide()时,内联可能会为您提供良好的实现但是,同样地,有很多情况下没有这样的内联。


0 My specializing I don't mean anything specific beyond compiling another version of the function appropriate in some restricted domain, in the same sense that say inlining is a form of specialization to a specific call site, or in the same way that most functions are somewhat specialized to the current context (eg, loaded classes, assumptions about nullness, etc). 0我的专业化并不意味着除了编译适用于某个受限域的函数的另一个版本之外的任何具体内容,同样意义上说内联是特定调用站点的一种特殊化形式,或者与大多数函数相同的方式有点专门针对当前上下文(例如,加载类,关于null的假设等)。

1 In particular, when one says "Can the JVM blah, blah?" 1特别是,当一个人说“JVM可以,等等吗?” one is usually talking about Hotspot, and I'm also mostly in Hotspot but also whether any other JVM can do this too. 一个人通常在谈论Hotspot,我也主要在Hotspot,但也有任何其他JVM也能做到这一点。

2 OK sure, you might have several version of a function, for on-stack-replacement, for different compiler levels, when deoptimization occurs, etc... 2确定,您可能有多个版本的函数,用于堆栈替换,用于不同的编译器级别,当进行去优化时等等...

  1. HotSpot JVM has at most one current, entrant version of compiled method. HotSpot JVM最多只有一个当前的进入版本的编译方法。 This is obvious from one-to-one relationship between Method and nmethod entities in the source code. 从源代码中的Methodnmethod实体之间的一对一关系可以nmethod这一点。 However, there can be multiple non-entrant previous versions (eg nmethods compiled at lower tier and OSR stubs). 但是,可能存在多个非进入的先前版本(例如,在较低层和OSR存根上编译的nmethod)。
  2. This single compiled version is often optimized for the most common case basing on run-time profiling. 此单个编译版本通常针对基于运行时分析的最常见情况进行优化。 For example, when during profiling of Base.doSomething() JIT sees that isSomethingEnabled() is always invoked on Derived instance (even if there are more subclasses), it will optimize the call for the fast case, leaving an uncommon trap for a slow one. 例如,在对Base.doSomething()进行概要分析时,JIT看到始终在Derived实例上调用isSomethingEnabled() (即使有更多的子类),它将优化对快速情况的调用,留下一个不常见的陷阱一。 After this optimization doSomething() will look like 在此优化之后, doSomething()将如下所示

      if (this.getClass() != Derived.class) { uncommon_trap(); // this causes deoptimization } return false; 

  1. Profile data is collected separately for each branch and for each call site. 为每个分支和每个呼叫站点单独收集配置文件数据。 This makes possible to optimize (specialize) a part of a method for one receiver, and the other part for a different receiver. 这使得可以针对一个接收器优化(专门化)方法的一部分,并且针对不同的接收器优化(专门化)另一部分。
  2. If two different receivers were detected during profiling, JIT can inline both callees guarded by a type check. 如果在分析期间检测到两个不同的接收器,JIT可以内联由类型检查保护的两个被监护者。
  3. A virtual call with more than two receivers will be compiled using vtable lookup. 使用vtable查找编译具有两个以上接收器的虚拟调用。

To see the method profile data use -XX:+PrintMethodData option available in debug builds of JVM. 要查看方法配置文件数据,请在JVM的调试版本中使用-XX:+PrintMethodData选项。

No, my understanding is that the JVM would not specialize a method on its own but rather optimize the base class function if it finds during profile optimization that divisor() often resolves to a certain method. 不,我的理解是JVM本身不会专门化一个方法,而是优化基类功能,如果它在配置文件优化期间发现divisor()经常解析为某个方法。

Have you tried to print from diagnostics to see what happens? 您是否尝试从诊断中打印以查看会发生什么?

Rather than trying to guess what the JIT is doing, you can take a peek at what's happening by turning on java command line flags: -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining (from Java JIT compiler inlining ) 您可以通过打开java命令行标志来查看正在发生的事情,而不是试图猜测JIT正在做什么:-XX:+ PrintCompilation -XX:+ UnlockDiagnosticVMOptions -XX:+ PrintInlining(来自Java JIT编译器内联

According to the OpenJDK Wiki : 根据OpenJDK Wiki

  • Methods are often inlined. 方法经常被内联。 This increases the compiler's "horizon" of optimization. 这增加了编译器的优化“范围”。
  • Static, private, final, and/or "special" invocations are easy to inline. 静态,私有,最终和/或“特殊”调用很容易内联。
  • Virtual (and interface) invocations are often demoted to "special" invocations, if the class hierarchy permits it. 如果类层次结构允许,虚拟(和接口)调用通常会降级为“特殊”调用。 A dependency is registered in case further class loading spoils things. 如果进一步的类加载会破坏事物,则会注册依赖项。
  • Virtual (and interface) invocations with a lopsided type profile are compiled with an optimistic check in favor of the historically common type (or two types). 具有不平衡类型配置文件的虚拟(和接口)调用使用乐观检查进行编译,以支持历史上常见的类型(或两种类型)。

That is, for the two most frequent receiver types, the derived methods would be inlined into their caller (if small enough, which should be the case here), and unreachable branches pruned. 也就是说,对于两种最常见的接收器类型,派生的方法将被内联到它们的调用者中(如果足够小,这应该是这种情况),并且修剪不可到达的分支。

Also, if the base method is small enough for inlining into its caller, it will be optimized for that caller's two most frequent receiver types. 此外,如果基本方法足够小以便内联到其调用者中,则它将针对该调用者的两种最常见的接收器类型进行优化。

That is, the Hotspot JVM specializes code if it is small enough for inlining for the two most frequent receiver types of that call site. 也就是说,Hotspot JVM专用于代码,如果它足够小,可以内联该呼叫站点的两种最常见的接收器类型。

The JVM doesn't define nor redefine types. JVM不定义也不重新定义类型。 It interprets running implementations of behaviors. 它解释了行为的运行实现。 It's the compiler, ie, the source language that deals in types. 它是编译器,即处理类型的源语言。 The JVM is the low level, the "metal" of the Java universe. JVM是Java平台的低级别,“金属”。 Types and their instances are instructions to create a series of observable events influenced by inputs. 类型及其实例是创建受输入影响的一系列可观察事件的指令。 That series of inputs and observable events over time constitute what computer scientists call the "semantics" of a program. 随着时间的推移,这一系列的输入和可观察事件构成了计算机科学家所称的程序的“语义”。

It's up to the JVM to figure out ways to carry out those instructions while preserving the semantics. 由JVM决定在保留语义的同时执行这些指令的方法。 It will, at times, destroy a class structure entirely, in effect. 它有时会完全破坏一个阶级结构。 Conceptually a class instance lives in heap memory with labeled attributes. 从概念上讲,类实例存在于具有标记属性的堆内存中。 For some period of time, until the semantics prohibit it because of some state change, the JVM might keep two active values in registers, not even in RAM, and ignore the whole rest of the defined class. 在一段时间内,直到语义因某些状态改变而禁止它,JVM可能会在寄存器中保留两个活动值,甚至不在RAM中,并忽略已定义类的其余部分。 Is that "specializing" the method? 这是“专业化”的方法吗?

No, it is not. 不它不是。 There is no new definition, no new set of Java-level instructions, no ephemeral type in the JVM. JVM中没有新的定义,没有新的Java级指令集,没有短暂的类型。 There's just a temporary, compiled and optimized way in the moment to fulfill the instructions. 目前只有一种临时的,经过编译和优化的方式来完成指令。 When the optimization no longer works, or matters as much, the JVM even reverts to interpreting bytecode. 当优化不再起作用或重要时,JVM甚至会恢复解释字节码。 And bytecode won't contain any new types, either. 字节码也不包含任何新类型。 It's an assembly language, and redefining what the high-level code demands is above its pay grade. 它是汇编语言,重新定义高级代码所要求的高于其薪酬等级。

Bottom line, the only types in the program are those mandated in the source code, not the bytecode or JVM. 最重要的是,程序中唯一的类型是源代码中强制要求的类型,而不是字节码或JVM。 All the semantics come from the source. 所有语义都来自源代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM