HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

深入浅出 Java 10 的实验性 JIT 编译器 Graal

  • 2018-04-11
  • 本文字数:4027 字

    阅读完需:约 13 分钟

引言

对于大部分应用开发者来说,Java 编译器指的是 JDK 自带的 javac 指令。这一指令可将 Java 源程序编译成.class 文件,其中包含的代码格式我们称之为 Java bytecode(Java 字节码)。这种代码格式无法直接运行,但可以被不同平台 JVM 中的 interpreter 解释执行。由于 interpreter 效率低下,JVM 中的 JIT compiler(即时编译器)会在运行时有选择性地将运行次数较多的方法编译成二进制代码,直接运行在底层硬件上。Oracle 的 HotSpot VM 便附带两个用 C++ 实现的 JIT compiler:C1 及 C2。

与 interpreter,GC 等 JVM 的其他子系统相比,JIT compiler 并不依赖于诸如直接内存访问的底层语言特性。它可以看成一个输入 Java bytecode 输出二进制码的黑盒,其实现方式取决于开发者对开发效率,可维护性等的要求。Graal 是一个以 Java 为主要编程语言,面向 Java bytecode 的编译器。与用 C++ 实现的 C1 及 C2 相比,它的模块化更加明显,也更加容易维护。Graal 既可以作为动态编译器,在运行时编译热点方法;亦可以作为静态编译器,实现 AOT 编译。在 Java 10 中,Graal 作为试验性 JIT compiler 一同发布( JEP 317 )。这篇文章将介绍 Graal 在动态编译上的应用。有关静态编译,可查阅 JEP 295 Substrate VM

