「JVM 编译优化」Graal 编译器

news2025/7/30 11:30:59

文章目录

      • 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 虚拟机》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/367832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue中的key值

1. 什么是key&#xff1f; 当我们对一个数据进行遍历生成DOM时&#xff0c;vue的内部会根据索引号对其进行key的定义&#xff0c;而key会作为每一个新生成DOM的唯一标识。 2.为什么不建议索引值作为key&#xff1f; 很多人喜欢直接使用索引值作为key&#xff0c;当所遍历的数…

项目缓存问题处理

1、public/index.html文件头部配置 <meta http-equiv"pragram" content"no-cache"> <meta http-equiv"cache-control" content"no-cache,no-store,must-revalidate"> <meta http-equiv"expires" content&…

Video 标签无法播放 mp4 的原因和解决办法

问题 用 QQ 的截图录屏功能录制的 mp4 视频&#xff0c;无法用 <video> 标签正常播放。 原因 通过搜索的说法是&#xff1a; 查阅文档&#xff08;不知道是啥文档&#xff09;&#xff0c;关于video标签所支持的视频格式和编码&#xff1a; MPEG4 带有H.264视频编码和…

【Windows Server 2019】发布服务器 | 远程桌面服务的安装与配置 Ⅰ——理论,实验拓扑和安装基于RemoteAPP的RDS

目录1. 理论1.1 什么是远程桌面服务2. 实验拓扑2.1 拓扑说明3. 安装基于RemoteAPP的RDS关联博文1. 理论 1.1 什么是远程桌面服务 远程桌面服务 (RDS) 是一个卓越的平台&#xff0c;可以生成虚拟化解决方案来满足每个最终客户的需求&#xff0c;包括交付独立的虚拟化应用程序、…

茂名市 2021 年高中信息技术学科素养展评

没事干&#xff0c;发一下去年去比赛的题目。 目录 第一题 30分 第二题 30分 第一题 30分 题目&#xff1a; “姐姐&#xff0c;乘除法运算太难了&#xff0c;有什么办法能熟练掌握吗&#xff1f;”今年 读小学四年级的表弟向李红求救。为了提高表弟的运算能力&#xff0c;…

Candence allegro 创建等长的方法

随着源同步时序电路的发展,越来越多的并行总线开始采用这种时序控制电路,最典型的代表当属目前炙手可热的DDRx系列。下图这种点到点结构的同步信号,对于攻城狮来说,设置等长约束就非常easy了图片。 But,对于有4、6、8、、、等多颗DDR芯片的ACC同步信号来说,要设置等长约束…

在Excel中按条件筛选数据并存入新的表

案例 老板想要看去年每月领料数量大于1000的数据。手动筛选并复制粘贴出来,需要重复操作12次,实在太麻烦了,还是让Python来做吧。磨刀不误砍柴工,先整理一下思路: 1读取原表,将数量大于1000的数据所对应的行整行提取(如同在excel表中按数字筛选大于1000的) 2将提取的数…

Mysql安装和基本使用

MySQLMySQL 是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;目前属于 Oracle 公司。MySQL 是一种关联数据库管理系统&#xff0c;关联数据库将数据保存在不同的表中&#xff0c;而不是将所有数据放在一个大仓库内&#xff0c;这样就增加了速度并提…

Linux 驱动基础

注册驱动模块时给模块传递参数 在一些情况下&#xff0c;我们要动态的改变驱动中某个变量的值&#xff0c;那么就可以在注册时给驱动模块传递参数。 给驱动模块中传递参数&#xff0c;需要定义好接受参数值的全局变量&#xff0c;并调用module_param 来引用它&#xff0c;具体…

Spring架构篇--2.6 远程通信基础--Rpc-Socket实战篇

前言&#xff1a;微服务之间怎么通过socket完成通信&#xff1b;本文通过demo 展示微服务如何通过socket 完成服务之间的通信&#xff1b; 1 使用maven新建两个springboot 服务&#xff1a;模拟实现订单通过订单号获取商品信息&#xff1a; 1.1 创建建springboot 项目后&…

1080T、2080T、4070T显卡的深度学习性能测试和结论

