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

「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…...

【python学习笔记】:输出与输入

01 输出方式 表达式语句、print()函数和使用文件对象的write()方法。 02 输出形式 格式化输出str.format()函数、转成字符串可以使用repr()或str()函数来实现。 (1)repr():产生一个解释器易读的表达形式,便于字符串的拼接。 例:输出平方与…...

汽车电子社区交流宣传

http://t.csdn.cn/VSLO0http://t.csdn.cn/VSLO0 当今的汽车行业已经进入了数字化时代,汽车电子软件的开发变得越来越重要。在这个领域,开发者们需要应对各种挑战,包括复杂的硬件和软件交互、高效的嵌入式编程和安全性要求。为了帮助汽车电子…...

String、StringBuilder 和 StringBuffer 详解

碎碎念 这是一道老生常谈的问题了,字符串是不仅是 Java 中非常重要的一个对象,它在其他语言中也存在。比如 C、Visual Basic、C# 等。字符串使用 String 来表示,字符串一旦被创建出来就不会被修改,当你想修改StringBuffer 或者是 …...

windows服务器上传文件解决方案

1.说明 1.如果上传到linux系统,通常使用ftp相关技术,配合windows端的ftp客户端工具比如FileZilla等进行大文件的上传工作。 2.同理windows服务器也可以开启ftp服务用来传输大文件。 3.本文介绍偷懒方式(常规是开启windows的ftp服务&#xff0…...

Android Studio翻译插件推介(Translation)

前言 Android Studio翻译插件适合英语水平不太好的程序员(比如:我),最常用的翻译插件Translation和AndroidLocalize,本文主要讲解Translation,亲测可用。 先看看效果:这里是Android的API,任意选…...

DNS,DNS污染劫持,DNS加密

1. DNS(Domain Name System)DNS(Domain Name System), 也叫网域名称系统,是互联网的一项服务。它实质上是一个 域名 和 IP 相互映射的分布式数据库.DNS(Domain Name Server,域名服务…...

【Python】如何度量优秀代码——静态分析工具

静态分析工具背景有哪些静态分析工具呢度量Python代码的静态属性度量Python的生态系统代码的坏味道在类层面上在方法层面上结语背景 静态代码分析工具能够提炼出丰富的代码静态属性信息,这使得程序员可以对代码的复杂性、可修改性和可读性有进一步的了解。 有哪些…...

Open3D 点云高程归一化(基于2维地面点,Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前的博客中Open3D 点云高程归一化(基于地面点,Python版本)是基于三维空间进行最近地面点的查询操作,这里对其进行修改一下,将点云投影到水平面,基于二维空间进行最近地面点的查询,这种方式对一些较为陡峭的…...

动态系统的建模与分析

前言 CS小菜鸡控制理论入门 视频学习笔记 视频传送门:动态系统的建模与分析】9_一阶系统的频率响应_低通滤波器_Matlab/Simulink分析 拉普拉斯变换 F(s)L{f(t)}∫0∞f(t)e−stdtF(s)\mathcal{L}\{f(t)\}\int_0^\infty f(t)e^{-st}\mathrm{d}tF(s)L{f(t)}∫0∞​f(t)…...

QCC51XX---HCI log

高通在新的S3/S5以及往后新的平台上面,引入了一个新的调试功能。就是标题说的HCI log,他类似air trace那样用来分析蓝牙协议的,这样我们就可以很详细地找到通信协议之间哪个部分出了问题。以前我们都是通过抓包器抓air trace分析的,抓包器一个要几十万,学会这个功能就相当…...

Redis四 原理篇

《Redis四 原理篇》 提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!! 《Redis四 原理篇》《Redis四 原理篇》1、原理篇-Redis数据结构1.1 Redis数据结构-动态字符串1.2 Redis数据结构-intset1.3 Redis数据结构-Dict1.4 Redis数据结构-ZipList1.4.1 Redis数据…...

从0开始写Vue项目-Vue实现数据渲染和数据的增删改查

从0开始写Vue项目-环境和项目搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue2集成Element-ui和后台主体框架搭建_慕言要努力的博客-CSDN博客从0开始写Vue项目-Vue页面主体布局和登录、注册页面_慕言要努力的博客-CSDN博客从0开始写Vue项目-SpringBoot整合Mybatis-plus实现…...

AI技术的发展,人工智能对我们的生活有那些影响?

人工智能(Artificial Intelligence),英文缩写为AI。它是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。 人工智能是计算机科学的一个分支,它企图了解智能的实质,并生产出一…...

Unity中的Mathf数学运算讲解(值得收藏)

Unity中的Mathf数学运算有哪些? Mathf.Abs(f)绝对值 计算并返回指定参数 f 绝对值 例如: // 输出 10 Debug.log(Mathf.Abs(-10)) Debug.log(Mathf.Abs(10))Mathf.Sin正弦 static function Sin (f : float) : float 计算并返回以弧度为单位指定的角 f 的…...

ABBYY FineReader16最新PDF图片文字识别软件

ABBYY FineReader16是非常好的一款 OCR 识别软件(可以识别不可编辑的PDF和图片文件),操作非常简单。ABBYY FineReader 16是一款知名的OCR文字识别软件(图片文字识别)。ABBYY 16采用了ABBYY最新推出的基于AI的OCR技术&a…...

Leetcode14. 最长公共前缀

一、题目描述: 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 “”。 示例 1: 输入:strs [“flower”,“flow”,“flight”] 输出:“fl” 示例 2: 输入:…...

JTT808jt1078

前言 交通部与2016年10月份推出了JT/T 1078-2016标准&#xff0c;全称是<道路运输车辆卫星定位系统视频通信协议> JTT808 808消息头内容如下表所示&#xff1a; 起始字节字段数据类型描述及要求0消息IDWORD2消息体属性WORD消息体属性格式结构图见图24终端手机号BCD[6…...

数字孪生加持,水利水电工程或将实现全生命周期管理

水利水电工程在数字孪生技术的加持&#xff0c;使得建设和运营更加高效和智能化&#xff0c;将工程中各种元素、过程和系统数字化&#xff0c;并建立数字孪生模型&#xff0c;以实现工程建设和运营的智能化管理。数字孪生对水利水电实现对工程建设的全生命周期管理&#xff0c;…...

RA4M2开发(3)----读取ISL29035数据,并在OLED上显示,串口打印

概述 HS3003是一种数字式温湿度传感器&#xff0c;可以测量环境中的温度和湿度。读取HS3003的数据需要连接传感器到一个数据采集系统&#xff0c;一般是微处理器或者单片机。以下是一个简单的读取HS3003数据的概述&#xff1a; 连接电路&#xff1a;将HS3003传感器连接到微处…...

密码复杂度

检查账户认证失败次数限制 修复建议&#xff1a; 配置SSH方式账户认证失败次数限制 编辑/etc/pam.d/sshd文件 在auth行下方添加&#xff1a; auth required pam_tally.so deny5 unlock_time600 no_lock_time 在account行下方添加&#xff1a; account required pam_tally.s…...

Python打印() 中的 SEP 参数

默认情况下&#xff0c;Python 中 print&#xff08;&#xff09; 函数的参数之间的分隔符是空格&#xff08;softspace 功能&#xff09;&#xff0c;可以根据我们的选择对其进行修改并设置为任何字符、整数或字符串。“sep”参数用于实现相同的目的&#xff0c;它仅在python …...

AG9300方案替代|替代AG9300设计Type-C转VGA方案|CS5260设计原理图

AG9300方案替代|替代AG9300设计Type-C转VGA方案|CS5260设计原理图 安格 AG9300是一款实现USB TYPE-C到VGA数据的单片机解决方案转换器。ALGOLTEK AG9300支持USB Type-C显示端口交替模式&#xff0c;AG9300可以将视频和音频流从USB Type-C接口传输到VGA端口。在AG9300&#xff0…...

力扣-文章浏览

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;1148. 文章浏览二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.其他总结前言 一、题目&#xff1a;1148…...

Unity提取场景中的静态文本

有些单机项目开发的时候没有做本地文本配置文件&#xff0c;全部写死在场景的对象上面&#xff0c;简单记录一下怎么提取场景里面的文本并且写入到配置文件里面using System.Collections.Generic;using System.IO;using TMPro;using UnityEditor;using UnityEngine;using Unity…...

Netty常用核心类说明

MessageToByteEncoder MessageToByteEncoder是一个抽象编码器&#xff0c;子类可重写encode方法把对象编码为ByteBuf输出。 MessageToByteEncoder继承自ChannelOutboundHandlerAdapter&#xff0c;encode在出站是被调用。 public class MyMessageEncoder extends MessageToB…...

ingress服务

user.default.svc.cluster.local是集群内部service的dns地址&#xff0c;集群外部想访问集群里面的service&#xff0c;可以通过LoadBalaner和NodePort。LoadBalaner可以获得一个公网ip&#xff1b;NodePort在宿主机上开一个端口&#xff0c;访问这个端口会把报文实际的转发到集…...

java 抽象类 详解

目录 一、抽象类概述&#xff1a; 二、抽象方法 : 1.概述 : 2.应用 : 3.特点 : 三、抽象类特点 : 1.关于abstract关键字 : 2.抽象类不能被实例化&#xff0c;只能创建其子类对象 : 3.抽象类子类的两个选择 &#xff1a; 四、抽象类的成员 : 1.成员变量 : 2.成员方…...

MySQL的安装(详解)

文章目录前言一、yum方式安装1、下载并安装MySQL2、 启动MySQL数据库3、查看MySQL初始密码4、登录数据库5、修改MySQL默认密码6、授予root用户远程管理权限7、输入exit退出数据库二、rpm安装方式1、检查2、卸载mariadb3、安装4、启动5、密码总结前言 本教程为Linux下安装mysql的…...

界面控件DevExpress WinForm——轻松构建类Visual Studio UI(二)

DevExpress WinForm拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜任…...