分层编译(tiered compilation

在介绍 Graal 前,我们先了解 HotSpot 中的 tiered compilation。前面提到,HotSpot 集成了两个 JIT compiler — C1 及 C2(或称为 Client 及 Server)。两者的区别在于,前者没有应用激进的优化技术,因为这些优化往往伴随着耗时较长的代码分析。因此,C1 的编译速度较快,而 C2 所编译的方法运行速度较快。在 Java 7 前,用户需根据自己的应用场景选择合适的 JIT compiler。举例来说,针对偏好高启动性能的 GUI 用户端程序则使用 C1,针对偏好高峰值性能的服务器端程序则使用 C2。

Java 7 引入了 tiered compilation 的概念,综合了 C1 的高启动性能及 C2 的高峰值性能。这两个 JIT compiler 以及 interpreter 将 HotSpot 的执行方式划分为五个级别:

  • level 0:interpreter 解释执行
  • level 1:C1 编译,无 profiling
  • level 2:C1 编译,仅方法及循环 back-edge 执行次数的 profiling
  • level 3:C1 编译,除 level 2 中的 profiling 外还包括 branch(针对分支跳转字节码)及 receiver type(针对成员方法调用或类检测,如 checkcast,instnaceof,aastore 字节码)的 profiling
  • level 4:C2 编译

其中,1 级和 4 级为接受状态 — 除非已编译的方法被 invalidated(通常在 deoptimization 中触发),否则 HotSpot 不会再发出该方法的编译请求。

上图列举了4 种编译模式(非全部)。通常情况下,一个方法先被解释执行(level 0),然后被C1 编译(level 3),再然后被得到profile 数据的C2 编译(level 4)。如果编译对象非常简单,虚拟机认为通过C1 编译或通过C2 编译并无区别,便会直接由C1 编译且不插入profiling 代码(level 1)。在C1 忙碌的情况下,interpreter 会触发profiling,而后方法会直接被C2 编译;在C2 忙碌的情况下,方法则会先由C1 编译并保持较少的profiling(level 2),以获取较高的执行效率(与3 级相比高30%)。

Graal 可替换 C2 成为 HotSpot 的顶层 JIT compiler,即上述 level 4。与 C2 相比,Graal 采用更加激进的优化方式,因此当程序达到稳定状态后,其执行效率(峰值性能)将更有优势。

早期的 Graal 同 C1 及 C2 一样,与 HotSpot 是紧耦合的。这意味着每次编译 Graal 均需重新编译 HotSpot。 JEP 243 将 Graal 中依赖于 HotSpot 的代码分离出来,形成 Java-Level JVM Compiler Interface(JVMCI)。该接口主要提供如下三种功能:

  • 响应 HotSpot 的编译请求,并分发给 Java-Level JIT compiler
  • 允许 Java-Level JIT compiler 访问 HotSpot 中与 JIT compilation 相关的数据结构,包括类,字段,方法及其 profiling 数据等,并提供这些数据结构在 Java 层面的抽象
  • 提供 HotSpot codecache 的 Java 抽象,允许 Java-Level JIT compiler 部署编译完成的二进制代码

综合利用这三种功能,我们可以将 Java-Level 编译器(不局限于 Graal)集成至 HotSpot 中,响应 HotSpot 发出的 level 4 的编译请求并将编译后的二进制代码部署到 HotSpot 的 codecache 中。此外,单独利用上述第三种功能可以绕开 HotSpot 的编译系统 — Java-Level 编译器将作为上层应用的类库直接部署编译后的二进制代码。Graal 自身的单元测试便是依赖于直接部署而非等待 HotSpot 发出编译请求; Truffle 亦是通过此机制部署编译后的语言解释器。

Graal v.s. C2

前面提到,JIT Compiler 并不依赖于底层语言特性,它仅仅是一种代码形式到另一种代码形式的转换。因此,理论上任意 C2 中以 C++ 实现的优化均可以在 Graal 中通过 Java 实现,反之亦然。事实上,许多 C2 中实现的优化均被移植到 Graal 中,如近期由其他开发者贡献的 String.compareTointrinsic 的移植。当然,局限于 C++ 的开发 / 维护难度(个人猜测),许多 Graal 中被证明有效的优化并没有被成功移植到 C2 上,这其中就包含 Graal 的 inlining 算法及 partial escape analysis(PEA)。

Inlining 是指在编译时识别 callsite 的目标方法,将其方法体纳入编译范围并用其返回结果替换原 callsite。最简单直观的例子便是 Java 中常见的 getter/setter 方法 — inlining 可以将一个方法中调用 getter/setter 的 callsite 优化成单一内存访问指令。Inlining 被业内戏称为优化之母,其原因在于它能引发更多优化。然而在实践中我们往往受制于编译单元大小或编译时间的限制,无法无限制地递归 inline。因此,inlining 的算法及策略很大程度上决定了编译器的优劣,尤其是在使用 Java 8 的 stream API 或使用 Scala 语言的场景下。这两种场景对应的 Java bytecode 包含大量的多层单方法调用。

Graal 拥有两个 inliner 实现。社区版的 inliner 采用的是深度优先的搜索方式,在分析某一方法时,一旦遇到不值得 inline 的 callsite 时便回溯至该方法的调用者。Graal 允许自定义策略以判断某一 callsite 值不值得 inline。默认情况下,Graal 会采取一种相对贪婪的策略,根据 callsite 的目标方法的大小做出相应的决定。Graal enterprise 的 inliner 则对所有 callsite 进行加权排序,其加权算法取决于目标方法的大小以及可能引发的优化。当目标方法被 inline 后,其包含的 callsite 同样会进入该加权队列中。这两种搜索方式都较为适合拥有多层单方法调用的应用场景。

Escape analysis(逃逸分析,EA)是一类识别对象动态范围的程序分析。编译器中常见的应用有两类:如果对象仅被单一线程访问,则可去除针对该对象的锁操作;如果对象为堆分配且仅被单一方法访问(inlining 的重要性再次体现),则可将该对象转化成栈分配。后者通常伴随着 scalar replacement,即将对对象字段的访问替换成对虚拟局部操作数的访问,从而进一步将对象由栈分配转换成虚拟分配。这不仅节省了原本用于存放对象 header 的内存空间,而且可以在 register allocator 的帮助下将(部分)对象字段存放在寄存器中,在节省内存的同时提高执行效率(内存访问转换成寄存器访问)。

Java 中常见的 for-each loop 是 EA 的一大目标客户。我们知道 for-each loop 会调用被遍历对象的 iterator 方法,返回一个实现 interface Iterator 的对象,并利用其 hasNext 及 next 接口进行遍历。Java collections 中的容器类(如 ArrayList)通常会构造一个新的 Iterator 实例,其生命周期局限于该 for-each loop 中。如若 Iterator 实例的构造函数以及 hasNext,next 方法调用(连同它们方法体中以 this 为 receiver 的方法调用,如 checkForComodification())都被 inline,EA 会认为该实例没有逃逸,并采取栈分配及 scalar replacement。

理想情况下,Foo.bar 会被优化成如下代码:

HotSpot 的 C2 便已应用控制流无关的 EA 实现 scalar replacement。而 Graal 的 PEA 则在此基础上引入了控制流信息,将所有的堆分配操作虚拟化,并仅在对象确定逃逸的分支 materialize。与 C2 的 EA 相比,PEA 分析效率较低,但能够在对象没有逃逸的分支上实现 scalar replacement。如下例所示,如果 then-branch 的执行概率为 1%,那么被 PEA 优化后的代码在 99% 的情况下并不会执行堆分配,而 C2 的 EA 则 100% 会执行堆分配。另一个典型的例子是渲染引擎 Sunflow — 在运行 DaCapo benchmark suite 所附带的默认 workload 时,Graal 的 PEA 判定约 27% 的堆分配(共占 700M)可被虚拟化。该数字远超 C2 的 EA。

使用 Graal

在Java 10 (Linux/x64, macOS/x64) 中,默认情况下HotSpot 仍使用C2,但通过向java 命令添加-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 参数便可将C2 替换成Graal。

Oracle Labs GraalVM 是由 Oracle Labs 直接发布的 JDK 版本。它基于 Java 8,并且囊括了 Graal enterprise。如果对源代码感兴趣,可直接签出 Graal 社区版的 GitHub repo 。源代码的编译需借助 mx 工具及 labsjdk (注:请下载页面最下方的 labsjdk,直接使用 GraalVM 可能会导致编译问题)。

在 graal/compiler 目录下使用 mx eclipseinit,mx intellijinit 或 mx netbeansinit 可分别生成 Eclipse,IntelliJ 或 NetBeans 的工程配置文件。

参考链

Upcoming: advanced topics in Graal compiler

  • Debugging compiled code
  • Deoptimization & Java-level assumption — SpeculationLog
  • Graal method substitution & Snippet
  • Implementing JVM intrinsics

作者介绍:郑雨迪,现于 Oracle Labs 任职高级研究员,是 Graal 编译器组的核心开发者之一。他的研究方向包括动态编译及程序分析。在加入 Oracle Labs 前,郑雨迪于瑞士卢加诺大学攻读并获得博士学位。他即将在 QCon 北京 2018 现场分享《GraalVM 及其生态系统》,敬请关注。

2018-04-11 17:4419364

评论

发布
暂无评论
发现更多内容

手把手教你用 Milvus 和 Towhee 搭建一个 AI 聊天机器人!

Zilliz

Milvus AIGC Towhee ChatGPT LLM

专业的动画交互设计 Principle 免激活版

胖墩儿不胖y

Mac软件 交互设计工具 动画交互设计

软件测试/测试开发丨探索AI与测试报告的完美结合,提升工作效率

测试人

人工智能 程序员 软件测试 ChatGPT

【网络安全】2023年堡垒机品牌大全

行云管家

网络安全 等保 堡垒机 等级保护

DEFI/DApp/DAO/IDO/LP子母币/单双币/机枪池流动性代币质押项目挖矿系统开发

l8l259l3365

高效视频处理工具 Topaz Video AI 激活最新版附激活码

mac大玩家j

Mac软件 视频处理软件 视频修复工具

应用架构的演进:亚马逊的微服务实践

亚马逊云科技 (Amazon Web Services)

Serverless DevOps 微服务

区块链数字货币交易所开发方案,交易平台搭建

V\TG【ch3nguang】

Mate 60系列搭载方舟引擎,华为游戏中心解锁飞驰游戏体验

最新动态

低功耗引擎Cliptrix有什么价值

Onegun

物联网 IoT

打造企业界双11效应丨华为云828 B2B企业节全面驱动企业商业增长

平平无奇爱好科技

高管解读:华为云828 B2B企业节意义非凡

平平无奇爱好科技

等保二级测评国家收费标准是多少?统一的吗?

行云管家

网络安全 等保 等级保护 等保测评 等保二级

微软考虑引入小型核反应堆;诺基亚推出“网络即代码”平台丨RTE开发者日报 Vol.58

声网

四问复合索引,让你的数据查询速度飞起

华为云开发者联盟

后端 华为云 图数据库 华为云开发者联盟 企业号9月PK榜

中国信通院马飞:小程序生态与标准建设规划

TRaaS

小程序

IPQ4019, IPQ4029, IPQ4018 and IPQ4028 Different Wi-Fi standards - offering a choice of different performance levels

wifi6-yiyi

IPQ4019 802.11ac 802. 11AC/AN

华为云828 B2B企业节,深度激活企业数实融合新动能

平平无奇爱好科技

一步教会你如何获取1688商品详情

Noah

API 开发

1周开发上线“中医舌诊”元服务,5天吸引超2万付费用户

最新动态

JDK的配置验证

小齐写代码

优化模型之标注错误篇

矩视智能

深度学习 工业机器视觉

亮相数字科技出海峰会,火山引擎边缘云助力数字化出海“加速度”

火山引擎边缘云

CDN 加速 火山引擎 内容分发 火山引擎边缘计算

SQL还是NoSQL?架构师必备选型技能

树上有只程序猿

nosql sql 业务

华为云828 B2B企业节优惠进行中,华为云耀云服务器L实例为中小企业和开发者量身定制

平平无奇爱好科技

华为云828 B2B企业节:精选优惠助力企业降本增效

平平无奇爱好科技

02. 人工智能核心基础 - 导论(1)

茶桁

人工智能

软件测试/测试开发丨利用人工智能自动找Bug

测试人

人工智能 程序员 软件测试 bug ChatGPT

从终端到云端,华为云828 B2B企业节加速中小企业数字化

平平无奇爱好科技

基于知识图谱、全文检索开发的数字知识库

金陵老街

Java第一个程序——Hello,World!

小齐写代码

深入浅出Java 10的实验性JIT编译器Graal_语言 & 开发_郑雨迪_InfoQ精选文章