[Inside HotSpot] C1编译器优化:条件表达式消除
1. 条件传送指令
日常编程中有很多根据某个条件对变量赋不同值这样的模式,比如:
int cmov(int num) {
int result = 10;
if(num<10){
result = 1;
}else{
result = 0;
}
return result;
}
如果不进行编译优化会产出cmp-jump组合,即根据cmp比较的结果进行跳转。可以使用gcc -O0查看:
cmov(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-20], edi
int result = 10;
mov DWORD PTR [rbp-4], 10
cmp DWORD PTR [rbp-20], 9
jg .L2
mov DWORD PTR [rbp-4], 1
jmp .L3
.L2:
mov DWORD PTR [rbp-4], 0
.L3:
mov eax, DWORD PTR [rbp-4]
pop rbp
ret
如果num>9就为result赋1,否则赋0。正因为跳转是条件的,CPU必须要等到条件成立才执行后面的指令(即数据依赖于条件),这会让处理器变慢,所以CPU通常会使用分支预测算法,提前执行某个概率更高的分支,即使这个时候条件没有成立。但问题是,这是预测不是断言,如果它预测条件成立进入A分支给R赋值V1,实际却可能是条件失败,应该继续B分支给R赋V2,那么CPU还需要撤销给R赋值V1。而条件传送系列指令(cmovcc,setcc)则不同,它根据EFLAGS的位条件性的选择给R赋V1还是V2,这样就没有分支预测失败的性能惩罚。回到上面的例子,使用gcc8的-O3可以看到产出的条件传送指令:
cmov(int):
xor eax, eax
cmp edi, 9
setle al
ret
产出变短了,code cache能容纳下更多代码不说,setle也会根据ZF结果,即小于等于9则给al赋1,否则赋0。我们还顺便证明了C++有条件表达式消除,那么HotSpot有吗?还真有,但是别高兴的太早,它可能和你想的大相径庭...
2. C1编译器中的条件表达式消除
在[Inside HotSpot] C1编译器工作流程及中间表示中我们提到OpenJDK12的C1编译器将字节码转化为机器码的过程分为多个阶段:

