当前位置: 首页 > news >正文

「JVM 编译优化」Graal 编译器

文章目录

      • 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 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-NodesIdeal GraphStructured 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 中&#xff0c;JDK 10 开始提供替换&#xff08;得益于 HotSpot 编译器接口&#xff0c;Jav…...

蓝牙标签操作指南

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

嵌入式 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前端学习:一

编辑器的基础使用 编辑器推荐使用&#xff1a; HBuilderx&#xff08;免费中文&#xff09;&#xff08;建议使用&#xff09; Sublime&#xff08;免费英文&#xff09; Sublime中文设置方法&#xff0c;下载语言插件&#xff1a; 1、进入Sublime后&#xff0c;ShiftCtrlP…...

SpringBoot集成Redis实现分布式会话

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

2023年关于身份安全的4 个预测

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

Linux期末考试应急

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

mars3d对geojson图层分属性设置样式

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

三、锁相关知识

文章目录锁的分类可重入锁、不可重入锁乐观锁、悲观锁公平锁、非公平锁互斥锁、共享锁深入synchronized类锁、对象锁synchronized的优化synchronized实现原理synchronized的锁升级重量锁底层ObjectMonitor深入ReentrantLockReentrantLock和synchronized的区别AQS概述加锁流程源…...

C语言数据类型

C 数据类型 在 C 语言中&#xff0c;数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间&#xff0c;以及如何解释存储的位模式。 C 中的类型可分为以下几种&#xff1a; 1 基本类型&#xff1a; 它们是算术类型&#xff0c;…...

华为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有哪些优势&#xff1f;1.性能媲美原生APP 2.使用JavaScript编码&#xff0c;只要学习这一种语言 3.绝大部分代码安卓和IOS都能共用 4.组件式开发&#xff0c;代码重用性很高 5.跟编写网页一般&#xff0c;修改代码后即可自动刷…...

【数据存储】浮点型在内存中的存储

目录 一、存储现象 二、IEEE标准规范 1.存储 2.读取 三、举例验证 1.存储 2.读取 浮点型存储的标准是IEEE&#xff08;电气电子工程师学会&#xff09;754制定的。 一、存储现象 浮点数由于其有小数点的特殊性&#xff0c;有很多浮点数是不能精确存储的&#xff0c;如&#…...

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. 泛型编程实现一个通用的交换函数&#xff0c;使用函数重载虽然可以实现&#xff0c;但是有以 下几个不好的地方&#xff1a;1. 重载的函数仅仅是类型不同&#xff0c;代码复用率比较低&#xff0c;只要有新类型出现时&#xff0c;就需要用户自己增加对应的函数2. 代码的可维…...

C++中的友元及运算符重载

友元 意义 程序中&#xff0c;有些私有属性也想让类外特殊的一些函数或者类进行访问&#xff0c;就要用到友元技术 关键字 friend 友元的三种实现 全局函数做友元 class Room{friend void test(Person &p);//friend class test;public:string phone_number;private:string…...

五、运行时数据区内部结构、JVM中的线程

内存是非常重要的系统资源&#xff0c;是硬盘和cpu的中间仓库及桥梁&#xff0c;承载着操作系统和应用程序的实时运行。JVM内存布局规定了Java在运行过程种内存申请、分配‘、管理的策略&#xff0c;保证了JVM的高效稳定运行&#xff0c;不同的JVM对于内存的划分方式和管理机制…...

Codeforces Round #848 (Div. 2)A-C

传送门 目录 A. Flip Flop Sum 代码&#xff1a; B. The Forbidden Permutation 代码&#xff1a; C. Flexible String 代码&#xff1a; A. Flip Flop Sum 题意&#xff1a;给你一个长度为n的数组&#xff08;数组元素只为1或者-1&#xff09;&#xff0c;你要且只能进行…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例

一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

三分算法与DeepSeek辅助证明是单峰函数

前置 单峰函数有唯一的最大值&#xff0c;最大值左侧的数值严格单调递增&#xff0c;最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值&#xff0c;最小值左侧的数值严格单调递减&#xff0c;最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

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