「JVM 编译优化」Graal 编译器
文章目录
- 1. 历史背景
- 2. 构建编译调试环境
- 3. JVMCI 编译器接口
- 4. 代码中间表示
- 5. 代码优化与生成
1. 历史背景
Graal 编译器在 JDK 9 以 Jaotc
提前编译工具的形式首次加入到官方的 JDK 中,JDK 10 开始提供替换(得益于 HotSpot 编译器接口,Java Level JVM Compiler Interface
,JVMCI
)服务端编译器支持;
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 PATH=`pwd`/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:CompileOnly=Demo::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:CompileOnly=Demo::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:CompileOnly=Demo::workload
97 1 Demo::workload (4 bytes)
HotSpotGraalCompiler
类中的 compileMethod()
方法实际实现了编译过程;
参数解释
-XX:+UnlockExperimentalVMOptions
,解锁实验性特性;-XX:+EnableJVMCI
,启用 JVMCI 接口;-XX:+UseJVMCICompiler
,启用 JVMCI 编译器;-XX:-TieredCompilation
,关闭分层编译;-XX:+PrintCompilation
,打印即时编译过的方法;-XX:CompileOnly=Demo::workload
,限定只允许编译 workload() 方法;
4. 代码中间表示
Graal 编译器与 HotSpot C2 编译器保持一致的中间表示形式:Sea-of-Nodes
,Ideal Graph
,Structured Graph
,一种程序依赖图
形式(Program Dependence Graph
,PDG);
理想图
(Ideal Graph
),一种有向图,其节点表示程序中的元素(变量,操作符、方法、字段等),其变表示数据流(虚线)或控制流(实线);
x+y 的理想图
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) {BinaryOp<Add> 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 {reexecute=0 rethrow=0 return_oop=0}; - Demo::workload@2 (line 10)0x000000010f71cda7: mov %esi,%eax ;*ireturn {reexecute=0 rethrow=0 return_oop=0}; - Demo::workload@3 (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 {reexecute=0 rethrow=0 return_oop=0}; - Demo::workload@2 (line 10)0x0000000107f451a7: mov %esi,%eax ;*ireturn {reexecute=0 rethrow=0 return_oop=0}; - Demo::workload@3 (line 10)0x0000000107f451a9: test %eax,-0x1db81a9(%rip) # 0x000000010618d006; {poll_return}
0x0000000107f451af: vzeroupper
0x0000000107f451b2: retq
0x000000010f71cda5: add
指令变为 0x0000000107f451a5: sub
;
Graal 编译器的出现对学习和研究虚拟机代码编译技术提供了巨大的便利,让 Java 开发人员不用额外接入 C++ 的研究;通过对 Java 编译器的深入了解,有助于开发者分辨哪些代码是编译器可以帮我们处理的,哪些代码是需要我们自己调节以便编译器更好优化的;
上一篇:「JVM 编译优化」编译器优化技术
PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料:
- [1]《深入理解 Java 虚拟机》
相关文章:

「JVM 编译优化」Graal 编译器
文章目录1. 历史背景2. 构建编译调试环境3. JVMCI 编译器接口4. 代码中间表示5. 代码优化与生成1. 历史背景 Graal 编译器在 JDK 9 以 Jaotc 提前编译工具的形式首次加入到官方的 JDK 中,JDK 10 开始提供替换(得益于 HotSpot 编译器接口,Jav…...

蓝牙标签操作指南
一、APP安装指南 1.APP权限问题 电子标签APP安装之后,会提示一些权限的申请,点击允许。否则某些会影响APP的正常运行。安装后,搜索不到蓝牙标签,可以关闭App,重新打开。 2.手机功能 运行APP时候,需要打开…...

嵌入式 Linux Shell编程
目录 1、shell脚本 2、执行shell脚本 3、shell脚本编写 3.1 shell变量 3.2 标准变量或环境变量 3.4 变量赋值有五种格式 3.5 运算符和表达式 关系运算符 布尔运算符 3.6 Test命令用法 1、判断表达式 2、判断字符串 3.判断整数 4、判断文件 3.7 数组 1、数组定义…...

Web前端学习:一
编辑器的基础使用 编辑器推荐使用: HBuilderx(免费中文)(建议使用) Sublime(免费英文) Sublime中文设置方法,下载语言插件: 1、进入Sublime后,ShiftCtrlP…...

SpringBoot集成Redis实现分布式会话
在单体应用的时代,Session 会话直接保存在服务器中,实现非常简单,但是随着微服务的流行,现代应用架构基本都是分布式架构,请求随机的分配到后端的多个应用中,此时session就需要共享,而存储在red…...

2023年关于身份安全的4 个预测
如果您身处技术领域,就会知道现在是时候盘点过去的一年,展望未来 365 天将影响业务、创新以及我们工作方式的因素的季节。这不是一门精确的科学,我们也不总是对的。但是推测很有趣,当我们看到其中一些趋势成为现实时会更有趣。本文…...

Linux期末考试应急
Linux期末考试应急 虚拟机添加硬盘、分区、格式化、挂载、卸载 fdisk -l#查看系统现有分区fdisk <指定磁盘>#指定磁盘分区sudo mkfs.ext3 <指定分区>#格式化磁盘###挂载磁盘1.新建一个目录sudo mkdir /mnt/test2.将指定分区挂载到对应目录sudo mount /dev/sdb10 /…...

mars3d对geojson图层分属性设置样式
开发中可能会遇到如下需求,在全省的数据中按某个属性⾼亮展示某市区。此时就需要使⽤分属性样式的api了。⽂档如下。GeoJsonLayer - Mars3D API文档属性是根据⽮量数据的属性进⾏匹配。可以通过 layer.graphics[0]?.attr ⽅式获取。 指导有哪些属性之后先设置…...

三、锁相关知识
文章目录锁的分类可重入锁、不可重入锁乐观锁、悲观锁公平锁、非公平锁互斥锁、共享锁深入synchronized类锁、对象锁synchronized的优化synchronized实现原理synchronized的锁升级重量锁底层ObjectMonitor深入ReentrantLockReentrantLock和synchronized的区别AQS概述加锁流程源…...
C语言数据类型
C 数据类型 在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。 C 中的类型可分为以下几种: 1 基本类型: 它们是算术类型,…...
华为OD机试真题Python实现【水仙花数】真题+解题思路+代码(20222023)
水仙花数 题目 所谓的水仙花数是指一个n位的正整数其各位数字的n次方的和等于该数本身, 例如153 = 1^3 + 5^3 + 3^3,153是一个三位数 🔥🔥🔥🔥🔥👉👉👉👉👉👉 华为OD机试(Python)真题目录汇总 输入 第一行输入一个整数N, 表示 N 位的正整数 N 在3…...

【华为OD机试模拟题】用 C++ 实现 - 非严格递增连续数字序列(2023.Q1)
最近更新的博客 华为OD机试 - 入栈出栈(C++) | 附带编码思路 【2023】 华为OD机试 - 箱子之形摆放(C++) | 附带编码思路 【2023】 华为OD机试 - 简易内存池 2(C++) | 附带编码思路 【2023】 华为OD机试 - 第 N 个排列(C++) | 附带编码思路 【2023】 华为OD机试 - 考古…...

RN面试题
RN面试题1.React Native相对于原生的ios和Android有哪些优势?1.性能媲美原生APP 2.使用JavaScript编码,只要学习这一种语言 3.绝大部分代码安卓和IOS都能共用 4.组件式开发,代码重用性很高 5.跟编写网页一般,修改代码后即可自动刷…...

【数据存储】浮点型在内存中的存储
目录 一、存储现象 二、IEEE标准规范 1.存储 2.读取 三、举例验证 1.存储 2.读取 浮点型存储的标准是IEEE(电气电子工程师学会)754制定的。 一、存储现象 浮点数由于其有小数点的特殊性,有很多浮点数是不能精确存储的,如&#…...
Servlet笔记(8):异常处理
1、错误页面配置 web.xml <!-- servlet 定义 --> <servlet><servlet-name>ErrorHandler</servlet-name><servlet-class>ErrorHandler</servlet-class> </servlet> <!-- servlet 映射 --> <servlet-mapping><servle…...

stm32f407探索者开发板(二十一)——窗口看门狗
文章目录一、窗口看门狗概述1.1 看门狗框图1.2 窗口看门狗工作过程总结1.3 超时时间1.4 为什么需要窗口看门狗1.5 其他注意事项二、常用寄存器和库函数2.1 控制寄存器WWDG_ CR2.2 配置寄存器WWDG_ CFR2.3 状态寄存器WWDG_SR三、手写窗口看门狗3.1 配置过程3.2 初始化窗口看门狗…...

C++ 模板
1. 泛型编程实现一个通用的交换函数,使用函数重载虽然可以实现,但是有以 下几个不好的地方:1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数2. 代码的可维…...
C++中的友元及运算符重载
友元 意义 程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就要用到友元技术 关键字 friend 友元的三种实现 全局函数做友元 class Room{friend void test(Person &p);//friend class test;public:string phone_number;private:string…...

五、运行时数据区内部结构、JVM中的线程
内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程种内存申请、分配‘、管理的策略,保证了JVM的高效稳定运行,不同的JVM对于内存的划分方式和管理机制…...
Codeforces Round #848 (Div. 2)A-C
传送门 目录 A. Flip Flop Sum 代码: B. The Forbidden Permutation 代码: C. Flexible String 代码: A. Flip Flop Sum 题意:给你一个长度为n的数组(数组元素只为1或者-1),你要且只能进行…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...