关于JVM分层编译中独立方法优化与内联顺序的技术问询
最近研究Oracle给出的JVM分层编译步骤时,我琢磨出一个关于优化顺序和内联时机的疑问,想和大家聊聊——是不是应该先对方法做脱离调用上下文的独立优化,再执行内联操作?
我觉得这个假设挺合理的:如果先在方法隔离的状态下完成优化(不依赖调用站点的上下文),再做内联,能避免后续的反优化问题。毕竟要是先内联未优化的方法,那它的“短板”会跟着调用次数,直接渗透到每个调用站点里。而且方法体积越大,做性能剖析(profiling)的难度也越高。所以我推测JVM会先在方法层面做独立的性能剖析和优化,搞定之后再把方法标记为可内联的候选。
不过这里有个小矛盾:大家都知道JIT是基于上下文需求来做优化的,但我觉得“基于上下文优化”和“先独立优化”其实并不冲突。毕竟有些优化完全不需要跨方法交互——比如死分支消除(Dead Branch Elimination)这类基础操作,完全可以在方法内部、字节码层面就完成。
这里的核心问题其实是:性能剖析和优化是自上而下(从调用站点到被调用方法),还是自下而上(从被调用方法到调用站点)?我觉得JVM完全可以两者兼顾,这也能支撑我的假设:如果某个方法被独立优化到“完美状态”,本质上也是因为它的调用站点先提出了性能需求。
目前我还没找到更细粒度的官方说明,但我推测内联步骤是属于C1或C2编译过程的一部分。另外像死分支消除这类优化,应该也属于C1/C2的工作范畴,因为它们可以直接在字节码层面完成。
这里得明确下我对“内联”的定义:我指的是把方法体直接插入到调用站点,而不是“直接调用(direct-call)”——后者属于去虚拟化(Devirtualization),但不算严格意义上的内联。
给大家举个具体的例子:
原代码:
void extremelyHotPath() { obj.something(); obj2.something(); obj3.something(); obj4.something(); obj5.something(); obj6.something(); } // 被调用的方法 final boolean flag = false; void doSomething() { if (flag) throw new IllegalStateException(); }
如果JVM采用自上而下的优化策略,会先内联,再做条件常量传播和死代码消除,优化后代码会变成这样:
void extremelyHotPath() { if (obj.flag) throw new IllegalStateException(); if (obj2.flag) throw new IllegalStateException(); if (obj3.flag) throw new IllegalStateException(); if (obj4.flag) throw new IllegalStateException(); if (obj5.flag) throw new IllegalStateException(); if (obj6.flag) throw new IllegalStateException(); }
但要是走自下而上的路线,就会先在doSomething()方法内部完成死代码消除(因为flag是final常量,if (flag)分支永远不会执行),再把优化后的空方法体(甚至直接去掉这个方法调用)内联到extremelyHotPath()里,最终得到的代码会精简很多。
内容来源于stack exchange