先说结论&#xff1a; 4070T显卡FP32的训练和推理速度跟3090应该基本类似。但由于显存12G偏低&#xff0c;4070T不太适合如今的深度学习模型训练&#xff08;新手列外&#xff0c;大部分模型都能训练起来&#xff0c;耗电也相对很低&#xff09;&#xff0c;更适合测试最新的一…

记录一次ubuntu下配置ssh登录出现的问题

现象描述: 1. 配置完服务器端公钥和本地的私钥之后&#xff0c;ssh登录始终会让输入密码&#xff0c;用ssh -vvv rootip 查看发现发送密钥之后就没反应了。 本机debug info: debug1: Trying private key: C:\Users\wangc/.ssh/id_xxxx &#xff08;私钥文件&#xff09; debug3…

每日站会如何进行优化流程,更高效?

1、每日站会时间要求 每日站会是开发团队一个以15分钟为限的活动。每日站会每一天的上午9点准时在会议室举行。开会时间需要把握精准&#xff0c;并需要每天坚持进行站会讨论活动。 每日站会如何进行优化流程&#xff0c;更高效&#xff1f;​ 2、团队中站会的角色和职责…

Unity(三)--导入3d模型并实现UGUI界面上嵌入3d模型

Unity支持的常用模型格式及建模软件: 格式建模软件网格动画材质骨骼FBX3DMax,C4D,Blender,Maya等√√√√OBJ3DMax,C4D,Blender,Maya等√目录 导入模型并调整好位置创建2D场景(UGUI)使3d模型显示在图片前面方法一:使用Render Texture注意点导入模型并调整好位置 以FBX为例,…

SAP MM 物料管理模块入门学习笔记 2023.2.24

https://zhuanlan.zhihu.com/p/555022893 SAP 企业组织结构 SAP 物料管理模块企业组织结构从上到下分为 集团——》公司——》工厂——》库存地点 集团&#xff1a;SAP 系统组织结构最高级别&#xff1a;内部包括一个完整的SAP系统全部数据 公司代码&#xff1a; 标识集团内一…

【Kubernetes 企业项目实战】09、Rancher 2.6 管理 k8s-v1.23 及以上版本高可用集群

目录 一、Rancher 介绍 1.1Rancher简介 1.2 Rancher 和 k8s 的区别 1.3 Rancher 企业使用案例 二、安装 Rancher 2.1 初始化环境 2.2 安装 Rancher 2.3 登录 Rancher 平台 三、通过 Rancher 管理已存在的 k8s 集群 3.1 配置 rancher 3.2 导入 k8s ​四、通过 Ranc…

啊哈 算法读书笔记 第 1 章 一大波数正在靠近——排序

目录 排序算法&#xff1a; 时间复杂度&#xff1a; 排序算法和冒泡排序之间的过渡&#xff1a; 冒泡排序 冒泡排序和快速排序之间的过渡&#xff1a; 快速排序 排序算法&#xff1a; 首先出场的是我们的主人公小哼&#xff0c;上面这个可爱的娃就是啦。期末考试完了老…

安装Ffmpeg音视频编解码工具和搭建EasyDarwin开源流媒体服务器

目录 一&#xff0c;安装Ffmpeg音视频编解码工具 1&#xff0c;简介 2&#xff0c;开发文档 3&#xff0c;安装部署 二&#xff0c;搭建EasyDarwin开源流媒体服务器 1&#xff0c;简介 2&#xff0c;主要功能特点 3&#xff0c;安装部署 4&#xff0c;效果图 三&…

「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾---InnoDB的BufferPool

「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾—InnoDB的Buffer Pool 文章目录「mysql是怎样运行的」第17章 调节磁盘和CPU的矛盾---InnoDB的Buffer Pool[toc]一、缓存的重要性二、InnoDB的Buffer Pool2.1 啥是Buffer Pool2.2 Buffer Pool内部组成2.3 free链表的管理2.4 缓…

Sallen-Key二阶低通滤波器——设计问题浅析

目录前言1 Sallen-Key二阶低通滤波器结构2 截止频率3 结语前言 这两天接了个简单的活&#xff0c;关于设计一个Sallen-Key二阶低通滤波器&#xff0c;有一些体会。 1 Sallen-Key二阶低通滤波器结构 这个结构很简单&#xff0c;优势就是在于简易实现二阶低通。这种类电压跟随器…