第一个阶段它生成HIR,一种C1内部用到的表示字节码的图结构。该阶段同时也会对它进行优化,其中就包括本文要讨论的条件表达式消除(Conditional expression elimination),这一部分代码位于c1_Optimizer.cpp。但是在研究之前还需要做一些准备工作,我们无法直接查看虚拟机JIT产出的本地代码,需要下载hsdis-amd64.dll,将它放在jdk/bin/server/目录下,然后虚拟机加上参数-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly。另外,为了确保虚拟机用C1编译方法且做了条件表达式消除,还要加上-Xcomp -XX:TieredStopAtLevel=1 -XX:+DoCEE。
3. CEE示例
准备好后我们可以开始尝试,准备一段和上面C++差不多的代码:
package com.github.kelthuzadx;
public class C1Optimizations {
public static int conditionalExpressionElimination(int depend){
if(depend<10){
return 1;
}else{
return 0;
}
}
public static void main(String[] args) {
conditionalExpressionElimination(1024);
}
}
然后加上参数查看汇编形式的产出(实际上还是本地代码的,只是汇编形式输出而已):
; Simplified
com/github/kelthuzadx/C1Optimizations.conditionalExpressionElimination(I)I
0x000001afb0a691c0: mov %eax,-0x9000(%rsp)
0x000001afb0a691c7: push %rbp
0x000001afb0a691c8: sub $0x30,%rsp ;*iload_0
0x000001afb0a691cc: cmp $0xa,%edx
0x000001afb0a691cf: jge L1 ;*if_icmpge
0x000001afb0a691d5: mov $0x1,%eax
0x000001afb0a691da: add $0x30,%rsp
0x000001afb0a691de: pop %rbp
0x000001afb0a691df: mov 0x120(%r15),%r10 ; 安全点
0x000001afb0a691e6: test %eax,(%r10) ; 轮询
0x000001afb0a691e9: retq ;*ireturn
L1:
0x000001afb0a691ea: mov $0x0,%eax
0x000001afb0a691ef: add $0x30,%rsp
0x000001afb0a691f3: pop %rbp
0x000001afb0a691f4: mov 0x120(%r15),%r10 ; 安全点
0x000001afb0a691fb: test %eax,(%r10) ; 轮询
0x000001afb0a691fe: retq
0x000001afb0a691ff: nop
0x000001afb0a69200: nop
并没有优化?HotSpot不认为上面的Java代码是条件表达式。它认为的条件表达式是三元表达式,有些出乎意料,但是也请继续吧:
package com.github.kelthuzadx;
public class C1Optimizations {
public static int conditionalExpressionElimination(int depend){
return depend>10?1:0;
}
public static void main(String[] args) {
conditionalExpressionElimination(1024);
}
}
要确定是HotSpot认为这是条件表达式而不是我们认为这是条件表达式,得加上-XX:+PrintIR -XX:+PrintLIRWithAssembly辅证(也可使用 -XX:+PrintCEE),产它出C1 HIR及其汇编表示。首先看看原始HIR:
IR after parsing
B4 [0, 0] -> B0 sux: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
. 0 0 14 std entry B0
B0 (SV) [0, 3] -> B2 B1 sux: B2 B1 pred: B4
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
1 0 i5 10
. 3 0 6 if i4 <= i5 then B2 else B1
B1 (V) [6, 7] -> B3 sux: B3 pred: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
6 0 i7 1
. 7 0 8 goto B3
stack [0:i7]
B3 (V) [11, 11] pred: B1 B2Stack:
0 i11 [ i7 i9]
stack [0:i11]
inlining depth 0
__bci__use__tid____instr____________________________________
. 11 0 i12 ireturn i11
B2 (V) [10, 11] -> B3 sux: B3 pred: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
10 0 i9 0
. 11 0 10 goto B3
stack [0:i9]
根据右边助记指令还是很好理解: 如果i4<i5即depend<10则返回1,反之返回0。接着查看条件表达式消除之后的HIR:
IR after CEE
B4 [0, 0] -> B0 sux: B0
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
. 0 0 14 std entry B0
B0 (SV) [0, 3] -> B3 sux: B3 pred: B4
empty stack
inlining depth 0
__bci__use__tid____instr____________________________________
1 0 i5 10
3 0 i15 0
3 0 i16 1
3 0 i17 i4 <= i5 ? i15 : i16
. 3 0 18 goto B3
stack [0:i17]
B3 (V) [11, 11] pred: B0
stack [0:i17]
inlining depth 0
__bci__use__tid____instr____________________________________
. 11 0 i12 ireturn i17
i17的值视条件而定,如果条件i4<i5则i17为0,否则为1,最后返回。这里的模式正是条件传送指令的工作机制,HIR也有明显的改变,经过一番折腾,我们成功的让HotSpot对条件表达式做了一次消除优化。不过还没有结束,再看看HIR经过一系列步骤最终生成的本地代码:
[Disassembling for mach='i386:x86-64']
2 std_entry
0x0000026a64f209a0: mov %eax,-0x9000(%rsp)
0x0000026a64f209a7: push %rbp
0x0000026a64f209a8: sub $0x30,%rsp
0x0000026a64f209ac: cmp $0xa,%edx
0x0000026a64f209af: mov $0x0,%eax
0x0000026a64f209b4: jle 0x0000026a64f209bf
0x0000026a64f209ba: mov $0x1,%eax
0x0000026a64f209bf: add $0x30,%rsp
0x0000026a64f209c3: pop %rbp
0x0000026a64f209c4: mov 0x120(%r15),%r10
0x0000026a64f209cb: test %eax,(%r10)
0x0000026a64f209ce: retq
很遗憾,即便高层次上HIR做了条件表达式消除,后面到低层次LIR再到本地代码生成也没有产出cmov系列指令。
[Inside HotSpot] C1编译器优化:条件表达式消除的更多相关文章
- [Inside HotSpot] C1编译器优化:全局值编号(GVN)
1. 值编号 我们知道C1内部使用的是一种图结构的HIR,它由基本块构成一个图,然后每个基本块里面是SSA形式的指令,关于这点如可以参考[Inside HotSpot] C1编译器工作流程及中间表示. ...
- [Inside HotSpot] C1编译器HIR的构造
1. 简介 这篇文章可以说是Christian Wimmer硕士论文Linear Scan Register Allocation for the Java HotSpot™ Client Compi ...
- [Inside HotSpot] C1编译器工作流程及中间表示
1. C1编译器线程 C1编译器(aka Client Compiler)的代码位于hotspot\share\c1.C1编译线程(C1 CompilerThread)会阻塞在任务队列,当发现队列有编 ...
- JavaScrip条件表达式优化
目录 1,前言 2,多条件if语句优化 3,参数默认值 4,Switch语句优化 1,前言 今早看了一篇文章<JavaScrip实现:如何写出漂亮的条件表达式>,原创于:华为云开发者社区, ...
- java编译器优化和运行期优化
概述 最近在看jvm优化,总结一下学习的相关知识 (一)javac编译器 编译过程 1.解析与填充符号表过程 1).词法.语法分析 词法分析将源代码的字符流转变为标记集合,单个字符是程序编 ...
- [Inside HotSpot] Java的方法调用
1. 方法调用模块入口 Java所有的方法调用都会经过JavaCalls模块.该模块又细分为call_virtual调用虚函数,call_static调用静态函数等.虚函数调用会根据对象类型进行方法决 ...
- C1编译器的实现
总览 词法.语法分析 分析方案 词法 语法 符号表 类型系统 AST 语义检查 EIR代码生成器 MIPS代码生成器 寄存器分配 体系结构相关特性优化 使用说明 编译 运行 总览 C1语言编译器及流程 ...
- C#编译器优化那点事
使用C#编写程序,给最终用户的程序,是需要使用release配置的,而release配置和debug配置,有一个关键区别,就是release的编译器优化默认是启用的. 优化代码开关即optimize开 ...
- 【转】C 编译器优化过程中的 Bug
C 编译器优化过程中的 Bug 一个朋友向我指出一个最近他们发现的 GCC 编译器优化过程(加上 -O3 选项)里的 bug,导致他们的产品出现非常诡异的行为.这使我想起以前见过的一个 GCC bug ...
随机推荐
- springBoot+springSecurity 数据库动态管理用户、角色、权限
使用spring Security3的四种方法概述 那么在Spring Security3的使用中,有4种方法: 一种是全部利用配置文件,将用户.权限.资源(url)硬编码在xml文件中,已经实现过, ...
- json数据取值
var data = JSON.parse(JSON.stringify(position, null, 4));
- TCP的延迟ACK机制
TCP的延迟ACK机制 TCP的延迟ACK机制一说到TCP,人们就喜欢开始扯三步握手之类的,那只是其中的一个环节而已.实际上每一个数据包的正确发送都是一个类似握手的过程,可以简单的把它视为两步握手.一 ...
- MyBatis的增删改查。
数据库的经典操作:增删改查. 在这一章我们主要说明一下简单的查询和增删改,并且对程序接口做了一些调整,以及对一些问题进行了解答. 1.调整后的结构图: 2.连接数据库文件配置分离: 一般的程序都会把连 ...
- Guava新增集合类型-Multiset
Guava新增集合类型-Multiset Guava引进了JDK里没有的,但是非常有用的一些新的集合类型.所有这些新集合类型都能和JDK里的集合平滑集成.Guava集合非常精准地实现了JDK定义的接口 ...
- BZOJ1854: [Scoi2010]游戏 二分图
很早之前写的题了,发现没有更博,想了想,更一发出来. Orz ljss 这是冬令营上的例题...之后,我推出来了一种时间复杂度没有问题,空间复杂度没有问题的方法,额(⊙o⊙)…和给出的正解不同,但是能 ...
- Volley手写属于自己的万能网络访问框架
用户在调用层(Activity或Service中),发起一个网络请求,该请求肯定包含url,请求参数(requestParameter),以及我们需要给调用层提供一个请求成功或失败以后回调监听的接口d ...
- tomcat设置直接通过域名访问项目(不需要接 /项目名)
本文转自 : https://blog.csdn.net/qq_33647275/article/details/52585489 效果图 打开 tomcat - ->conf- ->se ...
- Android 画文字图
画图 private Bitmap getbitmap(String content) { Bitmap bitmap = Bitmap.createBitmap(400, 400, Bitmap.C ...
- java quartz 计算近20次执行时间
/** * * @desc 计算表达式近20次时间 * @auth josnow * @date 2017年5月31日 下午12:16:25 * @param cron * @return */ pu ...