「JVM 编译优化」编译器优化技术
后端编译(即时编译、提前编译)的目标时将字节码翻译成本地机器码,而难点是输出优化质量较高的机器码;
文章目录
- 1. 优化技术概览
- 2. 方法内联(Inlining)
- 3. 逃逸分析(Escape Analysis)
- 4. 公共子表达式消除(Common Subexpression Elimination)
- 5. 数组边界检查消除(Array Bounds Checking Elimination)
1. 优化技术概览
PerformanceTacticIndex from OpenJDK Wiki
- compiler tactics,
编译器策略- delayed compilation,延迟编译
- tiered compilation,分层编译
- on-stack replacement,栈上替换
- delayed reoptimization,延迟优化
- program dependence graph representation,程序依赖图表示
- static single assignment representation,静态单赋值表示
- speculative (profile-based) techniques,
基于性能监控的优化技术- optimistic nullness assertions,乐观空值断言
- optimistic type assertions,乐观类型断言
- optimistic type strengthening,乐观类型增强
- optimistic array length strengthening,乐观数组长度增强
- untaken branch pruning,裁剪未被选择的分支
- optimistic N-morphic inlining,乐观的多态内联
- branch frequency prediction,分支频率预测
- call frequency prediction,调用频率预测
- proof-based techniques,基于证据的优化技术
- exact type inference,精确类型推断
- memory value inference,内存值推断
- memory value tracking,内存值跟踪
- constant folding,常量折叠
- reassociation,重组
- operator strength reduction,操作符退化
- null check elimination,空值检查消除
- type test strength reduction,类型检查退化
- type test elimination,类型检查消除
- algebraic simplification,代数简化
- common subexpression elimination,公共子表达式消除
- integer range typing
- flow-sensitive rewrites,数据流敏感重写
- conditional constant propagation,条件常量传播
- dominating test detection
- flow-carried type narrowing,基于流承载的类型缩减转换
- dead code elimination,无用代码消除
- language-specific techniques,语言相关的优化技术
- class hierarchy analysis,类型继承关系分析
- devirtualization,去虚拟机化
- symbolic constant propagation,符号常量传播
- autobox elimination,自动装箱消除
- escape analysis,逃逸分析
- lock elision,锁消除
- lock fusion,所膨胀
- de-reflection,反射消除
- memory and placement transformation,内存及代码位置变换
- expression hoisting,表达式提升
- expression sinking,表达式下沉
- redundant store elimination,冗余存储消除
- adjacent store fusion,相邻存储合并
- card-mark elimination
- merge-point splitting,交汇点分离
- loop transformations,循环变换
- loop unrolling,循环展开
- loop peeling,循环剥离
- safepoint elimination,安全点消除
- iteration range splitting,迭代范围分离
- range check elimination,范围查找消除
- loop vectorization,循环向量化
- global code shaping,全局代码调整
- inlining (graph integration),内联
- global code motion,全局代码外提
- heat-based code layout,基于热度的代码布局
- switch balancing,Swith 调整
- throw inlining
- control flow graph transformation,控制流图变换
- local code scheduling,本地代码编排
- local code bundling,本地代码封包
- delay slot filling,延迟槽填充
- graph-coloring register allocation,着色图寄存器分配
- linear scan register allocation,线性扫描寄存器分配
- live range splitting
- copy coalescing,复写聚合
- constant splitting,常量分裂
- copy removal,复写移除
- address mode matching,地址模式匹配
- instruction peepholing,指令窥孔优化
- DFA-based code generator,基于正确有限状态机的代码生成
方法内联
// 原始代码
static class B {int value;final int get() {return value;}
}public void foo() {y = b.get();// ...do stuff...z = b.get();sum = y + z;
}// 内联后的代码,即时编译实际效果是作用在代码中间表示或机器码之上的,这里只是以 Java 代码演示效果
public void foo() {y = b.value;// ...do stuff...z = b.value;sum = y + z;
}
内联可以去除方法调用的成本(如查找方法版本、建立栈帧等),可以为其他哟花建立良好条件;一般是优先级最高的优化手段;
冗余访问消除(Redundant Loads Elimination)
public void foo() {y = b.value;// ...do stuff...z = y;sum = y + z;
}
...do stuff... 不影响 b.value 的情况下,可以将 z=b.value 替换为 z=y,可以不再去访问 b 的局部变量,消除了公共子表达式 b.value(Common Subexpression Elimination);
复写传播(Copy Propagation)
public void foo() {y = b.value;// ...do stuff...y = y;sum = y + y;
}
变量 z 与变量 y 完全相等,是没有必要使用的,可以使用 y 代替 z;
无用代码消除(Dead Code Elimination)
public void foo() {y = b.value;// ...do stuff...sum = y + y;
}
上述这些优化带来的代码压缩和执行效率提升在实际机器指令上会表现得更明显;
2. 方法内联(Inlining)
最重要的优化技术之一;把目标方法的代码原封不动的复制到发起调用的方法中(实际 JVM 实现会复杂很多),除了消除方法调用的成本(查找方法版本、创建栈帧等),更重要的是为其他优化建立良好基础;
public static void foo(Object obj) {if (obj != null) {System.out.println("do something");}
}public static void testInline(String[] args) {Object obj = null;foo(obj);
}
示例中全部是Dead Code,但若没有内联,就无法通过无用代码消除来优化掉这些 Dead Code;
按照经典编译原理的优化理论,大多数 Java 方法无法进行内联;只有使用 invokespecial 调用私有方法、示例构造器、父类方法,使用 invokestatic 调用静态方法,使用 invokestatic 调用被 final 修饰的方法,才会在编译期进行解析,其他 Java 方法都属于虚方法,都必须在运行时进行方法接收者的多态选择;
如 b.get() 直接内联 b.value,若无上下文,将无法确认 b 的实际类型,从而无法内联到对应方法;
无法内联的解法
-
类型继承关系分析(Class Hierarchy Analysis,CHA),在整个应用程序范围做类型分析(确认当前已加载的类中,接口有哪些实现类、类是否存在子类、子类是否复写某个虚方法等); -
不是虚方法,直接进行内联; -
是虚方法,向 CHA 查询此方法在当前运行时状态下是否有多台选择;若只有一个版本,可假设应用程序的全貌就是只有一个版本,从而进行守护内联(Guarded Inlining),此后 VM 可能加载新的类型从而改变 CHA,因此这种内联属于激进预测性优化,必须预备退路(退回解释状态,或重新编译);若存在多态选择,即时编译器将进行最后一次努力,使用内联缓存(Inline Cache)来缩减方法调用开销; -
内联缓存(Inline Cache),建立在目标方法正常入口之前的缓存,在未发生方法调用之前,内联缓存状态为空,当第一次调用发生后,缓存记录下方法接收者的版本信息,以后进来的每次调用比较其版本;若版本一致(单态内联,Monomorphic Inline Cache),可通过缓存调用,仅比不内联的非虚方法调用多了一次类型判断开销;若版本不一致,则退化成超多态内联缓存(Megamorphic Inline Cache),相当于真正查找虚方法进行方法分派;
方法内联优化与面向对象的编程方式相矛盾,所以在 Java 语言进行方法内联大多数情况下都是激进优化,而类似的激进优化在高性能 JVM 中极为常见,比如激进优化移除出现概率很小的隐式异常、使用概率很小的分支等;但这些优化都必须具备逃生门(重回解释状态);
3. 逃逸分析(Escape Analysis)
最前沿的优化技术之一;分析对象动态作用域,虽不是直接优化代码的手段,却是其他优化措施的依据与前提(类似继承关系分析);
方法逃逸,一个在方法里定义的对象可能被外部方法引用(作为调用参数传递给其他方法);线程逃逸,一个对象被跨线程访问(赋值给可能被其他线程访问的实例变量);
逃逸分析的优化用途
栈上分配(Stack Allocations)
JVM 对堆中分配的对象进行 GC 需要大量计算标记筛选出可回收对象,并进行回收和整理;若一个对象不会发生线程逃逸(实际这类对象的比例很大),这个对象就可能在栈上分配内存,对象可以随方法的结束自动销毁,GC 的压力将大幅下降;
标量替换(Scalar Replacement)
将一个 Java 对象拆解成若干个用原始类型表示的成员变量;若一个对象不会发生方法逃逸,这个对象就可以被拆散为标量,程序执行就不用创建这个对象,而是直接在栈上创建成员变量(栈上分配,大几率分配到物理机器的高速寄存器中存储),这还可以作为进一步优化的基础; - 标量,无法再分解成更小数据表示的数据,如 JVM 的原始数据类型 int,long 等,以及 reference 类型; - 聚合量(Aggregate),一个可以继续分解的数据,如 Java 中的对象;
同步消除(Synchronization Elimination),若一个对象不会发生线程逃逸,那么对这个变量的读写同步操作可以被安全的消除掉;
逃逸分析的计算成本(如复杂的数据流敏感的过程间分析)非常高,无法保证逃逸分析带来的性能收益高于它的消耗,JDK 6 才开始支持逃逸分析(Update 23 服务端编译器默认开启);
inline 关键字定义 Java 内联类型可以实现 C# 中值类型相对应的功能,可以令逃逸分析变得简单很多;
逃逸分析模拟演示
// 原始代码
public int test(int x) {int xx = x + 2;Point p = new Point(xx, 42);return p.getX();
}
// 步骤1: 构造函数内联
public int test(int x) {int xx = x + 2;Point p = point_memory_alloc(); // 在堆中分配 P 对象的示意方法p.x = xx; // Point 构造函数被内联p.y = 42return p.x; // Point::getX()被内联
}
// 步骤2: 标量替换
public int test(int x) {int xx = x + 2;int px = xx; // p.x 与 p.y 不会发生方法逃逸,可以直接替换为标量 px、pyint py = 42;return px;
}
// 步骤3: 无效代码消除
public int test(int x) {return x + 2; // py 对运行效果无影响,为 Dead Code,可消除
}
逃逸分析相关参数
-XX:+DoEscapeAnalysis,手动开启逃逸分析;
-XX:+PrintEscapeAnalysis,查看分析结果;
-XX:+EliminateAllocations,开启标量替换;
-XX:+EliminateLocks,开启同步消除;
-XX:+PrintEliminateAllocations,查看标量的替换情况;
大型程序中实施逃逸分析可能出现效果不稳定的情况,或分析过程耗时却无法有效判别出非逃逸对象,导致性能(即时编译的收益)下降;
4. 公共子表达式消除(Common Subexpression Elimination)
语言无关的经典优化技术之一;对于公共子表达式,没必要重复计算,可以直接用前面计算的结果替换;
公共子表达式,如果一个子表达式已经被计算过,且表达式中变量的值不曾发生变化,那这个子表达式就可以当做公共子表达式;基本代码块中的为局部公共子表达式(Local Common Subexpression Elimination),跨基本代码块则可称为全局公共子表达式(Global Common Subexpression Elimination);
公共子表达式消除演示
// 原始代码
int d = (c * b) * 12 + a + (a + b * c);
javac 直译的 Class 格式效果
iload_2 // b
imul // 计算b*c
bipush 12 // 推入12
imul //计算(c*b)*12
iload_1 //a
iadd //计算(c*b)*12+a
iload_1 //a
iload_2 //b
iload_3 //c
imul //计算b*c
iadd //计算a+b*c
iadd //计算(c*b)*12+a+a+b*c
istore 4
即时编译优化效果
// 将 c*b 和 b*c 用 E 表示,消除公共子表达式
int d = E * 12 + a + (a + E);// 代数简化(Algebraic Simplification)
int d = E * 13 + a + a;
5. 数组边界检查消除(Array Bounds Checking Elimination)
语言相关的经典优化技术之一;Java 语言作为一门动态安全的语言,会自动对数组的读写访问索引合法性做检查,当超出地址范围,则抛出 java.lang.ArrayIndexOutOfBoundsException,这对软件开发很友好,但对 JVM 却是一个性能负担;若能在编译期根据数据流分析判定索引一直在数组边界内,就可以消除数组上下边界的检测,从而节省很多次条件判断操作;
类似的消除还可以发生在空指针检查(NullPointException)、除数为零检查(ArithmeticException)、自动装箱消除(Autobox Elimination)、安全点消除(Safepoint Elimination)、消除反射(Dereflection)等;针对这些检查的消除方式,还可以采用隐式异常处理的思路;
隐式异常处理示例
// 原始伪代码
if(foo != null) {return foo.value;
} else {throw new NullPointException();
}// 隐式异常消除后的伪代码
try {return foo.value;
} catch (segment_fault) {uncommon_trap();
}
JVM 注册一个 Segment Fault 信号的异常处理器(uncommon_trap(),针对进程层面的异常处理器,与 try-catch 的线程级异常处理器不同),当 foo 不为空,可以省去判空的开销;但若 foo 真为空,会转到异常处理器(涉及进程从用户态转内核态,结束后再转用户态)恢复中断并抛出 NullPointException,这将远比一次判空要慢;借助 VM 在运行期收集的性能监控信息,判定 foo 极少为空时,采用这样的优化方式会更值得;
上一篇:「JVM 编译优化」提前编译器
PS:感谢每一位志同道合者的阅读,欢迎关注、评论、赞!
参考资料:
- [1]《深入理解 Java 虚拟机》
相关文章:
「JVM 编译优化」编译器优化技术
后端编译(即时编译、提前编译)的目标时将字节码翻译成本地机器码,而难点是输出优化质量较高的机器码; 文章目录1. 优化技术概览2. 方法内联(Inlining)3. 逃逸分析(Escape Analysis)4…...
回溯问题(子集型回溯、组合型回溯、排列型回溯)【零神基础精讲】
来源0x3f:https://space.bilibili.com/206214 回溯分为【子集型回溯】【组合型回溯】【排列型回溯】 文章目录回溯基本概念[17. 电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/)子集型回溯(分割问题也可以看…...
源代码配置安装Apache
源代码配置安装Apache 📒博客主页: 微笑的段嘉许博客主页 💻微信公众号:微笑的段嘉许 🎉欢迎关注🔎点赞👍收藏⭐留言📝 📌本文由微笑的段嘉许原创! …...
css水平垂直居中各种方法实现方式
不定宽高水平垂直居中? 面试题回答方式: 通过display:flex;justify-content:center; align-items:center;就可以让子元素不定宽高水平垂直居中 也可以父display:flex;,子设置一个margin&#…...
PowerShell Install java 13
java 前言 Java具有大部分编程语言所共有的一些特征,被特意设计用于互联网的分布式环境。Java具有类似于C语言的形式和感觉,但它要比C语言更易于使用,而且在编程时彻底采用了一种以对象为导向的方式。 java download javadownloadPowersh…...
Python的PyQt框架的使用(汇总)
Python的PyQt框架的使用一、前言二、安装PyQt三、使用第三方开发工具四 、创建主窗体五、常用控件篇1.QLineEdit 文本框2.QPushButton按钮控件3.QRadioButton 单选按钮六、布局管理篇1.通过布局管理器布局2.绝对布局七、信号与槽的关联1.编辑信号/槽2.信号/槽编辑器八、资源文件…...
力扣热题100Day05:15.三数之和,17. 电话号码的字母组合,19. 删除链表的倒数第 N 个结点
15.三数之和 题目链接:15. 三数之和 - 力扣(Leetcode) 思路: (1)双指针,在外层for循环里加入两个指针,left和right (2)排序:为了更好地进行去…...
探索开源:获取完整的 GitHub 社区数据集
本篇文章聊聊 GitHub 开放数据集的获取和整理,分享一些数据整理的细节技巧,以及一些相对粗浅的数据背后的事情。 写在前面 分析 GitHub 上的项目和开发者获取是深入、真实的了解开源世界演进的方法之一。 在 GHArchive 项目中,我们能够看到…...
github ssh密钥配置,克隆远程仓库
GitHub的SSH配置 在往github上push项目的时候,如果走https的方式,每次都需要输入账号密码,非常麻烦。而采用ssh的方式,就不再需要输入,只需要在github自己账号下配置一个ssh key即可! 很多朋友在用github管…...
突破年薪百万难关!吃透这套Java真题合集
前言我相信大多 Java 开发的程序员或多或少经历过BAT一些大厂的面试,也清楚一线互联网大厂 Java 面试是有一定难度的,小编经历过多次面试,有满意的也有备受打击的。因此呢小编想把自己这么多次面试经历以及近期的面试真题来个汇总分析&#x…...
[黑马程序员SSM框架教程] Spring-11-setter注入
思考:向一个类中传递数据要几种? set方法构造方法 思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要数字或字符呢 引用类型简单类型(基本数据类型和字符串) 注入方式&#x…...
Java多线程(一)--多线程基础知识
1. 为什么要使用并发编程提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU去执行,每个CPU执行一个线程,这样就提高了CPU的使用效率&…...
AutoDock, AutoDock-vina等对接工具安装
AutoDock, AutoDock-vina等对接工具安装 AutoDock-GPU安装 下载地址: https://autodock.scripps.edu/downloads/ 将压缩包传送至安装目录中,并解压到当前路径 unzip AutoDock-GPU-develop.zip 找到服务器的cuda的路径,cuda的路径一般默认…...
MySQL常见面试题(2023年最新)
目录1.char和varchar的区别2.数据库的三大范式3.索引是什么4.索引的优点和缺点5.索引怎么设计(优化)6.索引的类型7.索引的数据类型8.索引为什么使用树结构9.二叉查找树、B树、B树10.为什么使用B树不用B树11.最左匹配原则12.MylSAM和InnoDB的区别13.什么是事务14.事务的四大特性…...
C# 泛型详解
C# 泛型详解1、泛型概述2、定义泛型3、泛型的特性4、泛型委托5、泛型的优点在 C# 中,泛型(Generic)是一种规范,它允许我们使用占位符来定义类和方法,编译器会在编译时将这些占位符替换为指定的类型,利用泛型…...
数据仓库相关术语
数据仓库数据集市事实维度级别数据清洗数据采集数据转换联机分析处理(OLAP OnlineAnalytical Processing )切片切块星型模式雪花模式粒度度量度量值口径指标 原子指标:派生指标衍生指标标签自然键持久键代理键退化维度下钻上卷T0与T1数据挖掘数据科学家总线架构总线…...
【IDEA】常用快捷键
代码补全 快捷键说明sout快速输出System.out.println();psvm快速输出public static void main(String[] args) {}Ctrl Alt Space代码补全 编辑类 快捷键说明Shift Enter向下键入一行,并将光标移到下一行的开头Ctrl Alt Enter当前行上方生成空行,并…...
【调试】sysRq按键使用方法
SysRq键简介 SysRq键是一个魔术案件,只要在内核没有完全卡死的情况下,内核都会相应SysRq 键的输入,使用这些组合键都可以搜集包括系统内存使用、CPU任务处理、进程运行状态等系统运行信息。 配置 内核配置选项中要使能CONFIG_MAGIC_SYSRQ选…...
Jenkins Pipeline 语法
官网 ## https://www.jenkins.io/doc/book/pipeline/ 参考文章 ## https://www.jianshu.com/p/215584419f3d 根据Jenkins官网Pipeline给出的解释, 流水线语法分为两种, 一种是声明式流水线(Declarative Pipeline)另一种是脚本…...
rust语言实现超级简单的杀毒软件,通过哈希扫描病毒。
以下是一个简单的rust语言程序,用于扫描病毒文件并使用哈希表进行比较。该程序可以扫描指定目录中的所有文件,并查找其中是否包含特定的病毒文件。程序可以通过计算文件哈希值并将其与已知的病毒哈希值进行比较来确定文件是否是病毒。注意,这…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Kubernetes 网络模型深度解析:Pod IP 与 Service 的负载均衡机制,Service到底是什么?
Pod IP 的本质与特性 Pod IP 的定位 纯端点地址:Pod IP 是分配给 Pod 网络命名空间的真实 IP 地址(如 10.244.1.2)无特殊名称:在 Kubernetes 中,它通常被称为 “Pod IP” 或 “容器 IP”生命周期:与 Pod …...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
对象回调初步研究
_OBJECT_TYPE结构分析 在介绍什么是对象回调前,首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
结构化文件管理实战:实现目录自动创建与归类
手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题,进而引发后续程序异常。使用工具进行标准化操作,能有效降低出错概率。 需要快速整理大量文件的技术用户而言,这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB,…...
