普通网站制作,网站建设公司盈利模式,高端的咨询行业网站设计,杭州培训网站建设文章目录1. 历史背景2. 构建编译调试环境3. JVMCI 编译器接口4. 代码中间表示5. 代码优化与生成1. 历史背景
Graal 编译器在 JDK 9 以 Jaotc 提前编译工具的形式首次加入到官方的 JDK 中#xff0c;JDK 10 开始提供替换#xff08;得益于 HotSpot 编译器接口#xff0c;Jav…
文章目录1. 历史背景2. 构建编译调试环境3. JVMCI 编译器接口4. 代码中间表示5. 代码优化与生成1. 历史背景
Graal 编译器在 JDK 9 以 Jaotc 提前编译工具的形式首次加入到官方的 JDK 中JDK 10 开始提供替换得益于 HotSpot 编译器接口Java Level JVM Compiler InterfaceJVMCI服务端编译器支持
JVMCI 的三个功能
响应 HotSpot 的编译请求并将请求分发给 Java 实现的即时编译器允许编译器访问 HotSpot 中与即时编译相关的数据结构包括类、字段、方法及其性能监控数据等提供这些数据结构在 Java 语言层面的抽象表示提供 HotSpot 代码缓存Code Cache的 Java 端抽象表示允许编译器部署编译完成的二进制机器码
JVMCI 可以把一个 HotSpot VM 外部的、用 Java 语言实现的即时编译器不限于 Graal集成到 HotSpot 中响应 HotSpot 发出的最顶层的编译请求并将编译后的二进制代码部署到 HotSpot 的代码缓存又绕开 HotSpot 的即时编译系统让编译器直接为应用类库编译出二进制机器码可当作提前编译器使用如 Jaotc
2. 构建编译调试环境
安装 Graal VM 构建工具 mx
git clone https://github.com/graalvm/mx.git
export PATHpwd/mx:$PATH安装带有 JVMCI 的 OpenJDK8
export JAVA_HOME~/devtools/oraclejdk1.8.0_212-jvmci-20-b01获取 Graal 编译器代码
git clone https://github.com/graalvm/graal.git使用 mx 构建 Graal 编译器
cd graal/compiler
mx build使用 mx 创建项目
cd graal/compiler
mx eclipseinit编译构建项目可能需要 2GB 已上内存因此需要调大 IDE 的堆最大内存
需将带有 JVMCI 的 JDK 作为 IDE 项目的编译运行使用的版本
3. JVMCI 编译器接口
interface JVMCICompiler {// 编译的输入是字节数组表示的字节码额外携带各种方法相关信息如局部变量表中的变量槽个数、操作数栈的最大深度、分层编译收集到的统计信息等// 编译输出的也是字节数组表示的二进制机器码void compileMethod(CompilationRequest request);
}interface CompilationRequest {JavaMethod getMethod();
}interface JavaMethod {byte[] getCode();int getMaxLocals();int getMaxStackSize();ProfilingInfo getProfilingInfo();... // 省略其他方法
}即时编译演示
// 示例代码
public class Demo {public static void main(String[] args) {// 循环构成热点代码触发即时编译while (true) {workload(14, 2);}}private static int workload(int a, int b) {return a b;}
}# 编译演示
$ javac Demo.java
$ java \-XX:PrintCompilation \-XX:CompileOnlyDemo::workload \Demo
...193 1 3 Demo::workload (4 bytes)199 2 1 Demo::workload (4 bytes)199 1 3 Demo::workload (4 bytes) made not entrant
...wordload() 方法被分层编译多次made not entrant 表示方法被丢弃
限制性编译演示
# JDK 8 的运行配置
-Djvmci.class.path.append~/graal/compiler/mxbuild/dists/jdk1.8/graal.jar:~/graal/sdk/mxbuild/dists/jdk1.8/graal-sdk.jar
-XX:UnlockExperimentalVMOptions
-XX:EnableJVMCI
-XX:UseJVMCICompiler
-XX:-TieredCompilation
-XX:PrintCompilation
-XX:CompileOnlyDemo::workload# JDK 9 或以上版本的运行配置
--module-path~/graal/sdk/mxbuild/dists/jdk11/graal.jar
--upgrade-module-path~graal/compiler/mxbuild/dists/jdk11/jdk.internal.vm.compiler.jar
-XX:UnlockExperimentalVMOptions
-XX:EnableJVMCI
-XX:UseJVMCICompiler
-XX:-TieredCompilation
-XX:PrintCompilation
-XX:CompileOnlyDemo::workload97 1 Demo::workload (4 bytes)HotSpotGraalCompiler 类中的 compileMethod() 方法实际实现了编译过程
参数解释
-XX:UnlockExperimentalVMOptions解锁实验性特性-XX:EnableJVMCI启用 JVMCI 接口-XX:UseJVMCICompiler启用 JVMCI 编译器-XX:-TieredCompilation 关闭分层编译-XX:PrintCompilation 打印即时编译过的方法-XX:CompileOnlyDemo::workload限定只允许编译 workload() 方法
4. 代码中间表示
Graal 编译器与 HotSpot C2 编译器保持一致的中间表示形式Sea-of-NodesIdeal GraphStructured Graph一种程序依赖图形式Program Dependence GraphPDG
理想图Ideal Graph一种有向图其节点表示程序中的元素变量操作符、方法、字段等其变表示数据流虚线或控制流实线
xy 的理想图 x、y 两个节点的数据流流入相加操作节点相加结果数据流出
getX() getY() 的理想图 先调佣 getX()再调用 getY()
-Dgraal.Dump输出 Graal 编译器构造的理想图可通过 mx igv 命令获得相应 Ideal Graph Visualizer 工具
(a b) / 2 的理想图
int average(int a, int b) {return (a b) / 2;
}P(0)、P(1) 表示参数 0、1流入相加操作节点C(2) 表示常量 2与相加结果一起流入除法操作节点
公共子表达式消除演示
// 公共子表达式能够被消除示例
int workload(int a, int b) {return (a b) * (a b);
}// 公共子表达式是不可以被消除示例
int workload() {return (getA() getB()) * (getA() getB());
}公共子表达式能够被消除的理想图 参数 0、1 的加法操作只进行了一次却流出了两条数据给乘法操作
公共子表达式是不可以被消除的理想图 四次方法调用全部执行两个加法操作在两个独立节点进行该版本不会进行公共子表达式消除
5. 代码优化与生成
Graal 编译器通过 greateGraph() 方法将字节码转成理想图
理想图是一组不为空的节点集合所有节点都是继承自 ValueNode 类型的不同子类型如 AddNode 表示加法操作BinaryArithmeticNode 表示二院算术操作、BinaryNode 表示二元操作字节码到理想图的过程与栈帧中操作数与指令的操作规则相对应在 BytecodeParser 类中实现BytecodeParser::getArithmeticOp() 可以看到 iadd 操作码的实现
getIntegerAdd() 创建 AddNode 节点
protected ValueNode genIntegerAdd(ValueNode x, ValueNode y) {return AddNode.create(x, y, NodeView.DEFAULT);
}理想图节点的主要操作
规范化Canonicalisation缩减理想图的规模优化代码生成机器码Generation代码翻译Graal 不直接讲理想图转换成机器码而是先生成 LIR机器指令集相关再交由 HotSpot 统一后端产生机器码Graal 编译器支持的指令集平台只有SPARC、x86-AMD64、ARMv8-AArch64
AddNode 的创建实现
public static ValueNode create(ValueNode x, ValueNode y, NodeView view) {BinaryOpAdd op ArithmeticOpTable.forStamp(x.stamp(view)).getAdd();Stamp stamp op.foldStamp(x.stamp(view), y.stamp(view));ConstantNode tryConstantFold tryConstantFold(op, x, y, stamp, view);if (tryConstantFold ! null) {return tryConstantFold;}if (x.isConstant() !y.isConstant()) {return canonical(null, op, y, x, view);} else {return canonical(null, op, x, y, view);}
}canonical() 实现节点的规范化算术聚合、符号合并等全局规范化则在 CanonicalizerPhase::tryGlobalValueNumbering() 中实现tryConstantFold() 尝试常量折叠AddNode::generate() 进行机器码生成
编译修改演示
class AddNode {void generate(...) {// 将原来的 emitAdd() 替换成 emitSub()... gen.emitSub(op1, op2, false) ...}
}不修改 AddNode::generate() 的汇编代码
0x000000010f71cda0: nopl 0x0(%rax,%rax,1)
0x000000010f71cda5: add %edx,%esi ;*iadd {reexecute0 rethrow0 return_oop0}; - Demo::workload2 (line 10)0x000000010f71cda7: mov %esi,%eax ;*ireturn {reexecute0 rethrow0 return_oop0}; - Demo::workload3 (line 10)0x000000010f71cda9: test %eax,-0xcba8da9(%rip) # 0x0000000102b74006; {poll_return}
0x000000010f71cdaf: vzeroupper
0x000000010f71cdb2: retq修改 AddNode::generate() 的汇编代码
0x0000000107f451a0: nopl 0x0(%rax,%rax,1)
0x0000000107f451a5: sub %edx,%esi ;*iadd {reexecute0 rethrow0 return_oop0}; - Demo::workload2 (line 10)0x0000000107f451a7: mov %esi,%eax ;*ireturn {reexecute0 rethrow0 return_oop0}; - Demo::workload3 (line 10)0x0000000107f451a9: test %eax,-0x1db81a9(%rip) # 0x000000010618d006; {poll_return}
0x0000000107f451af: vzeroupper
0x0000000107f451b2: retq0x000000010f71cda5: add 指令变为 0x0000000107f451a5: sub
Graal 编译器的出现对学习和研究虚拟机代码编译技术提供了巨大的便利让 Java 开发人员不用额外接入 C 的研究通过对 Java 编译器的深入了解有助于开发者分辨哪些代码是编译器可以帮我们处理的哪些代码是需要我们自己调节以便编译器更好优化的 上一篇「JVM 编译优化」编译器优化技术
PS感谢每一位志同道合者的阅读欢迎关注、评论、赞
参考资料
[1]《深入理解 Java 虚拟机》